mirror of
https://github.com/ProudMuBai/GoFilm.git
synced 2026-02-21 01:54:42 +08:00
add BAM
This commit is contained in:
@@ -27,7 +27,11 @@ const (
|
||||
|
||||
// ImgCacheFlag 是否开启将主站影片图片放入本地进行存储
|
||||
ImgCacheFlag = false
|
||||
ImageDir = "./resource/static/images"
|
||||
//ImageDir = "./resource/static/images"
|
||||
|
||||
FilmPictureUploadDir = "./static/upload/gallery"
|
||||
FilmPictureUrlPath = "/upload/pic/poster/"
|
||||
FilmPictureAccess = "/api/upload/pic/poster/"
|
||||
)
|
||||
|
||||
// -------------------------redis key-----------------------------------
|
||||
@@ -41,7 +45,7 @@ const (
|
||||
// MovieDetailKey movie detail影视详情信息 可以
|
||||
MovieDetailKey = "MovieDetail:Cid%d:Id%d"
|
||||
// MovieBasicInfoKey 影片基本信息, 简略版本
|
||||
MovieBasicInfoKey = "MovieBasicInfoKey:Cid%d:Id%d"
|
||||
MovieBasicInfoKey = "MovieBasicInfo:Cid%d:Id%d"
|
||||
|
||||
// MultipleSiteDetail 多站点影片信息存储key
|
||||
MultipleSiteDetail = "MultipleSource:%s"
|
||||
@@ -49,8 +53,15 @@ const (
|
||||
// SearchInfoTemp redis暂存检索数据信息
|
||||
SearchInfoTemp = "Search:SearchInfoTemp"
|
||||
|
||||
// SearchTitle 影片分类标题key
|
||||
SearchTitle = "Search:Pid%d:Title"
|
||||
SearchTag = "Search:Pid%d:%s"
|
||||
// SearchTag 影片剧情标签key
|
||||
SearchTag = "Search:Pid%d:%s"
|
||||
|
||||
// VirtualPictureKey 待同步图片临时存储 key
|
||||
VirtualPictureKey = "VirtualPicture"
|
||||
// MaxScanCount redis Scan 操作每次扫描的数据量, 每次最多扫描300条数据
|
||||
MaxScanCount = 300
|
||||
|
||||
// SearchCount Search scan 识别范围
|
||||
SearchCount = 3000
|
||||
@@ -62,6 +73,27 @@ const (
|
||||
SearchHeatListKey = "Search:SearchHeatList"
|
||||
)
|
||||
|
||||
const (
|
||||
AuthUserClaims = "UserClaims"
|
||||
)
|
||||
|
||||
// -------------------------manage 管理后台相关key----------------------------------
|
||||
const (
|
||||
// FilmSourceListKey 采集 API 信息列表key
|
||||
FilmSourceListKey = "Config:Collect:FilmSource"
|
||||
// ManageConfigExpired 管理配置key 长期有效, 暂定10年
|
||||
ManageConfigExpired = time.Hour * 24 * 365 * 10
|
||||
// SiteConfigBasic 网站参数配置
|
||||
SiteConfigBasic = "SystemConfig:SiteConfig:Basic"
|
||||
|
||||
// FilmCrontabKey 定时任务列表信息
|
||||
FilmCrontabKey = "Cron:Task:Film"
|
||||
// DefaultUpdateSpec 每20分钟执行一次
|
||||
DefaultUpdateSpec = "0 */20 * * * ?"
|
||||
// DefaultUpdateTime 每次采集最近 3 小时内更新的影片
|
||||
DefaultUpdateTime = 3
|
||||
)
|
||||
|
||||
// -------------------------Web API相关redis key-----------------------------------
|
||||
const (
|
||||
// IndexCacheKey , 首页数据缓存
|
||||
@@ -71,7 +103,10 @@ const (
|
||||
// -------------------------Database Connection Params-----------------------------------
|
||||
const (
|
||||
// SearchTableName 存放检索信息的数据表名
|
||||
SearchTableName = "search"
|
||||
SearchTableName = "search"
|
||||
UserTableName = "users"
|
||||
UserIdInitialVal = 10000
|
||||
PictureTableName = "picture"
|
||||
|
||||
//mysql服务配置信息 root:root 设置mysql账户的用户名和密码
|
||||
|
||||
|
||||
19
server/config/PrivideConfig.go
Normal file
19
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"
|
||||
)
|
||||
24
server/config/SecurityConfig.go
Normal file
24
server/config/SecurityConfig.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package config
|
||||
|
||||
const PrivateKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIBPAIBAAJBANNnshoUaT2gFNrihmFdmC1cBCs1XLFc5Fn3MfNOR3aOGDO0ohXl
|
||||
bku6Ir/qITN/yeH5pY34WEcETet3YhESpE8CAwEAAQJBAI7Ekdfg/u26RTtJDd2F
|
||||
WrcPVFVl1TKGfERxl08sB0D9HLvUSBfAEg/UpfWSQ57aSJ9b0gVKmDhgF8FymuUV
|
||||
v2kCIQDzXXSZ/oeKmqObwad0Fa82IFof3LeZdpbrjyz3w45JDQIhAN5hdmuW+y2w
|
||||
UgSy0o4zGFsEG/RBZsvVnSSfkdR47dPLAiEA2XbPNLQu5fnc7NeVDLQ7xsAOCJ6w
|
||||
KR/BKGjeI9/JCxkCIQCjMkU0ec2FXxMhzZXFs2uZR6+4FdL5nZ9ABDaCBekK9wIg
|
||||
XEfd11qabi9jPrbsOVNZCTk51B7Ug0ZwGyn0BA8Jlo0=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
||||
|
||||
const PublicKey = `-----BEGIN RSA PUBLIC KEY-----
|
||||
MEgCQQDTZ7IaFGk9oBTa4oZhXZgtXAQrNVyxXORZ9zHzTkd2jhgztKIV5W5LuiK/
|
||||
6iEzf8nh+aWN+FhHBE3rd2IREqRPAgMBAAE=
|
||||
-----END RSA PUBLIC KEY-----
|
||||
`
|
||||
|
||||
const (
|
||||
Issuer = "GoFilm"
|
||||
AuthTokenExpires = 10 * 24 // 单位 h
|
||||
UserTokenKey = "User:Token:%d"
|
||||
)
|
||||
217
server/controller/CronController.go
Normal file
217
server/controller/CronController.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"server/logic"
|
||||
"server/model/system"
|
||||
"server/plugin/spider"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ------------------------------------------------------ 定时任务管理 ------------------------------------------------------
|
||||
|
||||
// FilmCronTaskList 获取所有的定时任务信息
|
||||
func FilmCronTaskList(c *gin.Context) {
|
||||
tl := logic.CL.GetFilmCrontab()
|
||||
if len(tl) <= 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "暂无任务定时任务信息",
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"data": tl,
|
||||
})
|
||||
}
|
||||
|
||||
// GetFilmCronTask 通过Id获取对应的定时任务信息
|
||||
func GetFilmCronTask(c *gin.Context) {
|
||||
id := c.DefaultQuery("id", "")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "定时任务信息获取失败,任务Id不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
task, err := logic.CL.GetFilmCrontabById(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": fmt.Sprint("定时任务信息获取失败", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"data": task,
|
||||
})
|
||||
}
|
||||
|
||||
// FilmCronAdd 添加定时任务
|
||||
func FilmCronAdd(c *gin.Context) {
|
||||
var vo = system.FilmCronVo{}
|
||||
// 获取请求参数
|
||||
if err := c.ShouldBindJSON(&vo); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "请求参数异常",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 校验请求参数
|
||||
if err := validTaskAddVo(vo); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
// 去除cron表达式左右空格
|
||||
vo.Spec = strings.TrimSpace(vo.Spec)
|
||||
// 执行 定时任务信息保存逻辑
|
||||
if err := logic.CL.AddFilmCrontab(vo); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": fmt.Sprint("定时任务添加失败: ", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "定时任务添加成功",
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// FilmCronUpdate 更新定时任务信息
|
||||
func FilmCronUpdate(c *gin.Context) {
|
||||
var t = system.FilmCollectTask{}
|
||||
// 获取请求参数
|
||||
if err := c.ShouldBindJSON(&t); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "请求参数异常",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 校验必要参数
|
||||
if err := validTaskInfo(t); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
// 获取未更新的task信息
|
||||
task, err := logic.CL.GetFilmCrontabById(t.Id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": fmt.Sprint("更新失败: ", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
// 将task的可变更属性进行变更
|
||||
task.Ids = t.Ids
|
||||
task.Time = t.Time
|
||||
task.State = t.State
|
||||
task.Remark = t.Remark
|
||||
// 将变更后的task更新到系统中
|
||||
logic.CL.UpdateFilmCron(task)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": fmt.Sprintf("定时任务[%s]更新成功", task.Id),
|
||||
})
|
||||
}
|
||||
|
||||
// ChangeTaskState 开启 | 关闭Id 对应的定时任务
|
||||
func ChangeTaskState(c *gin.Context) {
|
||||
var t = system.FilmCollectTask{}
|
||||
// 获取请求参数
|
||||
if err := c.ShouldBindJSON(&t); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "请求参数异常",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 获取未更新的task信息
|
||||
task, err := logic.CL.GetFilmCrontabById(t.Id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": fmt.Sprint("更新失败: ", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
// 修改task的状态
|
||||
task.State = t.State
|
||||
// 将变更后的task更新到系统中
|
||||
logic.CL.UpdateFilmCron(task)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": fmt.Sprintf("定时任务[%s]更新成功", task.Id),
|
||||
})
|
||||
}
|
||||
|
||||
// DelFilmCron 删除定时任务
|
||||
func DelFilmCron(c *gin.Context) {
|
||||
id := c.DefaultQuery("id", "")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "删除失败,任务Id不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 如果Id不为空则执行删除逻辑
|
||||
if err := logic.CL.DelFilmCrontab(id); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": fmt.Sprintf("定时任务[%s]已删除", id),
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// -------------------------------------------------- 参数校验 --------------------------------------------------
|
||||
|
||||
// 定时任务必要属性校验
|
||||
func validTaskInfo(t system.FilmCollectTask) error {
|
||||
if len(t.Id) <= 0 {
|
||||
return errors.New("参数校验失败, 任务Id信息不能为空")
|
||||
}
|
||||
if t.Time == 0 {
|
||||
return errors.New("参数校验失败, 采集时长不能为零值")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 任务添加参数校验
|
||||
func validTaskAddVo(vo system.FilmCronVo) error {
|
||||
if vo.Model != 0 && vo.Model != 1 {
|
||||
return errors.New("参数校验失败, 未定义的任务类型")
|
||||
}
|
||||
if vo.Time == 0 {
|
||||
return errors.New("参数校验失败, 采集时长不能为零值")
|
||||
}
|
||||
if err := spider.ValidSpec(vo.Spec); err != nil {
|
||||
return errors.New(fmt.Sprint("参数校验失败 cron表达式校验失败: ", err.Error()))
|
||||
}
|
||||
if vo.Model == 1 && (vo.Ids == nil || len(vo.Ids) <= 0) {
|
||||
return errors.New("参数校验失败, 自定义更新未绑定任何资源站点")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
59
server/controller/FileController.go
Normal file
59
server/controller/FileController.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"path/filepath"
|
||||
"server/config"
|
||||
"server/logic"
|
||||
"server/model/system"
|
||||
"server/plugin/common/util"
|
||||
)
|
||||
|
||||
// SingleUpload 单文件上传, 暂定为图片上传
|
||||
func SingleUpload(c *gin.Context) {
|
||||
// 获取执行操作的用户信息
|
||||
v, ok := c.Get(config.AuthUserClaims)
|
||||
if !ok {
|
||||
system.Failed("上传失败, 当前用户信息异常", c)
|
||||
return
|
||||
}
|
||||
// 结合搜文件内容
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
system.Failed(err.Error(), c)
|
||||
return
|
||||
}
|
||||
// 创建文件保存路径, 如果不存在则创建
|
||||
//if _, err = os.Stat(config.ImageDir); os.IsNotExist(err) {
|
||||
// err = os.MkdirAll(config.ImageDir, os.ModePerm)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
//}
|
||||
|
||||
// 生成文件名, 保存文件到服务器
|
||||
fileName := fmt.Sprintf("%s/%s%s", config.FilmPictureUploadDir, util.RandomString(8), filepath.Ext(file.Filename))
|
||||
err = c.SaveUploadedFile(file, fileName)
|
||||
if err != nil {
|
||||
system.Failed(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
uc := v.(*system.UserClaims)
|
||||
// 记录图片信息到系统表中, 并获取返回的图片访问路径
|
||||
link := logic.FileL.SingleFileUpload(fileName, int(uc.UserID))
|
||||
// 返回图片访问地址以及成功的响应
|
||||
system.Success(link, "上传成功", c)
|
||||
|
||||
}
|
||||
|
||||
// PhotoWall 照片墙数据
|
||||
func PhotoWall(c *gin.Context) {
|
||||
|
||||
// 获取系统保存的文件的图片分页数据
|
||||
page := system.Page{PageSize: 10, Current: 1}
|
||||
// 获取分页数据
|
||||
pl := logic.FileL.GetPhotoPage(&page)
|
||||
system.Success(pl, "图片分页数据获取成功", c)
|
||||
}
|
||||
176
server/controller/FilmController.go
Normal file
176
server/controller/FilmController.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"server/logic"
|
||||
"server/model/system"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FilmSearchPage 获取影视分页数据
|
||||
func FilmSearchPage(c *gin.Context) {
|
||||
var s = system.SearchVo{Paging: &system.Page{}}
|
||||
var err error
|
||||
// 检索参数
|
||||
s.Name = c.DefaultQuery("name", "")
|
||||
s.Pid, err = strconv.ParseInt(c.DefaultQuery("pid", "0"), 10, 64)
|
||||
if err != nil {
|
||||
system.Failed("影片分页数据获取失败, 请求参数异常", c)
|
||||
return
|
||||
}
|
||||
s.Cid, err = strconv.ParseInt(c.DefaultQuery("cid", "0"), 10, 64)
|
||||
if err != nil {
|
||||
system.Failed("影片分页数据获取失败, 请求参数异常", c)
|
||||
return
|
||||
}
|
||||
s.Plot = c.DefaultQuery("plot", "")
|
||||
s.Area = c.DefaultQuery("area", "")
|
||||
s.Language = c.DefaultQuery("language", "")
|
||||
year := c.DefaultQuery("year", "")
|
||||
if year == "" {
|
||||
s.Year = 0
|
||||
} else {
|
||||
s.Year, err = strconv.ParseInt(year, 10, 64)
|
||||
if err != nil {
|
||||
system.Failed("影片分页数据获取失败, 请求参数异常", c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s.Remarks = c.DefaultQuery("remarks", "")
|
||||
// 处理时间参数
|
||||
begin := c.DefaultQuery("beginTime", "")
|
||||
if begin == "" {
|
||||
s.BeginTime = 0
|
||||
} else {
|
||||
beginTime, e := time.ParseInLocation(time.DateTime, begin, time.Local)
|
||||
if e != nil {
|
||||
system.Failed("影片分页数据获取失败, 请求参数异常", c)
|
||||
return
|
||||
}
|
||||
s.BeginTime = beginTime.Unix()
|
||||
}
|
||||
end := c.DefaultQuery("endTime", "")
|
||||
if end == "" {
|
||||
s.EndTime = 0
|
||||
} else {
|
||||
endTime, e := time.ParseInLocation(time.DateTime, end, time.Local)
|
||||
if e != nil {
|
||||
system.Failed("影片分页数据获取失败, 请求参数异常", c)
|
||||
return
|
||||
}
|
||||
s.EndTime = endTime.Unix()
|
||||
}
|
||||
|
||||
// 分页参数
|
||||
s.Paging.Current, err = strconv.Atoi(c.DefaultQuery("current", "1"))
|
||||
s.Paging.PageSize, err = strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
// 如果分页数据超出指定范围则设置为默认值
|
||||
if s.Paging.PageSize <= 0 || s.Paging.PageSize > 500 {
|
||||
s.Paging.PageSize = 10
|
||||
}
|
||||
if err != nil {
|
||||
system.Failed("影片分页数据获取失败, 请求参数异常", c)
|
||||
return
|
||||
}
|
||||
// 提供检索tag options
|
||||
options := logic.FL.GetSearchOptions()
|
||||
// 检索条件
|
||||
sl := logic.FL.GetFilmPage(s)
|
||||
system.Success(gin.H{
|
||||
"params": s,
|
||||
"list": sl,
|
||||
"options": options,
|
||||
}, "影片分页信息获取成功", c)
|
||||
}
|
||||
|
||||
// FilmAdd 手动添加影片
|
||||
func FilmAdd(c *gin.Context) {
|
||||
// 获取请求参数
|
||||
var fd = system.FilmDetailVo{}
|
||||
if err := c.ShouldBindJSON(&fd); err != nil {
|
||||
system.Failed("影片添加失败, 影片参数提交异常", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 如果绑定成功则执行影片信息处理保存逻辑
|
||||
if err := logic.FL.SaveFilmDetail(fd); err != nil {
|
||||
system.Failed(fmt.Sprint("影片添加失败, 影片信息保存错误: ", err.Error()), c)
|
||||
return
|
||||
}
|
||||
system.SuccessOnlyMsg("影片信息添加成功", c)
|
||||
}
|
||||
|
||||
//----------------------------------------------------影片分类处理----------------------------------------------------
|
||||
|
||||
// FilmClassTree 影片分类树数据
|
||||
func FilmClassTree(c *gin.Context) {
|
||||
// 获取影片分类树信息
|
||||
tree := logic.FL.GetFilmClassTree()
|
||||
system.Success(tree, "影片分类信息获取成功", c)
|
||||
return
|
||||
}
|
||||
|
||||
// FindFilmClass 获取指定ID对应的影片分类信息
|
||||
func FindFilmClass(c *gin.Context) {
|
||||
idStr := c.DefaultQuery("id", "")
|
||||
if idStr == "" {
|
||||
system.Failed("影片分类信息获取失败, 分类Id不能为空", c)
|
||||
return
|
||||
}
|
||||
// 转化id类型为int
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
system.Failed("影片分类信息获取失败, 参数分类Id格式异常", c)
|
||||
return
|
||||
}
|
||||
// 通过Id返回对应的分类信息
|
||||
class := logic.FL.GetFilmClassById(id)
|
||||
if class == nil {
|
||||
system.Failed("影片分类信息获取失败, 分类信息不存在", c)
|
||||
return
|
||||
}
|
||||
system.Success(class, "分类信息查找成功", c)
|
||||
}
|
||||
|
||||
func UpdateFilmClass(c *gin.Context) {
|
||||
// 获取修改后的分类信息
|
||||
var class = system.CategoryTree{}
|
||||
if err := c.ShouldBindJSON(&class); err != nil {
|
||||
system.Failed("更新失败, 请求参数异常", c)
|
||||
return
|
||||
}
|
||||
if class.Id == 0 {
|
||||
system.Failed("更新失败, 分类Id缺失", c)
|
||||
return
|
||||
}
|
||||
// 修改分类信息
|
||||
if err := logic.FL.UpdateClass(class); err != nil {
|
||||
system.Failed(err.Error(), c)
|
||||
return
|
||||
}
|
||||
system.SuccessOnlyMsg("影片分类信息更新成功", c)
|
||||
}
|
||||
|
||||
// DelFilmClass 删除指定ID对应的影片分类
|
||||
func DelFilmClass(c *gin.Context) {
|
||||
idStr := c.DefaultQuery("id", "")
|
||||
if idStr == "" {
|
||||
system.Failed("影片分类信息获取失败, 分类Id不能为空", c)
|
||||
return
|
||||
}
|
||||
// 转化id类型为int
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
system.Failed("影片分类信息获取失败, 参数分类Id格式异常", c)
|
||||
return
|
||||
}
|
||||
// 通过ID删除对应分类信息
|
||||
if err = logic.FL.DelClass(id); err != nil {
|
||||
system.Failed(err.Error(), c)
|
||||
return
|
||||
}
|
||||
system.SuccessOnlyMsg("当前分类已删除成功", c)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"server/logic"
|
||||
"server/model"
|
||||
"server/model/system"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -26,7 +26,8 @@ func Index(c *gin.Context) {
|
||||
|
||||
// CategoriesInfo 分类信息获取
|
||||
func CategoriesInfo(c *gin.Context) {
|
||||
data := logic.IL.GetCategoryInfo()
|
||||
//data := logic.IL.GetCategoryInfo()
|
||||
data := logic.IL.GetNavCategory()
|
||||
|
||||
if data == nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@@ -55,7 +56,7 @@ func FilmDetail(c *gin.Context) {
|
||||
// 获取影片详情信息
|
||||
detail := logic.IL.GetFilmDetail(id)
|
||||
// 获取相关推荐影片数据
|
||||
page := model.Page{Current: 0, PageSize: 14}
|
||||
page := system.Page{Current: 0, PageSize: 14}
|
||||
relateMovie := logic.IL.RelateMovie(detail, &page)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
@@ -82,7 +83,7 @@ func FilmPlayInfo(c *gin.Context) {
|
||||
// 获取影片详情信息
|
||||
detail := logic.IL.GetFilmDetail(id)
|
||||
// 推荐影片信息
|
||||
page := model.Page{Current: 0, PageSize: 14}
|
||||
page := system.Page{Current: 0, PageSize: 14}
|
||||
relateMovie := logic.IL.RelateMovie(detail, &page)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
@@ -101,7 +102,7 @@ func SearchFilm(c *gin.Context) {
|
||||
keyword := c.DefaultQuery("keyword", "")
|
||||
currStr := c.DefaultQuery("current", "1")
|
||||
current, _ := strconv.Atoi(currStr)
|
||||
page := model.Page{PageSize: 10, Current: current}
|
||||
page := system.Page{PageSize: 10, Current: current}
|
||||
bl := logic.IL.SearchFilmInfo(strings.TrimSpace(keyword), &page)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@@ -115,7 +116,7 @@ func SearchFilm(c *gin.Context) {
|
||||
|
||||
// FilmTagSearch 通过tag获取满足条件的对应影片
|
||||
func FilmTagSearch(c *gin.Context) {
|
||||
params := model.SearchTagsVO{}
|
||||
params := system.SearchTagsVO{}
|
||||
pidStr := c.DefaultQuery("Pid", "")
|
||||
cidStr := c.DefaultQuery("Category", "")
|
||||
yStr := c.DefaultQuery("Year", "")
|
||||
@@ -137,7 +138,7 @@ func FilmTagSearch(c *gin.Context) {
|
||||
// 设置分页信息
|
||||
currentStr := c.DefaultQuery("current", "1")
|
||||
current, _ := strconv.Atoi(currentStr)
|
||||
page := model.Page{PageSize: 49, Current: current}
|
||||
page := system.Page{PageSize: 49, Current: current}
|
||||
logic.IL.GetFilmsByTags(params, &page)
|
||||
// 获取当前分类Title
|
||||
// 返回对应信息
|
||||
@@ -175,7 +176,7 @@ func FilmClassify(c *gin.Context) {
|
||||
pid, _ := strconv.ParseInt(pidStr, 10, 64)
|
||||
title := logic.IL.GetPidCategory(pid)
|
||||
// 2. 设置分页信息
|
||||
page := model.Page{PageSize: 21, Current: 1}
|
||||
page := system.Page{PageSize: 21, Current: 1}
|
||||
// 3. 获取当前分类下的 最新上映, 排行榜, 最近更新 影片信息
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
@@ -207,7 +208,7 @@ func FilmCategory(c *gin.Context) {
|
||||
// 2 设置分页信息
|
||||
currentStr := c.DefaultQuery("current", "1")
|
||||
current, _ := strconv.Atoi(currentStr)
|
||||
page := model.Page{PageSize: 49, Current: current}
|
||||
page := system.Page{PageSize: 49, Current: current}
|
||||
// 2.1 如果不存在cid则根据Pid进行查询
|
||||
if cidStr == "" {
|
||||
// 2.2 如果存在pid则根据pid进行查找
|
||||
|
||||
374
server/controller/ManageController.go
Normal file
374
server/controller/ManageController.go
Normal file
@@ -0,0 +1,374 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"server/logic"
|
||||
"server/model/system"
|
||||
"server/plugin/SystemInit"
|
||||
"server/plugin/common/util"
|
||||
"server/plugin/spider"
|
||||
)
|
||||
|
||||
func ManageIndex(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "hahah",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// ------------------------------------------------------ 影视采集 ------------------------------------------------------
|
||||
|
||||
// FilmSourceList 采集站点信息
|
||||
func FilmSourceList(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"data": logic.ML.GetFilmSourceList(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// FindFilmSource 通过ID返回对应的资源站数据
|
||||
func FindFilmSource(c *gin.Context) {
|
||||
id := c.Query("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "参数异常, 资源站标识不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
fs := logic.ML.GetFilmSource(id)
|
||||
if fs == nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "数据异常,资源站信息不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"data": fs,
|
||||
})
|
||||
}
|
||||
|
||||
func FilmSourceAdd(c *gin.Context) {
|
||||
var s = system.FilmSource{}
|
||||
// 获取请求参数
|
||||
if err := c.ShouldBindJSON(&s); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "请求参数异常",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 校验必要参数
|
||||
if err := validFilmSource(s); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
// 如果采集站开启图片同步, 且采集站为附属站点则返回错误提示
|
||||
if s.SyncPictures && (s.Grade == system.SlaveCollect) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "附属站点无法开启图片同步功能",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 执行 spider
|
||||
if err := spider.CollectApiTest(s); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "资源接口测试失败, 请确认接口有效再添加",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 测试通过后将资源站信息添加到list
|
||||
if err := logic.ML.SaveFilmSource(s); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": fmt.Sprint("资源站添加失败: ", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "添加成功",
|
||||
})
|
||||
}
|
||||
|
||||
func FilmSourceUpdate(c *gin.Context) {
|
||||
var s = system.FilmSource{}
|
||||
// 获取请求参数
|
||||
if err := c.ShouldBindJSON(&s); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "请求参数异常",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 校验必要参数
|
||||
if err := validFilmSource(s); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
// 如果采集站开启图片同步, 且采集站为附属站点则返回错误提示
|
||||
if s.SyncPictures && (s.Grade == system.SlaveCollect) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "附属站点无法开启图片同步功能",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 校验Id信息是否为空
|
||||
if s.Id == "" {
|
||||
c.JSON(http.StatusOK, gin.H{"status": StatusFailed, "message": "参数异常, 资源站标识不能为空"})
|
||||
return
|
||||
}
|
||||
fs := logic.ML.GetFilmSource(s.Id)
|
||||
if fs == nil {
|
||||
c.JSON(http.StatusOK, gin.H{"status": StatusFailed, "message": "数据异常,资源站信息不存在"})
|
||||
return
|
||||
}
|
||||
// 如果 uri发生变更则执行spider测试
|
||||
if fs.Uri != s.Uri {
|
||||
// 执行 spider
|
||||
if err := spider.CollectApiTest(s); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "资源接口测试失败, 请确认更新的数据接口是否有效",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
// 更新资源站信息
|
||||
if err := logic.ML.UpdateFilmSource(s); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": fmt.Sprint("资源站更新失败: ", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "更新成功",
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func FilmSourceChange(c *gin.Context) {
|
||||
var s = system.FilmSource{}
|
||||
// 获取请求参数
|
||||
if err := c.ShouldBindJSON(&s); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "请求参数异常",
|
||||
})
|
||||
return
|
||||
}
|
||||
if s.Id == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "参数异常, 资源站标识不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 查找对应的资源站点信息
|
||||
fs := logic.ML.GetFilmSource(s.Id)
|
||||
if fs == nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "数据异常,资源站信息不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 如果采集站开启图片同步, 且采集站为附属站点则返回错误提示
|
||||
if s.SyncPictures && (fs.Grade == system.SlaveCollect) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "附属站点无法开启图片同步功能",
|
||||
})
|
||||
return
|
||||
}
|
||||
if s.State != fs.State || s.SyncPictures != fs.SyncPictures {
|
||||
// 执行更新操作
|
||||
s := system.FilmSource{Id: fs.Id, Name: fs.Name, Uri: fs.Uri, ResultModel: fs.ResultModel,
|
||||
Grade: fs.Grade, SyncPictures: s.SyncPictures, CollectType: fs.CollectType, State: s.State}
|
||||
// 更新资源站信息
|
||||
if err := logic.ML.UpdateFilmSource(s); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": fmt.Sprint("资源站更新失败: ", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "更新成功",
|
||||
})
|
||||
}
|
||||
|
||||
func FilmSourceDel(c *gin.Context) {
|
||||
id := c.Query("id")
|
||||
if len(id) <= 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "资源站ID信息不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
if err := logic.ML.DelFilmSource(id); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": fmt.Sprint("删除资源站失败: ", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "删除成功:",
|
||||
})
|
||||
}
|
||||
|
||||
// FilmSourceTest 测试影视站点数据是否可用
|
||||
func FilmSourceTest(c *gin.Context) {
|
||||
var s = system.FilmSource{}
|
||||
// 获取请求参数
|
||||
if err := c.ShouldBindJSON(&s); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "请求参数异常",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 校验必要参数
|
||||
if err := validFilmSource(s); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
// 执行 spider
|
||||
if err := spider.CollectApiTest(s); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "测试成功!!!",
|
||||
})
|
||||
}
|
||||
|
||||
// GetNormalFilmSource 获取状态为启用的采集站信息
|
||||
func GetNormalFilmSource(c *gin.Context) {
|
||||
// 获取所有的采集站信息
|
||||
var l []system.FilmTaskOptions
|
||||
for _, v := range logic.ML.GetFilmSourceList() {
|
||||
if v.State {
|
||||
l = append(l, system.FilmTaskOptions{Id: v.Id, Name: v.Name})
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"data": l,
|
||||
})
|
||||
}
|
||||
|
||||
// ------------------------------------------------------ 站点基本配置 ------------------------------------------------------
|
||||
|
||||
// SiteBasicConfig 网站基本配置
|
||||
func SiteBasicConfig(c *gin.Context) {
|
||||
system.Success(logic.ML.GetSiteBasicConfig(), "网站基本信息获取成功", c)
|
||||
}
|
||||
|
||||
// UpdateSiteBasic 更新网站配置信息
|
||||
func UpdateSiteBasic(c *gin.Context) {
|
||||
// 获取请求参数 && 校验关键配置项
|
||||
bc := system.BasicConfig{}
|
||||
if err := c.ShouldBindJSON(&bc); err == nil {
|
||||
// 对参数进行校验
|
||||
if !util.ValidDomain(bc.Domain) && !util.ValidIPHost(bc.Domain) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "域名格式校验失败: ",
|
||||
})
|
||||
return
|
||||
}
|
||||
if len(bc.SiteName) <= 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "网站名称不能为空: ",
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"status": StatusOk, "message": fmt.Sprint("参数提交失败: ", err)})
|
||||
return
|
||||
}
|
||||
|
||||
// 保存更新后的配置信息
|
||||
if err := logic.ML.UpdateSiteBasic(bc); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": fmt.Sprint("网站配置更新失败: ", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "更新成功: ",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// ResetSiteBasic 重置网站配置信息为初始化状态
|
||||
func ResetSiteBasic(c *gin.Context) {
|
||||
// 执行配置初始化方法直接覆盖当前基本配置信息
|
||||
SystemInit.BasicConfigInit()
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "重置成功: ",
|
||||
})
|
||||
}
|
||||
|
||||
// ------------------------------------------------------ 参数校验 ------------------------------------------------------
|
||||
func validFilmSource(fs system.FilmSource) error {
|
||||
// 资源名称不能为空 且长度不能超过20
|
||||
if len(fs.Name) <= 0 || len(fs.Name) > 20 {
|
||||
return errors.New("资源名称不能为空且长度不能超过20")
|
||||
}
|
||||
// Uri 采集链接测试格式
|
||||
if !util.ValidURL(fs.Uri) {
|
||||
return errors.New("资源链接格式异常, 请输入规范的URL链接")
|
||||
}
|
||||
// 校验接口类型是否是 JSON || XML
|
||||
if fs.ResultModel != system.JsonResult && fs.ResultModel != system.XmlResult {
|
||||
return errors.New("接口类型异常, 请提交正确的接口类型")
|
||||
}
|
||||
// 校验采集类型是否符合规范
|
||||
switch fs.CollectType {
|
||||
case system.CollectVideo, system.CollectArticle, system.CollectActor, system.CollectRole, system.CollectWebSite:
|
||||
return nil
|
||||
default:
|
||||
return errors.New("资源类型异常, 未知的资源类型")
|
||||
}
|
||||
}
|
||||
|
||||
func apiValidityCheck() {
|
||||
|
||||
}
|
||||
67
server/controller/ProvideController.go
Normal file
67
server/controller/ProvideController.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"server/logic"
|
||||
"server/model/system"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// 提供用于第三方站点采集的API
|
||||
|
||||
// HandleProvide 返回视频列表信息
|
||||
func HandleProvide(c *gin.Context) {
|
||||
// 将请求参数封装为一个map
|
||||
var params = map[string]string{
|
||||
"t": c.DefaultQuery("t", ""),
|
||||
//"pg": c.DefaultQuery("pg", ""),
|
||||
"wd": c.DefaultQuery("wd", ""),
|
||||
"h": c.DefaultQuery("h", ""),
|
||||
"ids": c.DefaultQuery("ids", ""),
|
||||
}
|
||||
// 设置分页信息
|
||||
currentStr := c.DefaultQuery("pg", "1")
|
||||
pageSizeStr := c.DefaultQuery("limit", "20")
|
||||
current, _ := strconv.Atoi(currentStr)
|
||||
pageSize, _ := strconv.Atoi(pageSizeStr)
|
||||
page := system.Page{PageSize: pageSize, Current: current}
|
||||
// ac-请求类型 t-类别ID pg-页码 wd-搜索关键字 h=几小时内的数据 ids-数据ID 多个ID逗好分割
|
||||
var ac string = c.DefaultQuery("ac", "")
|
||||
switch ac {
|
||||
case "list":
|
||||
c.JSON(http.StatusOK, logic.PL.GetFilmListPage(params, &page))
|
||||
case "detail", "videolist":
|
||||
c.JSON(http.StatusOK, logic.PL.GetFilmDetailPage(params, &page))
|
||||
default:
|
||||
c.JSON(http.StatusOK, logic.PL.GetFilmListPage(params, &page))
|
||||
}
|
||||
}
|
||||
|
||||
// HandleProvideXml 处理返回xml格式的数据
|
||||
func HandleProvideXml(c *gin.Context) {
|
||||
// 将请求参数封装为一个map
|
||||
var params = map[string]string{
|
||||
"t": c.DefaultQuery("t", ""),
|
||||
//"pg": c.DefaultQuery("pg", ""),
|
||||
"wd": c.DefaultQuery("wd", ""),
|
||||
"h": c.DefaultQuery("h", ""),
|
||||
"ids": c.DefaultQuery("ids", ""),
|
||||
}
|
||||
// 设置分页信息
|
||||
currentStr := c.DefaultQuery("pg", "1")
|
||||
pageSizeStr := c.DefaultQuery("limit", "20")
|
||||
current, _ := strconv.Atoi(currentStr)
|
||||
pageSize, _ := strconv.Atoi(pageSizeStr)
|
||||
page := system.Page{PageSize: pageSize, Current: current}
|
||||
// ac-请求类型 t-类别ID pg-页码 wd-搜索关键字 h=几小时内的数据 ids-数据ID 多个ID逗好分割
|
||||
var ac string = c.DefaultQuery("ac", "")
|
||||
switch ac {
|
||||
case "list":
|
||||
c.XML(http.StatusOK, logic.PL.GetFilmListXmlPage(params, &page))
|
||||
case "detail", "videolist":
|
||||
c.XML(http.StatusOK, logic.PL.GetFilmDetailXmlPage(params, &page))
|
||||
default:
|
||||
c.XML(http.StatusOK, logic.PL.GetFilmListXmlPage(params, &page))
|
||||
}
|
||||
}
|
||||
@@ -1,65 +1,135 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"server/config"
|
||||
"server/logic"
|
||||
"server/model/system"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// SpiderRe 数据清零重开
|
||||
func SpiderRe(c *gin.Context) {
|
||||
// 获取指令参数
|
||||
cip := c.Query("cipher")
|
||||
if cip != config.SpiderCipher {
|
||||
// CollectFilm 开启ID对应的资源站的采集任务
|
||||
func CollectFilm(c *gin.Context) {
|
||||
id := c.DefaultQuery("id", "")
|
||||
hourStr := c.DefaultQuery("h", "0")
|
||||
if id == "" || hourStr == "0" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "指令错误无法进行此操作",
|
||||
"message": "采集任务开启失败, 缺乏必要参数",
|
||||
})
|
||||
return
|
||||
}
|
||||
go logic.SL.ReZero()
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "ReZero 任务执行已成功开启",
|
||||
})
|
||||
}
|
||||
|
||||
// FixFilmDetail 修复因网络异常造成的影片详情数据丢失
|
||||
func FixFilmDetail(c *gin.Context) {
|
||||
// 获取指令参数
|
||||
cip := c.Query("cipher")
|
||||
if cip != config.SpiderCipher {
|
||||
h, err := strconv.Atoi(hourStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "指令错误无法进行此操作",
|
||||
"message": "采集任务开启失败, hour(时长)参数不符合规范",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 如果指令正确,则执行详情数据获取
|
||||
go logic.SL.FixDetail()
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "FixDetail 任务执行已成功开启",
|
||||
})
|
||||
}
|
||||
|
||||
// RefreshSitePlay 清空附属站点影片数据并重新获取
|
||||
func RefreshSitePlay(c *gin.Context) {
|
||||
// 获取指令参数
|
||||
cip := c.Query("cipher")
|
||||
if cip != config.SpiderCipher {
|
||||
// 执行采集逻处理逻辑
|
||||
if err = logic.SL.StartCollect(id, h); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "指令错误无法进行此操作",
|
||||
"message": fmt.Sprint("采集任务开启失败: ", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 执行多站点播放数据重置
|
||||
go logic.SL.SpiderMtPlayRe()
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "SpiderMtPlayRe 任务执行已成功开启",
|
||||
"message": "采集任务已成功开启!!!",
|
||||
})
|
||||
}
|
||||
|
||||
// StarSpider 开启并执行采集任务
|
||||
func StarSpider(c *gin.Context) {
|
||||
var cp system.CollectParams
|
||||
// 获取请求参数
|
||||
if err := c.ShouldBindJSON(&cp); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "请求参数异常",
|
||||
})
|
||||
return
|
||||
}
|
||||
if cp.Time == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "采集开启失败,采集时长不能为0",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 根据 Batch 执行对应的逻辑
|
||||
if cp.Batch {
|
||||
// 执行批量采集
|
||||
if cp.Ids == nil || len(cp.Ids) <= 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "批量采集开启失败, 关联的资源站信息为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 执行批量采集
|
||||
logic.SL.BatchCollect(cp.Time, cp.Ids)
|
||||
} else {
|
||||
if len(cp.Id) <= 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "批量采集开启失败, 资源站Id获取失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
if err := logic.SL.StartCollect(cp.Id, cp.Time); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": fmt.Sprint("采集任务开启失败: ", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
// 返回成功执行的信息
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "采集任务已成功开启!!!",
|
||||
})
|
||||
}
|
||||
|
||||
// SpiderReset 重置影视数据, 清空库存, 从零开始
|
||||
func SpiderReset(c *gin.Context) {
|
||||
var cp system.CollectParams
|
||||
// 获取请求参数
|
||||
if err := c.ShouldBindJSON(&cp); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "请求参数异常",
|
||||
})
|
||||
return
|
||||
}
|
||||
if cp.Time == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "采集开启失败,采集时长不能为0",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 后期加入一些前置验证
|
||||
if len(cp.Id) <= 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "SpiderReset Failed, 资源站Id获取失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
logic.SL.ZeroCollect(cp.Time)
|
||||
}
|
||||
|
||||
// CoverFilmClass 重置覆盖影片分类信息
|
||||
func CoverFilmClass(c *gin.Context) {
|
||||
// 执行分类采集, 覆盖当前分类信息
|
||||
if err := logic.SL.FilmClassCollect(); err != nil {
|
||||
system.Failed(err.Error(), c)
|
||||
return
|
||||
}
|
||||
system.SuccessOnlyMsg("影视分类信息重置成功, 请稍等片刻后刷新页面", c)
|
||||
}
|
||||
|
||||
144
server/controller/UserController.go
Normal file
144
server/controller/UserController.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"log"
|
||||
"net/http"
|
||||
"server/config"
|
||||
"server/logic"
|
||||
"server/model/system"
|
||||
"server/plugin/common/util"
|
||||
)
|
||||
|
||||
// Login 管理员登录接口
|
||||
func Login(c *gin.Context) {
|
||||
var u system.User
|
||||
if err := c.ShouldBindJSON(&u); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "数据格式异常!!!",
|
||||
})
|
||||
return
|
||||
}
|
||||
if len(u.UserName) <= 0 || len(u.Password) <= 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "用户名和密码信息不能为空!!!",
|
||||
})
|
||||
return
|
||||
}
|
||||
token, err := logic.UL.UserLogin(u.UserName, u.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.Header("new-token", token)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "登录成功!!!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Logout 退出登录
|
||||
func Logout(c *gin.Context) {
|
||||
// 获取已登录的用户信息
|
||||
v, ok := c.Get(config.AuthUserClaims)
|
||||
if !ok {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "登录信息异常!!!",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 清除redis中存储的对应token
|
||||
uc, ok := v.(*system.UserClaims)
|
||||
if !ok {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "登录信息异常!!!",
|
||||
})
|
||||
return
|
||||
}
|
||||
err := system.ClearUserToken(uc.UserID)
|
||||
if err != nil {
|
||||
log.Println("user logOut err: ", err)
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "logout success!!!",
|
||||
})
|
||||
}
|
||||
|
||||
// UserPasswordChange 修改用户密码
|
||||
func UserPasswordChange(c *gin.Context) {
|
||||
// 接收密码修改参数
|
||||
var params map[string]string
|
||||
if err := c.ShouldBindJSON(¶ms); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "数据格式异常!!!",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 校验参数是否存在空值
|
||||
if params["password"] == "" || params["newPassword"] == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "原密码和新密码不能为空!!!",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 校验新密码是否符合规范
|
||||
if err := util.ValidPwd(params["newPassword"]); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": fmt.Sprint("密码格式校验失败: ", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
// 获取已登录的用户信息
|
||||
v, ok := c.Get(config.AuthUserClaims)
|
||||
if !ok {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": "登录信息异常!!!",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 从context中获取用户的登录信息
|
||||
uc := v.(*system.UserClaims)
|
||||
if err := logic.UL.ChangePassword(uc.UserName, params["password"], params["newPassword"]); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusFailed,
|
||||
"message": fmt.Sprint("密码修改失败: ", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
// 密码修改成功后不主动使token失效, 以免影响体验
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": StatusOk,
|
||||
"message": "密码修改成功",
|
||||
})
|
||||
}
|
||||
|
||||
func UserInfo(c *gin.Context) {
|
||||
// 从context中获取用户的相关信息
|
||||
v, ok := c.Get(config.AuthUserClaims)
|
||||
if !ok {
|
||||
system.Failed("用户信息获取失败, 未获取到用户授权信息", c)
|
||||
return
|
||||
}
|
||||
uc, ok := v.(*system.UserClaims)
|
||||
if !ok {
|
||||
system.Failed("用户信息获取失败, 户授权信息异常", c)
|
||||
return
|
||||
}
|
||||
// 通过用户ID获取用户基本信息
|
||||
info := logic.UL.GetUserInfo(uc.UserID)
|
||||
system.Success(info, "成功获取用户信息", c)
|
||||
}
|
||||
@@ -5,12 +5,14 @@ go 1.20
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.9.0
|
||||
github.com/gocolly/colly/v2 v2.1.0
|
||||
github.com/golang-jwt/jwt/v5 v5.1.0
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.5.1 // indirect
|
||||
github.com/andybalholm/cascadia v1.2.0 // indirect
|
||||
|
||||
@@ -46,11 +46,14 @@ github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA=
|
||||
github.com/gocolly/colly/v2 v2.1.0 h1:k0DuZkDoCsx51bKpRJNEmcxcp+W5N8ziuwGaSDuFoGs=
|
||||
github.com/gocolly/colly/v2 v2.1.0/go.mod h1:I2MuhsLjQ+Ex+IzK3afNS8/1qP3AedHOusRPcRdC5o0=
|
||||
github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU=
|
||||
github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
||||
99
server/logic/CronLogic.go
Normal file
99
server/logic/CronLogic.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"server/model/system"
|
||||
"server/plugin/common/util"
|
||||
"server/plugin/spider"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CronLogic struct {
|
||||
}
|
||||
|
||||
var CL *CronLogic
|
||||
|
||||
// AddFilmCrontab 添加影片更新任务
|
||||
func (cl *CronLogic) AddFilmCrontab(cv system.FilmCronVo) error {
|
||||
// 如果 spec 表达式校验失败则直接返回错误信息并终止
|
||||
if err := spider.ValidSpec(cv.Spec); err != nil {
|
||||
return err
|
||||
}
|
||||
// 生成任务信息 生成一个唯一ID 作为Task唯一标识
|
||||
task := system.FilmCollectTask{Id: util.GenerateSalt(), Ids: cv.Ids, Time: cv.Time, Spec: cv.Spec, Model: cv.Model, State: cv.State, Remark: cv.Remark}
|
||||
// 添加一条定时任务
|
||||
switch task.Model {
|
||||
case 0:
|
||||
cid, err := spider.AddAutoUpdateCron(task.Id, task.Spec)
|
||||
// 如果任务添加失败则直接返回错误信息
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprint("影视自动更新任务添加失败: ", err.Error()))
|
||||
}
|
||||
// 将定时任务Id记录到Task中
|
||||
task.Cid = cid
|
||||
case 1:
|
||||
cid, err := spider.AddFilmUpdateCron(task.Id, task.Spec)
|
||||
// 如果任务添加失败则直接返回错误信息
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprint("影视更新定时任务添加失败: ", err.Error()))
|
||||
}
|
||||
// 将定时任务Id记录到Task中
|
||||
task.Cid = cid
|
||||
}
|
||||
// 如果没有异常则将当前定时任务信息记录到redis中
|
||||
system.SaveFilmTask(task)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFilmCrontab 获取所有定时任务信息
|
||||
func (cl *CronLogic) GetFilmCrontab() []system.CronTaskVo {
|
||||
var l []system.CronTaskVo
|
||||
tl := system.GetAllFilmTask()
|
||||
for _, t := range tl {
|
||||
e := spider.GetEntryById(t.Cid)
|
||||
taskVo := system.CronTaskVo{FilmCollectTask: t, PreV: e.Prev.Format(time.DateTime), Next: e.Next.Format(time.DateTime)}
|
||||
l = append(l, taskVo)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// GetFilmCrontabById 通过ID获取对应的定时任务信息
|
||||
func (cl *CronLogic) GetFilmCrontabById(id string) (system.FilmCollectTask, error) {
|
||||
t, err := system.GetFilmTaskById(id)
|
||||
//e := spider.GetEntryById(t.Cid)
|
||||
//taskVo := system.CronTaskVo{FilmCollectTask: t, PreV: e.Prev.Format(time.DateTime), Next: e.Next.Format(time.DateTime)}
|
||||
return t, err
|
||||
}
|
||||
|
||||
// ChangeFilmCrontab 改变定时任务的状态 开启 | 停止
|
||||
func (cl *CronLogic) ChangeFilmCrontab(id string, state bool) error {
|
||||
// 通过定时任务信息的唯一标识获取对应的定时任务信息
|
||||
ft, err := system.GetFilmTaskById(id)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("定时任务停止失败: %s", err.Error()))
|
||||
}
|
||||
// 修改当前定时任务的状态为 false, 则在定时执行方法时不会执行具体逻辑
|
||||
ft.State = state
|
||||
system.UpdateFilmTask(ft)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateFilmCron 更新定时任务的状态信息
|
||||
func (cl *CronLogic) UpdateFilmCron(t system.FilmCollectTask) {
|
||||
system.UpdateFilmTask(t)
|
||||
}
|
||||
|
||||
// DelFilmCrontab 删除定时任务
|
||||
func (cl *CronLogic) DelFilmCrontab(id string) error {
|
||||
// 通过定时任务信息的唯一Id标识获取对应的定时任务信息
|
||||
ft, err := system.GetFilmTaskById(id)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("定时任务删除失败: %s", err.Error()))
|
||||
}
|
||||
// 通过定时任务EntryID移出对应的定时任务
|
||||
spider.RemoveCron(ft.Cid)
|
||||
// 将定时任务相关信息删除
|
||||
system.DelFilmTask(id)
|
||||
return nil
|
||||
}
|
||||
29
server/logic/FileLogic.go
Normal file
29
server/logic/FileLogic.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"server/config"
|
||||
"server/model/system"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type FileLogic struct {
|
||||
}
|
||||
|
||||
var FileL FileLogic
|
||||
|
||||
func (fl *FileLogic) SingleFileUpload(fileName string, uid int) string {
|
||||
// 生成图片信息
|
||||
var p = system.Picture{Link: fmt.Sprint(config.FilmPictureAccess, filepath.Base(fileName)), Uid: uid, PicType: 0}
|
||||
p.PicUid = strings.TrimSuffix(filepath.Base(fileName), filepath.Ext(fileName))
|
||||
// 记录图片信息到系统表中
|
||||
system.SaveGallery(p)
|
||||
return p.Link
|
||||
}
|
||||
|
||||
// GetPhotoPage 获取系统内的图片分页信息
|
||||
func (fl *FileLogic) GetPhotoPage(page *system.Page) []system.Picture {
|
||||
return system.GetPicturePage(page)
|
||||
|
||||
}
|
||||
161
server/logic/FilmLogic.go
Normal file
161
server/logic/FilmLogic.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"server/model/system"
|
||||
"server/plugin/common/conver"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
处理影片管理相关业务
|
||||
*/
|
||||
|
||||
type FilmLogic struct {
|
||||
}
|
||||
|
||||
var FL *FilmLogic
|
||||
|
||||
//----------------------------------------------------影片管理业务逻辑----------------------------------------------------
|
||||
|
||||
func (fl *FilmLogic) GetFilmPage(s system.SearchVo) []system.SearchInfo {
|
||||
// 获取影片检索信息分页数据
|
||||
sl := system.GetSearchPage(s)
|
||||
//
|
||||
return sl
|
||||
}
|
||||
|
||||
// GetSearchOptions 获取影片检索的select的选项options
|
||||
func (fl *FilmLogic) GetSearchOptions() map[string]any {
|
||||
var options = make(map[string]any)
|
||||
// 获取分类 options
|
||||
tree := system.GetCategoryTree()
|
||||
tree.Name = "全部分类"
|
||||
options["class"] = conver.ConvertCategoryList(tree)
|
||||
options["remarks"] = []map[string]string{{"Name": `全部`, "Value": ``}, {"Name": `完结`, "Value": `完结`}, {"Name": `未完结`, "Value": `未完结`}}
|
||||
// 获取 剧情,地区,语言, 年份 组信息 (每个分类对应的检索信息并不相同)
|
||||
var tagGroup = make(map[int64]map[string]any)
|
||||
// 遍历一级分类获取对应的标签组信息
|
||||
for _, t := range tree.Children {
|
||||
option := system.GetSearchOptions(t.Id)
|
||||
if len(option) > 0 {
|
||||
tagGroup[t.Id] = system.GetSearchOptions(t.Id)
|
||||
// 如果年份信息不存在则独立一份年份信息
|
||||
if _, ok := options["year"]; !ok {
|
||||
options["year"] = tagGroup[t.Id]["Year"]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
options["tags"] = tagGroup
|
||||
return options
|
||||
}
|
||||
|
||||
// SaveFilmDetail 自定义上传保存影片信息
|
||||
func (fl *FilmLogic) SaveFilmDetail(fd system.FilmDetailVo) error {
|
||||
// 补全影片信息
|
||||
now := time.Now()
|
||||
fd.UpdateTime = now.Format(time.DateTime)
|
||||
fd.AddTime = fd.UpdateTime
|
||||
// 生成ID, 由于是自定义上传的影片, 避免和采集站点的影片冲突, 以当前时间时间戳作为ID
|
||||
fd.Id = now.Unix()
|
||||
// 生成影片详情信息
|
||||
detail, err := conver.CovertFilmDetailVo(fd)
|
||||
if err != nil || detail.PlayList == nil {
|
||||
return errors.New("影片参数格式异常或缺少关键信息")
|
||||
}
|
||||
|
||||
// 保存影片信息
|
||||
return system.SaveDetail(detail)
|
||||
}
|
||||
|
||||
//----------------------------------------------------影片分类业务逻辑----------------------------------------------------
|
||||
|
||||
// GetFilmClassTree 获取影片分类信息
|
||||
func (fl *FilmLogic) GetFilmClassTree() system.CategoryTree {
|
||||
// 获取原本的影片分类信息
|
||||
return system.GetCategoryTree()
|
||||
}
|
||||
|
||||
func (fl *FilmLogic) GetFilmClassById(id int64) *system.CategoryTree {
|
||||
tree := system.GetCategoryTree()
|
||||
for _, c := range tree.Children {
|
||||
// 如果是一级分类, 则相等时直接返回
|
||||
if c.Id == id {
|
||||
return c
|
||||
}
|
||||
// 如果当前分类含有子分类, 则继续遍历匹配
|
||||
if c.Children != nil {
|
||||
for _, subC := range c.Children {
|
||||
if subC.Id == id {
|
||||
return subC
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateClass 更新分类信息
|
||||
func (fl *FilmLogic) UpdateClass(class system.CategoryTree) error {
|
||||
// 遍历影片分类信息
|
||||
tree := system.GetCategoryTree()
|
||||
for _, c := range tree.Children {
|
||||
// 如果是一级分类, 则相等时直接修改对应的name和show属性
|
||||
if c.Id == class.Id {
|
||||
if class.Name != "" {
|
||||
c.Name = class.Name
|
||||
}
|
||||
c.Show = class.Show
|
||||
if err := system.SaveCategoryTree(&tree); err != nil {
|
||||
return fmt.Errorf("影片分类信息更新失败: %s", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// 如果当前分类含有子分类, 则继续遍历匹配
|
||||
if c.Children != nil {
|
||||
for _, subC := range c.Children {
|
||||
if subC.Id == class.Id {
|
||||
if class.Name != "" {
|
||||
subC.Name = class.Name
|
||||
}
|
||||
subC.Show = class.Show
|
||||
if err := system.SaveCategoryTree(&tree); err != nil {
|
||||
return fmt.Errorf("影片分类信息更新失败: %s", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors.New("需要更新的分类信息不存在")
|
||||
}
|
||||
|
||||
// DelClass 删除分类信息
|
||||
func (fl *FilmLogic) DelClass(id int64) error {
|
||||
tree := system.GetCategoryTree()
|
||||
for i, c := range tree.Children {
|
||||
// 如果是一级分类, 则相等时直接返回
|
||||
if c.Id == id {
|
||||
tree.Children = append(tree.Children[:i], tree.Children[i+1:]...)
|
||||
if err := system.SaveCategoryTree(&tree); err != nil {
|
||||
return fmt.Errorf("影片分类信息删除失败: %s", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// 如果当前分类含有子分类, 则继续遍历匹配
|
||||
if c.Children != nil {
|
||||
for j, subC := range c.Children {
|
||||
if subC.Id == id {
|
||||
c.Children = append(c.Children[:j], c.Children[j+1:]...)
|
||||
if err := system.SaveCategoryTree(&tree); err != nil {
|
||||
return fmt.Errorf("影片分类信息删除失败: %s", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors.New("需要删除的分类信息不存在")
|
||||
}
|
||||
@@ -5,9 +5,8 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"regexp"
|
||||
"server/config"
|
||||
"server/model"
|
||||
"server/model/system"
|
||||
"server/plugin/db"
|
||||
"server/plugin/spider"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -27,45 +26,53 @@ func (i *IndexLogic) IndexPage() map[string]interface{} {
|
||||
//Info := make(map[string]interface{})
|
||||
// 首页请求时长较高, 采用redis进行缓存, 在定时任务更新影片时清除对应缓存
|
||||
// 判断是否存在缓存数据, 存在则直接将数据返回
|
||||
Info := model.GetCacheData(config.IndexCacheKey)
|
||||
Info := system.GetCacheData(config.IndexCacheKey)
|
||||
if Info != nil {
|
||||
return Info
|
||||
}
|
||||
Info = make(map[string]interface{})
|
||||
// 1. 首页分类数据处理 导航分类数据处理, 只提供 电影 电视剧 综艺 动漫 四大顶级分类和其子分类
|
||||
tree := model.CategoryTree{Category: &model.Category{Id: 0, Name: "分类信息"}}
|
||||
sysTree := model.GetCategoryTree()
|
||||
tree := system.CategoryTree{Category: &system.Category{Id: 0, Name: "分类信息"}}
|
||||
sysTree := system.GetCategoryTree()
|
||||
// 由于采集源数据格式不一,因此采用名称匹配
|
||||
//for _, c := range sysTree.Children {
|
||||
// switch c.Category.Name {
|
||||
// case "电影", "电影片", "连续剧", "电视剧", "综艺", "综艺片", "动漫", "动漫片":
|
||||
// tree.Children = append(tree.Children, c)
|
||||
// }
|
||||
//}
|
||||
|
||||
for _, c := range sysTree.Children {
|
||||
switch c.Category.Name {
|
||||
case "电影", "电影片", "连续剧", "电视剧", "综艺", "综艺片", "动漫", "动漫片":
|
||||
// 只针对一级分类进行处理
|
||||
if c.Show {
|
||||
tree.Children = append(tree.Children, c)
|
||||
}
|
||||
}
|
||||
|
||||
Info["category"] = tree
|
||||
// 2. 提供用于首页展示的顶级分类影片信息, 每分类 14条数据
|
||||
var list []map[string]interface{}
|
||||
for _, c := range tree.Children {
|
||||
page := model.Page{PageSize: 14, Current: 1}
|
||||
movies := model.GetMovieListByPid(c.Id, &page)
|
||||
page := system.Page{PageSize: 14, Current: 1}
|
||||
movies := system.GetMovieListByPid(c.Id, &page)
|
||||
// 获取当前分类的本月热门影片
|
||||
HotMovies := model.GetHotMovieByPid(c.Id, &page)
|
||||
HotMovies := system.GetHotMovieByPid(c.Id, &page)
|
||||
item := map[string]interface{}{"nav": c, "movies": movies, "hot": HotMovies}
|
||||
list = append(list, item)
|
||||
}
|
||||
Info["content"] = list
|
||||
// 不存在首页数据缓存时将查询数据缓存到redis中
|
||||
model.DataCache(config.IndexCacheKey, Info)
|
||||
//system.DataCache(config.IndexCacheKey, Info)
|
||||
return Info
|
||||
}
|
||||
|
||||
// GetFilmDetail 影片详情信息页面处理
|
||||
func (i *IndexLogic) GetFilmDetail(id int) model.MovieDetail {
|
||||
func (i *IndexLogic) GetFilmDetail(id int) system.MovieDetail {
|
||||
// 通过Id 获取影片search信息
|
||||
search := model.SearchInfo{}
|
||||
search := system.SearchInfo{}
|
||||
db.Mdb.Where("mid", id).First(&search)
|
||||
// 获取redis中的完整影视信息 MovieDetail:Cid11:Id24676
|
||||
movieDetail := model.GetDetailByKey(fmt.Sprintf(config.MovieDetailKey, search.Cid, search.Mid))
|
||||
movieDetail := system.GetDetailByKey(fmt.Sprintf(config.MovieDetailKey, search.Cid, search.Mid))
|
||||
//查找其他站点是否存在影片对应的播放源
|
||||
multipleSource(&movieDetail)
|
||||
return movieDetail
|
||||
@@ -76,7 +83,7 @@ func (i *IndexLogic) GetCategoryInfo() gin.H {
|
||||
// 组装nav导航所需的信息
|
||||
nav := gin.H{}
|
||||
// 1.获取所有分类信息
|
||||
tree := model.GetCategoryTree()
|
||||
tree := system.GetCategoryTree()
|
||||
// 2. 过滤出主页四大分类的tree信息
|
||||
for _, t := range tree.Children {
|
||||
switch t.Category.Name {
|
||||
@@ -94,34 +101,48 @@ func (i *IndexLogic) GetCategoryInfo() gin.H {
|
||||
return nav
|
||||
}
|
||||
|
||||
func (i *IndexLogic) GetNavCategory() []*system.Category {
|
||||
// 1.获取所有分类信息
|
||||
tree := system.GetCategoryTree()
|
||||
// 遍历一级分类返回可展示的分类数据
|
||||
var cl []*system.Category
|
||||
for _, c := range tree.Children {
|
||||
if c.Show {
|
||||
cl = append(cl, c.Category)
|
||||
}
|
||||
}
|
||||
// 返回一级分类列表数据
|
||||
return cl
|
||||
}
|
||||
|
||||
// SearchFilmInfo 获取关键字匹配的影片信息
|
||||
func (i *IndexLogic) SearchFilmInfo(key string, page *model.Page) []model.MovieBasicInfo {
|
||||
func (i *IndexLogic) SearchFilmInfo(key string, page *system.Page) []system.MovieBasicInfo {
|
||||
// 1. 从mysql中获取满足条件的数据, 每页10条
|
||||
sl := model.SearchFilmKeyword(key, page)
|
||||
sl := system.SearchFilmKeyword(key, page)
|
||||
// 2. 获取redis中的basicMovieInfo信息
|
||||
var bl []model.MovieBasicInfo
|
||||
var bl []system.MovieBasicInfo
|
||||
for _, s := range sl {
|
||||
bl = append(bl, model.GetBasicInfoByKey(fmt.Sprintf(config.MovieBasicInfoKey, s.Cid, s.Mid)))
|
||||
bl = append(bl, system.GetBasicInfoByKey(fmt.Sprintf(config.MovieBasicInfoKey, s.Cid, s.Mid)))
|
||||
}
|
||||
return bl
|
||||
}
|
||||
|
||||
// GetFilmCategory 根据Pid或Cid获取指定的分页数据
|
||||
func (i *IndexLogic) GetFilmCategory(id int64, idType string, page *model.Page) []model.MovieBasicInfo {
|
||||
func (i *IndexLogic) GetFilmCategory(id int64, idType string, page *system.Page) []system.MovieBasicInfo {
|
||||
// 1. 根据不同类型进不同的查找
|
||||
var basicList []model.MovieBasicInfo
|
||||
var basicList []system.MovieBasicInfo
|
||||
switch idType {
|
||||
case "pid":
|
||||
basicList = model.GetMovieListByPid(id, page)
|
||||
basicList = system.GetMovieListByPid(id, page)
|
||||
case "cid":
|
||||
basicList = model.GetMovieListByCid(id, page)
|
||||
basicList = system.GetMovieListByCid(id, page)
|
||||
}
|
||||
return basicList
|
||||
}
|
||||
|
||||
// GetPidCategory 获取pid对应的分类信息
|
||||
func (i *IndexLogic) GetPidCategory(pid int64) *model.CategoryTree {
|
||||
tree := model.GetCategoryTree()
|
||||
func (i *IndexLogic) GetPidCategory(pid int64) *system.CategoryTree {
|
||||
tree := system.GetCategoryTree()
|
||||
for _, t := range tree.Children {
|
||||
if t.Id == pid {
|
||||
return t
|
||||
@@ -131,7 +152,7 @@ func (i *IndexLogic) GetPidCategory(pid int64) *model.CategoryTree {
|
||||
}
|
||||
|
||||
// RelateMovie 根据当前影片信息匹配相关的影片
|
||||
func (i *IndexLogic) RelateMovie(detail model.MovieDetail, page *model.Page) []model.MovieBasicInfo {
|
||||
func (i *IndexLogic) RelateMovie(detail system.MovieDetail, page *system.Page) []system.MovieBasicInfo {
|
||||
/*
|
||||
根据当前影片信息匹配相关的影片
|
||||
1. 分类Cid,
|
||||
@@ -140,20 +161,20 @@ func (i *IndexLogic) RelateMovie(detail model.MovieDetail, page *model.Page) []m
|
||||
4. 地区 area
|
||||
5. 语言 Language
|
||||
*/
|
||||
search := model.SearchInfo{
|
||||
search := system.SearchInfo{
|
||||
Cid: detail.Cid,
|
||||
Name: detail.Name,
|
||||
ClassTag: detail.ClassTag,
|
||||
Area: detail.Area,
|
||||
Language: detail.Language,
|
||||
}
|
||||
return model.GetRelateMovieBasicInfo(search, page)
|
||||
return system.GetRelateMovieBasicInfo(search, page)
|
||||
}
|
||||
|
||||
// SearchTags 整合对应分类的搜索tag
|
||||
func (i *IndexLogic) SearchTags(pid int64) map[string]interface{} {
|
||||
// 通过pid 获取对应分类的 tags
|
||||
return model.GetSearchTag(pid)
|
||||
return system.GetSearchTag(pid)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -162,33 +183,34 @@ func (i *IndexLogic) SearchTags(pid int64) map[string]interface{} {
|
||||
2. 仅对主站点影片name进行映射关系处理并将结果添加到map中
|
||||
例如: xxx第一季 xxx
|
||||
*/
|
||||
func multipleSource(detail *model.MovieDetail) {
|
||||
func multipleSource(detail *system.MovieDetail) {
|
||||
// 整合多播放源, 初始化存储key map
|
||||
names := make(map[string]int)
|
||||
// 1. 判断detail的dbId是否存在, 存在则添加到names中作为匹配条件
|
||||
if detail.DbId > 0 {
|
||||
names[model.GenerateHashKey(detail.DbId)] = 0
|
||||
names[system.GenerateHashKey(detail.DbId)] = 0
|
||||
}
|
||||
// 2. 对name进行去除特殊格式处理
|
||||
names[model.GenerateHashKey(detail.Name)] = 0
|
||||
names[system.GenerateHashKey(detail.Name)] = 0
|
||||
// 3. 对包含第一季的name进行处理
|
||||
names[model.GenerateHashKey(regexp.MustCompile(`第一季$`).ReplaceAllString(detail.Name, ""))] = 0
|
||||
names[system.GenerateHashKey(regexp.MustCompile(`第一季$`).ReplaceAllString(detail.Name, ""))] = 0
|
||||
|
||||
// 4. 将subtitle进行切分,放入names中
|
||||
if len(detail.SubTitle) > 0 && strings.Contains(detail.SubTitle, ",") {
|
||||
for _, v := range strings.Split(detail.SubTitle, ",") {
|
||||
names[model.GenerateHashKey(v)] = 0
|
||||
names[system.GenerateHashKey(v)] = 0
|
||||
}
|
||||
}
|
||||
if len(detail.SubTitle) > 0 && strings.Contains(detail.SubTitle, "/") {
|
||||
for _, v := range strings.Split(detail.SubTitle, "/") {
|
||||
names[model.GenerateHashKey(v)] = 0
|
||||
names[system.GenerateHashKey(v)] = 0
|
||||
}
|
||||
}
|
||||
// 遍历站点列表
|
||||
for _, s := range spider.SiteList {
|
||||
sc := system.GetCollectSourceListByGrade(system.SlaveCollect)
|
||||
for _, s := range sc {
|
||||
for k, _ := range names {
|
||||
pl := model.GetMultiplePlay(s.Name, k)
|
||||
pl := system.GetMultiplePlay(s.Name, k)
|
||||
if len(pl) > 0 {
|
||||
// 如果当前站点已经匹配到数据则直接退出当前循环
|
||||
detail.PlayList = append(detail.PlayList, pl)
|
||||
@@ -196,26 +218,25 @@ func multipleSource(detail *model.MovieDetail) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// GetFilmsByTags 通过searchTag 返回满足条件的分页影片信息
|
||||
func (i *IndexLogic) GetFilmsByTags(st model.SearchTagsVO, page *model.Page) []model.MovieBasicInfo {
|
||||
func (i *IndexLogic) GetFilmsByTags(st system.SearchTagsVO, page *system.Page) []system.MovieBasicInfo {
|
||||
// 获取满足条件的影片id 列表
|
||||
sl := model.GetSearchInfosByTags(st, page)
|
||||
sl := system.GetSearchInfosByTags(st, page)
|
||||
// 通过key 获取对应影片的基本信息
|
||||
return model.GetBasicInfoBySearchInfos(sl...)
|
||||
return system.GetBasicInfoBySearchInfos(sl...)
|
||||
}
|
||||
|
||||
// GetFilmClassify 通过Pid返回当前所属分类下的首页展示数据
|
||||
func (i *IndexLogic) GetFilmClassify(pid int64, page *model.Page) map[string]interface{} {
|
||||
func (i *IndexLogic) GetFilmClassify(pid int64, page *system.Page) map[string]interface{} {
|
||||
res := make(map[string]interface{})
|
||||
// 最新上映 (上映时间)
|
||||
res["news"] = model.GetMovieListBySort(0, pid, page)
|
||||
res["news"] = system.GetMovieListBySort(0, pid, page)
|
||||
// 排行榜 (暂定为热度排行)
|
||||
res["top"] = model.GetMovieListBySort(1, pid, page)
|
||||
res["top"] = system.GetMovieListBySort(1, pid, page)
|
||||
// 最近更新 (更新时间)
|
||||
res["recent"] = model.GetMovieListBySort(2, pid, page)
|
||||
res["recent"] = system.GetMovieListBySort(2, pid, page)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
53
server/logic/ManageLogic.go
Normal file
53
server/logic/ManageLogic.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"server/model/system"
|
||||
)
|
||||
|
||||
type ManageLogic struct {
|
||||
}
|
||||
|
||||
var ML *ManageLogic
|
||||
|
||||
// GetFilmSourceList 获取采集站列表数据
|
||||
func (ml *ManageLogic) GetFilmSourceList() []system.FilmSource {
|
||||
// 返回当前已添加的采集站列表信息
|
||||
return system.GetCollectSourceList()
|
||||
}
|
||||
|
||||
func (ml *ManageLogic) GetFilmSource(id string) *system.FilmSource {
|
||||
return system.FindCollectSourceById(id)
|
||||
}
|
||||
|
||||
func (ml *ManageLogic) UpdateFilmSource(s system.FilmSource) error {
|
||||
return system.UpdateCollectSource(s)
|
||||
}
|
||||
|
||||
func (ml *ManageLogic) SaveFilmSource(s system.FilmSource) error {
|
||||
return system.AddCollectSource(s)
|
||||
}
|
||||
|
||||
func (ml *ManageLogic) DelFilmSource(id string) error {
|
||||
// 先查找是否存在对应ID的站点信息
|
||||
s := system.FindCollectSourceById(id)
|
||||
if s == nil {
|
||||
return errors.New("当前资源站信息不存在, 请勿重复操作")
|
||||
}
|
||||
// 如果是主站点则返回提示禁止直接删除
|
||||
if s.Grade == system.MasterCollect {
|
||||
return errors.New("主站点无法直接删除, 请先降级为附属站点再进行删除")
|
||||
}
|
||||
system.DelCollectResource(id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSiteBasicConfig 获取网站基本配置信息
|
||||
func (ml *ManageLogic) GetSiteBasicConfig() system.BasicConfig {
|
||||
return system.GetSiteBasic()
|
||||
}
|
||||
|
||||
// UpdateSiteBasic 更新网站配置信息
|
||||
func (ml *ManageLogic) UpdateSiteBasic(c system.BasicConfig) error {
|
||||
return system.SaveSiteBasic(c)
|
||||
}
|
||||
114
server/logic/ProvideLogic.go
Normal file
114
server/logic/ProvideLogic.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"server/config"
|
||||
"server/model/collect"
|
||||
"server/model/system"
|
||||
"server/plugin/common/conver"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ProvideLogic struct {
|
||||
}
|
||||
|
||||
var PL *ProvideLogic
|
||||
|
||||
// GetFilmDetailPage 处理请求参数, 返回filmDetail数据
|
||||
func (pl *ProvideLogic) GetFilmDetailPage(params map[string]string, page *system.Page) collect.FilmDetailLPage {
|
||||
return filmDetailPage(params, page)
|
||||
}
|
||||
|
||||
// GetFilmListPage 处理请求参数, 返回filmList数据
|
||||
func (pl *ProvideLogic) GetFilmListPage(params map[string]string, page *system.Page) collect.FilmListPage {
|
||||
dp := filmDetailPage(params, page)
|
||||
var p collect.FilmListPage = collect.FilmListPage{
|
||||
Code: dp.Code,
|
||||
Msg: dp.Msg,
|
||||
Page: dp.Page,
|
||||
PageCount: dp.PageCount,
|
||||
Limit: dp.Limit,
|
||||
Total: dp.Total,
|
||||
List: conver.DetailCovertList(dp.List),
|
||||
Class: collect.GetFilmClass(),
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (pl *ProvideLogic) GetFilmDetailXmlPage(params map[string]string, page *system.Page) collect.RssD {
|
||||
dp := filmDetailPage(params, page)
|
||||
var dxp = collect.RssD{
|
||||
Version: config.RssVersion,
|
||||
List: collect.FilmDetailPageX{
|
||||
Page: fmt.Sprint(dp.Page),
|
||||
PageCount: dp.PageCount,
|
||||
PageSize: fmt.Sprint(dp.Limit),
|
||||
RecordCount: len(dp.List),
|
||||
Videos: conver.DetailCovertXml(dp.List),
|
||||
},
|
||||
}
|
||||
return dxp
|
||||
}
|
||||
|
||||
func (pl *ProvideLogic) GetFilmListXmlPage(params map[string]string, page *system.Page) collect.RssL {
|
||||
dp := filmDetailPage(params, page)
|
||||
cl := collect.GetFilmClass()
|
||||
var dxp = collect.RssL{
|
||||
Version: config.RssVersion,
|
||||
List: collect.FilmListPageX{
|
||||
Page: dp.Page,
|
||||
PageCount: dp.PageCount,
|
||||
PageSize: dp.Limit,
|
||||
RecordCount: len(dp.List),
|
||||
Videos: conver.DetailCovertListXml(dp.List),
|
||||
},
|
||||
ClassXL: conver.ClassListCovertXml(cl),
|
||||
}
|
||||
return dxp
|
||||
}
|
||||
|
||||
func filmDetailPage(params map[string]string, page *system.Page) collect.FilmDetailLPage {
|
||||
var p collect.FilmDetailLPage = collect.FilmDetailLPage{
|
||||
Code: 1,
|
||||
Msg: "数据列表",
|
||||
Page: fmt.Sprint(page.Current),
|
||||
}
|
||||
// 如果params中的ids不为空, 则直接返回ids对应的数据
|
||||
if len(params["ids"]) > 0 {
|
||||
var ids []int64
|
||||
for _, idStr := range strings.Split(params["ids"], ",") {
|
||||
if id, err := strconv.ParseInt(idStr, 10, 64); err == nil {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
page.Total = len(ids)
|
||||
page.PageCount = int((len(ids) + page.PageSize - 1) / page.PageSize)
|
||||
// 获取id对应的数据
|
||||
for i := 0; i >= (page.Current-1)*page.PageSize && i < page.Total && i < page.Current*page.PageSize; i++ {
|
||||
if fd, err := collect.GetOriginalDetailById(ids[i]); err == nil {
|
||||
p.List = append(p.List, conver.FilterFilmDetail(fd, 0))
|
||||
}
|
||||
}
|
||||
p.PageCount = page.PageCount
|
||||
p.Limit = fmt.Sprint(page.PageSize)
|
||||
p.Total = page.Total
|
||||
return p
|
||||
}
|
||||
|
||||
// 如果请求参数中不包含 ids, 则通过条件进行对应查找
|
||||
l, err := system.FindFilmIds(params, page)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
for _, id := range l {
|
||||
if fd, e := collect.GetOriginalDetailById(id); e == nil {
|
||||
p.List = append(p.List, conver.FilterFilmDetail(fd, 0))
|
||||
}
|
||||
}
|
||||
p.PageCount = page.PageCount
|
||||
p.Limit = fmt.Sprint(page.PageSize)
|
||||
p.Total = page.Total
|
||||
return p
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"log"
|
||||
"server/config"
|
||||
"server/model"
|
||||
"server/model/system"
|
||||
"server/plugin/spider"
|
||||
)
|
||||
|
||||
@@ -13,32 +12,49 @@ type SpiderLogic struct {
|
||||
|
||||
var SL *SpiderLogic
|
||||
|
||||
// ReZero 清空所有数据从零开始拉取
|
||||
func (sl *SpiderLogic) ReZero() {
|
||||
// 如果指令正确,则执行重制
|
||||
spider.StartSpiderRe()
|
||||
// BatchCollect 批量采集
|
||||
func (sl *SpiderLogic) BatchCollect(time int, ids []string) {
|
||||
go spider.BatchCollect(time, ids...)
|
||||
}
|
||||
|
||||
// FixDetail 重新获取主站点数据信息
|
||||
func (sl *SpiderLogic) FixDetail() {
|
||||
spider.MainSiteSpider()
|
||||
log.Println("FilmDetail 重制完成!!!")
|
||||
// 先截断表中的数据
|
||||
model.TunCateSearchTable()
|
||||
// 重新扫描完整的信息到mysql中
|
||||
spider.SearchInfoToMdb()
|
||||
log.Println("SearchInfo 重制完成!!!")
|
||||
}
|
||||
|
||||
// SpiderMtPlayRe 多站点播放数据清空重新获取
|
||||
func (sl *SpiderLogic) SpiderMtPlayRe() {
|
||||
// 先清空有所附加播放源
|
||||
var keys []string
|
||||
for _, site := range spider.SiteList {
|
||||
keys = append(keys, fmt.Sprintf(config.MultipleSiteDetail, site.Name))
|
||||
// StartCollect 执行对指定站点的采集任务
|
||||
func (sl *SpiderLogic) StartCollect(id string, h int) error {
|
||||
// 先判断采集站是否存在于系统数据中
|
||||
if fs := system.FindCollectSourceById(id); fs == nil {
|
||||
return errors.New("采集任务开启失败采集站信息不存在")
|
||||
}
|
||||
model.DelMtPlay(keys)
|
||||
// 如果指令正确,则执行详情数据获取
|
||||
spider.MtSiteSpider()
|
||||
log.Println("MtSiteSpider 重制完成!!!")
|
||||
// 存在则开启协程执行采集方法
|
||||
go func() {
|
||||
err := spider.HandleCollect(id, h)
|
||||
if err != nil {
|
||||
log.Printf("资源站[%s]采集任务执行失败: %s", id, err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AutoCollect 自动采集
|
||||
func (sl *SpiderLogic) AutoCollect(time int) {
|
||||
go spider.AutoCollect(time)
|
||||
}
|
||||
|
||||
// ZeroCollect 数据清除从零开始采集
|
||||
func (sl *SpiderLogic) ZeroCollect(time int) {
|
||||
go spider.StarZero(time)
|
||||
}
|
||||
|
||||
// FilmClassCollect 影视分类采集, 直接覆盖当前分类数据
|
||||
func (sl *SpiderLogic) FilmClassCollect() error {
|
||||
l := system.GetCollectSourceListByGrade(system.MasterCollect)
|
||||
if l == nil {
|
||||
return errors.New("未获取到主采集站信息")
|
||||
}
|
||||
// 获取主站点信息, 只取第一条有效
|
||||
for _, fs := range l {
|
||||
if fs.State {
|
||||
go spider.CollectCategory(&fs)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("未获取到已启用的主采集站信息")
|
||||
}
|
||||
|
||||
66
server/logic/UserLogic.go
Normal file
66
server/logic/UserLogic.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"server/model/system"
|
||||
"server/plugin/common/util"
|
||||
)
|
||||
|
||||
type UserLogic struct {
|
||||
}
|
||||
|
||||
var UL *UserLogic
|
||||
|
||||
// UserLogin 用户登录
|
||||
func (ul *UserLogic) UserLogin(account, password string) (token string, err error) {
|
||||
// 根据 username 或 email 查询用户信息
|
||||
var u *system.User = system.GetUserByNameOrEmail(account)
|
||||
// 用户信息不存在则返回提示信息
|
||||
if u == nil {
|
||||
return "", errors.New(" 用户信息不存在!!!")
|
||||
}
|
||||
// 校验用户信息, 执行账号密码校验逻辑
|
||||
if util.PasswordEncrypt(password, u.Salt) != u.Password {
|
||||
return "", errors.New("用户名或密码错误")
|
||||
}
|
||||
// 密码校验成功后下发token
|
||||
token, err = system.GenToken(u.ID, u.UserName)
|
||||
err = system.SaveUserToken(token, u.ID)
|
||||
return
|
||||
}
|
||||
|
||||
// UserLogout 用户退出登录 注销
|
||||
func (ul *UserLogic) UserLogout() {
|
||||
// 通过用户ID清除Redis中的token信息
|
||||
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
func (ul *UserLogic) ChangePassword(account, password, newPassword string) error {
|
||||
// 根据 username 或 email 查询用户信息
|
||||
var u *system.User = system.GetUserByNameOrEmail(account)
|
||||
// 用户信息不存在则返回提示信息
|
||||
if u == nil {
|
||||
return errors.New(" 用户信息不存在!!!")
|
||||
}
|
||||
// 首先校验用户的旧密码是否正确
|
||||
if util.PasswordEncrypt(password, u.Salt) != u.Password {
|
||||
return errors.New("原密码校验失败")
|
||||
}
|
||||
// 密码校验正确则生成新的用户信息
|
||||
newUser := system.User{}
|
||||
newUser.ID = u.ID
|
||||
// 将新密码进行加密
|
||||
newUser.Password = util.PasswordEncrypt(newPassword, u.Salt)
|
||||
// 更新用户信息
|
||||
system.UpdateUserInfo(newUser)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ul *UserLogic) GetUserInfo(id uint) system.UserInfoVo {
|
||||
// 通过用户ID查询对应的用户信息
|
||||
u := system.GetUserById(id)
|
||||
// 去除user信息中的不必要信息
|
||||
var vo = system.UserInfoVo{Id: u.ID, UserName: u.UserName, Email: u.Email, Gender: u.Gender, NickName: u.NickName, Avatar: u.Avatar, Status: u.Status}
|
||||
return vo
|
||||
}
|
||||
@@ -3,7 +3,8 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"server/config"
|
||||
"server/model"
|
||||
"server/model/system"
|
||||
"server/plugin/SystemInit"
|
||||
"server/plugin/db"
|
||||
"server/plugin/spider"
|
||||
"server/router"
|
||||
@@ -24,26 +25,39 @@ func init() {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
start()
|
||||
}
|
||||
|
||||
func start() {
|
||||
// 开启前先判断是否需要执行Spider
|
||||
ExecSpider()
|
||||
//ExecSpider()
|
||||
// web服务启动后开启定时任务, 用于定期更新资源
|
||||
spider.RegularUpdateMovie()
|
||||
//spider.RegularUpdateMovie()
|
||||
|
||||
// 启动前先执行数据库内容的初始化工作
|
||||
DefaultDataInit()
|
||||
// 开启路由监听
|
||||
r := router.SetupRouter()
|
||||
_ = r.Run(fmt.Sprintf(":%s", config.ListenerPort))
|
||||
|
||||
}
|
||||
|
||||
func ExecSpider() {
|
||||
// 判断分类信息是否存在
|
||||
isStart := model.ExistsCategoryTree()
|
||||
isStart := system.ExistsCategoryTree()
|
||||
// 如果分类信息不存在则进行一次完整爬取
|
||||
if !isStart {
|
||||
DefaultDataInit()
|
||||
spider.StartSpider()
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultDataInit() {
|
||||
// 初始化影视来源列表信息
|
||||
SystemInit.SpiderInit()
|
||||
// 初始化数据库相关数据
|
||||
SystemInit.TableInIt()
|
||||
// 初始化网站基本配置信息
|
||||
SystemInit.BasicConfigInit()
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package model
|
||||
|
||||
type SearchTagsVO struct {
|
||||
Pid int64
|
||||
Cid int64
|
||||
Plot string
|
||||
Area string
|
||||
Language string
|
||||
Year int64
|
||||
Sort string
|
||||
}
|
||||
201
server/model/collect/FilmDetail.go
Normal file
201
server/model/collect/FilmDetail.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"log"
|
||||
"server/config"
|
||||
"server/plugin/db"
|
||||
)
|
||||
|
||||
/*
|
||||
视频详情接口序列化 struct
|
||||
*/
|
||||
|
||||
//-------------------------------------------------Json 格式-------------------------------------------------
|
||||
|
||||
// FilmDetailLPage 视频详情分页数据
|
||||
type FilmDetailLPage struct {
|
||||
Code int `json:"code"` // 响应状态码
|
||||
Msg string `json:"msg"` // 数据类型
|
||||
Page any `json:"page"` // 页码
|
||||
PageCount int `json:"pagecount"` // 总页数
|
||||
Limit any `json:"limit"` // 每页数据量
|
||||
Total int `json:"total"` // 总数据量
|
||||
List []FilmDetail `json:"list"` // 影片详情数据List集合
|
||||
}
|
||||
|
||||
// FilmDetail 视频详情列表
|
||||
type FilmDetail struct {
|
||||
VodID int64 `json:"vod_id"` // 影片ID
|
||||
TypeID int64 `json:"type_id"` // 分类ID
|
||||
TypeID1 int64 `json:"type_id_1"` // 一级分类ID
|
||||
GroupID int `json:"group_id"` // 用户组ID
|
||||
VodName string `json:"vod_name"` // 影片名称
|
||||
VodSub string `json:"vod_sub"` // 影片别名
|
||||
VodEn string `json:"vod_en"` // 影片名中文拼音
|
||||
VodStatus int64 `json:"vod_status"` // 影片状态
|
||||
VodLetter string `json:"vod_letter"` // 影片名首字母(大写)
|
||||
VodColor string `json:"vod_color"` // UI展示颜色
|
||||
VodTag string `json:"vod_tag"` // 索引标签
|
||||
VodClass string `json:"vod_class"` // 剧情分类标签
|
||||
VodPic string `json:"vod_pic"` // 影片封面图
|
||||
VodPicThumb string `json:"vod_pic_thumb"` // 缩略图
|
||||
VodPicSlide string `json:"vod_pic_slide"` // 幻灯图片
|
||||
VodPicScreenshot string `json:"vod_pic_screenshot"` // ?截图
|
||||
VodActor string `json:"vod_actor"` // 演员名
|
||||
VodDirector string `json:"vod_director"` // 导演
|
||||
VodWriter string `json:"vod_writer"` // 作者
|
||||
VodBehind string `json:"vod_behind"` // 幕后
|
||||
VodBlurb string `json:"vod_blurb"` // 内容简介
|
||||
VodRemarks string `json:"vod_remarks"` // 更新状态 ( 完结 || 更新值 xx集)
|
||||
VodPubDate string `json:"vod_pubdate"` // 上映日期
|
||||
VodTotal int64 `json:"vod_total"` // 总集数
|
||||
VodSerial string `json:"vod_serial"` // 连载数
|
||||
VodTv string `json:"vod_tv"` // 上映电视台
|
||||
VodWeekday string `json:"vod_weekday"` // 节目周期
|
||||
VodArea string `json:"vod_area"` // 地区
|
||||
VodLang string `json:"vod_lang"` // 语言
|
||||
VodYear string `json:"vod_year"` // 年代
|
||||
VodVersion string `json:"vod_version"` // 画质版本 DVD || HD || 720P
|
||||
VodState string `json:"vod_state"` // 影片类别 正片 || 花絮 || 预告
|
||||
VodAuthor string `json:"vod_author"` // 编辑人员
|
||||
VodJumpUrl string `json:"vod_jumpurl"` // 跳转url
|
||||
VodTpl string `json:"vod_tpl"` // 独立模板
|
||||
VodTplPlay string `json:"vod_tpl_play"` // 独立播放页模板
|
||||
VodTplDown string `json:"vod_tpl_down"` // 独立下载页模板
|
||||
VodIsEnd int64 `json:"vod_isend"` // 是否完结
|
||||
VodLock int64 `json:"vod_lock"` // 锁定
|
||||
VodLevel int64 `json:"vod_level"` // 推荐级别
|
||||
VodCopyright int64 `json:"vod_copyright"` // 版权
|
||||
VodPoints int64 `json:"vod_points"` // 积分
|
||||
VodPointsPlay int64 `json:"vod_points_play"` // 点播付费
|
||||
VodPointsDown int64 `json:"vod_points_down"` // 下载付费
|
||||
VodHits int64 `json:"vod_hits"` // 总点击量
|
||||
VodHitsDay int64 `json:"vod_hits_day"` // 日点击量
|
||||
VodHitsWeek int64 `json:"vod_hits_week"` // 周点击量
|
||||
VodHitsMonth int64 `json:"vod_hits_month"` // 月点击量
|
||||
VodDuration string `json:"vod_duration"` // 时长
|
||||
VodUp int64 `json:"vod_up"` // 顶数
|
||||
VodDown int64 `json:"vod_down"` // 踩数
|
||||
VodScore string `json:"vod_score"` // 平均分
|
||||
VodScoreAll int64 `json:"vod_score_all"` // 总评分
|
||||
VodScoreNum int64 `json:"vod_score_num"` // 评分次数
|
||||
VodTime string `json:"vod_time"` // 更新时间
|
||||
VodTimeAdd int64 `json:"vod_time_add"` // 添加时间
|
||||
VodTimeHits int64 `json:"vod_time_hits"` // 点击时间
|
||||
VodTimeMake int64 `json:"vod_time_make"` // 生成时间
|
||||
VodTrySee int64 `json:"vod_trysee"` // 试看时长
|
||||
VodDouBanID int64 `json:"vod_douban_id"` // 豆瓣ID
|
||||
VodDouBanScore string `json:"vod_douban_score"` // 豆瓣评分
|
||||
VodReRrl string `json:"vod_reurl"` // 来源地址
|
||||
VodRelVod string `json:"vod_rel_vod"` // 关联视频ids
|
||||
VodRelArt string `json:"vod_rel_art"` // 关联文章 ids
|
||||
VodPwd string `json:"vod_pwd"` // 访问内容密码
|
||||
VodPwdURL string `json:"vod_pwd_url"` // 访问密码连接
|
||||
VodPwdPlay string `json:"vod_pwd_play"` // 访问播放页密码
|
||||
VodPwdPlayURL string `json:"vod_pwd_play_url"` // 获取访问密码连接
|
||||
VodPwdDown string `json:"vod_pwd_down"` // 访问下载页密码
|
||||
VodPwdDownURL string `json:"vod_pwd_down_url"` // 获取下载密码连接
|
||||
VodContent string `json:"vod_content"` // 详细介绍
|
||||
VodPlayFrom string `json:"vod_play_from"` // 播放组
|
||||
VodPlayServer string `json:"vod_play_server"` // 播放组服务器
|
||||
VodPlayNote string `json:"vod_play_note"` // 播放组备注 (分隔符)
|
||||
VodPlayURL string `json:"vod_play_url"` // 播放地址
|
||||
VodDownFrom string `json:"vod_down_from"` // 下载组
|
||||
VodDownServer string `json:"vod_down_server"` // 瞎子服务器组
|
||||
VodDownNote string `json:"vod_down_note"` // 下载备注 (分隔符)
|
||||
VodDownURL string `json:"vod_down_url"` // 下载地址
|
||||
VodPlot int64 `json:"vod_plot"` // 是否包含分级剧情
|
||||
VodPlotName string `json:"vod_plot_name"` // 分类剧情名称
|
||||
VodPlotDetail string `json:"vod_plot_detail"` // 分集剧情详情
|
||||
TypeName string `json:"type_name"` // 分类名称
|
||||
}
|
||||
|
||||
//-------------------------------------------------Xml 格式-------------------------------------------------
|
||||
|
||||
type RssD struct {
|
||||
XMLName xml.Name `xml:"rss"`
|
||||
Version string `xml:"version,attr"`
|
||||
List FilmDetailPageX `xml:"list"`
|
||||
}
|
||||
|
||||
type CDATA struct {
|
||||
Text string `xml:",cdata"`
|
||||
}
|
||||
|
||||
type FilmDetailPageX struct {
|
||||
XMLName xml.Name `xml:"list"`
|
||||
Page string `xml:"page,attr"`
|
||||
PageCount int `xml:"pagecount,attr"`
|
||||
PageSize string `xml:"pagesize,attr"`
|
||||
RecordCount int `xml:"recordcount,attr"`
|
||||
Videos []VideoDetail `xml:"video"`
|
||||
}
|
||||
type VideoDetail struct {
|
||||
XMLName xml.Name `xml:"video"`
|
||||
Last string `xml:"last"`
|
||||
ID int64 `xml:"id"`
|
||||
Tid int64 `xml:"tid"`
|
||||
Name CDATA `xml:"name"`
|
||||
Type string `xml:"type"`
|
||||
Pic string `xml:"pic"`
|
||||
Lang string `xml:"lang"`
|
||||
Area string `xml:"area"`
|
||||
Year string `xml:"year"`
|
||||
State string `xml:"state"`
|
||||
Note CDATA `xml:"note"`
|
||||
Actor CDATA `xml:"actor"`
|
||||
Director CDATA `xml:"director"`
|
||||
DL DL `xml:"dl"`
|
||||
Des CDATA `xml:"des"`
|
||||
}
|
||||
|
||||
type DL struct {
|
||||
XMLName xml.Name `xml:"dl"`
|
||||
DD []DD `xml:"dd"`
|
||||
}
|
||||
|
||||
type DD struct {
|
||||
XMLName xml.Name `xml:"dd"`
|
||||
Flag string `xml:"flag,attr"`
|
||||
Value string `xml:",cdata"`
|
||||
}
|
||||
|
||||
//-------------------------------------------------Json 格式-------------------------------------------------
|
||||
|
||||
// BatchSaveOriginalDetail 批量保存原始影片详情数据
|
||||
func BatchSaveOriginalDetail(dl []FilmDetail) {
|
||||
for _, d := range dl {
|
||||
SaveOriginalDetail(d)
|
||||
}
|
||||
}
|
||||
|
||||
// SaveOriginalDetail 保存未处理的完整影片详情信息到redis
|
||||
func SaveOriginalDetail(fd FilmDetail) {
|
||||
data, err := json.Marshal(fd)
|
||||
if err != nil {
|
||||
log.Println("Json Marshal FilmDetail Error: ", err)
|
||||
}
|
||||
if err = db.Rdb.Set(db.Cxt, fmt.Sprintf(config.OriginalFilmDetailKey, fd.VodID), data, config.ResourceExpired).Err(); err != nil {
|
||||
log.Println("Save Original FilmDetail Error: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
// GetOriginalDetailById 获取原始的影片详情数据
|
||||
func GetOriginalDetailById(id int64) (FilmDetail, error) {
|
||||
data, err := db.Rdb.Get(db.Cxt, fmt.Sprintf(config.OriginalFilmDetailKey, id)).Result()
|
||||
if err != nil {
|
||||
log.Println("Get OriginalDetail Fail: ", err)
|
||||
}
|
||||
var fd = FilmDetail{}
|
||||
err = json.Unmarshal([]byte(data), &fd)
|
||||
if err != nil {
|
||||
log.Println("json.Unmarshal OriginalDetail Fail: ", err)
|
||||
return fd, err
|
||||
}
|
||||
|
||||
return fd, nil
|
||||
|
||||
}
|
||||
109
server/model/collect/FilmList.go
Normal file
109
server/model/collect/FilmList.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"server/config"
|
||||
"server/plugin/db"
|
||||
)
|
||||
|
||||
/*
|
||||
视频列表接口序列化 struct
|
||||
*/
|
||||
|
||||
//-------------------------------------------------Json 格式-------------------------------------------------
|
||||
|
||||
// CommonPage 影视列表接口分页数据结构体
|
||||
type CommonPage struct {
|
||||
Code int `json:"code"` // 响应状态码
|
||||
Msg string `json:"msg"` // 数据类型
|
||||
Page any `json:"page"` // 页码
|
||||
PageCount int `json:"pagecount"` // 总页数
|
||||
Limit any `json:"limit"` // 每页数据量
|
||||
Total int `json:"total"` // 总数据量
|
||||
}
|
||||
|
||||
// FilmListPage 影视列表接口分页数据结构体
|
||||
type FilmListPage struct {
|
||||
Code int `json:"code"` // 响应状态码
|
||||
Msg string `json:"msg"` // 数据类型
|
||||
Page any `json:"page"` // 页码
|
||||
PageCount int `json:"pagecount"` // 总页数
|
||||
Limit any `json:"limit"` // 每页数据量
|
||||
Total int `json:"total"` // 总数据量
|
||||
List []FilmList `json:"list"` // 影片列表数据List集合
|
||||
Class []FilmClass `json:"class"` // 影片分类信息
|
||||
}
|
||||
|
||||
// FilmList 影视列表单部影片信息结构体
|
||||
type FilmList struct {
|
||||
VodID int64 `json:"vod_id"` // 影片ID
|
||||
VodName string `json:"vod_name"` // 影片名称
|
||||
TypeID int64 `json:"type_id"` // 分类ID
|
||||
TypeName string `json:"type_name"` // 分类名称
|
||||
VodEn string `json:"vod_en"` // 影片名中文拼音
|
||||
VodTime string `json:"vod_time"` // 更新时间
|
||||
VodRemarks string `json:"vod_remarks"` // 更新状态
|
||||
VodPlayFrom string `json:"vod_play_from"` // 播放来源
|
||||
}
|
||||
|
||||
// FilmClass 影视分类信息结构体
|
||||
type FilmClass struct {
|
||||
TypeID int64 `json:"type_id"` // 分类ID
|
||||
TypePid int64 `json:"type_pid"` // 父级ID
|
||||
TypeName string `json:"type_name"` // 类型名称
|
||||
}
|
||||
|
||||
//-------------------------------------------------Xml 格式-------------------------------------------------
|
||||
|
||||
type RssL struct {
|
||||
XMLName xml.Name `xml:"rss"`
|
||||
Version string `xml:"version,attr"`
|
||||
List FilmListPageX `xml:"list"`
|
||||
ClassXL ClassXL `xml:"class"`
|
||||
}
|
||||
type FilmListPageX struct {
|
||||
XMLName xml.Name `xml:"list"`
|
||||
Page any `xml:"page,attr"`
|
||||
PageCount int `xml:"pagecount,attr"`
|
||||
PageSize any `xml:"pagesize,attr"`
|
||||
RecordCount int `xml:"recordcount,attr"`
|
||||
Videos []VideoList `xml:"video"`
|
||||
}
|
||||
|
||||
type VideoList struct {
|
||||
Last string `xml:"last"`
|
||||
ID int64 `xml:"id"`
|
||||
Tid int64 `xml:"tid"`
|
||||
Name CDATA `xml:"name"`
|
||||
Type string `xml:"type"`
|
||||
Dt string `xml:"dt"`
|
||||
Note CDATA `xml:"note"`
|
||||
}
|
||||
|
||||
type ClassXL struct {
|
||||
XMLName xml.Name `xml:"class"`
|
||||
ClassX []ClassX `xml:"ty"`
|
||||
}
|
||||
|
||||
type ClassX struct {
|
||||
XMLName xml.Name `xml:"ty"`
|
||||
ID int64 `xml:"id,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
//-------------------------------------------------redis Func-------------------------------------------------
|
||||
|
||||
// SaveFilmClass 保存影片分类列表信息
|
||||
func SaveFilmClass(list []FilmClass) error {
|
||||
data, _ := json.Marshal(list)
|
||||
return db.Rdb.Set(db.Cxt, config.FilmClassKey, data, config.ResourceExpired).Err()
|
||||
}
|
||||
|
||||
// GetFilmClass 获取分类列表信息
|
||||
func GetFilmClass() []FilmClass {
|
||||
var l []FilmClass
|
||||
data := db.Rdb.Get(db.Cxt, config.FilmClassKey).Val()
|
||||
_ = json.Unmarshal([]byte(data), &l)
|
||||
return l
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -12,6 +12,7 @@ type Category struct {
|
||||
Id int64 `json:"id"` // 分类ID
|
||||
Pid int64 `json:"pid"` // 父级分类ID
|
||||
Name string `json:"name"` // 分类名称
|
||||
Show bool `json:"show"` // 是否展示
|
||||
}
|
||||
|
||||
// CategoryTree 分类信息树形结构
|
||||
@@ -20,9 +21,12 @@ type CategoryTree struct {
|
||||
Children []*CategoryTree `json:"children"` // 子分类信息
|
||||
}
|
||||
|
||||
// 影视分类展示树形结构
|
||||
|
||||
// SaveCategoryTree 保存影片分类信息
|
||||
func SaveCategoryTree(tree string) error {
|
||||
return db.Rdb.Set(db.Cxt, config.CategoryTreeKey, tree, config.CategoryTreeExpired).Err()
|
||||
func SaveCategoryTree(tree *CategoryTree) error {
|
||||
data, _ := json.Marshal(tree)
|
||||
return db.Rdb.Set(db.Cxt, config.CategoryTreeKey, data, config.CategoryTreeExpired).Err()
|
||||
}
|
||||
|
||||
// GetCategoryTree 获取影片分类信息
|
||||
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
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
package model
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"hash/fnv"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"server/config"
|
||||
"server/plugin/common/util"
|
||||
"server/plugin/db"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -93,23 +91,6 @@ type MovieDetail struct {
|
||||
|
||||
// ===================================Redis数据交互========================================================
|
||||
|
||||
// SaveMoviePic 保存影片图片到服务器
|
||||
func SaveMoviePic(details ...*MovieDetail) {
|
||||
for _, d := range details {
|
||||
// 判断 detail 在redis中是否已经存在
|
||||
if db.Rdb.Exists(db.Cxt, fmt.Sprintf(config.MovieDetailKey, d.Cid, d.Id)).Val() == 1 {
|
||||
// 如果已经存在则直接continue
|
||||
continue
|
||||
}
|
||||
// 将影片信息中的pic图片下载保存到resource/images 文件夹下
|
||||
err := util.SaveOnlineFile(d.Picture, config.ImageDir)
|
||||
// 如果没有异常则将detail的图片路径替换为本地的保存路径
|
||||
if err == nil {
|
||||
d.Picture = fmt.Sprintf("http://127.0.0.1:%s/static/image/%s", config.ListenerPort, filepath.Base(d.Picture))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SaveDetails 保存影片详情信息到redis中 格式: MovieDetail:Cid?:Id?
|
||||
func SaveDetails(list []MovieDetail) (err error) {
|
||||
// 遍历list中的信息
|
||||
@@ -135,6 +116,27 @@ func SaveDetails(list []MovieDetail) (err error) {
|
||||
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{
|
||||
@@ -194,12 +196,6 @@ func BatchSaveSearchInfo(list []MovieDetail) {
|
||||
}
|
||||
// 将检索信息存入redis中做一次转存
|
||||
RdbSaveSearchInfo(infoList)
|
||||
|
||||
// 废弃方案, 频繁大量入库容易引起主键冲突, 事务影响速率
|
||||
// 批量插入时应对已存在数据进行检测, 使用mysql事务进行锁表
|
||||
//BatchSave(infoList)
|
||||
// 使用批量添加or更新
|
||||
//BatchSaveOrUpdate(infoList)
|
||||
}
|
||||
|
||||
// ConvertSearchInfo 将detail信息处理成 searchInfo
|
||||
@@ -239,6 +235,8 @@ func GetBasicInfoByKey(key string) MovieBasicInfo {
|
||||
data := []byte(db.Rdb.Get(db.Cxt, key).Val())
|
||||
basic := MovieBasicInfo{}
|
||||
_ = json.Unmarshal(data, &basic)
|
||||
// 执行本地图片匹配
|
||||
ReplaceBasicDetailPic(&basic)
|
||||
return basic
|
||||
}
|
||||
|
||||
@@ -248,6 +246,9 @@ func GetDetailByKey(key string) MovieDetail {
|
||||
data := []byte(db.Rdb.Get(db.Cxt, key).Val())
|
||||
detail := MovieDetail{}
|
||||
_ = json.Unmarshal(data, &detail)
|
||||
|
||||
// 执行本地图片匹配
|
||||
ReplaceDetailPic(&detail)
|
||||
return detail
|
||||
}
|
||||
|
||||
@@ -258,6 +259,9 @@ func GetBasicInfoBySearchInfos(infos ...SearchInfo) []MovieBasicInfo {
|
||||
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
|
||||
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)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package system
|
||||
|
||||
/*
|
||||
量子资源JSON解析
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"server/config"
|
||||
"server/plugin/common/param"
|
||||
"server/plugin/db"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -24,7 +25,7 @@ type SearchInfo struct {
|
||||
Pid int64 `json:"pid"` //上级分类ID
|
||||
Name string `json:"name"` // 片名
|
||||
SubTitle string `json:"subTitle"` // 影片子标题
|
||||
CName string `json:"CName"` // 分类名称
|
||||
CName string `json:"cName"` // 分类名称
|
||||
ClassTag string `json:"classTag"` //类型标签
|
||||
Area string `json:"area"` // 地区
|
||||
Language string `json:"language"` // 语言
|
||||
@@ -38,15 +39,6 @@ type SearchInfo struct {
|
||||
ReleaseStamp int64 `json:"releaseStamp"` //上映时间 时间戳
|
||||
}
|
||||
|
||||
// Page 分页信息结构体
|
||||
type Page struct {
|
||||
PageSize int `json:"pageSize"` // 每页大小
|
||||
Current int `json:"current"` // 当前页
|
||||
PageCount int `json:"pageCount"` // 总页数
|
||||
Total int `json:"total"` // 总记录数
|
||||
//List []interface{} `json:"list"` // 数据
|
||||
}
|
||||
|
||||
// Tag 影片分类标签结构体
|
||||
type Tag struct {
|
||||
Name string `json:"name"`
|
||||
@@ -71,31 +63,31 @@ func RdbSaveSearchInfo(list []SearchInfo) {
|
||||
db.Rdb.ZAdd(db.Cxt, config.SearchInfoTemp, members...)
|
||||
}
|
||||
|
||||
// ScanSearchInfo 批量扫描处理详情检索信息, 返回检索信息列表和下次开始的游标
|
||||
func ScanSearchInfo(cursor uint64, count int64) ([]SearchInfo, uint64) {
|
||||
// 1.从redis中批量扫描详情信息
|
||||
list, nextCursor := db.Rdb.ZScan(db.Cxt, config.SearchInfoTemp, cursor, "*", count).Val()
|
||||
// 2. 处理数据
|
||||
var resList []SearchInfo
|
||||
for i, s := range list {
|
||||
// 3. 判断当前是否是元素
|
||||
if i%2 == 0 {
|
||||
info := SearchInfo{}
|
||||
_ = json.Unmarshal([]byte(s), &info)
|
||||
info.Model = gorm.Model{}
|
||||
resList = append(resList, info)
|
||||
}
|
||||
}
|
||||
return resList, nextCursor
|
||||
}
|
||||
|
||||
// RemoveAll 删除所有库存数据
|
||||
func RemoveAll() {
|
||||
// FilmZero 删除所有库存数据
|
||||
func FilmZero() {
|
||||
// 删除redis中当前库存储的所有数据
|
||||
db.Rdb.FlushDB(db.Cxt)
|
||||
//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 清空附加播放源信息
|
||||
@@ -207,7 +199,10 @@ func HandleSearchTags(preTags string, k string) {
|
||||
f("、")
|
||||
default:
|
||||
// 获取 tag对应的score
|
||||
if len(preTags) == 0 || preTags == "其它" {
|
||||
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()
|
||||
@@ -226,10 +221,8 @@ func BatchHandleSearchTag(infos ...SearchInfo) {
|
||||
|
||||
// CreateSearchTable 创建存储检索信息的数据表
|
||||
func CreateSearchTable() {
|
||||
// 1. 判断表中是否存在当前表
|
||||
isExist := db.Mdb.Migrator().HasTable(&SearchInfo{})
|
||||
// 如果不存在则创建表
|
||||
if !isExist {
|
||||
if !ExistSearchTable() {
|
||||
err := db.Mdb.AutoMigrate(&SearchInfo{})
|
||||
if err != nil {
|
||||
log.Println("Create Table SearchInfo Failed: ", err)
|
||||
@@ -237,6 +230,11 @@ func CreateSearchTable() {
|
||||
}
|
||||
}
|
||||
|
||||
func ExistSearchTable() bool {
|
||||
// 1. 判断表中是否存在当前表
|
||||
return db.Mdb.Migrator().HasTable(&SearchInfo{})
|
||||
}
|
||||
|
||||
// AddSearchIndex search表中数据保存完毕后 将常用字段添加索引提高查询效率
|
||||
func AddSearchIndex() {
|
||||
var s *SearchInfo
|
||||
@@ -298,22 +296,38 @@ func BatchSaveOrUpdate(list []SearchInfo) {
|
||||
tx.Commit()
|
||||
}
|
||||
|
||||
// SaveSearchData 添加影片检索信息
|
||||
func SaveSearchData(s SearchInfo) {
|
||||
// SaveSearchInfo 添加影片检索信息
|
||||
func SaveSearchInfo(s SearchInfo) error {
|
||||
// 先查询数据库中是否存在对应记录
|
||||
isExist := SearchMovieInfo(s.Mid)
|
||||
// 如果不存在对应记录则 保存当前记录
|
||||
if !isExist {
|
||||
db.Mdb.Create(&s)
|
||||
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
|
||||
}
|
||||
|
||||
// SearchMovieInfo 通过Mid查询符合条件的数据
|
||||
func SearchMovieInfo(mid int64) bool {
|
||||
search := SearchInfo{}
|
||||
db.Mdb.Where("mid", mid).First(&search)
|
||||
// reflect.DeepEqual(a, A{})
|
||||
return !reflect.DeepEqual(search, SearchInfo{})
|
||||
// ExistSearchInfo 通过Mid查询是否存在影片的检索信息
|
||||
func ExistSearchInfo(mid int64) bool {
|
||||
var count int64
|
||||
db.Mdb.Model(&SearchInfo{}).Where("mid", mid).Count(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
// TunCateSearchTable 截断SearchInfo数据表
|
||||
@@ -325,12 +339,56 @@ func TunCateSearchTable() {
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
// 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 数据接口信息处理 =================================
|
||||
@@ -597,7 +655,7 @@ func GetSearchInfosByTags(st SearchTagsVO, page *Page) []SearchInfo {
|
||||
|
||||
// 返回分页参数
|
||||
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)
|
||||
@@ -631,6 +689,76 @@ func GetMovieListBySort(t int, pid int64, page *Page) []MovieBasicInfo {
|
||||
|
||||
}
|
||||
|
||||
// ================================= 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请求 数据缓存
|
||||
@@ -654,3 +782,36 @@ func GetCacheData(key string) map[string]interface{} {
|
||||
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"` // 状态
|
||||
}
|
||||
15
server/plugin/SystemInit/DatabaseInIt.go
Normal file
15
server/plugin/SystemInit/DatabaseInIt.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package SystemInit
|
||||
|
||||
import "server/model/system"
|
||||
|
||||
// TableInIt 初始化 mysql 数据库相关数据
|
||||
func TableInIt() {
|
||||
// 创建 User Table
|
||||
system.CreateUserTable()
|
||||
// 初始化管理员账户
|
||||
system.InitAdminAccount()
|
||||
// 创建 Search Table
|
||||
system.CreateSearchTable()
|
||||
// 创建图片信息管理表
|
||||
system.CreatePictureTable()
|
||||
}
|
||||
89
server/plugin/SystemInit/SpiderInit.go
Normal file
89
server/plugin/SystemInit/SpiderInit.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package SystemInit
|
||||
|
||||
import (
|
||||
"log"
|
||||
"server/config"
|
||||
"server/model/system"
|
||||
"server/plugin/common/util"
|
||||
"server/plugin/spider"
|
||||
)
|
||||
|
||||
// SpiderInit 数据采集相关信息初始化
|
||||
func SpiderInit() {
|
||||
FilmSourceInit()
|
||||
CollectCrontabInit()
|
||||
}
|
||||
|
||||
// FilmSourceInit 初始化预存站点信息 提供一些预存采集连Api链接
|
||||
func FilmSourceInit() {
|
||||
// 首先获取filmSourceList 数据, 如果存在则直接返回
|
||||
if system.ExistCollectSourceList() {
|
||||
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(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(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},
|
||||
}
|
||||
err := system.SaveCollectSourceList(l)
|
||||
if err != nil {
|
||||
log.Println("SaveSourceApiList Error: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CollectCrontabInit 初始化系统预定义的定时任务
|
||||
func CollectCrontabInit() {
|
||||
// 如果系统已经存在Task定时任务信息,则直接返回
|
||||
if system.ExistTask() {
|
||||
// 将系统中的定时任务重新设置到 CollectCron中
|
||||
for _, task := range system.GetAllFilmTask() {
|
||||
switch task.Model {
|
||||
case 0:
|
||||
cid, err := spider.AddAutoUpdateCron(task.Id, task.Spec)
|
||||
// 如果任务添加失败则直接返回错误信息
|
||||
if err != nil {
|
||||
log.Println("影视自动更新任务添加失败: ", err.Error())
|
||||
continue
|
||||
}
|
||||
// 将新的定时任务Id记录到Task中
|
||||
task.Cid = cid
|
||||
case 1:
|
||||
cid, err := spider.AddFilmUpdateCron(task.Id, task.Spec)
|
||||
// 如果任务添加失败则直接返回错误信息
|
||||
if err != nil {
|
||||
log.Println("影视更新定时任务添加失败: ", err.Error())
|
||||
continue
|
||||
}
|
||||
// 将定时任务Id记录到Task中
|
||||
task.Cid = cid
|
||||
}
|
||||
system.UpdateFilmTask(task)
|
||||
}
|
||||
} else {
|
||||
// 如果系统中不存在任何定时任务信息, 则添加默认的定时任务
|
||||
// 1. 添加一条默认任务, 定时更新所有已启用站点的影片信息
|
||||
// 生成任务信息
|
||||
task := system.FilmCollectTask{Id: util.GenerateSalt(), Time: config.DefaultUpdateTime, Spec: config.DefaultUpdateSpec,
|
||||
Model: 0, State: false, Remark: "每20分钟执行一次已启用站点数据的自动更新"}
|
||||
// 添加一条定时任务
|
||||
cid, err := spider.AddAutoUpdateCron(task.Id, task.Spec)
|
||||
// 如果任务添加失败则直接返回错误信息
|
||||
if err != nil {
|
||||
log.Println("影视更新定时任务添加失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
// 将定时任务Id记录到Task中
|
||||
task.Cid = cid
|
||||
// 如果没有异常则将当前定时任务信息记录到redis中
|
||||
system.SaveFilmTask(task)
|
||||
}
|
||||
|
||||
// 完成初始化后启动 Cron
|
||||
spider.CronCollect.Start()
|
||||
}
|
||||
22
server/plugin/SystemInit/WebSiteInit.go
Normal file
22
server/plugin/SystemInit/WebSiteInit.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package SystemInit
|
||||
|
||||
import "server/model/system"
|
||||
|
||||
// SiteConfigInit 网站配置初始化
|
||||
func SiteConfigInit() {
|
||||
|
||||
}
|
||||
|
||||
// BasicConfigInit 初始化网站基本配置信息
|
||||
func BasicConfigInit() {
|
||||
var bc = system.BasicConfig{
|
||||
SiteName: "GoFilm",
|
||||
Domain: "http://127.0.0.1:3600",
|
||||
Logo: "https://s2.loli.net/2023/12/05/O2SEiUcMx5aWlv4.jpg",
|
||||
Keyword: "在线视频, 免费观影",
|
||||
Describe: "自动采集, 多播放源集成,在线观影网站",
|
||||
State: true,
|
||||
Hint: "网站升级中, 暂时无法访问 !!!",
|
||||
}
|
||||
_ = system.SaveSiteBasic(bc)
|
||||
}
|
||||
283
server/plugin/common/conver/Collect.go
Normal file
283
server/plugin/common/conver/Collect.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package conver
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"log"
|
||||
"server/config"
|
||||
"server/model/collect"
|
||||
"server/model/system"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
处理 不同结构体数据之间的转化
|
||||
统一转化为内部结构体
|
||||
*/
|
||||
|
||||
// GenCategoryTree 解析处理 filmListPage数据 生成分类树形数据
|
||||
func GenCategoryTree(list []collect.FilmClass) *system.CategoryTree {
|
||||
// 遍历所有分类进行树形结构组装
|
||||
tree := &system.CategoryTree{Category: &system.Category{Id: 0, Pid: -1, Name: "分类信息", Show: true}}
|
||||
temp := make(map[int64]*system.CategoryTree)
|
||||
temp[tree.Id] = tree
|
||||
for _, c := range list {
|
||||
// 判断当前节点ID是否存在于 temp中
|
||||
category, ok := temp[c.TypeID]
|
||||
if ok {
|
||||
// 将当前节点信息保存
|
||||
category.Category = &system.Category{Id: c.TypeID, Pid: c.TypePid, Name: c.TypeName, Show: true}
|
||||
} else {
|
||||
// 如果不存在则将当前分类存放到 temp中
|
||||
category = &system.CategoryTree{Category: &system.Category{Id: c.TypeID, Pid: c.TypePid, Name: c.TypeName, Show: true}}
|
||||
temp[c.TypeID] = category
|
||||
}
|
||||
// 根据 pid获取父节点信息
|
||||
parent, ok := temp[category.Pid]
|
||||
if !ok {
|
||||
// 如果不存在父节点存在, 则将父节点存放到temp中
|
||||
temp[c.TypePid] = parent
|
||||
}
|
||||
// 将当前节点存放到父节点的Children中
|
||||
parent.Children = append(parent.Children, category)
|
||||
}
|
||||
|
||||
return tree
|
||||
}
|
||||
|
||||
// ConvertCategoryList 将分类树形数据转化为list类型
|
||||
func ConvertCategoryList(tree system.CategoryTree) []system.Category {
|
||||
var cl = []system.Category{system.Category{Id: tree.Id, Pid: tree.Pid, Name: tree.Name, Show: tree.Show}}
|
||||
for _, c := range tree.Children {
|
||||
cl = append(cl, system.Category{Id: c.Id, Pid: c.Pid, Name: c.Name, Show: c.Show})
|
||||
if c.Children != nil && len(c.Children) > 0 {
|
||||
for _, subC := range c.Children {
|
||||
cl = append(cl, system.Category{Id: subC.Id, Pid: subC.Pid, Name: subC.Name, Show: subC.Show})
|
||||
}
|
||||
}
|
||||
}
|
||||
return cl
|
||||
}
|
||||
|
||||
// ConvertFilmDetails 批量处理影片详情信息
|
||||
func ConvertFilmDetails(details []collect.FilmDetail) []system.MovieDetail {
|
||||
var dl []system.MovieDetail
|
||||
for _, d := range details {
|
||||
dl = append(dl, ConvertFilmDetail(d))
|
||||
}
|
||||
return dl
|
||||
|
||||
}
|
||||
|
||||
// ConvertFilmDetail 将影片详情数据处理转化为 system.MovieDetail
|
||||
func ConvertFilmDetail(detail collect.FilmDetail) system.MovieDetail {
|
||||
md := system.MovieDetail{
|
||||
Id: detail.VodID,
|
||||
Cid: detail.TypeID,
|
||||
Pid: detail.TypeID1,
|
||||
Name: detail.VodName,
|
||||
Picture: detail.VodPic,
|
||||
DownFrom: detail.VodDownFrom,
|
||||
MovieDescriptor: system.MovieDescriptor{
|
||||
SubTitle: detail.VodSub,
|
||||
CName: detail.TypeName,
|
||||
EnName: detail.VodEn,
|
||||
Initial: detail.VodLetter,
|
||||
ClassTag: detail.VodClass,
|
||||
Actor: detail.VodActor,
|
||||
Director: detail.VodDirector,
|
||||
Writer: detail.VodWriter,
|
||||
Blurb: detail.VodBlurb,
|
||||
Remarks: detail.VodRemarks,
|
||||
ReleaseDate: detail.VodPubDate,
|
||||
Area: detail.VodArea,
|
||||
Language: detail.VodLang,
|
||||
Year: detail.VodYear,
|
||||
State: detail.VodState,
|
||||
UpdateTime: detail.VodTime,
|
||||
AddTime: detail.VodTimeAdd,
|
||||
DbId: detail.VodDouBanID,
|
||||
DbScore: detail.VodDouBanScore,
|
||||
Hits: detail.VodHits,
|
||||
Content: detail.VodContent,
|
||||
},
|
||||
}
|
||||
// 通过分割符切分播放源信息 PlaySeparator $$$
|
||||
md.PlayFrom = strings.Split(detail.VodPlayFrom, detail.VodPlayNote)
|
||||
// v2 只保留m3u8播放源
|
||||
md.PlayList = GenFilmPlayList(detail.VodPlayURL, detail.VodPlayNote)
|
||||
md.DownloadList = GenFilmPlayList(detail.VodDownURL, detail.VodPlayNote)
|
||||
|
||||
return md
|
||||
}
|
||||
|
||||
// GenFilmPlayList 处理影片播放地址数据, 只保留m3u8与mp4格式的链接,生成playList
|
||||
func GenFilmPlayList(playUrl, separator string) [][]system.MovieUrlInfo {
|
||||
var res [][]system.MovieUrlInfo
|
||||
if separator != "" {
|
||||
// 1. 通过分隔符切分播放源地址
|
||||
for _, l := range strings.Split(playUrl, separator) {
|
||||
// 2.只对m3u8播放源 和 .mp4下载地址进行处理
|
||||
if strings.Contains(l, ".m3u8") || strings.Contains(l, ".mp4") {
|
||||
// 2. 将每组播放源对应的播放列表信息存储到列表中
|
||||
res = append(res, ConvertPlayUrl(l))
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 1.只对m3u8播放源 和 .mp4下载地址进行处理
|
||||
if strings.Contains(playUrl, ".m3u8") || strings.Contains(playUrl, ".mp4") {
|
||||
// 2. 将每组播放源对应的播放列表信息存储到列表中
|
||||
res = append(res, ConvertPlayUrl(playUrl))
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// GenAllFilmPlayList 处理影片播放地址数据, 保留全部播放链接,生成playList
|
||||
func GenAllFilmPlayList(playUrl, separator string) [][]system.MovieUrlInfo {
|
||||
var res [][]system.MovieUrlInfo
|
||||
if separator != "" {
|
||||
// 1. 通过分隔符切分播放源地址
|
||||
for _, l := range strings.Split(playUrl, separator) {
|
||||
// 将playUrl中的所有播放格式链接均进行转换保存
|
||||
res = append(res, ConvertPlayUrl(l))
|
||||
}
|
||||
return res
|
||||
}
|
||||
// 将playUrl中的所有播放格式链接均进行转换保存
|
||||
res = append(res, ConvertPlayUrl(playUrl))
|
||||
return res
|
||||
}
|
||||
|
||||
// ConvertPlayUrl 将单个playFrom的播放地址字符串处理成列表形式
|
||||
func ConvertPlayUrl(playUrl string) []system.MovieUrlInfo {
|
||||
// 对每个片源的集数和播放地址进行分割 Episode$Link#Episode$Link
|
||||
var l []system.MovieUrlInfo
|
||||
for _, p := range strings.Split(playUrl, "#") {
|
||||
// 处理 Episode$Link 形式的播放信息
|
||||
if strings.Contains(p, "$") {
|
||||
l = append(l, system.MovieUrlInfo{
|
||||
Episode: strings.Split(p, "$")[0],
|
||||
Link: strings.Split(p, "$")[1],
|
||||
})
|
||||
} else {
|
||||
l = append(l, system.MovieUrlInfo{
|
||||
Episode: "(`・ω・´)",
|
||||
Link: p,
|
||||
})
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// ConvertVirtualPicture 将影片详情信息转化为虚拟图片信息
|
||||
func ConvertVirtualPicture(details []system.MovieDetail) []system.VirtualPicture {
|
||||
var l []system.VirtualPicture
|
||||
for _, d := range details {
|
||||
if len(d.Picture) > 0 {
|
||||
l = append(l, system.VirtualPicture{Id: d.Id, Link: d.Picture})
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// ----------------------------------Provide API---------------------------------------------------
|
||||
|
||||
// DetailCovertList 将影视详情信息转化为列表信息
|
||||
func DetailCovertList(details []collect.FilmDetail) []collect.FilmList {
|
||||
var l []collect.FilmList
|
||||
for _, d := range details {
|
||||
fl := collect.FilmList{
|
||||
VodID: d.VodID,
|
||||
VodName: d.VodName,
|
||||
TypeID: d.TypeID,
|
||||
TypeName: d.TypeName,
|
||||
VodEn: d.VodEn,
|
||||
VodTime: d.VodTime,
|
||||
VodRemarks: d.VodRemarks,
|
||||
VodPlayFrom: d.VodPlayFrom,
|
||||
}
|
||||
l = append(l, fl)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// DetailCovertXml 将影片详情信息转化为Xml格式的对象
|
||||
func DetailCovertXml(details []collect.FilmDetail) []collect.VideoDetail {
|
||||
var vl []collect.VideoDetail
|
||||
for _, d := range details {
|
||||
vl = append(vl, collect.VideoDetail{
|
||||
Last: d.VodTime,
|
||||
ID: d.VodID,
|
||||
Tid: d.TypeID,
|
||||
Name: collect.CDATA{Text: d.VodName},
|
||||
Type: d.TypeName,
|
||||
Pic: d.VodPic,
|
||||
Lang: d.VodLang,
|
||||
Area: d.VodArea,
|
||||
Year: d.VodYear,
|
||||
State: d.VodState,
|
||||
Note: collect.CDATA{Text: d.VodRemarks},
|
||||
Actor: collect.CDATA{Text: d.VodActor},
|
||||
Director: collect.CDATA{Text: d.VodDirector},
|
||||
DL: collect.DL{DD: []collect.DD{collect.DD{Flag: d.VodPlayFrom, Value: d.VodPlayURL}}},
|
||||
Des: collect.CDATA{Text: d.VodContent},
|
||||
})
|
||||
}
|
||||
return vl
|
||||
}
|
||||
|
||||
// DetailCovertListXml 将影片详情信息转化为Xml格式FilmList的对象
|
||||
func DetailCovertListXml(details []collect.FilmDetail) []collect.VideoList {
|
||||
var vl []collect.VideoList
|
||||
for _, d := range details {
|
||||
vl = append(vl, collect.VideoList{
|
||||
Last: d.VodTime,
|
||||
ID: d.VodID,
|
||||
Tid: d.TypeID,
|
||||
Name: collect.CDATA{Text: d.VodName},
|
||||
Type: d.TypeName,
|
||||
Dt: d.VodPlayFrom,
|
||||
Note: collect.CDATA{Text: d.VodRemarks},
|
||||
})
|
||||
}
|
||||
s, _ := xml.Marshal(vl[0])
|
||||
log.Println(string(s))
|
||||
return vl
|
||||
}
|
||||
|
||||
// ClassListCovertXml 将影片分类列表转化为XML格式
|
||||
func ClassListCovertXml(cl []collect.FilmClass) collect.ClassXL {
|
||||
var l collect.ClassXL
|
||||
for _, c := range cl {
|
||||
l.ClassX = append(l.ClassX, collect.ClassX{ID: c.TypeID, Value: c.TypeName})
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// FilterFilmDetail 对影片详情数据进行处理, t 修饰类型 0-返回m3u8,mp4 | 1 返回 云播链接 | 2 返回全部
|
||||
func FilterFilmDetail(fd collect.FilmDetail, t int64) collect.FilmDetail {
|
||||
// 只保留 mu38 | mp4 格式的播放源, 如果包含多种格式的播放数据
|
||||
if strings.Contains(fd.VodPlayURL, fd.VodPlayNote) {
|
||||
switch t {
|
||||
case 2:
|
||||
fd.VodPlayFrom = config.PlayFormAll
|
||||
case 1, 0:
|
||||
for _, v := range strings.Split(fd.VodPlayURL, fd.VodPlayNote) {
|
||||
if t == 0 && (strings.Contains(v, ".m3u8") || strings.Contains(v, ".mp4")) {
|
||||
fd.VodPlayFrom = config.PlayForm
|
||||
fd.VodPlayURL = v
|
||||
} else if t == 1 && !strings.Contains(v, ".m3u8") && !strings.Contains(v, ".mp4") {
|
||||
fd.VodPlayFrom = config.PlayFormCloud
|
||||
fd.VodPlayURL = v
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
// 如果只有一种类型的播放链,则默认为m3u8 修改 VodPlayFrom 信息
|
||||
fd.VodPlayFrom = config.PlayForm
|
||||
}
|
||||
|
||||
return fd
|
||||
}
|
||||
53
server/plugin/common/conver/System.go
Normal file
53
server/plugin/common/conver/System.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package conver
|
||||
|
||||
import (
|
||||
"server/model/system"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
系统内部对象想换转换
|
||||
*/
|
||||
|
||||
// CovertFilmDetailVo 将 FilmDetailVo 转化为 MovieDetail
|
||||
func CovertFilmDetailVo(fd system.FilmDetailVo) (system.MovieDetail, error) {
|
||||
t, err := time.ParseInLocation(time.DateTime, fd.AddTime, time.Local)
|
||||
md := system.MovieDetail{
|
||||
Id: fd.Id,
|
||||
Cid: fd.Cid,
|
||||
Pid: fd.Pid,
|
||||
Name: fd.Name,
|
||||
Picture: fd.Picture,
|
||||
DownFrom: fd.DownFrom,
|
||||
MovieDescriptor: system.MovieDescriptor{
|
||||
SubTitle: fd.SubTitle,
|
||||
CName: fd.CName,
|
||||
EnName: fd.EnName,
|
||||
Initial: fd.Initial,
|
||||
ClassTag: fd.ClassTag,
|
||||
Actor: fd.Actor,
|
||||
Director: fd.Director,
|
||||
Writer: fd.Writer,
|
||||
Blurb: fd.Content,
|
||||
Remarks: fd.Remarks,
|
||||
ReleaseDate: fd.ReleaseDate,
|
||||
Area: fd.Area,
|
||||
Language: fd.Language,
|
||||
Year: fd.Year,
|
||||
State: fd.State,
|
||||
UpdateTime: fd.UpdateTime,
|
||||
AddTime: t.Unix(),
|
||||
DbId: fd.DbId,
|
||||
DbScore: fd.DbScore,
|
||||
Hits: fd.Hits,
|
||||
Content: fd.Content,
|
||||
},
|
||||
}
|
||||
// 通过分割符切分播放源信息 PlaySeparator $$$
|
||||
//md.PlayFrom = strings.Split(fd.VodPlayFrom, fd.VodPlayNote)
|
||||
// v2 只保留m3u8播放源
|
||||
md.PlayList = GenFilmPlayList(fd.PlayLink, "$$$")
|
||||
//md.DownloadList = GenFilmPlayList(fd.DownloadLink, fd.VodPlayNote)
|
||||
|
||||
return md, err
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
package dp
|
||||
|
||||
import (
|
||||
"server/model"
|
||||
"server/model/system"
|
||||
)
|
||||
|
||||
// =================Spider数据处理=======================
|
||||
|
||||
// CategoryTree 组装树形菜单
|
||||
func CategoryTree(list []model.ClassInfo) *model.CategoryTree {
|
||||
func CategoryTree(list []system.ClassInfo) *system.CategoryTree {
|
||||
// 遍历所有分类进行树形结构组装
|
||||
tree := &model.CategoryTree{Category: &model.Category{Id: 0, Pid: -1, Name: "分类信息"}}
|
||||
temp := make(map[int64]*model.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 {
|
||||
@@ -18,10 +18,10 @@ func CategoryTree(list []model.ClassInfo) *model.CategoryTree {
|
||||
category, ok := temp[c.Id]
|
||||
if ok {
|
||||
// 将当前节点信息保存
|
||||
category.Category = &model.Category{Id: c.Id, Pid: c.Pid, Name: c.Name}
|
||||
category.Category = &system.Category{Id: c.Id, Pid: c.Pid, Name: c.Name}
|
||||
} else {
|
||||
// 如果不存在则将当前分类存放到 temp中
|
||||
category = &model.CategoryTree{Category: &model.Category{Id: c.Id, Pid: c.Pid, Name: c.Name}}
|
||||
category = &system.CategoryTree{Category: &system.Category{Id: c.Id, Pid: c.Pid, Name: c.Name}}
|
||||
temp[c.Id] = category
|
||||
}
|
||||
// 根据 pid获取父节点信息
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package dp
|
||||
|
||||
import (
|
||||
"server/model"
|
||||
"server/model/system"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ProcessMovieListInfo 处理影片列表中的信息
|
||||
func ProcessMovieListInfo(list []model.MovieInfo) []model.Movie {
|
||||
var movies []model.Movie
|
||||
func ProcessMovieListInfo(list []system.MovieInfo) []system.Movie {
|
||||
var movies []system.Movie
|
||||
for _, info := range list {
|
||||
movies = append(movies, model.Movie{
|
||||
movies = append(movies, system.Movie{
|
||||
Id: info.Id,
|
||||
Name: info.Name,
|
||||
Cid: info.Cid,
|
||||
@@ -24,8 +24,8 @@ func ProcessMovieListInfo(list []model.MovieInfo) []model.Movie {
|
||||
}
|
||||
|
||||
// ProcessMovieDetailList 处理影片详情列表数据
|
||||
func ProcessMovieDetailList(list []model.MovieDetailInfo) []model.MovieDetail {
|
||||
var detailList []model.MovieDetail
|
||||
func ProcessMovieDetailList(list []system.MovieDetailInfo) []system.MovieDetail {
|
||||
var detailList []system.MovieDetail
|
||||
for _, d := range list {
|
||||
detailList = append(detailList, ProcessMovieDetail(d))
|
||||
}
|
||||
@@ -33,15 +33,15 @@ func ProcessMovieDetailList(list []model.MovieDetailInfo) []model.MovieDetail {
|
||||
}
|
||||
|
||||
// ProcessMovieDetail 处理单个影片详情信息
|
||||
func ProcessMovieDetail(detail model.MovieDetailInfo) model.MovieDetail {
|
||||
md := model.MovieDetail{
|
||||
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: model.MovieDescriptor{
|
||||
MovieDescriptor: system.MovieDescriptor{
|
||||
SubTitle: detail.SubTitle,
|
||||
CName: detail.CName,
|
||||
EnName: detail.EnName,
|
||||
@@ -74,21 +74,21 @@ func ProcessMovieDetail(detail model.MovieDetailInfo) model.MovieDetail {
|
||||
}
|
||||
|
||||
// ProcessPlayInfo 处理影片播放数据信息
|
||||
func ProcessPlayInfo(info, sparator string) [][]model.MovieUrlInfo {
|
||||
var res [][]model.MovieUrlInfo
|
||||
func ProcessPlayInfo(info, separator string) [][]system.MovieUrlInfo {
|
||||
var res [][]system.MovieUrlInfo
|
||||
// 1. 通过分隔符区分多个片源数据
|
||||
for _, l := range strings.Split(info, sparator) {
|
||||
for _, l := range strings.Split(info, separator) {
|
||||
// 2.对每个片源的集数和播放地址进行分割
|
||||
var item []model.MovieUrlInfo
|
||||
var item []system.MovieUrlInfo
|
||||
for _, p := range strings.Split(l, "#") {
|
||||
// 3. 处理 Episode$Link 形式的播放信息
|
||||
if strings.Contains(p, "$") {
|
||||
item = append(item, model.MovieUrlInfo{
|
||||
item = append(item, system.MovieUrlInfo{
|
||||
Episode: strings.Split(p, "$")[0],
|
||||
Link: strings.Split(p, "$")[1],
|
||||
})
|
||||
} else {
|
||||
item = append(item, model.MovieUrlInfo{
|
||||
item = append(item, system.MovieUrlInfo{
|
||||
Episode: "O(∩_∩)O",
|
||||
Link: p,
|
||||
})
|
||||
@@ -101,24 +101,24 @@ func ProcessPlayInfo(info, sparator string) [][]model.MovieUrlInfo {
|
||||
}
|
||||
|
||||
// ProcessPlayInfoV2 处理影片信息方案二 只保留m3u8播放源
|
||||
func ProcessPlayInfoV2(info, sparator string) [][]model.MovieUrlInfo {
|
||||
var res [][]model.MovieUrlInfo
|
||||
if sparator != "" {
|
||||
func ProcessPlayInfoV2(info, separator string) [][]system.MovieUrlInfo {
|
||||
var res [][]system.MovieUrlInfo
|
||||
if separator != "" {
|
||||
// 1. 通过分隔符切分播放源地址
|
||||
for _, l := range strings.Split(info, sparator) {
|
||||
for _, l := range strings.Split(info, separator) {
|
||||
// 只对m3u8播放源 和 .mp4下载地址进行处理
|
||||
if strings.Contains(l, ".m3u8") || strings.Contains(l, ".mp4") {
|
||||
// 2.对每个片源的集数和播放地址进行分割
|
||||
var item []model.MovieUrlInfo
|
||||
var item []system.MovieUrlInfo
|
||||
for _, p := range strings.Split(l, "#") {
|
||||
// 3. 处理 Episode$Link 形式的播放信息
|
||||
if strings.Contains(p, "$") {
|
||||
item = append(item, model.MovieUrlInfo{
|
||||
item = append(item, system.MovieUrlInfo{
|
||||
Episode: strings.Split(p, "$")[0],
|
||||
Link: strings.Split(p, "$")[1],
|
||||
})
|
||||
} else {
|
||||
item = append(item, model.MovieUrlInfo{
|
||||
item = append(item, system.MovieUrlInfo{
|
||||
Episode: "O(∩_∩)O",
|
||||
Link: p,
|
||||
})
|
||||
@@ -132,16 +132,16 @@ func ProcessPlayInfoV2(info, sparator string) [][]model.MovieUrlInfo {
|
||||
// 只对m3u8播放源 和 .mp4下载地址进行处理
|
||||
if strings.Contains(info, ".m3u8") || strings.Contains(info, ".mp4") {
|
||||
// 2.对每个片源的集数和播放地址进行分割
|
||||
var item []model.MovieUrlInfo
|
||||
var item []system.MovieUrlInfo
|
||||
for _, p := range strings.Split(info, "#") {
|
||||
// 3. 处理 Episode$Link 形式的播放信息
|
||||
if strings.Contains(p, "$") {
|
||||
item = append(item, model.MovieUrlInfo{
|
||||
item = append(item, system.MovieUrlInfo{
|
||||
Episode: strings.Split(p, "$")[0],
|
||||
Link: strings.Split(p, "$")[1],
|
||||
})
|
||||
} else {
|
||||
item = append(item, model.MovieUrlInfo{
|
||||
item = append(item, system.MovieUrlInfo{
|
||||
Episode: "O(∩_∩)O",
|
||||
Link: p,
|
||||
})
|
||||
|
||||
@@ -2,8 +2,10 @@ package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"server/config"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -11,11 +13,16 @@ import (
|
||||
*/
|
||||
|
||||
// SaveOnlineFile 保存网络文件, 提供下载url和保存路径, 返回保存后的文件访问url相对路径
|
||||
func SaveOnlineFile(url, dir string) (err error) {
|
||||
func SaveOnlineFile(url, dir string) (path string, err error) {
|
||||
// 请求获取文件内容
|
||||
r := &RequestInfo{Uri: url}
|
||||
ApiGet(r)
|
||||
// 创建保存文件的目录
|
||||
// 如果请求结果为空则直接跳过当前图片的同步, 等待后续触发时重试
|
||||
if len(r.Resp) <= 0 {
|
||||
err = errors.New("SyncPicture Failed: response is empty")
|
||||
return
|
||||
}
|
||||
// 成功拿到图片数据 则创建保存文件的目录
|
||||
if _, err = os.Stat(dir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, os.ModePerm)
|
||||
if err != nil {
|
||||
@@ -29,11 +36,18 @@ func SaveOnlineFile(url, dir string) (err error) {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
//_, _ = file.Write(r.Resp)
|
||||
|
||||
// 将文件内容写入到file
|
||||
writer := bufio.NewWriter(file)
|
||||
_, err = writer.Write(r.Resp)
|
||||
err = writer.Flush()
|
||||
return
|
||||
return filepath.Base(fileName), err
|
||||
|
||||
}
|
||||
|
||||
func CreateBaseDir() error {
|
||||
// 如果不存在指定目录则创建该目录
|
||||
if _, err := os.Stat(config.FilmPictureUploadDir); os.IsNotExist(err) {
|
||||
return os.MkdirAll(config.FilmPictureUploadDir, os.ModePerm)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func CreateClient() *colly.Collector {
|
||||
c := colly.NewCollector()
|
||||
|
||||
// 设置请求使用clash的socks5代理
|
||||
setProxy(c)
|
||||
//setProxy(c)
|
||||
|
||||
// 设置代理信息
|
||||
//if proxy, err := proxy.RoundRobinProxySwitcher("127.0.0.1:7890"); err != nil {
|
||||
@@ -81,8 +81,8 @@ func ApiGet(r *RequestInfo) {
|
||||
//extensions.Referer(Client)
|
||||
// 请求成功后的响应
|
||||
Client.OnResponse(func(response *colly.Response) {
|
||||
// 将响应结构封装到 RequestInfo.Resp中
|
||||
if len(response.Body) > 0 {
|
||||
if (response.StatusCode == 200 || (response.StatusCode >= 300 && response.StatusCode <= 399)) && len(response.Body) > 0 {
|
||||
// 将响应结构封装到 RequestInfo.Resp中
|
||||
r.Resp = response.Body
|
||||
} else {
|
||||
r.Resp = []byte{}
|
||||
@@ -99,6 +99,24 @@ func ApiGet(r *RequestInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
// ApiTest 处理API请求后的数据, 主测试
|
||||
func ApiTest(r *RequestInfo) error {
|
||||
// 请求成功后的响应
|
||||
Client.OnResponse(func(response *colly.Response) {
|
||||
// 判断请求状态
|
||||
if (response.StatusCode == 200 || (response.StatusCode >= 300 && response.StatusCode <= 399)) && len(response.Body) > 0 {
|
||||
// 将响应结构封装到 RequestInfo.Resp中
|
||||
r.Resp = response.Body
|
||||
} else {
|
||||
r.Resp = []byte{}
|
||||
}
|
||||
})
|
||||
// 执行请求返回错误结果
|
||||
err := Client.Visit(fmt.Sprintf("%s?%s", r.Uri, r.Params.Encode()))
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 本地代理测试
|
||||
func setProxy(c *colly.Collector) {
|
||||
proxyUrl, _ := url.Parse("socks5://127.0.0.1:7890")
|
||||
|
||||
126
server/plugin/common/util/StringUtil.go
Normal file
126
server/plugin/common/util/StringUtil.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// GenerateUUID 生成UUID
|
||||
func GenerateUUID() (uuid string) {
|
||||
b := make([]byte, 16)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
uuid = fmt.Sprintf("%X-%X-%X-%X-%X",
|
||||
b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
|
||||
return
|
||||
}
|
||||
|
||||
// RandomString 生成指定长度两倍的随机字符串
|
||||
func RandomString(length int) (uuid string) {
|
||||
b := make([]byte, length)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
uuid = fmt.Sprintf("%x", b)
|
||||
return
|
||||
}
|
||||
|
||||
// GenerateSalt 生成 length为16的随机字符串
|
||||
func GenerateSalt() (uuid string) {
|
||||
b := make([]byte, 8)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
uuid = fmt.Sprintf("%X", b)
|
||||
return
|
||||
}
|
||||
|
||||
// PasswordEncrypt 密码加密 , (password+salt) md5 * 3
|
||||
func PasswordEncrypt(password, salt string) string {
|
||||
b := []byte(fmt.Sprint(password, salt)) // 将字符串转换为字节切片
|
||||
var r [16]byte
|
||||
for i := 0; i < 3; i++ {
|
||||
r = md5.Sum(b) // 调用md5.Sum()函数进行加密
|
||||
b = []byte(hex.EncodeToString(r[:]))
|
||||
}
|
||||
return hex.EncodeToString(r[:])
|
||||
}
|
||||
|
||||
// ParsePriKeyBytes 解析私钥
|
||||
func ParsePriKeyBytes(buf []byte) (*rsa.PrivateKey, error) {
|
||||
p := &pem.Block{}
|
||||
p, buf = pem.Decode(buf)
|
||||
if p == nil {
|
||||
return nil, errors.New("private key parse error")
|
||||
}
|
||||
return x509.ParsePKCS1PrivateKey(p.Bytes)
|
||||
}
|
||||
|
||||
// ParsePubKeyBytes 解析公钥
|
||||
func ParsePubKeyBytes(buf []byte) (*rsa.PublicKey, error) {
|
||||
p, _ := pem.Decode(buf)
|
||||
if p == nil {
|
||||
return nil, errors.New("parse publicKey content nil")
|
||||
}
|
||||
pubKey, err := x509.ParsePKCS1PublicKey(p.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.New("x509.ParsePKCS1PublicKey error")
|
||||
}
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
// ValidDomain 域名校验(http://example.xxx)
|
||||
func ValidDomain(s string) bool {
|
||||
return regexp.MustCompile(`^(http|https)://[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*\.[a-z]{2,6}(:[0-9]{1,5})?$`).MatchString(s)
|
||||
}
|
||||
|
||||
// ValidIPHost 校验是否符合http|https//ip 格式
|
||||
func ValidIPHost(s string) bool {
|
||||
return regexp.MustCompile(`^(http|https)://(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(:[0-9]{1,5})?$`).MatchString(s)
|
||||
}
|
||||
|
||||
// ValidURL 校验http链接是否是符合规范的URL
|
||||
func ValidURL(s string) bool {
|
||||
_, err := url.ParseRequestURI(s)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ValidPwd(s string) error {
|
||||
if len(s) < 8 || len(s) > 12 {
|
||||
return fmt.Errorf("密码长度不符合规范, 必须为8-10位")
|
||||
}
|
||||
// 分别校验数字 大小写字母和特殊字符
|
||||
num := `[0-9]{1}`
|
||||
l := `[a-z]{1}`
|
||||
u := `[A-Z]{1}`
|
||||
symbol := `[!@#~$%^&*()+|_]{1}`
|
||||
if b, err := regexp.MatchString(num, s); !b || err != nil {
|
||||
return errors.New("密码必须包含数字 ")
|
||||
}
|
||||
if b, err := regexp.MatchString(l, s); !b || err != nil {
|
||||
return errors.New("密码必须包含小写字母")
|
||||
}
|
||||
if b, err := regexp.MatchString(u, s); !b || err != nil {
|
||||
return errors.New("密码必须包含大写字母")
|
||||
}
|
||||
if b, err := regexp.MatchString(symbol, s); !b || err != nil {
|
||||
return errors.New("密码必须包含特殊字")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package db
|
||||
import (
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
"server/config"
|
||||
)
|
||||
@@ -24,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
|
||||
}
|
||||
|
||||
42
server/plugin/middleware/Cors.go
Normal file
42
server/plugin/middleware/Cors.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Cors 开启跨域请求
|
||||
func Cors() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
method := c.Request.Method
|
||||
origin := c.Request.Header.Get("Origin") //请求头部
|
||||
if origin != "" {
|
||||
//接收客户端发送的origin (重要!)
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
//服务器支持的所有跨域请求的方法
|
||||
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
|
||||
//允许跨域设置可以返回其他子段,可以自定义字段
|
||||
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session, Content-Type")
|
||||
// 允许浏览器(客户端)可以解析的头部 (重要)
|
||||
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
|
||||
//设置缓存时间
|
||||
c.Header("Access-Control-Max-Age", "172800")
|
||||
//允许客户端传递校验信息比如 cookie (重要)
|
||||
c.Header("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
|
||||
//允许类型校验
|
||||
if method == "OPTIONS" {
|
||||
c.JSON(http.StatusOK, "ok!")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("Panic info is: %v\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
64
server/plugin/middleware/HandleJwt.go
Normal file
64
server/plugin/middleware/HandleJwt.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"net/http"
|
||||
"server/config"
|
||||
"server/model/system"
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
|
||||
// AuthToken 用户登录Token拦截
|
||||
func AuthToken() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 从请求头中获取token
|
||||
authToken := c.Request.Header.Get("auth-token")
|
||||
// 如果没有登录信息则直接清退
|
||||
if authToken == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "ok", "message": "用户未授权,请先登录."})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// 解析token中的信息
|
||||
uc, err := system.ParseToken(authToken)
|
||||
// 从Redis中获取对应的token是否存在, 如果存在则刷新token
|
||||
t := system.GetUserTokenById(uc.UserID)
|
||||
// 如果 redis中获取的token为空则登录已过期需重新登录
|
||||
if len(t) <= 0 {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"status": "ok",
|
||||
"message": "身份验证信息已过期,请重新登录!!!",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// 如果redis中存在对应token, 校验authToken是否与redis中的一致
|
||||
if t != authToken {
|
||||
// 如果不一致则证明authToken已经失效或在其他地方登录, 则需要重新登录
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"status": "ok",
|
||||
"message": "账号在其它设备登录,身份验证信息失效,请重新登录!!!",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
} else if err != nil && errors.Is(err, jwt.ErrTokenExpired) {
|
||||
// 如果token已经过期,且redis中的token与authToken 相同则更新 token
|
||||
// 生成新token
|
||||
newToken, _ := system.GenToken(uc.UserID, uc.UserName)
|
||||
// 将新token同步到redis中
|
||||
_ = system.SaveUserToken(newToken, uc.UserID)
|
||||
// 解析出新的 UserClaims
|
||||
uc, _ = system.ParseToken(newToken)
|
||||
c.Header("new-token", newToken)
|
||||
}
|
||||
|
||||
// 将UserClaims存放到context中
|
||||
c.Set(config.AuthUserClaims, uc)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
15
server/plugin/middleware/HandleXml.go
Normal file
15
server/plugin/middleware/HandleXml.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func AddXmlHeader() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if c.NegotiateFormat(gin.MIMEXML, gin.MIMEJSON) == gin.MIMEXML {
|
||||
_, _ = c.Writer.Write([]byte(xml.Header))
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -1,138 +1,181 @@
|
||||
package spider
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"server/config"
|
||||
"server/model"
|
||||
"server/plugin/common/dp"
|
||||
"server/model/system"
|
||||
"server/plugin/common/conver"
|
||||
"server/plugin/common/util"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
舍弃第一版的数据处理思路, v2版本
|
||||
直接分页获取采集站点的影片详情信息
|
||||
|
||||
采集逻辑 v3
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
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 进行匹配
|
||||
*/
|
||||
var spiderCore = &JsonCollect{}
|
||||
|
||||
const (
|
||||
MainSite = "https://cj.lzcaiji.com/api.php/provide/vod/"
|
||||
)
|
||||
// =========================通用采集方法==============================
|
||||
|
||||
type Site struct {
|
||||
Name string
|
||||
Uri string
|
||||
}
|
||||
|
||||
// SiteList 播放源采集站
|
||||
var SiteList = []Site{
|
||||
// 备用采集站
|
||||
//{"lz_bk", "https://cj.lzcaiji.com/api.php/provide/vod/"},
|
||||
//{"fs", "https://www.feisuzyapi.com/api.php/provide/vod/"},
|
||||
//{"su", "https://subocaiji.com/api.php/provide/vod/at/json"},
|
||||
//{"bf", "https://bfzyapi.com/api.php/provide/vod/"},
|
||||
//{"ff", "https://svip.ffzyapi8.com/api.php/provide/vod/"},
|
||||
|
||||
//{"lz", "https://cj.lziapi.com/api.php/provide/vod/"},
|
||||
{"kk", "https://kuaikan-api.com/api.php/provide/vod/from/kuaikan"},
|
||||
{"bf", "http://by.bfzyapi.com/api.php/provide/vod/"},
|
||||
{"ff", "https://cj.ffzyapi.com/api.php/provide/vod/"},
|
||||
}
|
||||
|
||||
// StartSpider 执行多源spider
|
||||
func StartSpider() {
|
||||
// 保存分类树
|
||||
CategoryList()
|
||||
log.Println("CategoryList 影片分类信息保存完毕")
|
||||
// 爬取主站点数据
|
||||
MainSiteSpider()
|
||||
log.Println("MainSiteSpider 主站点影片信息保存完毕")
|
||||
// 查找并创建search数据库, 保存search信息, 添加索引
|
||||
time.Sleep(time.Second * 10)
|
||||
model.CreateSearchTable()
|
||||
SearchInfoToMdb()
|
||||
model.AddSearchIndex()
|
||||
log.Println("SearchInfoToMdb 影片检索信息保存完毕")
|
||||
//获取其他站点数据
|
||||
go MtSiteSpider()
|
||||
log.Println("Spider End , 数据保存执行完成")
|
||||
time.Sleep(time.Second * 10)
|
||||
}
|
||||
|
||||
// CategoryList 获取分类数据
|
||||
func CategoryList() {
|
||||
// 设置请求参数信息
|
||||
r := util.RequestInfo{Uri: MainSite, Params: url.Values{}}
|
||||
r.Params.Set(`ac`, "list")
|
||||
r.Params.Set(`pg`, "1")
|
||||
r.Params.Set(`t`, "1")
|
||||
// 执行请求, 获取一次list数据
|
||||
util.ApiGet(&r)
|
||||
// 解析resp数据
|
||||
movieListInfo := model.MovieListInfo{}
|
||||
if len(r.Resp) <= 0 {
|
||||
log.Println("MovieListInfo数据获取异常 : Resp Is Empty")
|
||||
// HandleCollect 影视采集 id-采集站ID h-时长/h
|
||||
func HandleCollect(id string, h int) error {
|
||||
// 1. 首先通过ID获取对应采集站信息
|
||||
s := system.FindCollectSourceById(id)
|
||||
if s == nil {
|
||||
log.Println("Cannot Find Collect Source Site")
|
||||
return errors.New(" Cannot Find Collect Source Site ")
|
||||
} else if !s.State {
|
||||
log.Println(" The acquisition site was disabled ")
|
||||
return errors.New(" The acquisition site was disabled ")
|
||||
}
|
||||
// 如果是主站点且状态为启用则先获取分类tree信息
|
||||
if s.Grade == system.MasterCollect && s.State {
|
||||
// 是否存在分类树信息, 不存在则获取
|
||||
if !system.ExistsCategoryTree() {
|
||||
CollectCategory(s)
|
||||
}
|
||||
}
|
||||
|
||||
// 生成 RequestInfo
|
||||
r := util.RequestInfo{Uri: s.Uri, Params: url.Values{}}
|
||||
// 如果 h == 0 则直接返回错误信息
|
||||
if h == 0 {
|
||||
log.Println(" Collect time cannot be zero ")
|
||||
return errors.New(" Collect time cannot be zer ")
|
||||
}
|
||||
// 如果 h = -1 则进行全量采集
|
||||
if h > 0 {
|
||||
r.Params.Set("h", fmt.Sprint(h))
|
||||
}
|
||||
// 2. 首先获取分页采集的页数
|
||||
pageCount, err := spiderCore.GetPageCount(r)
|
||||
// 分页页数失败 则再进行一次尝试
|
||||
if err != nil {
|
||||
// 如果第二次获取分页页数依旧获取失败则关闭当前采集任务
|
||||
pageCount, err = spiderCore.GetPageCount(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 通过采集类型分别执行不同的采集方法
|
||||
switch s.CollectType {
|
||||
case system.CollectVideo:
|
||||
// 采集视频资源
|
||||
if pageCount <= config.MAXGoroutine*2 {
|
||||
// 少量数据不开启协程
|
||||
for i := 1; i <= pageCount; i++ {
|
||||
collectFilm(s, h, i)
|
||||
}
|
||||
} else {
|
||||
// 如果分页数量较大则开启协程
|
||||
ConcurrentPageSpider(pageCount, s, h, collectFilm)
|
||||
}
|
||||
// 视频数据采集完成后同步相关信息到mysql
|
||||
if s.Grade == system.MasterCollect {
|
||||
// 每次成功执行完都清理redis中的相关API接口数据缓存
|
||||
clearCache()
|
||||
// 执行影片信息更新操作
|
||||
if h > 0 {
|
||||
// 执行数据更新操作
|
||||
system.SyncSearchInfo(1)
|
||||
} else {
|
||||
// 清空searchInfo中的数据并重新添加, 否则执行
|
||||
system.SyncSearchInfo(0)
|
||||
}
|
||||
// 开启图片同步
|
||||
if s.SyncPictures {
|
||||
system.SyncFilmPicture()
|
||||
}
|
||||
}
|
||||
|
||||
case system.CollectArticle, system.CollectActor, system.CollectRole, system.CollectWebSite:
|
||||
log.Println("暂未开放此采集功能!!!")
|
||||
return errors.New("暂未开放此采集功能")
|
||||
}
|
||||
log.Println("Spider Task Exercise Success")
|
||||
return nil
|
||||
}
|
||||
|
||||
// CollectCategory 影视分类采集
|
||||
func CollectCategory(s *system.FilmSource) {
|
||||
// 获取分类树形数据
|
||||
categoryTree, err := spiderCore.GetCategoryTree(util.RequestInfo{Uri: s.Uri, Params: url.Values{}})
|
||||
if err != nil {
|
||||
log.Println("GetCategoryTree Error: ", err)
|
||||
return
|
||||
}
|
||||
_ = json.Unmarshal(r.Resp, &movieListInfo)
|
||||
// 获取分类列表信息
|
||||
classList := movieListInfo.Class
|
||||
// 组装分类数据信息树形结构
|
||||
categoryTree := dp.CategoryTree(classList)
|
||||
// 序列化tree
|
||||
data, _ := json.Marshal(categoryTree)
|
||||
// 保存 tree 到redis
|
||||
err := model.SaveCategoryTree(string(data))
|
||||
err = system.SaveCategoryTree(categoryTree)
|
||||
if err != nil {
|
||||
log.Println("SaveCategoryTree Error: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
// MainSiteSpider 主站点数据处理
|
||||
func MainSiteSpider() {
|
||||
// 获取分页页数
|
||||
pageCount, err := GetPageCount(util.RequestInfo{Uri: MainSite, Params: url.Values{}})
|
||||
// 主站点分页出错直接终止程序
|
||||
if err != nil {
|
||||
panic(err)
|
||||
// 影视详情采集
|
||||
func collectFilm(s *system.FilmSource, h, pg int) {
|
||||
// 生成请求参数
|
||||
r := util.RequestInfo{Uri: s.Uri, Params: url.Values{}}
|
||||
// 设置分页页数
|
||||
r.Params.Set("pg", fmt.Sprint(pg))
|
||||
// 如果 h = -1 则进行全量采集
|
||||
if h > 0 {
|
||||
r.Params.Set("h", fmt.Sprint(h))
|
||||
}
|
||||
// 开启协程加快分页请求速度
|
||||
ch := make(chan int, pageCount)
|
||||
// 执行采集方法 获取影片详情list
|
||||
list, err := spiderCore.GetFilmDetail(r)
|
||||
if err != nil || len(list) <= 0 {
|
||||
log.Println("GetMovieDetail Error: ", err)
|
||||
return
|
||||
}
|
||||
// 通过采集站 Grade 类型, 执行不同的存储逻辑
|
||||
switch s.Grade {
|
||||
case system.MasterCollect:
|
||||
// 主站点 保存完整影片详情信息到 redis
|
||||
if err = system.SaveDetails(list); err != nil {
|
||||
log.Println("SaveDetails Error: ", err)
|
||||
}
|
||||
// 如果主站点开启了图片同步, 则将图片url以及对应的mid存入ZSet集合中
|
||||
if s.SyncPictures {
|
||||
if err = system.SaveVirtualPic(conver.ConvertVirtualPicture(list)); err != nil {
|
||||
log.Println("SaveVirtualPic Error: ", err)
|
||||
}
|
||||
}
|
||||
case system.SlaveCollect:
|
||||
// 附属站点 仅保存影片播放信息到redis
|
||||
if err = system.SaveSitePlayList(s.Name, list); err != nil {
|
||||
log.Println("SaveDetails Error: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ConcurrentPageSpider 并发分页采集, 不限类型
|
||||
func ConcurrentPageSpider(capacity int, s *system.FilmSource, h int, collectFunc func(s *system.FilmSource, hour, pageNumber int)) {
|
||||
// 开启协程并发执行
|
||||
ch := make(chan int, capacity)
|
||||
waitCh := make(chan int)
|
||||
for i := 1; i <= pageCount; i++ {
|
||||
for i := 1; i <= capacity; i++ {
|
||||
ch <- i
|
||||
}
|
||||
close(ch)
|
||||
for i := 0; i < config.MAXGoroutine; i++ {
|
||||
// 开启 MAXGoroutine 数量的协程, 如果分页页数小于协程数则将协程数限制为分页页数
|
||||
var GoroutineNum = config.MAXGoroutine
|
||||
if capacity < GoroutineNum {
|
||||
GoroutineNum = capacity
|
||||
}
|
||||
for i := 0; i < GoroutineNum; i++ {
|
||||
go func() {
|
||||
defer func() { waitCh <- 0 }()
|
||||
for {
|
||||
// 从channel中获取 pageNumber
|
||||
pg, ok := <-ch
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
list, e := GetMovieDetail(pg, util.RequestInfo{Uri: MainSite, Params: url.Values{}})
|
||||
if e != nil {
|
||||
log.Println("GetMovieDetail Error: ", err)
|
||||
continue
|
||||
}
|
||||
// 保存影片详情信息到redis
|
||||
if err = model.SaveDetails(list); err != nil {
|
||||
log.Println("SaveDetails Error: ", err)
|
||||
}
|
||||
// 执行对应的采集方法
|
||||
collectFunc(s, h, pg)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -141,198 +184,36 @@ func MainSiteSpider() {
|
||||
}
|
||||
}
|
||||
|
||||
// MtSiteSpider 附属数据源处理
|
||||
func MtSiteSpider() {
|
||||
for _, s := range SiteList {
|
||||
// 执行每个站点的播放url缓存
|
||||
PlayDetailSpider(s)
|
||||
log.Println(s.Name, "playUrl 爬取完毕!!!")
|
||||
}
|
||||
}
|
||||
|
||||
// PlayDetailSpider SpiderSimpleInfo 获取单个站点的播放源
|
||||
func PlayDetailSpider(s Site) {
|
||||
// 获取分页页数
|
||||
pageCount, err := 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 := GetMovieDetail(pg, util.RequestInfo{Uri: s.Uri, Params: url.Values{}})
|
||||
if e != nil || len(list) <= 0 {
|
||||
log.Println("GetMovieDetail Error: ", err)
|
||||
continue
|
||||
}
|
||||
// 保存影片播放信息到redis
|
||||
if err = model.SaveSitePlayList(s.Name, list); err != nil {
|
||||
log.Println("SaveDetails Error: ", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
for i := 0; i < config.MAXGoroutine; i++ {
|
||||
<-waitCh
|
||||
}
|
||||
}
|
||||
|
||||
// SearchInfoToMdb 扫描redis中的检索信息, 并批量存入mysql
|
||||
func SearchInfoToMdb() {
|
||||
// 1. 从redis的Zset集合中scan扫描数据, 每次100条
|
||||
var cursor uint64 = 0
|
||||
var count int64 = 100
|
||||
for {
|
||||
infoList, nextStar := model.ScanSearchInfo(cursor, count)
|
||||
// 2. 将扫描到的数据插入mysql中
|
||||
model.BatchSave(infoList)
|
||||
// 3.设置下次开始的游标
|
||||
cursor = nextStar
|
||||
// 4. 判断迭代是否已经结束 cursor为0则表示已经迭代完毕
|
||||
if cursor == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// UpdateMovieDetail 定时更新主站点和其余播放源信息
|
||||
func UpdateMovieDetail() {
|
||||
// 更新主站系列信息
|
||||
UpdateMainDetail()
|
||||
// 更新播放源数据信息
|
||||
UpdatePlayDetail()
|
||||
}
|
||||
|
||||
// UpdateMainDetail 更新主站点的最新影片
|
||||
func UpdateMainDetail() {
|
||||
// 获取分页页数
|
||||
r := util.RequestInfo{Uri: MainSite, Params: url.Values{}}
|
||||
r.Params.Set("h", config.UpdateInterval)
|
||||
pageCount, err := GetPageCount(r)
|
||||
if err != nil {
|
||||
log.Printf("Update MianStieDetail failed")
|
||||
}
|
||||
// 保存本次更新的所有详情信息
|
||||
var ds []model.MovieDetail
|
||||
// 获取分页数据
|
||||
for i := 1; i <= pageCount; i++ {
|
||||
list, err := GetMovieDetail(i, r)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// 保存更新的影片信息, 同类型直接覆盖
|
||||
if err = model.SaveDetails(list); err != nil {
|
||||
log.Printf("Update MianStieDetail failed, SaveDetails Error ")
|
||||
}
|
||||
ds = append(ds, list...)
|
||||
}
|
||||
|
||||
// 整合详情信息切片
|
||||
var sl []model.SearchInfo
|
||||
for _, d := range ds {
|
||||
// 通过id 获取对应的详情信息
|
||||
sl = append(sl, model.ConvertSearchInfo(d))
|
||||
}
|
||||
// 调用批量保存或更新方法, 如果对应mid数据存在则更新, 否则执行插入
|
||||
model.BatchSaveOrUpdate(sl)
|
||||
}
|
||||
|
||||
// UpdatePlayDetail 更新最x小时的影片播放源数据
|
||||
func UpdatePlayDetail() {
|
||||
for _, s := range SiteList {
|
||||
// 获取单个站点的分页数
|
||||
r := util.RequestInfo{Uri: s.Uri, Params: url.Values{}}
|
||||
r.Params.Set("h", config.UpdateInterval)
|
||||
pageCount, err := GetPageCount(r)
|
||||
if err != nil {
|
||||
log.Printf("Update %s playDetail failed", s.Name)
|
||||
}
|
||||
for i := 1; i <= pageCount; i++ {
|
||||
// 获取详情信息, 保存到对应hashKey中
|
||||
list, e := GetMovieDetail(i, r)
|
||||
if e != nil || len(list) <= 0 {
|
||||
log.Println("GetMovieDetail Error: ", err)
|
||||
continue
|
||||
}
|
||||
// 保存影片播放信息到redis
|
||||
if err = model.SaveSitePlayList(s.Name, list); err != nil {
|
||||
log.Println("SaveDetails Error: ", err)
|
||||
// BatchCollect 批量采集, 采集指定的所有站点最近x小时内更新的数据
|
||||
func BatchCollect(h int, ids ...string) {
|
||||
for _, id := range ids {
|
||||
// 如果查询到对应Id的资源站信息, 且资源站处于启用状态
|
||||
if fs := system.FindCollectSourceById(id); fs != nil && fs.State {
|
||||
// 执行当前站点的采集任务
|
||||
if err := HandleCollect(fs.Id, h); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StartSpiderRe 清空存储数据,从零开始获取
|
||||
func StartSpiderRe() {
|
||||
// 删除已有的存储数据, redis 和 mysql中的存储数据全部清空
|
||||
model.RemoveAll()
|
||||
// 执行完整数据获取
|
||||
StartSpider()
|
||||
}
|
||||
|
||||
// =========================公共方法==============================
|
||||
|
||||
// GetPageCount 获取总页数
|
||||
func GetPageCount(r util.RequestInfo) (count int, err error) {
|
||||
// 发送请求获取pageCount
|
||||
r.Params.Set("ac", "detail")
|
||||
r.Params.Set("pg", "2")
|
||||
util.ApiGet(&r)
|
||||
// 判断请求结果是否为空, 如果为空直接输出错误并终止
|
||||
if len(r.Resp) <= 0 {
|
||||
err = errors.New("response is empty")
|
||||
return
|
||||
}
|
||||
// 获取pageCount
|
||||
res := model.DetailListInfo{}
|
||||
err = json.Unmarshal(r.Resp, &res)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
count = int(res.PageCount)
|
||||
return
|
||||
}
|
||||
|
||||
// GetMovieDetail 处理详情接口请求返回的数据
|
||||
func GetMovieDetail(pageNumber int, r util.RequestInfo) (list []model.MovieDetail, err error) {
|
||||
// 防止json解析异常引发panic
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
log.Println("GetMovieDetail Failed : ", e)
|
||||
// AutoCollect 自动进行对所有已启用站点的采集任务
|
||||
func AutoCollect(h int) {
|
||||
// 获取采集站中所有站点, 进行遍历
|
||||
for _, s := range system.GetCollectSourceList() {
|
||||
// 如果当前站点为启用状态 则执行 HandleCollect 进行数据采集
|
||||
if s.State {
|
||||
if err := HandleCollect(s.Id, h); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
// 设置分页请求参数
|
||||
r.Params.Set(`ac`, `detail`)
|
||||
r.Params.Set(`pg`, fmt.Sprint(pageNumber))
|
||||
util.ApiGet(&r)
|
||||
// 影视详情信息
|
||||
details := model.DetailListInfo{}
|
||||
// 如果返回数据为空则直接结束本次循环
|
||||
if len(r.Resp) <= 0 {
|
||||
err = errors.New("response is empty")
|
||||
return
|
||||
}
|
||||
// 序列化详情数据
|
||||
if err = json.Unmarshal(r.Resp, &details); err != nil {
|
||||
return
|
||||
}
|
||||
// 处理details信息
|
||||
list = dp.ProcessMovieDetailList(details.List)
|
||||
return
|
||||
}
|
||||
|
||||
// StarZero 情况站点内所有影片信息
|
||||
func StarZero(h int) {
|
||||
// 首先清除影视信息
|
||||
system.FilmZero()
|
||||
// 开启自动采集
|
||||
AutoCollect(h)
|
||||
}
|
||||
|
||||
@@ -1 +1,158 @@
|
||||
package spider
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"server/model/collect"
|
||||
"server/model/system"
|
||||
"server/plugin/common/conver"
|
||||
"server/plugin/common/util"
|
||||
)
|
||||
|
||||
/*
|
||||
Spider 数据 爬取 & 处理 & 转换
|
||||
*/
|
||||
|
||||
type FilmCollect interface {
|
||||
// GetCategoryTree 获取影视分类数据
|
||||
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)
|
||||
}
|
||||
|
||||
// ------------------------------------------------- JSON Collect -------------------------------------------------
|
||||
|
||||
// JsonCollect 处理返回值为JSON格式的采集数据
|
||||
type JsonCollect struct {
|
||||
}
|
||||
|
||||
// GetCategoryTree 获取分类树形数据
|
||||
func (jc *JsonCollect) GetCategoryTree(r util.RequestInfo) (*system.CategoryTree, error) {
|
||||
// 设置请求参数信息
|
||||
r.Params.Set(`ac`, "list")
|
||||
r.Params.Set(`pg`, "1")
|
||||
// 执行请求, 获取一次list数据
|
||||
util.ApiGet(&r)
|
||||
// 解析resp数据
|
||||
filmListPage := collect.FilmListPage{}
|
||||
if len(r.Resp) <= 0 {
|
||||
log.Println("filmListPage 数据获取异常 : Resp Is Empty")
|
||||
return nil, errors.New("filmListPage 数据获取异常 : Resp Is Empty")
|
||||
}
|
||||
err := json.Unmarshal(r.Resp, &filmListPage)
|
||||
// 获取分类列表信息
|
||||
cl := filmListPage.Class
|
||||
// 组装分类数据信息树形结构
|
||||
tree := conver.GenCategoryTree(cl)
|
||||
|
||||
// 将分类列表信息存储到redis
|
||||
_ = collect.SaveFilmClass(cl)
|
||||
|
||||
return tree, err
|
||||
}
|
||||
|
||||
// GetPageCount 获取总页数
|
||||
func (jc *JsonCollect) GetPageCount(r util.RequestInfo) (count int, err error) {
|
||||
// 发送请求获取pageCount, 默认为获取 ac = detail
|
||||
if len(r.Params.Get("ac")) <= 0 {
|
||||
r.Params.Set("ac", "detail")
|
||||
}
|
||||
r.Params.Set("pg", "1")
|
||||
util.ApiGet(&r)
|
||||
// 判断请求结果是否为空, 如果为空直接输出错误并终止
|
||||
if len(r.Resp) <= 0 {
|
||||
err = errors.New("response is empty")
|
||||
return
|
||||
}
|
||||
// 获取pageCount
|
||||
res := collect.CommonPage{}
|
||||
err = json.Unmarshal(r.Resp, &res)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
count = int(res.PageCount)
|
||||
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
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
log.Println("GetMovieDetail Failed : ", e)
|
||||
}
|
||||
}()
|
||||
// 设置分页请求参数
|
||||
r.Params.Set(`ac`, `detail`)
|
||||
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
|
||||
}
|
||||
|
||||
// ------------------------------------------------- XML Collect -------------------------------------------------
|
||||
|
||||
// XmlCollect 处理返回值为XML格式的采集数据
|
||||
type XmlCollect struct {
|
||||
}
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
package spider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/robfig/cron/v3"
|
||||
"log"
|
||||
"server/config"
|
||||
"server/model"
|
||||
"server/model/system"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
CronCollect *cron.Cron = CreateCron()
|
||||
)
|
||||
|
||||
// RegularUpdateMovie 定时更新, 每半小时获取一次站点的最近x小时数据
|
||||
func RegularUpdateMovie() {
|
||||
//创建一个定时任务对象
|
||||
c := cron.New(cron.WithSeconds())
|
||||
// 开启定时任务每x 分钟更新一次最近x小时的影片数据
|
||||
_, err := c.AddFunc(config.CornMovieUpdate, func() {
|
||||
// 添加定时任务每x 分钟更新一次最近x小时的影片数据
|
||||
taskId, err := c.AddFunc(config.CornMovieUpdate, func() {
|
||||
// 执行更新最近x小时影片的Spider
|
||||
log.Println("执行一次影片更新任务...")
|
||||
UpdateMovieDetail()
|
||||
@@ -20,7 +28,7 @@ func RegularUpdateMovie() {
|
||||
})
|
||||
|
||||
// 开启定时任务每月最后一天凌晨两点, 执行一次清库重取数据
|
||||
_, err = c.AddFunc(config.CornUpdateAll, func() {
|
||||
taskId2, err := c.AddFunc(config.CornUpdateAll, func() {
|
||||
StartSpiderRe()
|
||||
})
|
||||
|
||||
@@ -28,10 +36,91 @@ func RegularUpdateMovie() {
|
||||
log.Println("Corn Start Error: ", err)
|
||||
}
|
||||
|
||||
c.Start()
|
||||
log.Println(taskId, "------", taskId2)
|
||||
log.Printf("%v", c.Entries())
|
||||
|
||||
//c.Start()
|
||||
}
|
||||
|
||||
// StartCrontab 启动定时任务
|
||||
func StartCrontab() {
|
||||
// 从redis中读取待启动的定时任务列表
|
||||
|
||||
// 影片更新定时任务列表
|
||||
CronCollect.Start()
|
||||
}
|
||||
|
||||
func CreateCron() *cron.Cron {
|
||||
return cron.New(cron.WithSeconds())
|
||||
}
|
||||
|
||||
// AddFilmUpdateCron 添加影片更新定时任务
|
||||
func AddFilmUpdateCron(id, spec string) (cron.EntryID, error) {
|
||||
// 校验 spec 表达式的有效性
|
||||
if err := ValidSpec(spec); err != nil {
|
||||
return -99, errors.New(fmt.Sprint("定时任务添加失败,Cron表达式校验失败: ", err.Error()))
|
||||
}
|
||||
return CronCollect.AddFunc(spec, func() {
|
||||
// 通过创建任务时生成的 Id 获取任务相关数据
|
||||
ft, err := system.GetFilmTaskById(id)
|
||||
if err != nil {
|
||||
log.Println("FilmCollectCron Exec Failed: ", err)
|
||||
}
|
||||
// 如果当前定时任务状态为开启则执行对应的采集任务
|
||||
if ft.State && ft.Model == 1 {
|
||||
// 对指定ids的资源站数据进行更新操作
|
||||
BatchCollect(ft.Time, ft.Ids...)
|
||||
}
|
||||
// 任务执行完毕
|
||||
log.Printf("执行一次定时任务: Task[%s]\n", ft.Id)
|
||||
})
|
||||
}
|
||||
|
||||
// AddAutoUpdateCron 自动更新定时任务
|
||||
func AddAutoUpdateCron(id, spec string) (cron.EntryID, error) {
|
||||
// 校验 spec 表达式的有效性
|
||||
if err := ValidSpec(spec); err != nil {
|
||||
return -99, errors.New(fmt.Sprint("定时任务添加失败,Cron表达式校验失败: ", err.Error()))
|
||||
}
|
||||
return CronCollect.AddFunc(spec, func() {
|
||||
// 通过 Id 获取任务相关数据
|
||||
ft, err := system.GetFilmTaskById(id)
|
||||
if err != nil {
|
||||
log.Println("FilmCollectCron Exec Failed: ", err)
|
||||
}
|
||||
// 开启对系统中已启用站点的自动更新
|
||||
if ft.State && ft.Model == 0 {
|
||||
AutoCollect(ft.Time)
|
||||
log.Println("执行一次自动更新任务")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveCron 删除定时任务
|
||||
func RemoveCron(id cron.EntryID) {
|
||||
// 通过定时任务EntryID移出对应的定时任务
|
||||
CronCollect.Remove(id)
|
||||
}
|
||||
|
||||
// GetEntryById 返回定时任务的相关时间信息
|
||||
func GetEntryById(id cron.EntryID) cron.Entry {
|
||||
log.Printf("%+v\n", CronCollect.Entries())
|
||||
log.Println("", CronCollect.Entry(id).Next.Format(time.DateTime))
|
||||
return CronCollect.Entry(id)
|
||||
}
|
||||
|
||||
// ValidSpec 校验cron表达式是否有效 不能精确到秒
|
||||
func ValidSpec(spec string) error {
|
||||
// 自定义解释器
|
||||
parser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
|
||||
//if _, err := parser.Parse(spec); err != nil {
|
||||
// return err
|
||||
//}
|
||||
_, err := parser.Parse(spec)
|
||||
return err
|
||||
}
|
||||
|
||||
// 清理API接口数据缓存
|
||||
func clearCache() {
|
||||
model.RemoveCache(config.IndexCacheKey)
|
||||
system.RemoveCache(config.IndexCacheKey)
|
||||
}
|
||||
|
||||
275
server/plugin/spider/SpiderOriginal.go
Normal file
275
server/plugin/spider/SpiderOriginal.go
Normal file
@@ -0,0 +1,275 @@
|
||||
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()))
|
||||
}
|
||||
@@ -2,20 +2,19 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"log"
|
||||
"net/http"
|
||||
"server/config"
|
||||
"server/controller"
|
||||
"server/plugin/middleware"
|
||||
)
|
||||
|
||||
func SetupRouter() *gin.Engine {
|
||||
|
||||
r := gin.Default()
|
||||
// 开启跨域
|
||||
r.Use(Cors())
|
||||
r.Use(middleware.Cors())
|
||||
|
||||
// 静态资源配置
|
||||
r.Static("/static/image", config.ImageDir)
|
||||
r.Static(config.FilmPictureUrlPath, config.FilmPictureUploadDir)
|
||||
|
||||
r.GET(`/index`, controller.Index)
|
||||
r.GET(`/navCategory`, controller.CategoriesInfo)
|
||||
@@ -24,53 +23,88 @@ func SetupRouter() *gin.Engine {
|
||||
r.GET(`/searchFilm`, controller.SearchFilm)
|
||||
r.GET(`/filmClassify`, controller.FilmClassify)
|
||||
r.GET(`/filmClassifySearch`, controller.FilmTagSearch)
|
||||
// 弃用
|
||||
//r.GET(`/filmCategory`, controller.FilmCategory)
|
||||
//r.GET(`/filmCategory`, controller.FilmCategory) 弃用
|
||||
r.POST(`/login`, controller.Login)
|
||||
r.GET(`/logout`, middleware.AuthToken(), controller.Logout)
|
||||
r.POST(`/changePassword`, middleware.AuthToken(), controller.UserPasswordChange)
|
||||
|
||||
// 触发spider
|
||||
spiderRoute := r.Group(`/spider`)
|
||||
// 管理员API路由组
|
||||
manageRoute := r.Group(`/manage`)
|
||||
manageRoute.Use(middleware.AuthToken())
|
||||
{
|
||||
// 清空全部数据并从零开始获取数据
|
||||
spiderRoute.GET("/SpiderRe", controller.SpiderRe)
|
||||
// 获取影片详情, 用于网路不稳定导致的影片数据缺失
|
||||
spiderRoute.GET(`/FixFilmDetail`, controller.FixFilmDetail)
|
||||
spiderRoute.GET(`/RefreshSitePlay`, controller.RefreshSitePlay)
|
||||
manageRoute.GET(`/index`, controller.ManageIndex)
|
||||
// 系统相关
|
||||
sysConfig := manageRoute.Group(`/config`)
|
||||
{
|
||||
sysConfig.GET("/basic", controller.SiteBasicConfig)
|
||||
sysConfig.POST("/basic/update", controller.UpdateSiteBasic)
|
||||
sysConfig.GET("/basic/reset", controller.ResetSiteBasic)
|
||||
}
|
||||
|
||||
userRoute := manageRoute.Group(`/user`)
|
||||
{
|
||||
userRoute.GET(`/info`, controller.UserInfo)
|
||||
}
|
||||
|
||||
// 采集路相关
|
||||
collect := manageRoute.Group(`/collect`)
|
||||
{
|
||||
collect.GET(`/list`, controller.FilmSourceList)
|
||||
collect.GET(`/find`, controller.FindFilmSource)
|
||||
collect.POST(`/test`, controller.FilmSourceTest)
|
||||
collect.POST(`/add`, controller.FilmSourceAdd)
|
||||
collect.POST(`/update`, controller.FilmSourceUpdate)
|
||||
collect.POST(`/change`, controller.FilmSourceChange)
|
||||
//collect.GET(`/star`, controller.CollectFilm)
|
||||
collect.GET(`/del`, controller.FilmSourceDel)
|
||||
collect.GET(`/options`, controller.GetNormalFilmSource)
|
||||
}
|
||||
|
||||
// 定时任务相关
|
||||
collectCron := manageRoute.Group(`/cron`)
|
||||
{
|
||||
collectCron.GET(`/list`, controller.FilmCronTaskList)
|
||||
collectCron.GET(`/find`, controller.GetFilmCronTask)
|
||||
//collectCron.GET(`/options`, controller.GetNormalFilmSource)
|
||||
collectCron.POST(`/add`, controller.FilmCronAdd)
|
||||
collectCron.POST(`/update`, controller.FilmCronUpdate)
|
||||
collectCron.POST(`/change`, controller.ChangeTaskState)
|
||||
collectCron.GET(`/del`, controller.DelFilmCron)
|
||||
}
|
||||
// spider 数据采集
|
||||
spiderRoute := manageRoute.Group(`/spider`)
|
||||
{
|
||||
spiderRoute.POST(`/start`, controller.StarSpider)
|
||||
spiderRoute.GET(`/zero`, controller.SpiderReset)
|
||||
spiderRoute.GET(`/class/cover`, controller.CoverFilmClass)
|
||||
}
|
||||
// filmManage 影视管理
|
||||
filmRoute := manageRoute.Group(`/film`)
|
||||
{
|
||||
filmRoute.POST(`/add`, controller.FilmAdd)
|
||||
filmRoute.GET(`/search/list`, controller.FilmSearchPage)
|
||||
|
||||
filmRoute.GET(`/class/tree`, controller.FilmClassTree)
|
||||
filmRoute.GET(`/class/find`, controller.FindFilmClass)
|
||||
filmRoute.POST(`/class/update`, controller.UpdateFilmClass)
|
||||
filmRoute.GET(`/class/del`, controller.DelFilmClass)
|
||||
}
|
||||
|
||||
// 文件管理
|
||||
fileRoute := manageRoute.Group(`/file`)
|
||||
{
|
||||
fileRoute.POST(`/upload`, controller.SingleUpload)
|
||||
fileRoute.GET(`/list`, controller.PhotoWall)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 供第三方采集的API
|
||||
//provideRoute := r.Group(`/provide`)
|
||||
//{
|
||||
// provideRoute.GET(`/vod`, controller.HandleProvide)
|
||||
// provideRoute.GET(`/vod/xml`, middleware.AddXmlHeader(), controller.HandleProvideXml)
|
||||
//}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Cors 开启跨域请求
|
||||
func Cors() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
method := c.Request.Method
|
||||
origin := c.Request.Header.Get("Origin") //请求头部
|
||||
if origin != "" {
|
||||
//接收客户端发送的origin (重要!)
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
//服务器支持的所有跨域请求的方法
|
||||
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
|
||||
//允许跨域设置可以返回其他子段,可以自定义字段
|
||||
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session, Content-Type")
|
||||
// 允许浏览器(客户端)可以解析的头部 (重要)
|
||||
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
|
||||
//设置缓存时间
|
||||
c.Header("Access-Control-Max-Age", "172800")
|
||||
//允许客户端传递校验信息比如 cookie (重要)
|
||||
c.Header("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
|
||||
//允许类型校验
|
||||
if method == "OPTIONS" {
|
||||
c.JSON(http.StatusOK, "ok!")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("Panic info is: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user