├── db ├── drive │ ├── mongoDb │ │ └── db.go │ └── gormDb │ │ ├── db.go │ │ ├── backup.go │ │ ├── category.go │ │ ├── system.go │ │ ├── class.go │ │ ├── source.go │ │ └── content.go ├── type │ └── type.go ├── struct │ └── struct.go └── interface.go ├── util ├── version.go └── logger.go ├── dist └── dist.go ├── getter ├── interface.go ├── info.go ├── race.go ├── get.go ├── getDaily.go ├── getAll.go ├── getter.go └── tool.go ├── router ├── public.go ├── router.go ├── MiddleWare │ ├── auth.go │ ├── cors.go │ └── cache.go ├── static.go ├── front.go └── back.go ├── .gitignore ├── dockerfile ├── conf.ini ├── session ├── drive │ ├── gormDb │ │ ├── new.go │ │ └── session.go │ └── new.go ├── session.go └── interface.go ├── manager ├── session.go ├── getter.go ├── manager.go └── db.go ├── main.go ├── proxy └── proxy.go ├── LICENSE ├── docker-compose.yml ├── .github └── workflows │ ├── release.yml │ ├── auto-docker-img.yml │ └── release-docker-img.yml ├── Makefile ├── README.md ├── go.mod ├── config └── config.go └── go.sum /db/drive/mongoDb/db.go: -------------------------------------------------------------------------------- 1 | package mongoDb 2 | -------------------------------------------------------------------------------- /util/version.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | const Version = "1.2.0" 4 | -------------------------------------------------------------------------------- /db/type/type.go: -------------------------------------------------------------------------------- 1 | package _type 2 | 3 | const ( 4 | Gorm = iota // gorm 5 | Mongo // mongo 6 | ) 7 | -------------------------------------------------------------------------------- /dist/dist.go: -------------------------------------------------------------------------------- 1 | package dist 2 | 3 | import embed "embed" 4 | 5 | //go:embed front 6 | var Front embed.FS 7 | 8 | //go:embed admin 9 | var Admin embed.FS 10 | -------------------------------------------------------------------------------- /getter/interface.go: -------------------------------------------------------------------------------- 1 | package getter 2 | 3 | type getter interface { 4 | JudgeGetting() bool 5 | ReGet() 6 | StartGet() 7 | StopGet() 8 | Rename() 9 | } 10 | -------------------------------------------------------------------------------- /getter/info.go: -------------------------------------------------------------------------------- 1 | package getter 2 | 3 | func (here *Getter) Rename(name string) { 4 | here.name = name 5 | } 6 | 7 | func (here *Getter) Reurl(url string) { 8 | here.url = url 9 | } 10 | -------------------------------------------------------------------------------- /router/public.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | mm "movie/manager" 5 | ) 6 | 7 | type Movie struct { 8 | Movies []mm.Movie `json:"movies"` 9 | PgCount int `json:"pgCount"` 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | clover-db 2 | *.exe 3 | json 4 | *.7z 5 | *.pprof 6 | *.json 7 | *.db 8 | .idea 9 | movie 10 | movie-getter 11 | dist/front 12 | dist/admin 13 | src 14 | zig-cache 15 | movie-getter_linux_arm 16 | movie-getter_linux_x86-64_musl 17 | movie-getter_windows_x86-64 18 | movie-getter_linux-arm64-musl 19 | -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | # 第一阶段构建 2 | FROM golang:alpine AS builder 3 | 4 | LABEL stage=gobuilder 5 | 6 | WORKDIR /build 7 | 8 | COPY . . 9 | RUN go build -ldflags="-s -w" -o /app/movie . 10 | 11 | # 第二阶段构建 12 | FROM alpine:latest 13 | 14 | WORKDIR /app 15 | COPY --from=builder /app/movie /app/movie 16 | 17 | EXPOSE 8000 18 | 19 | CMD ["/app/movie"] 20 | -------------------------------------------------------------------------------- /getter/race.go: -------------------------------------------------------------------------------- 1 | package getter 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | var ( 8 | rw sync.RWMutex 9 | interval int 10 | ) 11 | 12 | func GetInterval() int { 13 | rw.RLock() 14 | defer rw.RUnlock() 15 | return interval 16 | } 17 | 18 | func ChangeInterval(tmp int) { 19 | rw.Lock() 20 | defer rw.Unlock() 21 | interval = tmp 22 | } 23 | -------------------------------------------------------------------------------- /router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | mm "movie/manager" 5 | "movie/proxy" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | /* 11 | 12 | 这里是一段屎一样的代码,后面需要重写这里的路由逻辑 13 | 14 | */ 15 | 16 | func Router(r *gin.Engine, manager *mm.Manager) { 17 | // 图片代理 18 | r.GET("/img/proxy", proxy.Proxy) 19 | 20 | front(r, manager) // 前台接口 21 | back(r, manager) // 后台接口 22 | } 23 | -------------------------------------------------------------------------------- /conf.ini: -------------------------------------------------------------------------------- 1 | ; 监听的地址 2 | listen_addr = 0.0.0.0:8000 3 | ; session的加密密钥 4 | session_secret = secret 5 | 6 | ; 日志输出文件,默认为输出到std 7 | ; log_put=movie.log 8 | ; 日志等级:debug、info、warn、error,默认是error 9 | log_level=info, 10 | 11 | ; 以下为数据库连接,当前支持mysql和sqlite 12 | 13 | ; [mysql] 14 | ; host = 127.0.0.1:3306 15 | ; user = root 16 | ; password = 123456 17 | ; database_name = movie 18 | 19 | [sqlite] 20 | addr = movie.db 21 | -------------------------------------------------------------------------------- /router/MiddleWare/auth.go: -------------------------------------------------------------------------------- 1 | package MiddleWare 2 | 3 | import ( 4 | mm "movie/manager" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func Auth(manager *mm.Manager) func(*gin.Context) { 11 | return func(c *gin.Context) { 12 | err := manager.SessionGet(c.Request, "login") 13 | if err == nil { 14 | c.AbortWithStatus(http.StatusUnauthorized) 15 | return 16 | } 17 | c.Next() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /session/drive/gormDb/new.go: -------------------------------------------------------------------------------- 1 | package gormDb 2 | 3 | import ( 4 | "github.com/wader/gormstore/v2" 5 | "gorm.io/gorm" 6 | "movie/config" 7 | "time" 8 | ) 9 | 10 | func NewSession(db *gorm.DB) *Session { 11 | store := gormstore.New(db, []byte(config.SessionSecret)) 12 | store.MaxAge(25200) // 设置session存活时间 13 | go store.PeriodicCleanup(24*time.Hour, nil) // 隔一天执行一次回收操作 14 | return &Session{ 15 | ss: store, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /session/drive/new.go: -------------------------------------------------------------------------------- 1 | package drive 2 | 3 | import ( 4 | "movie/db" 5 | _type "movie/db/type" 6 | "movie/session" 7 | ssGorm "movie/session/drive/gormDb" 8 | 9 | "gorm.io/gorm" 10 | ) 11 | 12 | func NewSession(db db.Db, dbType int) session.Session { 13 | var result session.Session 14 | switch dbType { 15 | case _type.Gorm: 16 | result = ssGorm.NewSession(db.DbGet().(*gorm.DB)) 17 | case _type.Mongo: 18 | // 返回mongo的句柄 19 | } 20 | return result 21 | } 22 | -------------------------------------------------------------------------------- /session/session.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "movie/config" 5 | "time" 6 | 7 | "github.com/wader/gormstore/v2" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | // NewSession 构造函数 12 | func NewSession(db *gorm.DB) *gormstore.Store { 13 | store := gormstore.New(db, []byte(config.SessionSecret)) 14 | store.MaxAge(25200) // 设置session存活时间 15 | go store.PeriodicCleanup(24*time.Hour, make(<-chan struct{})) // 隔一天执行一次回收操作 16 | return store 17 | } 18 | -------------------------------------------------------------------------------- /session/interface.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import "net/http" 4 | 5 | type Session interface { 6 | Init(w http.ResponseWriter, r *http.Request, long bool, kv map[interface{}]interface{}) // session 初始化 7 | Set(w http.ResponseWriter, r *http.Request, kv map[interface{}]interface{}) // 设置值 8 | Get(r *http.Request, key string) interface{} // 获取值 9 | Destroy(w http.ResponseWriter, r *http.Request) // 销毁session 10 | } 11 | -------------------------------------------------------------------------------- /getter/get.go: -------------------------------------------------------------------------------- 1 | package getter 2 | 3 | import "time" 4 | 5 | func (here *Getter) get() { 6 | for !here.ok { 7 | select { 8 | case <-here.ctx.Done(): 9 | // 被取消了,返回 10 | here.run.Store(false) 11 | return 12 | default: 13 | here.getAll() 14 | } 15 | } 16 | 17 | t := time.NewTicker(time.Duration(GetInterval()) * time.Hour) 18 | defer t.Stop() 19 | 20 | // 此处使用一个自执行匿名函数 21 | func() { 22 | here.getDaily() 23 | }() 24 | 25 | for { 26 | select { 27 | case <-here.ctx.Done(): 28 | // 被取消了,返回 29 | here.run.Store(false) 30 | return 31 | case <-t.C: 32 | here.getDaily() 33 | } 34 | } 35 | } 36 | 37 | // JudgeGetting 判断是否在采集中 38 | func (here *Getter) JudgeGetting() bool { 39 | return here.run.Load().(bool) 40 | } 41 | -------------------------------------------------------------------------------- /router/static.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "io/fs" 5 | "movie/dist" 6 | "net/http" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func Static(r *gin.Engine) { 12 | // 前台 13 | 14 | { 15 | front, _ := fs.Sub(dist.Front, "front") 16 | // 映射index 17 | r.GET("/", func(c *gin.Context) { 18 | data, _ := fs.ReadFile(front, "index.html") 19 | c.Data(http.StatusOK, "text/html;charset=utf-8", data) 20 | }) 21 | // 映射logo 22 | r.StaticFileFS("/logo.ico", "logo.ico", http.FS(front)) 23 | assest_tmp, _ := fs.Sub(front, "assets") 24 | // 映射assets 25 | r.StaticFS("/assets", http.FS(assest_tmp)) 26 | } 27 | // 后台 28 | { 29 | admin, _ := fs.Sub(dist.Admin, "admin") 30 | r.StaticFS("/admin", http.FS(admin)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /manager/session.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // SessionGet get 操作 8 | func (here *Manager) SessionGet(r *http.Request, key string) interface{} { 9 | return here.session.Get(r, key) 10 | } 11 | 12 | // SessionInit 登陆后进行的session初始化操作 13 | func (here *Manager) SessionInit(w http.ResponseWriter, r *http.Request, long bool, kv map[interface{}]interface{}) { 14 | here.session.Init(w, r, long, kv) 15 | } 16 | 17 | // SessionSet set 操作 18 | func (here *Manager) SessionSet(w http.ResponseWriter, r *http.Request, kv map[interface{}]interface{}) { 19 | here.session.Set(w, r, kv) 20 | } 21 | 22 | // SessionDestroy destroy 操作 23 | func (here *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) { 24 | here.session.Destroy(w, r) 25 | } 26 | -------------------------------------------------------------------------------- /manager/getter.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | // GetStart 开始全局采集和定时采集 4 | func (here *Manager) GetStart() { 5 | here.getters_mutex.Lock() 6 | for _, v := range here.getters { 7 | v.StartGet() 8 | } 9 | here.getters_mutex.Unlock() 10 | } 11 | 12 | // GetStop 停止全局采集和定时采集 13 | func (here *Manager) GetStop() { 14 | here.getters_mutex.Lock() 15 | for _, v := range here.getters { 16 | v.StopGet() 17 | } 18 | here.getters_mutex.Unlock() 19 | } 20 | 21 | // GetStartById 根据别名开启全局采集和定时采集 22 | func (here *Manager) GetStartById(id uint) { 23 | here.getters_mutex.Lock() 24 | if v, ok := here.getters[id]; ok { 25 | v.StartGet() 26 | } 27 | here.getters_mutex.Unlock() 28 | } 29 | 30 | // GetStopById 根据别名关闭全局采集和定时采集 31 | func (here *Manager) GetStopById(id uint) { 32 | here.getters_mutex.Lock() 33 | if v, ok := here.getters[id]; ok { 34 | v.StopGet() 35 | } 36 | here.getters_mutex.Unlock() 37 | } 38 | -------------------------------------------------------------------------------- /manager/manager.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | database "movie/db" 5 | "movie/db/drive/gormDb" 6 | "movie/getter" 7 | "movie/session" 8 | "movie/session/drive" 9 | "sync" 10 | ) 11 | 12 | type Manager struct { 13 | db database.Db // db 部分 14 | // 这里的getter应该原子化 TODO 15 | getters map[uint]*getter.Getter // getter部分 16 | getters_mutex *sync.Mutex 17 | session session.Session // session 部分 18 | } 19 | 20 | /* 21 | 22 | todo 处理根据配置文件构建db句柄 23 | 24 | */ 25 | 26 | func NewManager() *Manager { 27 | db := gormDb.NewDb() // 获取数据库db TODO:!!!! 28 | getters := getter.SetDb(db) // 从数据库获取所有的资源地址构建getter 29 | session := drive.NewSession(db, db.DbType()) // 开始构建session 30 | 31 | newManager := &Manager{ 32 | db: db, 33 | getters: getters, 34 | getters_mutex: &sync.Mutex{}, 35 | session: session, 36 | } 37 | return newManager 38 | } 39 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "movie/config" 6 | "movie/manager" 7 | "movie/router" 8 | "movie/router/MiddleWare" 9 | "movie/util" 10 | 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func main() { 15 | // 调整为生产模式 16 | gin.SetMode(gin.ReleaseMode) 17 | 18 | // 构造manager 19 | manager := manager.NewManager() 20 | 21 | // { 22 | // 23 | // data, _ := manager.Exports() 24 | // manager.Imports(data) 25 | // } 26 | 27 | // gin句柄构建 28 | r := gin.New() 29 | 30 | // 使用默认的gin恢复句柄,防止程序奔溃 31 | r.Use(gin.Recovery()) 32 | { 33 | // 处理跨域 34 | r.Use(MiddleWare.Cors()) 35 | // 静态资源映射 36 | router.Static(r) 37 | // 配置路由 38 | router.Router(r, manager) 39 | } 40 | 41 | // 打印版本和监听地址 42 | fmt.Printf("Version:%s\n", util.Version) 43 | fmt.Printf("Status is OK, listen to http://%s\n", config.Addr) 44 | 45 | // 监听端口默认为localhost:8080 46 | err := r.Run(config.Addr) 47 | if err != nil { 48 | util.Logger.Fatalf("Listen port failed, addr is %s", config.Addr) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /util/logger.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "movie/config" 5 | "os" 6 | 7 | log "github.com/sirupsen/logrus" 8 | ) 9 | 10 | var Logger *log.Logger 11 | 12 | func init() { 13 | Logger = log.New() 14 | // 设置日志格式为json格式 15 | Logger.SetFormatter(&log.TextFormatter{ 16 | DisableColors: true, 17 | }) 18 | 19 | // 设置将日志输出到标准输出(默认的输出为stderr,标准错误) 20 | // 日志消息输出可以是任意的io.writer类型 21 | if config.LogPut == "" { 22 | Logger.SetOutput(os.Stdout) 23 | } else { 24 | file, err := os.OpenFile(config.LogPut, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 25 | if err == nil { 26 | // 设置将日志输出到文件 27 | Logger.SetOutput(file) 28 | } else { 29 | panic("can't open the log file!") 30 | } 31 | } 32 | 33 | Logger.SetLevel(logLevelParse()) 34 | } 35 | 36 | func logLevelParse() log.Level { 37 | switch config.LogLevel { 38 | case "debug": 39 | return log.DebugLevel 40 | case "info": 41 | return log.InfoLevel 42 | case "warn": 43 | return log.WarnLevel 44 | default: 45 | return log.ErrorLevel 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /proxy/proxy.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "crypto/tls" 5 | "movie/util" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func Proxy(c *gin.Context) { 13 | // 获取url参数 14 | url := c.Query("url") 15 | 16 | // 去除头尾空格 17 | url = strings.TrimSpace(url) 18 | 19 | // 判断是否为空 20 | if url == "" { 21 | c.Status(http.StatusBadRequest) 22 | return 23 | } 24 | 25 | tr := &http.Transport{ 26 | TLSClientConfig: &tls.Config{ 27 | InsecureSkipVerify: true, 28 | }, 29 | } 30 | 31 | client := &http.Client{Transport: tr} 32 | 33 | // 进行请求 34 | resp, err := client.Get(url) 35 | if err != nil { 36 | util.Logger.Errorf("client get server source failed, err is %s", err) 37 | c.Status(http.StatusBadGateway) 38 | return 39 | } 40 | defer resp.Body.Close() 41 | 42 | header := make(map[string]string) 43 | header["content-disposition"] = resp.Header.Get("content-disposition") 44 | 45 | c.DataFromReader( 46 | resp.StatusCode, 47 | resp.ContentLength, 48 | resp.Header.Get("content-type"), 49 | resp.Body, 50 | header, 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ostafen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | 4 | db: 5 | image: "mysql:5.7" 6 | # 5.7内存占用小,如果要性能更高可以使用mysql最新版 7 | container_name: movie_mysql 8 | command: 9 | [ 10 | '--character-set-server=utf8mb4', #设置utf8编码 11 | '--collation-server=utf8mb4_unicode_ci' 12 | ] 13 | volumes: 14 | - mysql_data:/var/lib/mysql 15 | restart: always 16 | environment: 17 | TZ: Asia/Shanghai 18 | MYSQL_ROOT_PASSWORD: 123456 # 数据库root密码 19 | MYSQL_DATABASE: movie # 初始化的数据库名 20 | healthcheck: 21 | test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] 22 | timeout: 20s 23 | retries: 10 24 | 25 | movie: 26 | depends_on: 27 | db: 28 | condition: service_healthy 29 | image: "yunyizhiying/movie" 30 | container_name: movie 31 | ports: 32 | - "8000:8000" 33 | restart: always 34 | environment: 35 | TZ: Asia/Shanghai 36 | MYSQL_HOST: db:3306 # mysql地址 37 | MYSQL_USER: root # mysql用户名 38 | MYSQL_PASSWORD: 123456 # mysql密码 39 | DATABASE_NAME: movie # mysql数据库 40 | LISTEN_ADDR: :8000 # 主程序监听地址,与ports后面端口保持一致 41 | 42 | volumes: 43 | mysql_data: 44 | -------------------------------------------------------------------------------- /db/drive/gormDb/db.go: -------------------------------------------------------------------------------- 1 | package gormDb 2 | 3 | import ( 4 | "fmt" 5 | "movie/config" 6 | _struct "movie/db/struct" 7 | _type "movie/db/type" 8 | "os" 9 | 10 | "gorm.io/driver/sqlite" 11 | "gorm.io/driver/mysql" 12 | "gorm.io/gorm" 13 | ) 14 | 15 | type Db struct { 16 | db *gorm.DB 17 | } 18 | 19 | // NewDb 构造函数 20 | func NewDb() *Db { 21 | var dialector gorm.Dialector 22 | if config.MysqlAddr != "" { 23 | dialector = mysql.Open(config.MysqlAddr) 24 | } else if config.SqliteAddr != "" { 25 | dialector = sqlite.Open(config.SqliteAddr) 26 | } 27 | db, err := gorm.Open(dialector, &gorm.Config{ 28 | SkipDefaultTransaction: true, // 关闭事务 29 | }) 30 | if err != nil { 31 | fmt.Println("连接数据库失败!") 32 | os.Exit(1) 33 | } 34 | err = db.AutoMigrate(&_struct.System{}, &_struct.Source{}, &_struct.Category{}, &_struct.Class{}, &_struct.Content{}) 35 | if err != nil { 36 | fmt.Println("数据库同步失败!") 37 | os.Exit(1) 38 | } 39 | result := &Db{ 40 | db: db, 41 | } 42 | result.systemInit() // 进行初次启动的初始化操作 43 | return result 44 | } 45 | 46 | // DbGet 获取gorm句柄 47 | func (here *Db) DbGet() interface{} { 48 | return here.db 49 | } 50 | 51 | // DbType 获取数据库类型 52 | func (here *Db) DbType() int { 53 | return _type.Gorm 54 | } 55 | -------------------------------------------------------------------------------- /router/MiddleWare/cors.go: -------------------------------------------------------------------------------- 1 | package MiddleWare 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func Cors() gin.HandlerFunc { 10 | return func(c *gin.Context) { 11 | method := c.Request.Method // 请求方法 12 | origin := c.Request.Header.Get("Origin") // 请求头部 13 | 14 | if origin != "" { 15 | 16 | c.Header("Access-Control-Allow-Origin", origin) // 这是允许访问所有域 17 | 18 | c.Header( 19 | "Access-Control-Allow-Methods", 20 | "POST, GET, OPTIONS, PUT, DELETE,UPDATE", 21 | ) // 服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求 22 | 23 | // header的类型 24 | c.Header( 25 | "Access-Control-Allow-Headers", 26 | "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma", 27 | ) // 允许跨域设置返回其他子段 28 | 29 | c.Header( 30 | "Access-Control-Expose-Headers", 31 | "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar", 32 | ) // 跨域关键设置 让浏览器可以解析 33 | 34 | c.Header("Access-Control-Max-Age", "172800") // 缓存请求信息 单位为秒 35 | 36 | c.Header("Access-Control-Allow-Credentials", "true") // 跨域请求是否需要带cookie信息 默认设置为true 37 | 38 | } 39 | 40 | // 放行所有OPTIONS方法 41 | if method == "OPTIONS" { 42 | c.JSON(http.StatusOK, "Options Request!") 43 | } 44 | // 处理请求 45 | c.Next() // 处理请求 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /router/MiddleWare/cache.go: -------------------------------------------------------------------------------- 1 | package MiddleWare 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "encoding/hex" 7 | "time" 8 | 9 | "github.com/bluele/gcache" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | var gc gcache.Cache = gcache.New(20). 14 | LRU(). 15 | Expiration(time.Hour * 4). 16 | Build() 17 | 18 | type CacheStruct struct { 19 | status int 20 | contentType string 21 | data []byte 22 | } 23 | 24 | type bodyLogWriter struct { 25 | gin.ResponseWriter 26 | body *bytes.Buffer 27 | } 28 | 29 | func (w bodyLogWriter) Write(b []byte) (int, error) { 30 | w.body.Write(b) 31 | return w.ResponseWriter.Write(b) 32 | } 33 | 34 | func Cache() gin.HandlerFunc { 35 | return func(c *gin.Context) { 36 | if c.Request.Method == "POST" || c.Request.Method == "GET" { 37 | hash := url_hash(c) 38 | value, err := gc.Get(hash) 39 | if err == nil { 40 | res := value.(CacheStruct) 41 | c.Data(res.status, res.contentType, res.data) 42 | c.Abort() 43 | return 44 | } 45 | blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer} 46 | c.Writer = blw 47 | c.Next() 48 | gc.Set(hash, CacheStruct{ 49 | status: c.Writer.Status(), 50 | contentType: c.Writer.Header().Get("Content-Type"), 51 | data: blw.body.Bytes(), 52 | }) 53 | } 54 | } 55 | } 56 | 57 | func Pruge() { 58 | gc.Purge() 59 | } 60 | 61 | func url_hash(c *gin.Context) string { 62 | c.Request.ParseForm() 63 | URL := c.Request.URL.Path + "?" + c.Request.Form.Encode() 64 | c1 := md5.Sum([]byte(URL)) 65 | return hex.EncodeToString(c1[:]) 66 | } 67 | -------------------------------------------------------------------------------- /db/drive/gormDb/backup.go: -------------------------------------------------------------------------------- 1 | package gormDb 2 | 3 | import ( 4 | _struct "movie/db/struct" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | // 该函数用于数据库的导出功能 10 | // 导出需要的内容有:采集源的信息,还有自建分类的信息,然后对应的采集类和自建分类的绑定为可选导出 11 | func (here *Db) Exports() (_struct.DATA, error) { 12 | var res _struct.DATA 13 | 14 | var result *gorm.DB 15 | 16 | var sources []_struct.Source 17 | result = here.db.Select("id", "name", "url").Find(&sources) 18 | 19 | if result.Error != nil { 20 | return res, result.Error 21 | } 22 | 23 | var categories []_struct.Category 24 | result = here.db.Find(&categories) 25 | 26 | if result.Error != nil { 27 | return res, result.Error 28 | } 29 | 30 | var classes []_struct.Class 31 | result = here.db.Select("id", "name", "class_id", "get", "source_id", "category_id").Find(&classes) 32 | if result.Error != nil { 33 | return res, result.Error 34 | } 35 | 36 | res.Sources = sources 37 | res.Categories = categories 38 | res.Classes = classes 39 | 40 | return res, nil 41 | } 42 | 43 | func (here *Db) Imports(data _struct.DATA) error { 44 | var result *gorm.DB 45 | 46 | sources := data.Sources 47 | categories := data.Categories 48 | classes := data.Classes 49 | 50 | return here.db.Transaction(func(tx *gorm.DB) error { 51 | result = tx.Create(&sources) 52 | if result.Error != nil { 53 | return result.Error 54 | } 55 | 56 | result = tx.Create(&categories) 57 | if result.Error != nil { 58 | return result.Error 59 | } 60 | 61 | result = tx.Create(&classes) 62 | if result.Error != nil { 63 | return result.Error 64 | } 65 | 66 | // 返回 nil 提交事务 67 | return nil 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: build-go-binary 2 | 3 | on: 4 | release: 5 | types: [created] # 表示在创建新的 Release 时触发 6 | 7 | jobs: 8 | build-go-binary: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Node Setup 13 | uses: actions/setup-node@v3 14 | with: 15 | node-version: latest 16 | 17 | - name: Pnpm Setup 18 | uses: pnpm/action-setup@v2 19 | with: 20 | version: 8 21 | 22 | - name: Go setup 23 | uses: actions/setup-go@v4 24 | with: 25 | go-version: 'stable' 26 | 27 | - uses: actions/checkout@v3 28 | 29 | - name: build front src 30 | run: | 31 | git clone https://github.com/jinzhongjia/newMovie.git --depth=1 src/front 32 | cd src/front;pnpm install;pnpm build 33 | cd .. 34 | cd .. 35 | cp -r src/front/dist/ dist/front 36 | git clone https://github.com/jinzhongjia/newMovieAdmin.git --depth=1 src/admin 37 | cd src/admin;pnpm install;pnpm build 38 | cd .. 39 | cd .. 40 | cp -r src/admin/dist/ dist/admin 41 | 42 | - name: build 43 | run: | 44 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o movie-linux 45 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o movie-window.exe 46 | 47 | - name: Upload assets 48 | uses: softprops/action-gh-release@v1 49 | with: 50 | prerelease: false 51 | files: | 52 | conf.ini 53 | movie-linux 54 | movie-window.exe 55 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | override OUT_NAME := movie 2 | override MAKEFLAGS += -rR 3 | 4 | .PHONY: all 5 | all: windows linux 6 | 7 | # 获取资源 8 | 9 | get-src: 10 | git clone https://github.com/jinzhongjia/newMovie.git --depth=1 src/front 11 | git clone https://github.com/jinzhongjia/newMovieAdmin.git --depth=1 src/admin 12 | 13 | get-front: 14 | cd src/front; \ 15 | git pull; \ 16 | pnpm install; \ 17 | pnpm build 18 | cp -r src/front/dist/ dist/front 19 | 20 | get-admin: 21 | cd src/admin; \ 22 | git pull; \ 23 | pnpm install; \ 24 | pnpm build 25 | cp -r src/admin/dist/ dist/admin 26 | 27 | # 只构建前端资源 28 | src: get-admin get-front 29 | 30 | .PHONY: linux 31 | linux: get-admin get-front 32 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o $(OUT_NAME) 33 | 34 | # 只构建linux版本 35 | l: 36 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o $(OUT_NAME) 37 | 38 | .PHONY: windows 39 | windows: get-admin get-front 40 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o $(OUT_NAME).exe 41 | 42 | # 只构建windows版本 43 | w: 44 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o $(OUT_NAME).exe 45 | 46 | .PHONY: dbuild 47 | dbuild: get-admin get-front 48 | rm -rf src 49 | docker buildx build --network=host -t yunyizhiying/movie:latest . 50 | 51 | .PHONY: dpush 52 | dpush: 53 | docker push yunyizhiying/movie:latest 54 | 55 | clean: 56 | rm -rf dist/front 57 | rm -rf dist/admin 58 | rm $(OUT_NAME).exe $(OUT_NAME)_windows.exe $(OUT_NAME) $(OUT_NAME)_linux 59 | 60 | runl: 61 | ./$(OUT_NAME) 62 | 63 | runw: 64 | .\$(OUT_NAME) 65 | 66 | format: 67 | gofmt -w -l . 68 | 69 | release: 70 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o $(OUT_NAME)_windows.exe 71 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o $(OUT_NAME)_linux 72 | -------------------------------------------------------------------------------- /getter/getDaily.go: -------------------------------------------------------------------------------- 1 | package getter 2 | 3 | import ( 4 | "io" 5 | "movie/util" 6 | "strconv" 7 | 8 | "github.com/tidwall/gjson" 9 | ) 10 | 11 | // 获取最近更新的内容子执行 12 | func (here *Getter) getDaily() { 13 | defer protect() 14 | // 获取页数 15 | pgCount := here.getPgCountDaily() 16 | // 遍历页数 17 | for pg := 1; pg <= pgCount; pg++ { 18 | // 根据页数请求最近采集 19 | list := here.getListDaily(pg) 20 | 21 | // 逻辑判断是否允许采集 22 | for _, v := range list { 23 | select { 24 | case <-here.ctx.Done(): 25 | // 被取消了,返回 26 | return 27 | default: 28 | // 默认继续采集 29 | id := int(v.Value().(float64)) 30 | here.getContent(id) 31 | } 32 | } 33 | // 再次请求进行更新页数 34 | pgCount = here.getPgCountDaily() 35 | 36 | } 37 | } 38 | 39 | // 获取最近更新内容页数 40 | func (here *Getter) getPgCountDaily() int { 41 | c := newHttpHandle() 42 | res, err := c.Get(here.url + "?ac=list&h=" + strconv.Itoa(GetInterval())) 43 | if err != nil { 44 | util.Logger.Panicf("getter get recent the resource station called %s, getting page failed, err is %s", here.name, err) 45 | // panic("采集资源站“" + here.name + "获取采集页数失败") 46 | } 47 | defer res.Body.Close() 48 | body, _ := io.ReadAll(res.Body) 49 | pageCount := gjson.Get(string(body), "pagecount").Value() 50 | return int(pageCount.(float64)) 51 | } 52 | 53 | // 请求最近列表 54 | func (here *Getter) getListDaily(pg int) []gjson.Result { 55 | c := newHttpHandle() 56 | res, err := c.Get( 57 | here.url + "?ac=list&h=" + strconv.Itoa(GetInterval()) + "&pg=" + strconv.Itoa(pg), 58 | ) 59 | if err != nil { 60 | util.Logger.Panicf("getter get recent the resource station called %s, list failed, err is %s", here.name, err) 61 | // panic("采集资源站“" + here.name + "获取采集页数失败") 62 | } 63 | defer res.Body.Close() 64 | body, _ := io.ReadAll(res.Body) 65 | list := gjson.Get(string(body), "list.#.vod_id").Array() 66 | return list 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 这是什么? 2 | 3 | 一款由 GO 编写的影视采集器,可以采集支持 MacCMS 的影视站 4 | 5 | 特色: 6 | 7 | - 简单轻量,部署即开即用 8 | - 高效,采用 gin 框架 9 | - 易用,api 接口语义化 10 | - 现代化,前后端分离 11 | 12 | ### 前端 13 | 14 | 前台源码:[Go](https://github.com/jinzhongjia/newMovie) 15 | 后台源码:[Go](https://github.com/jinzhongjia/newMovieAdmin) 16 | 17 | ## 功能 18 | 19 | ### 已实现 20 | 21 | - 全局资源采集 22 | - 采集间隔自定义 23 | - 采集分类可控 24 | - 搜索功能 25 | - 分类功能 26 | - 图片资源反向代理,客户端无感知 27 | - 自定义采集源 28 | - 前台后台管理页面分离 29 | - 支持 mysql、sqlite 30 | - 热点数据缓存(LRU 算法) 31 | - 日志系统 32 | - 数据管理(导出导入) 33 | - 全新的[后台管理系统](https://github.com/jinzhongjia/newMovieAdmin),功能更完善(前后端适配) 34 | 35 | ### 待实现(完成) 36 | 37 | - 流量统计 38 | - 采集逻辑抽象化,方便支持更多类型的采集接口 39 | - 对外api提供 40 | 41 | ## 技术栈 42 | 43 | gjson、gorm、gin、vue、naive-ui 44 | 45 | 后端 api 文档:请查看`router/back.go`和`router/front.go`两个文件,其中定义了所有api 46 | 47 | ## 使用 48 | 49 | ### 手动构建 50 | 51 | 所需环境: 52 | 53 | > Golang >= 1.18 54 | > 55 | > nodejs 56 | > 57 | > pnpm 58 | > 59 | > make(构建工具) 60 | 61 | 步骤: 62 | 63 | ```sh 64 | git clone https://github.com/jinzhongjia/movie-getter.git 65 | cd movie-getter 66 | make get-src 67 | # 构建linux版本 68 | make linux 69 | # 构建windows版本 70 | make windows 71 | # 同时构建windows和linux,release版本 72 | make release 73 | ``` 74 | 75 | 或者 76 | 77 | 安装 zig nightly 78 | 79 | ```sh 80 | git clone https://github.com/jinzhongjia/movie-getter.git 81 | cd movie-getter 82 | make get-src 83 | make get-admin 84 | make get-front 85 | zig build 86 | # 构建完成后会出现三个文件,分别是linux-arm64-musl、linux-x86-64-musl、windows-x86-64 87 | ``` 88 | 89 | ### Docker-compose 部署(推荐) 90 | 91 | 下载本仓库的 master 分支的`docker-compose.yml`文件,运行以下命令 92 | 93 | ```bash 94 | docker-compose -p movie up -d 95 | ``` 96 | 97 | --- 98 | 99 | 后台地址:www.example.com/admin/ 100 | 101 | 默认管理员账户:admin 102 | 103 | 默认密码:admin888 104 | 105 | ## 贡献代码 106 | 107 | 欢迎 fork 本仓库,该项目是我业余之时制作,欢迎贡献代码! 108 | -------------------------------------------------------------------------------- /session/drive/gormDb/session.go: -------------------------------------------------------------------------------- 1 | package gormDb 2 | 3 | import ( 4 | "movie/util" 5 | "net/http" 6 | 7 | "github.com/wader/gormstore/v2" 8 | ) 9 | 10 | type Session struct { 11 | ss *gormstore.Store 12 | } 13 | 14 | func (s *Session) Init( 15 | w http.ResponseWriter, 16 | r *http.Request, 17 | long bool, 18 | kv map[interface{}]interface{}, 19 | ) { 20 | session, err := s.ss.Get(r, "data") 21 | if err != nil { 22 | util.Logger.Errorf("Get session info failed when init session,err is %s", err) 23 | } 24 | // session.Options.SameSite = http.SameSiteNoneMode // 设置cookie的策略 25 | // session.Options.Secure = true // 设置cookie策略 26 | if long { 27 | session.Options.MaxAge = 2592000 // 设置存活时间 28 | } 29 | for k, v := range kv { 30 | session.Values[k] = v 31 | } 32 | err = session.Save(r, w) 33 | if err != nil { 34 | util.Logger.Errorf("Session saving failed when init session, err is %s", err) 35 | } 36 | } 37 | 38 | func (s *Session) Set(w http.ResponseWriter, r *http.Request, kv map[interface{}]interface{}) { 39 | session, err := s.ss.Get(r, "data") 40 | if err != nil { 41 | util.Logger.Errorf("Get session handle failed when set session, err is %s", err) 42 | } 43 | for k, v := range kv { 44 | session.Values[k] = v 45 | } 46 | err = session.Save(r, w) 47 | if err != nil { 48 | util.Logger.Errorf("Session saving failed, err is %s", err) 49 | } 50 | } 51 | 52 | func (s *Session) Get(r *http.Request, key string) interface{} { 53 | session, err := s.ss.Get(r, "data") 54 | if err != nil { 55 | util.Logger.Errorf("Get session handle failed when get session, err is %s", err) 56 | } 57 | 58 | return session.Values[key] 59 | } 60 | 61 | func (s *Session) Destroy(w http.ResponseWriter, r *http.Request) { 62 | session, err := s.ss.Get(r, "data") 63 | if err != nil { 64 | util.Logger.Errorf("Get session handle failed when destroy session, err is %s", err) 65 | } 66 | session.Options.MaxAge = -1 67 | err = session.Save(r, w) 68 | if err != nil { 69 | util.Logger.Errorf("Save session failed when destroy session, err is %s", err) 70 | return 71 | } 72 | s.ss.Cleanup() 73 | } 74 | -------------------------------------------------------------------------------- /db/struct/struct.go: -------------------------------------------------------------------------------- 1 | package _struct 2 | 3 | type Source struct { 4 | ID uint 5 | Name string `gorm:"unique;not null;index"` // 资源库名字 6 | Url string `gorm:"unique;not null;index"` // 资源库地址 7 | Ok bool `gorm:"default:false" json:"ok,omitempty"` // 资源库是否采集完 8 | Pg int `gorm:"default:1" json:"pg,omitempty"` // 资源库采集的页数 9 | Class []Class `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` 10 | Content []Content `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` 11 | } // 资源库表定义 12 | 13 | type Content struct { 14 | ID uint 15 | ContentId int // 影片编号 16 | Name string `gorm:"index"` // 影片名 17 | Class string // 所属类别 作为保留字段 18 | Pic string // 影片图片地址 19 | Actor string // 主演 20 | Director string // 导演 21 | Duration string // 时长 22 | Description string `gorm:"type:longText"` // 简介 23 | Url string `gorm:"type:longText"` // 视频链接 24 | Stamp int64 `gorm:"index"` // 创建时间戳 25 | SourceID uint `gorm:"index;default:0"` 26 | ClassID uint `gorm:"index;default:0"` 27 | // 属于分类 28 | } // 资源内容 29 | 30 | type Class struct { 31 | ID uint 32 | Name string // 采集分类名 33 | ClassId int `gorm:"not null;index"` // 采集分类id 34 | Get bool `gorm:"default:true"` // 是否采集 35 | Content []Content `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` 36 | SourceID uint `gorm:"default:0"` 37 | CategoryID uint `gorm:"default:0"` 38 | // 属于资源库 39 | // 属于自建分类 40 | } // 采集资源分类 41 | 42 | type Category struct { 43 | ID uint `json:"id"` 44 | Name string `json:"name" gorm:"unique;not null;index"` // 自建分类名 45 | Main bool `json:"main" gorm:"default:true"` 46 | Class []Class `json:",omitempty" gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` 47 | } // 自建分类 48 | 49 | type System struct { 50 | Account string // 账户 51 | Password string // 密码 52 | CollectInterval int `gorm:"default:24"` // 采集间隔时间 53 | } 54 | 55 | type DATA struct { 56 | Sources []Source 57 | Categories []Category 58 | Classes []Class 59 | } 60 | -------------------------------------------------------------------------------- /db/drive/gormDb/category.go: -------------------------------------------------------------------------------- 1 | package gormDb 2 | 3 | import ( 4 | _struct "movie/db/struct" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | // AddCategory 添加分类 10 | func (here *Db) AddCategory(name string) (uint, error) { 11 | category := &_struct.Category{ 12 | Name: name, 13 | } 14 | db := here.db.Create(category) 15 | return category.ID, db.Error 16 | } 17 | 18 | // UpdateCategoryName 更新分类名字 19 | func (here *Db) UpdateCategoryName(id uint, newName string) error { 20 | db := here.db.Model(&_struct.Category{ 21 | ID: id, 22 | }).Update("name", newName) 23 | return db.Error 24 | } 25 | 26 | // DelCategory 删除分类 27 | func (here *Db) DelCategory(id uint) error { 28 | var db *gorm.DB 29 | 30 | // 创建事务 31 | tx := here.db.Begin() 32 | // 尝试删除关系 33 | err := tx.Model(&_struct.Category{ 34 | ID: id, 35 | }).Association("Class").Clear() 36 | if err != nil { 37 | tx.Rollback() 38 | return err 39 | } 40 | // 尝试删除自建分类 41 | db = tx.Delete(&_struct.Category{ 42 | ID: id, 43 | }) 44 | if db.Error != nil { 45 | tx.Rollback() 46 | return db.Error 47 | } 48 | // 提交事务 49 | tx.Commit() 50 | return nil 51 | } 52 | 53 | // AllCategory 获取所有分类 54 | func (here *Db) AllCategory() ([]_struct.Category, error) { 55 | categories := make([]_struct.Category, 0) 56 | db := here.db.Find(&categories) 57 | return categories, db.Error 58 | } 59 | 60 | // CategoryMovieCount 获取当前自建分类下所有的影片数目 61 | func (here *Db) CategoryMovieCount(categoryId uint) (int, error) { 62 | classIds := make([]uint, 0) 63 | err := here.db.Model(&_struct.Category{ 64 | ID: categoryId, 65 | }).Select("id").Association("Class").Find(&classIds) 66 | result := 0 67 | for _, classId := range classIds { 68 | result += here.ClassMovieNum(classId) 69 | } 70 | return result, err 71 | } 72 | 73 | // CategoryClassCount 获取采集类的影片数 74 | func (here *Db) CategoryClassCount(categoryId uint) int { 75 | result := here.db.Model(&_struct.Category{ 76 | ID: categoryId, 77 | }).Association("Class").Count() 78 | return int(result) 79 | } 80 | 81 | // SetCategoryMain 设置主分类 82 | func (here *Db) SetCategoryMain(id uint, main bool) error { 83 | db := here.db.Model(&_struct.Category{ 84 | ID: id, 85 | }).Update("main", main) 86 | return db.Error 87 | } 88 | -------------------------------------------------------------------------------- /db/drive/gormDb/system.go: -------------------------------------------------------------------------------- 1 | package gormDb 2 | 3 | import ( 4 | _struct "movie/db/struct" 5 | "movie/util" 6 | 7 | "golang.org/x/crypto/bcrypt" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | func (here *Db) systemInit() { 12 | var num int64 13 | here.db.Model(&_struct.System{}).Count(&num) 14 | if num == 0 { 15 | hash, err := bcrypt.GenerateFromPassword([]byte("admin888"), bcrypt.DefaultCost) 16 | if err != nil { 17 | util.Logger.Panicf("systemInit passwd encrypt failed, err is %s", err) 18 | } 19 | here.db.Create(&_struct.System{ 20 | Account: "admin", 21 | Password: string(hash), 22 | }) 23 | } 24 | } 25 | 26 | // Login 登录 27 | func (here *Db) Login(account string, password string) bool { 28 | system := &_struct.System{} 29 | here.db.Model(&_struct.System{}).Where("account = ?", account).Find(system) 30 | return bcrypt.CompareHashAndPassword([]byte(system.Password), []byte(password)) == nil 31 | } 32 | 33 | // UpdateAccount 更新后台账户 34 | func (here *Db) UpdateAccount(oldAccount string, newAccount string) error { 35 | db := here.db.Model(&_struct.System{}).Where("account = ?", oldAccount).Update("account", newAccount) 36 | return db.Error 37 | } 38 | 39 | // UpdatePassword 更新后台密码 40 | func (here *Db) UpdatePassword(account string, newPassword string) error { 41 | hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost) 42 | if err != nil { 43 | util.Logger.Errorf("when db UpdatePassword, passwd encrypt failed!") 44 | return err 45 | } 46 | db := here.db.Model(&_struct.System{}).Where("account = ?", account).Update("password", string(hash)) 47 | return db.Error 48 | } 49 | 50 | // ChangeCollectInterval 修改采集间隔 51 | func (here *Db) ChangeCollectInterval(interval int) error { 52 | db := here.db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&_struct.System{}).Update("collect_interval", interval) 53 | return db.Error 54 | } 55 | 56 | // GetCollectInterval 获取采集间隔 57 | func (here *Db) GetCollectInterval() int { 58 | var system _struct.System 59 | db := here.db.Select("collect_interval").Find(&system) 60 | if db.Error != nil { 61 | util.Logger.Errorf("get collect interval failed, err is %s", db.Error) 62 | } 63 | if system.CollectInterval == 0 { 64 | return 24 65 | } 66 | return system.CollectInterval 67 | } 68 | -------------------------------------------------------------------------------- /getter/getAll.go: -------------------------------------------------------------------------------- 1 | package getter 2 | 3 | import ( 4 | "io" 5 | "movie/util" 6 | "strconv" 7 | 8 | "github.com/tidwall/gjson" 9 | ) 10 | 11 | // 全局采集 12 | func (here *Getter) getAll() { 13 | defer protect() 14 | pgCount := here.getPgCount() 15 | if here.pg >= pgCount { 16 | here.ok = true 17 | err := db.UpdateSourceOk(here.id, here.ok) 18 | if err != nil { 19 | util.Logger.Errorf("sorrry, when getter change the source status it failed, err is %s", err) 20 | } 21 | return 22 | } 23 | 24 | // 获取页数 25 | list := here.getList(pgCount) 26 | 27 | for _, v := range list { 28 | // 添加一个逻辑,判断是否允许采集 29 | select { 30 | case <-here.ctx.Done(): 31 | // 被取消了,返回 32 | return 33 | default: 34 | id := int(v.Value().(float64)) 35 | here.getContent(id) 36 | } 37 | } 38 | 39 | // 更新页数 40 | here.updatePg() 41 | } 42 | 43 | // 更新页数 44 | func (here *Getter) updatePg() { 45 | here.pg++ 46 | err := db.UpdateSourcePg(here.id, here.pg) 47 | if err != nil { 48 | util.Logger.Errorf("getter update the source page failed, err is %s", err) 49 | } 50 | } 51 | 52 | // 获取所有采集页数 53 | func (here *Getter) getPgCount() int { 54 | c := newHttpHandle() 55 | res, err := c.Get(here.url + "?ac=list") 56 | if err != nil { 57 | // panic("采集资源站“" + here.name + "“获取采集页数失败") 58 | util.Logger.Panicf("getter get all the resource station called %s, getting page failed, err is %s", here.name, err) 59 | } 60 | defer res.Body.Close() 61 | body, _ := io.ReadAll(res.Body) 62 | pageCount := gjson.Get(string(body), "pagecount").Value() 63 | return int(pageCount.(float64)) 64 | } 65 | 66 | // 获取list 67 | func (here *Getter) getList(pgCount int) []gjson.Result { 68 | // fmt.Println("采集资源站“", here.name, "”,第", here.pg, "页") 69 | util.Logger.Infof("getter get the resource station %s, page is %d", here.name, here.pg) 70 | c := newHttpHandle() 71 | res, err := c.Get(here.url + "?ac=list&pg=" + strconv.Itoa(pgCount-here.pg)) 72 | if err != nil { 73 | // panic("采集资源站“" + here.name + "“获取采集页数失败") 74 | util.Logger.Panicf("getter get the resource station called %s, getting page failed, err is %s", here.name, err) 75 | } 76 | defer res.Body.Close() 77 | body, _ := io.ReadAll(res.Body) 78 | list := gjson.Get(string(body), "list.#.vod_id").Array() 79 | return list 80 | } 81 | -------------------------------------------------------------------------------- /.github/workflows/auto-docker-img.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | name: build docker image 3 | # Controls when the action will run. 4 | on: 5 | push: 6 | branches: 7 | - dev 8 | 9 | jobs: 10 | buildx: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Get current date 17 | id: date 18 | run: echo "::set-output name=today::$(date +'%Y-%m-%d_%H-%M')" 19 | 20 | - name: Node Setup 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: latest 24 | 25 | - name: Pnpm Setup 26 | uses: pnpm/action-setup@v2 27 | with: 28 | version: 8 29 | 30 | - name: Set up QEMU 31 | uses: docker/setup-qemu-action@v3 32 | 33 | - name: Set up Docker Buildx 34 | id: buildx 35 | uses: docker/setup-buildx-action@v3 36 | 37 | - name: Available platforms 38 | run: echo ${{ steps.buildx.outputs.platforms }} 39 | 40 | - name: Login to DockerHub 41 | uses: docker/login-action@v3 42 | with: 43 | username: ${{ secrets.DOCKERHUB_USERNAME }} 44 | password: ${{ secrets.DOCKERHUB_TOKEN }} 45 | 46 | - name: build front src 47 | run: | 48 | git clone https://github.com/jinzhongjia/newMovie.git --depth=1 src/front 49 | cd src/front;pnpm install;pnpm build 50 | cd .. 51 | cd .. 52 | cp -r src/front/dist/ dist/front 53 | git clone https://github.com/jinzhongjia/newMovieAdmin.git --depth=1 src/admin 54 | cd src/admin;pnpm install;pnpm build 55 | cd .. 56 | cd .. 57 | cp -r src/admin/dist/ dist/admin 58 | 59 | - name: Build and push 60 | uses: docker/build-push-action@v5 61 | with: 62 | context: . 63 | file: ./Dockerfile 64 | # 所需要的体系结构,可以在 Available platforms 步骤中获取所有的可用架构 65 | platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm64/v8 66 | # 镜像推送时间 67 | push: ${{ github.event_name != 'pull_request' }} 68 | # 给清单打上多个标签 69 | tags: | 70 | yunyizhiying/movie:${{ steps.date.outputs.today }} 71 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module movie 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/bluele/gcache v0.0.2 7 | github.com/gin-gonic/gin v1.9.1 8 | github.com/sirupsen/logrus v1.9.3 9 | github.com/tidwall/gjson v1.17.0 10 | github.com/wader/gormstore/v2 v2.0.3 11 | golang.org/x/crypto v0.45.0 12 | gopkg.in/ini.v1 v1.67.0 13 | gorm.io/driver/mysql v1.5.2 14 | gorm.io/gorm v1.25.5 15 | ) 16 | 17 | require ( 18 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect 19 | github.com/chenzhuoyu/iasm v0.9.1 // indirect 20 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 21 | github.com/gin-contrib/sse v0.1.0 // indirect 22 | github.com/go-playground/locales v0.14.1 // indirect 23 | github.com/go-playground/universal-translator v0.18.1 // indirect 24 | github.com/goccy/go-json v0.10.2 // indirect 25 | github.com/google/go-cmp v0.5.9 // indirect 26 | github.com/gorilla/securecookie v1.1.2 // indirect 27 | github.com/gorilla/sessions v1.2.2 // indirect 28 | github.com/jinzhu/inflection v1.0.0 // indirect 29 | github.com/jinzhu/now v1.1.5 // indirect 30 | github.com/json-iterator/go v1.1.12 // indirect 31 | github.com/leodido/go-urn v1.2.4 // indirect 32 | github.com/mattn/go-isatty v0.0.20 // indirect 33 | github.com/mattn/go-sqlite3 v1.14.18 // indirect 34 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 35 | github.com/modern-go/reflect2 v1.0.2 // indirect 36 | github.com/pelletier/go-toml/v2 v2.1.0 // indirect 37 | github.com/tidwall/match v1.1.1 // indirect 38 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 39 | github.com/ugorji/go/codec v1.2.12 // indirect 40 | golang.org/x/arch v0.6.0 // indirect 41 | golang.org/x/sys v0.38.0 // indirect 42 | golang.org/x/text v0.31.0 // indirect 43 | gopkg.in/yaml.v3 v3.0.1 // indirect 44 | ) 45 | 46 | require ( 47 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 48 | github.com/bytedance/sonic v1.10.2 // indirect 49 | github.com/go-playground/validator/v10 v10.16.0 // indirect 50 | github.com/go-sql-driver/mysql v1.7.1 // indirect 51 | github.com/klauspost/cpuid/v2 v2.2.6 // indirect 52 | github.com/tidwall/pretty v1.2.1 // indirect 53 | golang.org/x/net v0.47.0 // indirect 54 | google.golang.org/protobuf v1.33.0 // indirect 55 | gorm.io/driver/sqlite v1.5.4 56 | ) 57 | -------------------------------------------------------------------------------- /db/drive/gormDb/class.go: -------------------------------------------------------------------------------- 1 | package gormDb 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | "movie/db/struct" 6 | ) 7 | 8 | // AddClass 添加class 9 | func (here *Db) AddClass(sourceId uint, name string, class_Id int) error { 10 | var db *gorm.DB 11 | class := &_struct.Class{ 12 | Name: name, 13 | ClassId: class_Id, 14 | } 15 | // 创建事务 16 | tx := here.db.Begin() 17 | // 开始创建class 18 | db = tx.Create(class) 19 | if db.Error != nil { 20 | tx.Rollback() 21 | return db.Error 22 | } 23 | // 添加与source的关系 24 | err := tx.Model(&_struct.Source{ 25 | ID: sourceId, 26 | }).Association("Class").Append(class) 27 | if err != nil { 28 | tx.Rollback() 29 | return err 30 | } 31 | // 提交事务 32 | tx.Commit() 33 | return nil 34 | } 35 | 36 | // DistributeClass 分配class 37 | func (here *Db) DistributeClass(classId uint, categoryId uint) error { 38 | return here.db.Model(&_struct.Category{ 39 | ID: categoryId, 40 | }).Association("Class").Append(&_struct.Class{ 41 | ID: classId, 42 | }) 43 | } 44 | 45 | // JudgeClass 判断判断当前分类是否允许采集 46 | func (here *Db) JudgeClass(SourceId uint, class_Id uint) bool { 47 | var class _struct.Class 48 | err := here.db.Model(&_struct.Source{ 49 | ID: SourceId, 50 | }).Select("get").Where("class_id = ?", class_Id).Association("Class").Find(&class) 51 | if err != nil { 52 | return false 53 | } 54 | return class.Get 55 | } 56 | 57 | // 通过source和class_id获取classId 58 | func (here *Db) getClassIdBySourceId(sourceId uint, class_Id int) uint { 59 | var class _struct.Class 60 | _ = here.db.Model(&_struct.Source{ 61 | ID: sourceId, 62 | }).Where("class_id = ?", class_Id).Association("Class").Find(&class) 63 | return class.ID 64 | } 65 | 66 | // GetClass 获取某资源库下所有采集类 67 | func (here *Db) GetClass(sourceId uint) ([]_struct.Class, error) { 68 | classes := make([]_struct.Class, 0) 69 | err := here.db.Model(&_struct.Source{ 70 | ID: sourceId, 71 | }).Select("id", "name", "get", "category_id").Association("Class").Find(&classes) 72 | return classes, err 73 | } 74 | 75 | // ChangeClassGet 改变采集状态 76 | func (here *Db) ChangeClassGet(classId uint, get bool) error { 77 | db := here.db.Model(&_struct.Class{ 78 | ID: classId, 79 | }).Update("get", get) 80 | return db.Error 81 | } 82 | 83 | // ClassMovieNum 获取某个采集分类下的所有影片数目 84 | func (here *Db) ClassMovieNum(classId uint) int { 85 | result := here.db.Model(&_struct.Class{ 86 | ID: classId, 87 | }).Association("Content").Count() 88 | return int(result) 89 | } 90 | -------------------------------------------------------------------------------- /getter/getter.go: -------------------------------------------------------------------------------- 1 | package getter 2 | 3 | import ( 4 | "context" 5 | database "movie/db" 6 | "movie/util" 7 | "sync/atomic" 8 | ) 9 | 10 | var db database.Db 11 | 12 | func SetDb(tmp database.Db) map[uint]*Getter { 13 | // 设置数据库 14 | db = tmp 15 | // 查询数据库获取所有的source 16 | sources, err := db.AllSource() 17 | if err != nil { 18 | util.Logger.Panicf("SetDb failed because query database failed, err is %s", err) 19 | } 20 | getters := make(map[uint]*Getter) 21 | for _, v := range sources { 22 | getters[v.ID] = NewGetter(v.ID, v.Name, v.Url, v.Ok, v.Pg) 23 | } 24 | ChangeInterval(db.GetCollectInterval()) 25 | return getters 26 | } 27 | 28 | type Getter struct { 29 | id uint // 专属id 30 | name string // 采集源的名字 31 | url string // 采集源的地址 32 | ok bool // 是否采集完成 33 | pg int // 当前采集到的页数 34 | run *atomic.Value // 是否正在采集 35 | ctx context.Context // 上下文 36 | cancel context.CancelFunc // 取消函数 37 | } 38 | 39 | // NewGetter 构造函数 40 | func NewGetter(id uint, name string, url string, ok bool, pg int) *Getter { 41 | if db == nil { 42 | util.Logger.Panicf("the db handle is nil!") 43 | } 44 | // 初始化采集所用的ctx 45 | ctx, cancel := context.WithCancel(context.Background()) 46 | cancel() 47 | 48 | get := &Getter{ 49 | id: id, 50 | name: name, 51 | url: url, 52 | ok: ok, 53 | pg: pg, 54 | run: newAtomicBool(), 55 | ctx: ctx, 56 | cancel: cancel, 57 | } 58 | return get 59 | } 60 | 61 | // StartGet 开启采集 62 | func (here *Getter) StartGet() { 63 | if !here.run.Load().(bool) { 64 | here.ctx, here.cancel = context.WithCancel(context.Background()) 65 | here.run.Store(true) 66 | go here.get() 67 | } else { 68 | util.Logger.Infof("all getters have started!") 69 | } 70 | } 71 | 72 | // StopGet 关闭采集 73 | func (here *Getter) StopGet() { 74 | here.cancel() 75 | } 76 | 77 | func (here *Getter) ReGet() { 78 | here.StopGet() 79 | for here.JudgeGetting() { 80 | // 进入一个自旋 81 | } 82 | err := db.UpdateSourcePg(here.id, 1) // 数据库中采集页数更新到1页 83 | if err != nil { 84 | util.Logger.Errorf("getter reget source failed, because update the page failed, err is %s", err) 85 | } 86 | err = db.UpdateSourceOk(here.id, false) // 更新数据库中的采集进度未false 87 | if err != nil { 88 | util.Logger.Errorf("getter reget source failed, because update the status failed, err is %s", err) 89 | } 90 | here.pg = 1 // getter本身也更新为1页 91 | here.ok = false // 采集进度调整为false,即未采集完成 92 | here.StartGet() 93 | } 94 | -------------------------------------------------------------------------------- /.github/workflows/release-docker-img.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | name: release docker image 3 | # Controls when the action will run. 4 | on: 5 | # Allows you to run this workflow manually from the Actions tab 6 | # 可以手动触发 7 | workflow_dispatch: 8 | inputs: 9 | logLevel: 10 | description: 'Log level' 11 | required: true 12 | default: 'warning' 13 | tags: 14 | description: 'Test scenario tags' 15 | 16 | jobs: 17 | buildx: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Get current date 24 | id: date 25 | run: echo "::set-output name=today::$(date +'%Y-%m-%d_%H-%M')" 26 | 27 | - name: Node Setup 28 | uses: actions/setup-node@v3 29 | with: 30 | node-version: latest 31 | 32 | - name: Pnpm Setup 33 | uses: pnpm/action-setup@v2 34 | with: 35 | version: 8 36 | 37 | - name: Set up QEMU 38 | uses: docker/setup-qemu-action@v3 39 | 40 | - name: Set up Docker Buildx 41 | id: buildx 42 | uses: docker/setup-buildx-action@v3 43 | 44 | - name: Available platforms 45 | run: echo ${{ steps.buildx.outputs.platforms }} 46 | 47 | - name: Login to DockerHub 48 | uses: docker/login-action@v3 49 | with: 50 | username: ${{ secrets.DOCKERHUB_USERNAME }} 51 | password: ${{ secrets.DOCKERHUB_TOKEN }} 52 | 53 | - name: build front src 54 | run: | 55 | git clone https://github.com/jinzhongjia/newMovie.git --depth=1 src/front 56 | cd src/front;pnpm install;pnpm build 57 | cd .. 58 | cd .. 59 | cp -r src/front/dist/ dist/front 60 | git clone https://github.com/jinzhongjia/newMovieAdmin.git --depth=1 src/admin 61 | cd src/admin;pnpm install;pnpm build 62 | cd .. 63 | cd .. 64 | cp -r src/admin/dist/ dist/admin 65 | 66 | - name: Build and push 67 | uses: docker/build-push-action@v5 68 | with: 69 | context: . 70 | file: ./Dockerfile 71 | # 所需要的体系结构,可以在 Available platforms 步骤中获取所有的可用架构 72 | platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm64/v8 73 | # 镜像推送时间 74 | push: ${{ github.event_name != 'pull_request' }} 75 | # 给清单打上多个标签 76 | tags: | 77 | yunyizhiying/movie:latest 78 | -------------------------------------------------------------------------------- /db/drive/gormDb/source.go: -------------------------------------------------------------------------------- 1 | package gormDb 2 | 3 | import ( 4 | "crypto/tls" 5 | "io" 6 | _struct "movie/db/struct" 7 | "movie/util" 8 | "net/http" 9 | 10 | "github.com/tidwall/gjson" 11 | ) 12 | 13 | // AddSource 添加source 14 | func (here *Db) AddSource(name string, url string) (uint, bool) { 15 | source := &_struct.Source{ 16 | Name: name, 17 | Url: url, 18 | } 19 | sourceId, err := here.sourceInit(url, source) 20 | if err != nil { 21 | return 0, false 22 | } 23 | return sourceId, true 24 | } 25 | 26 | // UpdateSourceName 更新资源库名字 27 | func (here *Db) UpdateSourceName(id uint, newName string) error { 28 | db := here.db.Model(&_struct.Source{ 29 | ID: id, 30 | }).Update("name", newName) 31 | return db.Error 32 | } 33 | 34 | // UpdateSourceUrl 更新资源库的地址 35 | func (here *Db) UpdateSourceUrl(id uint, newUrl string) error { 36 | db := here.db.Model(&_struct.Source{ 37 | ID: id, 38 | }).Update("url", newUrl) 39 | return db.Error 40 | } 41 | 42 | // DelSource 删除资源库 43 | func (here *Db) DelSource(id uint) error { 44 | db := here.db.Select("Class", "Content").Delete(&_struct.Source{ 45 | ID: id, 46 | }) 47 | return db.Error 48 | } 49 | 50 | // UpdateSourceOk 更新source的ok状态 51 | func (here *Db) UpdateSourceOk(id uint, status bool) error { 52 | db := here.db.Model(&_struct.Source{ 53 | ID: id, 54 | }).Update("ok", status) 55 | return db.Error 56 | } 57 | 58 | // AllSource 获取所有的source 59 | func (here *Db) AllSource() ([]_struct.Source, error) { 60 | sources := make([]_struct.Source, 0) 61 | db := here.db.Find(&sources) 62 | return sources, db.Error 63 | } 64 | 65 | // UpdateSourcePg 更新source采集的页数 66 | func (here *Db) UpdateSourcePg(id uint, pg int) error { 67 | db := here.db.Model(&_struct.Source{ 68 | ID: id, 69 | }).Update("pg", pg) 70 | return db.Error 71 | } 72 | 73 | // SourceMovieNum 获取该采集源下影片数目 74 | func (here *Db) SourceMovieNum(sourceId uint) int { 75 | result := here.db.Model(&_struct.Source{ 76 | ID: sourceId, 77 | }).Association("Content").Count() 78 | return int(result) 79 | } 80 | 81 | // 资源库初始化 82 | func (here *Db) sourceInit(url string, source *_struct.Source) (uint, error) { 83 | c := &http.Client{ 84 | Transport: &http.Transport{ 85 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 86 | }, 87 | } 88 | 89 | res, err := c.Get(url + "?ac=list&t=1&pg=1") 90 | if err != nil { 91 | util.Logger.Errorf("get source's classes failed when add source, err is %s", err) 92 | return 0, err 93 | } 94 | defer res.Body.Close() 95 | body, _ := io.ReadAll(res.Body) 96 | classes := gjson.Get(string(body), "class").Array() 97 | db := here.db.Create(source) 98 | if db.Error != nil { 99 | util.Logger.Errorf("Create source in database failed when add source, err is %s", err) 100 | return 0, nil 101 | } 102 | for _, vv := range classes { 103 | v := vv.Value().(map[string]interface{}) 104 | name := v["type_name"].(string) 105 | class_Id := int(v["type_id"].(float64)) 106 | err := here.AddClass(source.ID, name, class_Id) 107 | if err != nil { 108 | util.Logger.Errorf("when sourceInit, add class failed, err is %s", err) 109 | } 110 | } 111 | return source.ID, nil 112 | } 113 | -------------------------------------------------------------------------------- /getter/tool.go: -------------------------------------------------------------------------------- 1 | package getter 2 | 3 | import ( 4 | "crypto/tls" 5 | "io" 6 | "movie/util" 7 | "net/http" 8 | "regexp" 9 | "strconv" 10 | "strings" 11 | "sync/atomic" 12 | "time" 13 | 14 | "github.com/tidwall/gjson" 15 | ) 16 | 17 | // 采集content 18 | func (here *Getter) getContent(id int) { 19 | c := newHttpHandle() 20 | res, err := c.Get(here.url + "?ac=detail&ids=" + strconv.Itoa(id)) 21 | if err != nil { 22 | util.Logger.Panicf("getter get content failed, err is %s", err) 23 | // panic后通过外部的recover来重新获取json 24 | } 25 | defer res.Body.Close() 26 | // 获取body 27 | body, _ := io.ReadAll(res.Body) 28 | 29 | // 获取所属采集类号 30 | class := int(gjson.Get(string(body), "list.0.type_id").Value().(float64)) 31 | 32 | if !db.JudgeClass(here.id, uint(class)) { 33 | return 34 | } 35 | 36 | // 获取影片名 37 | name := gjson.Get(string(body), "list.0.vod_name").Value().(string) 38 | 39 | // 获取图片封面地址 40 | pic := gjson.Get(string(body), "list.0.vod_pic").Value().(string) 41 | pic = urlHandle(pic) 42 | 43 | // 获取主演列表 44 | actor := "" 45 | actor_val := gjson.Get(string(body), "list.0.vod_actor").Value() 46 | if actor_val != nil { 47 | actor = actor_val.(string) 48 | } 49 | 50 | // 获取导演 51 | director := "" 52 | director_val := gjson.Get(string(body), "list.0.vod_director").Value() 53 | if director_val != nil { 54 | director = director_val.(string) 55 | } 56 | 57 | // 获取时长 58 | duration := "" 59 | duration_val := gjson.Get(string(body), "list.0.vod_duration").Value() 60 | if duration_val != nil { 61 | duration = duration_val.(string) 62 | } 63 | 64 | // 获取简介 65 | description := "" 66 | description_val := gjson.Get(string(body), "list.0.vod_content").Value() 67 | if description_val != nil { 68 | description = description_val.(string) 69 | } 70 | description = desHandle(description) // 净化功能 71 | 72 | // 获取播放链接 73 | url := gjson.Get(string(body), "list.0.vod_play_url").Value().(string) 74 | url = urlHandle(url) 75 | 76 | // 获取属于的source 77 | belong := here.id 78 | util.Logger.Infof("collect resource station called %s, get a film called %s", here.name, name) 79 | err = db.AddContent(id, name, pic, actor, director, duration, description, url, class, belong) 80 | if err != nil { 81 | util.Logger.Errorf("getter get content, store the data to database failed, err is %s", err) 82 | } 83 | // 每当获取完一条信息后就尝试休眠一秒 84 | time.Sleep(1 * time.Second) 85 | } 86 | 87 | // url处理函数 88 | func urlHandle(s string) string { 89 | return strings.Replace(s, "\\/", "/", -1) 90 | } 91 | 92 | // recover 93 | func protect() { 94 | err := recover() 95 | if err != nil { 96 | util.Logger.Errorf("some error occurred when get, err is %s", err) 97 | } 98 | } 99 | 100 | // 净化描述、去除html标签、去除头尾空格 101 | func desHandle(s string) string { 102 | return regexp.MustCompile(`<[^>]+>|(^\s*)|(\s*$)| `).ReplaceAllString(s, "") 103 | } 104 | 105 | func newAtomicBool() *atomic.Value { 106 | var value atomic.Value 107 | value.Store(false) 108 | return &value 109 | } 110 | 111 | func newHttpHandle() *http.Client { 112 | return &http.Client{ 113 | Transport: &http.Transport{ 114 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 115 | }, 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "gopkg.in/ini.v1" 9 | ) 10 | 11 | var ( 12 | SqliteAddr string // sqlite 的地质 13 | MysqlAddr string // 数据库的地址 14 | Addr = "0.0.0.0:8000" // 监听的地址 15 | SessionSecret = "secret" // session加密的秘钥 16 | LogPut string // 日志输出位置 17 | LogLevel string 18 | ) 19 | 20 | func init() { 21 | env() // 读取环境变量 22 | config() // 尝试读取配置文件 23 | cli() // 尝试读取命令行配置 24 | } 25 | 26 | // 读取环境变量 27 | func env() { 28 | { 29 | host := os.Getenv("SQLITE") 30 | SqliteAddr = host 31 | } 32 | 33 | { 34 | // mysql配置 35 | host := os.Getenv("MYSQL_HOST") 36 | user := os.Getenv("MYSQL_USER") 37 | password := os.Getenv("MYSQL_PASSWORD") 38 | databaseName := os.Getenv("DATABASE_NAME") 39 | if host != "" { 40 | MysqlAddr = fmt.Sprintf("%v:%v@tcp(%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", user, password, host, databaseName) 41 | } 42 | } 43 | 44 | listenAddr := os.Getenv("LISTEN_ADDR") // 监听地址 45 | 46 | if listenAddr != "" { 47 | Addr = listenAddr 48 | } 49 | 50 | sessionSecret := os.Getenv("SESSION_SECRET") // session秘钥 51 | 52 | if sessionSecret != "" { 53 | SessionSecret = sessionSecret 54 | } 55 | 56 | logput := os.Getenv("OUTPUT") 57 | 58 | if logput != "" { 59 | LogPut = logput 60 | } 61 | 62 | loglevel := os.Getenv("LOGLEVEL") 63 | 64 | if loglevel != "" { 65 | LogLevel = loglevel 66 | } 67 | } 68 | 69 | // 尝试读取配置文件 70 | func config() { 71 | cfg, err := ini.Load("conf.ini") 72 | if err != nil { 73 | return 74 | } 75 | { 76 | sqlite := cfg.Section("sqlite") 77 | SqliteAddr = sqlite.Key("addr").String() 78 | } 79 | { 80 | // mysql配置 81 | mysql := cfg.Section("mysql") 82 | host := mysql.Key("host").String() 83 | user := mysql.Key("user").String() 84 | password := mysql.Key("password").String() 85 | databaseName := mysql.Key("database_name").String() 86 | if host != "" { 87 | MysqlAddr = fmt.Sprintf("%v:%v@tcp(%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", user, password, host, databaseName) 88 | } 89 | } 90 | 91 | listenAddr := cfg.Section("").Key("listen_addr").String() 92 | 93 | if listenAddr != "" { 94 | Addr = listenAddr 95 | } 96 | 97 | sessionSecret := cfg.Section("").Key("session_secret").String() 98 | 99 | if sessionSecret != "" { 100 | SessionSecret = sessionSecret 101 | } 102 | 103 | logput := cfg.Section("").Key("log_put").String() 104 | 105 | if logput != "" { 106 | LogPut = logput 107 | } 108 | 109 | loglevel := cfg.Section("").Key("log_level").String() 110 | 111 | if loglevel != "" { 112 | LogLevel = loglevel 113 | } 114 | } 115 | 116 | // 尝试读取命令行配置 117 | func cli() { 118 | SqliteAddr = *flag.String("SQLITE", "data.db", "the sqlite file name!") 119 | // mysql配置 120 | host := flag.String("MYSQL_HOST", "", "the host of mysql") 121 | user := flag.String("MYSQL_USER", "", "the user of mysql") 122 | password := flag.String("MYSQL_PASSWORD", "", "the password of mysql") 123 | databaseName := flag.String("DATABASE_NAME", "movie", "the database name of mysql") 124 | 125 | addr := flag.String("LISTEN_ADDR", "", "the program listen addr") 126 | sessionSecret := flag.String("SESSION_SECRET", "", "the secret of session") 127 | logput := flag.String("LOG_PUT", "", "the location of log put") 128 | loglevel := flag.String("LOG_LEVEL", "error", "the log level") 129 | 130 | flag.Parse() 131 | 132 | if *host == "" || *user == "" || *password == "" { 133 | if *host != "" || *user != "" || *password != "" { 134 | fmt.Println("the params is not enough") 135 | os.Exit(1) // 出现错误的状态码 136 | } 137 | return 138 | } 139 | 140 | if *host != "" { 141 | MysqlAddr = fmt.Sprintf("%v:%v@tcp(%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", user, password, host, databaseName) 142 | } 143 | 144 | if *addr != "" { 145 | Addr = *addr 146 | } 147 | 148 | if *sessionSecret != "" { 149 | SessionSecret = *sessionSecret 150 | } 151 | 152 | if *logput != "" { 153 | LogPut = *logput 154 | } 155 | 156 | if *loglevel != "" { 157 | LogLevel = *loglevel 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /db/interface.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import _struct "movie/db/struct" 4 | 5 | type Db interface { 6 | // Category 分类接口 7 | 8 | AddCategory(name string) (uint, error) // 添加分类 9 | UpdateCategoryName(id uint, newName string) error // 更改分类名字 10 | DelCategory(id uint) error // 删除分类 11 | AllCategory() ([]_struct.Category, error) // 获取所有分类 12 | CategoryMovieCount(categoryId uint) (int, error) // 获取分类下影片数目 13 | CategoryClassCount(categoryId uint) int // 获取分类下采集类数目 14 | SetCategoryMain(id uint, main bool) error // 设置采集分类为main 15 | 16 | // Class 采集类接口 17 | 18 | AddClass(sourceId uint, name string, class_Id int) error // 添加采集类 19 | DistributeClass(classId uint, categoryId uint) error // 分配采集类 20 | JudgeClass(SourceId uint, class_Id uint) bool // 判断采集类是否允许采集 21 | // GetClass getClassIdBySourceId(sourceId uint, class_Id int) uint // 通过采集源id获取采集类id 22 | GetClass(sourceId uint) ([]_struct.Class, error) // 通过采集源id获取其下所有采集类 23 | ChangeClassGet(classId uint, get bool) error // 改变采集状态 24 | ClassMovieNum(classId uint) int // 获得某个采集类下所有影片 25 | 26 | // Content 影片接口 27 | 28 | AddContent( 29 | content_Id int, name string, pic string, actor string, director string, 30 | duration string, description string, url string, class_Id int, sourceId uint, 31 | ) error // 添加影片 32 | DelContent( 33 | id uint, 34 | ) error // 删除影片 35 | Rename(id uint, name string) error // 重命名影片名 36 | Repic(id uint, pic string) error // 重命名影片海报地址 37 | Reactor(id uint, actor string) error // 重命名影片演员 38 | Redirector(id uint, director string) error //重命名影片导演 39 | Reduration(id uint, duration string) error // 重命名影片时长 40 | Redesc(id uint, desc string) error //重命名影片的简介 41 | Reurl(id uint, url string) error // 重命名影片的播放地址 42 | 43 | SearchContent( 44 | keyword string, 45 | num int, 46 | pg int, 47 | ) ([]_struct.Content, int, error) // 全局搜索影片 48 | SearchContent_Category( 49 | categoryId uint, 50 | keyword string, 51 | num int, 52 | pg int, 53 | ) ([]_struct.Content, int, error) // 搜索自建分类下影片 54 | SearchContent_Class( 55 | classId uint, 56 | keyword string, 57 | num int, 58 | pg int, 59 | ) ([]_struct.Content, int, error) // 搜索采集分类下影片 60 | SearchContent_Source( 61 | sourceId uint, 62 | keyword string, 63 | num int, 64 | pg int, 65 | ) ([]_struct.Content, int, error) // 搜索某个采集源下的影片 66 | ContentList( 67 | num int, 68 | pg int, 69 | ) ([]_struct.Content, int, error) // 全局影片列表 70 | ContentList_Category( 71 | categoryId uint, 72 | num int, 73 | pg int, 74 | ) ([]_struct.Content, int, error) // 自建分类影片列表 75 | ContentList_Class( 76 | classId uint, 77 | num int, 78 | pg int, 79 | ) ([]_struct.Content, int, error) // 采集类影片列表 80 | ContentList_Source( 81 | sourceId uint, 82 | num int, 83 | pg int, 84 | ) ([]_struct.Content, int, error) // 采集源影片列表 85 | GetContent( 86 | id uint, 87 | ) (_struct.Content, error) // 获取影片 88 | BrowseContentByCategory( 89 | categoryId uint, 90 | num int, 91 | pg int, 92 | ) ([]_struct.Content, int, error) // 分类下的影片 93 | ContentCount() int // 获取全部影片的数目 94 | 95 | // Source 采集源接口 96 | 97 | AddSource(name string, url string) (uint, bool) // 添加采集源 98 | UpdateSourceName(id uint, newName string) error // 更新资源库名字 99 | UpdateSourceUrl(id uint, newUrl string) error // 更新资源库地址 100 | DelSource(id uint) error // 删除资源库 101 | UpdateSourceOk(id uint, status bool) error // 资源库采集状态(是否完成) 102 | AllSource() ([]_struct.Source, error) // 获取所有采集源 103 | UpdateSourcePg(id uint, pg int) error // 更新采集页数 104 | SourceMovieNum(sourceId uint) int // 获取采集源下影片数目 105 | 106 | // System 系统接口 107 | 108 | Login(account string, password string) bool // 登录验证 109 | UpdateAccount(oldAccount string, newAccount string) error // 更新后台账户 110 | UpdatePassword(account string, newPassword string) error // 更新后台密码 111 | ChangeCollectInterval(interval int) error // 更改采集间隔 112 | GetCollectInterval() int // 获取采集间隔 113 | 114 | // db 特性,使用时需要检验返回的是否为nil空指针 115 | 116 | DbGet() interface{} // 获取数据库句柄 117 | DbType() int // 获取数据库类型 118 | 119 | Exports() (_struct.DATA, error) //导出库 120 | Imports(_struct.DATA) error 121 | } 122 | -------------------------------------------------------------------------------- /router/front.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | mm "movie/manager" 5 | "movie/router/MiddleWare" 6 | "movie/util" 7 | "net/http" 8 | "sort" 9 | "strconv" 10 | 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | // TODO 该文件log函数未替换 15 | 16 | // 总front函数 17 | func front(r *gin.Engine, manager *mm.Manager) { 18 | // 搜索功能 19 | search(r, manager) 20 | 21 | // 获取影片信息 22 | play(r, manager) 23 | 24 | // 获取某个分类下最新的影片 25 | category(r, manager) 26 | 27 | // 获取在首页显示的分类 28 | mainCategory(r, manager) 29 | } 30 | 31 | // 搜索功能 32 | func search(r *gin.Engine, manager *mm.Manager) { 33 | r.POST("/search", MiddleWare.Cache(), func(c *gin.Context) { 34 | // 获取关键字 35 | keyword := c.PostForm("keyword") 36 | if keyword == "" { 37 | util.Logger.Warn("search movie failed, the keyword is blank") 38 | c.Status(http.StatusBadRequest) 39 | return 40 | } 41 | // 获取页码 42 | pgV := c.PostForm("pg") 43 | pg, err := strconv.Atoi(pgV) 44 | if err != nil { 45 | util.Logger.Warn("search movie failed, the pg is not a integer") 46 | c.Status(http.StatusBadRequest) 47 | return 48 | } 49 | // 获取数量 50 | numV := c.PostForm("num") 51 | num, err := strconv.Atoi(numV) 52 | if err != nil { 53 | util.Logger.Warn("search movie failed, the num is not a integer") 54 | c.Status(http.StatusBadRequest) 55 | return 56 | } 57 | movies, pgCount, err := manager.SearchContent(keyword, num, pg) 58 | if err != nil { 59 | util.Logger.Error("search movie from database failed,keyword:", keyword, " err:", err) 60 | c.Status(http.StatusInternalServerError) 61 | return 62 | } 63 | movie := Movie{ 64 | Movies: movies, 65 | PgCount: pgCount, 66 | } 67 | util.Logger.Info("search movie, keyword is ", keyword) 68 | c.JSON(http.StatusOK, movie) 69 | }) 70 | } 71 | 72 | // 获取影片信息 73 | func play(r *gin.Engine, manager *mm.Manager) { 74 | r.GET("/play/:id", MiddleWare.Cache(), func(c *gin.Context) { 75 | idV := c.Param("id") 76 | id, err := strconv.Atoi(idV) 77 | if err != nil { 78 | util.Logger.Warn("get movie info failed, the id is not a integer") 79 | c.Status(http.StatusBadRequest) 80 | return 81 | } 82 | movie, err := manager.GetContent(uint(id)) 83 | if err != nil { 84 | util.Logger.Error("get movie info failed, err:", err) 85 | c.Status(http.StatusInternalServerError) 86 | return 87 | } 88 | c.JSON(http.StatusOK, movie) 89 | }) 90 | } 91 | 92 | // 获取某个分类下最新的影片 93 | func category(r *gin.Engine, manager *mm.Manager) { 94 | r.POST("/category/:id", MiddleWare.Cache(), func(c *gin.Context) { 95 | idV := c.Param("id") 96 | id, err := strconv.Atoi(idV) 97 | if err != nil { 98 | util.Logger.Warn("get category movie list fialed, the category id is not a integer") 99 | c.Status(http.StatusBadRequest) 100 | return 101 | } 102 | pgV := c.PostForm("pg") 103 | pg, err := strconv.Atoi(pgV) 104 | // pg-- 105 | if err != nil { 106 | util.Logger.Warn("get category movie list failed, the pg is not a integer") 107 | 108 | c.Status(http.StatusBadRequest) 109 | return 110 | } 111 | numV := c.PostForm("num") 112 | num, err := strconv.Atoi(numV) 113 | if err != nil { 114 | util.Logger.Warn("get category movie list failed, the num is not a integer") 115 | c.Status(http.StatusBadRequest) 116 | return 117 | } 118 | movies, pgCount, err := manager.BrowseContentByCategory(uint(id), num, pg) 119 | if err != nil { 120 | util.Logger.Error("get category movie list failed, err:", err) 121 | c.Status(http.StatusInternalServerError) 122 | } 123 | movie := Movie{ 124 | Movies: movies, 125 | PgCount: pgCount, 126 | } 127 | util.Logger.Info("get category movie list, category id:", id) 128 | c.JSON(http.StatusOK, movie) 129 | }) 130 | } 131 | 132 | // 获取在首页显示的分类 133 | func mainCategory(r *gin.Engine, manager *mm.Manager) { 134 | r.GET("/mainCategory", MiddleWare.Cache(), func(c *gin.Context) { 135 | categories, err := manager.GetMainCategory() 136 | if err != nil { 137 | util.Logger.Error("get main category from database failed, err:", err) 138 | c.Status(http.StatusInternalServerError) 139 | return 140 | } 141 | sort.Slice(categories, func(i, j int) bool { 142 | return categories[i].ID < categories[j].ID 143 | }) 144 | util.Logger.Info("get main category") 145 | c.JSON(http.StatusOK, categories) 146 | }) 147 | } 148 | -------------------------------------------------------------------------------- /db/drive/gormDb/content.go: -------------------------------------------------------------------------------- 1 | package gormDb 2 | 3 | import ( 4 | "math" 5 | _struct "movie/db/struct" 6 | "time" 7 | 8 | "gorm.io/gorm" 9 | ) 10 | 11 | func (here *Db) AddContent( 12 | content_Id int, 13 | name string, // 影片名 14 | pic string, // 15 | actor string, 16 | director string, 17 | duration string, 18 | description string, 19 | url string, 20 | class_Id int, // 所属类别 21 | sourceId uint, // source id 22 | ) error { 23 | id, ok := here.existContent(content_Id, sourceId) 24 | if ok { 25 | return here.updateContent(id, 26 | content_Id, 27 | name, 28 | pic, 29 | actor, 30 | director, 31 | duration, 32 | description, 33 | url, 34 | // class_Id, 35 | // sourceId, 36 | ) 37 | } 38 | return here.addContent( 39 | content_Id, 40 | name, 41 | pic, 42 | actor, 43 | director, 44 | duration, 45 | description, 46 | url, 47 | class_Id, 48 | sourceId) 49 | } 50 | 51 | func (here *Db) existContent(content_Id int, sourceId uint) (uint, bool) { 52 | var content _struct.Content 53 | here.db.Where("content_id = ? AND source_id = ?", content_Id, sourceId).Select("id").Find(&content) 54 | if content.ID == 0 { 55 | return 0, false 56 | } 57 | 58 | return content.ID, true 59 | } 60 | 61 | func (here *Db) updateContent( 62 | id uint, 63 | content_Id int, 64 | name string, // 影片名 65 | pic string, // 66 | actor string, 67 | director string, 68 | duration string, 69 | description string, 70 | url string, 71 | // class_Id int, //所属类别 72 | // sourceId uint, // source id 73 | ) error { 74 | content := &_struct.Content{ 75 | ContentId: content_Id, 76 | Name: name, 77 | Pic: pic, 78 | Actor: actor, 79 | Director: director, 80 | Duration: duration, 81 | Description: description, 82 | Url: url, 83 | Stamp: time.Now().Unix(), 84 | } 85 | db := here.db.Model(&_struct.Content{ 86 | ID: id, 87 | }).Updates(content) 88 | return db.Error 89 | } 90 | 91 | func (here *Db) addContent( 92 | content_Id int, 93 | name string, // 影片名 94 | pic string, // 95 | actor string, 96 | director string, 97 | duration string, 98 | description string, 99 | url string, 100 | class_Id int, // 所属类别 101 | sourceId uint, // source id 102 | ) error { 103 | var db *gorm.DB 104 | content := &_struct.Content{ 105 | ContentId: content_Id, 106 | Name: name, 107 | Pic: pic, 108 | Actor: actor, 109 | Director: director, 110 | Duration: duration, 111 | Description: description, 112 | Url: url, 113 | Stamp: time.Now().Unix(), 114 | } 115 | // 创建事务 116 | tx := here.db.Begin() 117 | 118 | // 创建content 119 | db = tx.Create(content) 120 | if db.Error != nil { 121 | tx.Rollback() 122 | return db.Error 123 | } 124 | 125 | // 添加content与source关联 126 | err := tx.Model(&_struct.Source{ 127 | ID: sourceId, 128 | }).Association("Content").Append(content) 129 | if err != nil { 130 | tx.Rollback() 131 | return err 132 | } 133 | 134 | // 尝试寻找class 135 | classId := here.getClassIdBySourceId(sourceId, class_Id) 136 | 137 | // 尝试添加content与class关系 138 | err = tx.Model(&_struct.Class{ 139 | ID: classId, 140 | }).Association("Content").Append(content) 141 | if err != nil { 142 | tx.Rollback() 143 | return err 144 | } 145 | 146 | // 提交事务 147 | tx.Commit() 148 | return nil 149 | } 150 | 151 | // DelContent 删除content 152 | func (here *Db) DelContent(id uint) error { 153 | db := here.db.Delete(&_struct.Content{}, id) 154 | return db.Error 155 | } 156 | 157 | // SearchContent 全局搜索影片 158 | func (here *Db) SearchContent(keyword string, num int, pg int) ([]_struct.Content, int, error) { 159 | // 尝试进行搜索操作 160 | contents := make([]_struct.Content, 0) 161 | db := here.db.Select("id", "name", "pic", "actor", "director", "duration", "description", "url").Where("name LIKE ?", "%"+keyword+"%").Offset(num * (pg - 1)).Limit(num).Find(&contents) 162 | 163 | // 尝试进行计数操作 164 | var count int64 165 | here.db.Model(&_struct.Content{}).Where("name LIKE ?", "%"+keyword+"%").Count(&count) 166 | 167 | // 返回结果 168 | return contents, int(math.Ceil(float64(count) / float64(num))), db.Error 169 | } 170 | 171 | // SearchContent_Category 搜索自建分类下影片 172 | func (here *Db) SearchContent_Category(categoryId uint, keyword string, num int, pg int) ([]_struct.Content, int, error) { 173 | // 查询所属分类下的采集类 174 | var class []int 175 | _ = here.db.Model(&_struct.Category{ 176 | ID: categoryId, 177 | }).Select("id").Association("Class").Find(&class) 178 | 179 | // 定义变量结果存储 180 | var contents []_struct.Content 181 | db := here.db.Select("id", "name", "pic", "actor", "director", "duration", "description", "url").Where("name LIKE ? AND class_id IN ?", "%"+keyword+"%", class).Offset(num * (pg - 1)).Limit(num).Find(&contents) 182 | 183 | // 尝试进行计数操作 184 | var count int64 185 | here.db.Model(&_struct.Content{}).Where("name LIKE ? AND class_id IN ?", "%"+keyword+"%", class).Count(&count) 186 | 187 | // 返回结果 188 | return contents, int(math.Ceil(float64(count) / float64(num))), db.Error 189 | } 190 | 191 | // SearchContent_Class 搜索采集分类下影片 192 | func (here *Db) SearchContent_Class(classId uint, keyword string, num int, pg int) ([]_struct.Content, int, error) { 193 | // 定义变量结果存储 194 | var contents []_struct.Content 195 | db := here.db.Select("id", "name", "pic", "actor", "director", "duration", "description", "url").Where("name LIKE ? AND class_id = ?", "%"+keyword+"%", classId).Offset(num * (pg - 1)).Limit(num).Find(&contents) 196 | 197 | // 尝试进行计数操作 198 | var count int64 199 | here.db.Model(&_struct.Content{}).Where("name LIKE ? AND class_id = ?", "%"+keyword+"%", classId).Count(&count) 200 | 201 | // 返回结果 202 | return contents, int(math.Ceil(float64(count) / float64(num))), db.Error 203 | } 204 | 205 | // SearchContent_Source 搜索某个采集源下的影片 206 | func (here *Db) SearchContent_Source(sourceId uint, keyword string, num int, pg int) ([]_struct.Content, int, error) { 207 | // 定义变量结果存储 208 | var contents []_struct.Content 209 | db := here.db.Select("id", "name", "pic", "actor", "director", "duration", "description", "url").Where("name LIKE ? AND source_id = ?", "%"+keyword+"%", sourceId).Offset(num * (pg - 1)).Limit(num).Find(&contents) 210 | 211 | // 尝试进行计数操作 212 | var count int64 213 | here.db.Model(&_struct.Content{}).Where("name LIKE ? AND source_id = ?", "%"+keyword+"%", sourceId).Count(&count) 214 | 215 | // 返回结果 216 | return contents, int(math.Ceil(float64(count) / float64(num))), db.Error 217 | } 218 | 219 | // ContentList 全局影片列表 220 | func (here *Db) ContentList(num int, pg int) ([]_struct.Content, int, error) { 221 | // 尝试进行搜索操作 222 | var contents []_struct.Content 223 | db := here.db.Select("id", "name", "pic", "actor", "director", "duration", "description", "url").Offset(num * (pg - 1)).Limit(num).Find(&contents) 224 | 225 | // 尝试进行计数操作 226 | var count int64 227 | here.db.Model(&_struct.Content{}).Count(&count) 228 | 229 | // 返回结果 230 | return contents, int(math.Ceil(float64(count) / float64(num))), db.Error 231 | } 232 | 233 | // ContentList_Category 自建分类影片列表 234 | func (here *Db) ContentList_Category(categoryId uint, num int, pg int) ([]_struct.Content, int, error) { 235 | // 查询所属分类下的采集类 236 | var class []int 237 | _ = here.db.Model(&_struct.Category{ 238 | ID: categoryId, 239 | }).Select("id").Association("Class").Find(&class) 240 | 241 | // 定义变量结果存储 242 | var contents []_struct.Content 243 | db := here.db.Select("id", "name", "pic", "actor", "director", "duration", "description", "url").Where("class_id IN ?", class).Offset(num * (pg - 1)).Limit(num).Find(&contents) 244 | 245 | // 尝试进行计数操作 246 | var count int64 247 | here.db.Model(&_struct.Content{}).Where("class_id IN ?", class).Count(&count) 248 | 249 | // 返回结果 250 | return contents, int(math.Ceil(float64(count) / float64(num))), db.Error 251 | } 252 | 253 | // ContentList_Class 采集类影片列表 254 | func (here *Db) ContentList_Class(classId uint, num int, pg int) ([]_struct.Content, int, error) { 255 | // 定义变量结果存储 256 | var contents []_struct.Content 257 | db := here.db.Select("id", "name", "pic", "actor", "director", "duration", "description", "url").Where("class_id = ?", classId).Offset(num * (pg - 1)).Limit(num).Find(&contents) 258 | 259 | // 尝试进行计数操作 260 | var count int64 261 | here.db.Model(&_struct.Content{}).Where("class_id = ?", classId).Count(&count) 262 | 263 | // 返回结果 264 | return contents, int(math.Ceil(float64(count) / float64(num))), db.Error 265 | } 266 | 267 | // ContentList_Source 采集源影片列表 268 | func (here *Db) ContentList_Source(sourceId uint, num int, pg int) ([]_struct.Content, int, error) { 269 | // 定义变量结果存储 270 | var contents []_struct.Content 271 | db := here.db.Select("id", "name", "pic", "actor", "director", "duration", "description", "url").Where("source_id = ?", sourceId).Offset(num * (pg - 1)).Limit(num).Find(&contents) 272 | 273 | // 尝试进行计数操作 274 | var count int64 275 | here.db.Model(&_struct.Content{}).Where("source_id = ?", sourceId).Count(&count) 276 | 277 | // 返回结果 278 | return contents, int(math.Ceil(float64(count) / float64(num))), db.Error 279 | } 280 | 281 | // GetContent 获取影片 282 | func (here *Db) GetContent(id uint) (_struct.Content, error) { 283 | var content _struct.Content 284 | db := here.db.First(&content, id) 285 | return content, db.Error 286 | } 287 | 288 | // BrowseContentByCategory 分类下的影片 289 | func (here *Db) BrowseContentByCategory(categoryId uint, num int, pg int) ([]_struct.Content, int, error) { 290 | // 查询所属分类下的采集类 291 | var class []int 292 | _ = here.db.Model(&_struct.Category{ 293 | ID: categoryId, 294 | }).Select("id").Association("Class").Find(&class) 295 | 296 | // 创建一个存储content的切片 297 | var contents []_struct.Content 298 | 299 | // 查询content 300 | db := here.db.Model(&_struct.Content{}).Where("class_id IN ?", class).Order("stamp desc").Select("id", "name", "pic", "actor", "director", "duration", "description").Offset(num * (pg - 1)).Limit(num).Find(&contents) 301 | 302 | // 查询计数 303 | var count int64 304 | here.db.Model(&_struct.Content{}).Where("class_id IN ?", class).Count(&count) 305 | 306 | return contents, int(math.Ceil(float64(count) / float64(num))), db.Error 307 | } 308 | 309 | // ContentCount 获取全部影片的数目 310 | func (here *Db) ContentCount() int { 311 | var result int64 312 | here.db.Model(&_struct.Content{}).Count(&result) 313 | return int(result) 314 | } 315 | 316 | func (here *Db) Rename(id uint, name string) error { 317 | db := here.db.Model(&_struct.Content{ 318 | ID: id, 319 | }).Update("name", name) 320 | return db.Error 321 | } 322 | 323 | func (here *Db) Repic(id uint, pic string) error { 324 | db := here.db.Model(&_struct.Content{ 325 | ID: id, 326 | }).Update("pic", pic) 327 | return db.Error 328 | } 329 | 330 | func (here *Db) Reactor(id uint, actor string) error { 331 | db := here.db.Model(&_struct.Content{ 332 | ID: id, 333 | }).Update("actor", actor) 334 | return db.Error 335 | } 336 | 337 | func (here *Db) Redirector(id uint, director string) error { 338 | db := here.db.Model(&_struct.Content{ 339 | ID: id, 340 | }).Update("director", director) 341 | return db.Error 342 | } 343 | 344 | func (here *Db) Reduration(id uint, duration string) error { 345 | db := here.db.Model(&_struct.Content{ 346 | ID: id, 347 | }).Update("duration", duration) 348 | return db.Error 349 | } 350 | 351 | func (here *Db) Redesc(id uint, desc string) error { 352 | db := here.db.Model(&_struct.Content{ 353 | ID: id, 354 | }).Update("description", desc) 355 | return db.Error 356 | } 357 | 358 | func (here *Db) Reurl(id uint, url string) error { 359 | db := here.db.Model(&_struct.Content{ 360 | ID: id, 361 | }).Update("url", url) 362 | return db.Error 363 | } 364 | -------------------------------------------------------------------------------- /manager/db.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | _struct "movie/db/struct" 7 | "movie/getter" 8 | "movie/util" 9 | "sort" 10 | "strconv" 11 | ) 12 | 13 | func (here *Manager) GetSource() ([]Source, error) { 14 | sources := make([]Source, 0) 15 | v, err := here.db.AllSource() 16 | here.getters_mutex.Lock() 17 | for _, v := range v { 18 | sources = append(sources, Source{ 19 | ID: v.ID, 20 | Name: v.Name, 21 | Url: v.Url, 22 | Get: here.getters[v.ID].JudgeGetting(), 23 | Ok: v.Ok, 24 | Pg: v.Pg, 25 | }) 26 | } 27 | here.getters_mutex.Unlock() 28 | sort.SliceStable(sources, func(i, j int) bool { 29 | return sources[i].ID < sources[j].ID 30 | }) 31 | return sources, err 32 | } 33 | 34 | // AddSource 增加采集源 35 | func (here *Manager) AddSource(name string, url string) bool { 36 | id, ok := here.db.AddSource(name, url) 37 | if !ok { 38 | return ok 39 | } 40 | here.getters_mutex.Lock() 41 | here.getters[id] = getter.NewGetter(id, name, url, false, 1) 42 | here.getters_mutex.Unlock() 43 | return ok 44 | } 45 | 46 | // 重命名source名字 47 | func (here *Manager) RenameSource(id uint, name string) error { 48 | err := here.db.UpdateSourceName(id, name) 49 | if err != nil { 50 | util.Logger.Warnf("rename source fialed, err: %s", err) 51 | return err 52 | } 53 | here.getters_mutex.Lock() 54 | getter, ok := here.getters[id] 55 | if !ok { 56 | return errors.New("the source which id is" + strconv.Itoa(int(id)) + " not exists") 57 | } 58 | getter.Rename(name) 59 | here.getters_mutex.Unlock() 60 | return nil 61 | } 62 | 63 | // 重命名source地址 64 | func (here *Manager) ReurlSource(id uint, url string) error { 65 | err := here.db.UpdateSourceUrl(id, url) 66 | if err != nil { 67 | util.Logger.Warnf("rename source fialed, err: %s", err) 68 | return err 69 | } 70 | here.getters_mutex.Lock() 71 | getter, ok := here.getters[id] 72 | if !ok { 73 | return errors.New("the source which id is" + strconv.Itoa(int(id)) + " not exists") 74 | } 75 | getter.Reurl(url) 76 | here.getters_mutex.Unlock() 77 | return nil 78 | } 79 | 80 | // DelSource 删除采集源 81 | func (here *Manager) DelSource(id uint) error { 82 | here.getters_mutex.Lock() 83 | getter, ok := here.getters[id] 84 | here.getters_mutex.Unlock() 85 | if !ok { 86 | return errors.New("the source which id is" + strconv.Itoa(int(id)) + " not exists") 87 | } 88 | getter.StopGet() 89 | // 直到采集停下前,会一直阻塞,应该采取一个定时任务(例如context定时)防止任务超时 TODO 90 | for getter.JudgeGetting() { 91 | } 92 | err := here.db.DelSource(id) 93 | if err != nil { 94 | return err 95 | } 96 | { 97 | 98 | here.getters_mutex.Lock() 99 | delete(here.getters, id) 100 | here.getters_mutex.Unlock() 101 | } 102 | return err 103 | } 104 | 105 | // Copy main information 106 | func handleContents(contents []_struct.Content) []Movie { 107 | movies := make([]Movie, 0) 108 | for _, content := range contents { 109 | movies = append(movies, Movie{ 110 | Id: int(content.ID), 111 | Name: content.Name, 112 | Pic: content.Pic, 113 | Actor: content.Actor, 114 | Director: content.Director, 115 | Duration: content.Duration, 116 | Description: content.Description, 117 | Url: content.Url, 118 | }) 119 | } 120 | return movies 121 | } 122 | 123 | // SearchContent 前台接口,搜索所有影片 124 | func (here *Manager) SearchContent(keyword string, num int, pg int) ([]Movie, int, error) { 125 | contents, pgCount, err := here.db.SearchContent(keyword, num, pg) 126 | movies := make([]Movie, 0) 127 | for _, content := range contents { 128 | movies = append(movies, Movie{ 129 | Id: int(content.ID), 130 | Name: content.Name, 131 | Pic: content.Pic, 132 | Actor: content.Actor, 133 | Director: content.Director, 134 | Duration: content.Duration, 135 | Description: content.Description, 136 | }) 137 | } 138 | return movies, pgCount, err 139 | } 140 | 141 | // SearchContent_bk 后台接口,全局搜索影片 142 | func (here *Manager) SearchContent_bk(keyword string, num int, pg int) ([]Movie, int, error) { 143 | contents, pgCount, err := here.db.SearchContent(keyword, num, pg) 144 | movies := handleContents(contents) 145 | return movies, pgCount, err 146 | } 147 | 148 | // SearchContent_bk_Category 后台接口,搜索自建分类下影片 149 | func (here *Manager) SearchContent_bk_Category( 150 | categoryId uint, 151 | keyword string, 152 | num int, 153 | pg int, 154 | ) ([]Movie, int, error) { 155 | contents, pgCount, err := here.db.SearchContent_Category(categoryId, keyword, num, pg) 156 | movies := handleContents(contents) 157 | return movies, pgCount, err 158 | } 159 | 160 | // SearchContent_bk_Class 后台接口,搜索采集类下影片 161 | func (here *Manager) SearchContent_bk_Class( 162 | classId uint, 163 | keyword string, 164 | num int, 165 | pg int, 166 | ) ([]Movie, int, error) { 167 | contents, pgCount, err := here.db.SearchContent_Class(classId, keyword, num, pg) 168 | movies := handleContents(contents) 169 | return movies, pgCount, err 170 | } 171 | 172 | // SearchContent_bk_Source 后台接口,根据采集源搜索影片 173 | func (here *Manager) SearchContent_bk_Source( 174 | sourceId uint, 175 | keyword string, 176 | num int, 177 | pg int, 178 | ) ([]Movie, int, error) { 179 | contents, pgCount, err := here.db.SearchContent_Source(sourceId, keyword, num, pg) 180 | movies := handleContents(contents) 181 | return movies, pgCount, err 182 | } 183 | 184 | // ContentList 后台接口,所有影片列表 185 | func (here *Manager) ContentList(num int, pg int) ([]Movie, int, error) { 186 | contents, pgCount, err := here.db.ContentList(num, pg) 187 | movies := handleContents(contents) 188 | return movies, pgCount, err 189 | } 190 | 191 | // ContentList_Category 后台接口,自建分类影片列表 192 | func (here *Manager) ContentList_Category(categoryId uint, num int, pg int) ([]Movie, int, error) { 193 | contents, pgCount, err := here.db.ContentList_Category(categoryId, num, pg) 194 | movies := handleContents(contents) 195 | return movies, pgCount, err 196 | } 197 | 198 | // ContentList_Class 后台接口,采集类影片列表 199 | func (here *Manager) ContentList_Class(classId uint, num int, pg int) ([]Movie, int, error) { 200 | contents, pgCount, err := here.db.ContentList_Class(classId, num, pg) 201 | movies := handleContents(contents) 202 | return movies, pgCount, err 203 | } 204 | 205 | // ContentList_Source 后台接口,采集源影片列表 206 | func (here *Manager) ContentList_Source(sourceId uint, num int, pg int) ([]Movie, int, error) { 207 | contents, pgCount, err := here.db.ContentList_Source(sourceId, num, pg) 208 | movies := handleContents(contents) 209 | return movies, pgCount, err 210 | } 211 | 212 | // GetContent 获取影片详细信息 213 | func (here *Manager) GetContent(id uint) (Movie, error) { 214 | content, err := here.db.GetContent(id) 215 | movie := Movie{ 216 | Id: int(content.ID), 217 | Name: content.Name, 218 | Pic: content.Pic, 219 | Actor: content.Actor, 220 | Director: content.Director, 221 | Duration: content.Duration, 222 | Description: content.Description, 223 | Url: content.Url, 224 | } 225 | return movie, err 226 | } 227 | 228 | // DelContent 删除影片 229 | func (here *Manager) DelContent(id uint) error { 230 | return here.db.DelContent(id) 231 | } 232 | 233 | // UpdateSourceName 更新采集源名字 234 | func (here *Manager) UpdateSourceName(id uint, newName string) error { 235 | return here.db.UpdateSourceName(id, newName) 236 | } 237 | 238 | // UpdateSourceUrl 更新采集源地址 239 | func (here *Manager) UpdateSourceUrl(id uint, newUrl string) error { 240 | return here.db.UpdateSourceUrl(id, newUrl) 241 | } 242 | 243 | // AddCategory 增加自定义分类 244 | func (here *Manager) AddCategory(name string) error { 245 | _, ok := here.db.AddCategory(name) 246 | return ok 247 | } 248 | 249 | // SetCategoryMain 设置采集分类是否显示在首页 250 | func (here *Manager) SetCategoryMain(id uint, main bool) error { 251 | err := here.db.SetCategoryMain(id, main) 252 | return err 253 | } 254 | 255 | // GetCategory 获取所有分类 256 | func (here *Manager) GetCategory() ([]Category, error) { 257 | categories := make([]Category, 0) 258 | tmp, err := here.db.AllCategory() 259 | if err != nil { 260 | return categories, err 261 | } 262 | for _, v := range tmp { 263 | num, err := here.db.CategoryMovieCount(v.ID) 264 | if err != nil { 265 | return categories, err 266 | } 267 | categories = append(categories, Category{ 268 | ID: v.ID, 269 | Name: v.Name, 270 | ClassNum: here.db.CategoryClassCount(v.ID), 271 | Main: v.Main, 272 | MovieNum: num, 273 | }) 274 | } 275 | return categories, nil 276 | } 277 | 278 | // GetMainCategory 获取主分类(也就是应该在主页显示的分类) 279 | func (here *Manager) GetMainCategory() ([]Category, error) { 280 | res := make([]Category, 0) 281 | arr, err := here.db.AllCategory() 282 | if err != nil { 283 | return res, err 284 | } 285 | for _, v := range arr { 286 | if v.Main { 287 | res = append(res, Category{ 288 | ID: v.ID, 289 | Name: v.Name, 290 | // Main: v.Main, 291 | // should not return the info of main 292 | }) 293 | } 294 | } 295 | return res, nil 296 | } 297 | 298 | // DelCategory 删除分类 299 | func (here *Manager) DelCategory(id uint) error { 300 | return here.db.DelCategory(id) 301 | } 302 | 303 | // UpdateCategory 更新分类 304 | func (here *Manager) UpdateCategory(id uint, newName string) error { 305 | return here.db.UpdateCategoryName(id, newName) 306 | } 307 | 308 | // BrowseContentByCategory 获取分类下影片 309 | func (here *Manager) BrowseContentByCategory( 310 | categoryId uint, 311 | num int, 312 | pg int, 313 | ) ([]Movie, int, error) { 314 | contents, pgCount, err := here.db.BrowseContentByCategory(categoryId, num, pg) 315 | 316 | movies := make([]Movie, 0) 317 | 318 | for _, content := range contents { 319 | movies = append(movies, Movie{ 320 | Id: int(content.ID), 321 | Name: content.Name, 322 | Pic: content.Pic, 323 | Actor: content.Actor, 324 | Director: content.Director, 325 | Duration: content.Duration, 326 | Description: content.Description, 327 | }) 328 | } 329 | 330 | return movies, pgCount, err 331 | } 332 | 333 | func (here *Manager) GetClass(sourceId uint) ([]Class, error) { 334 | v, err := here.db.GetClass(sourceId) 335 | classes := make([]Class, 0) 336 | for _, v := range v { 337 | classes = append(classes, Class{ 338 | ID: v.ID, 339 | Name: v.Name, 340 | Get: v.Get, 341 | CategoryId: v.CategoryID, 342 | }) 343 | } 344 | return classes, err 345 | } 346 | 347 | // DistributeClass 分配采集类 348 | func (here *Manager) DistributeClass(classId uint, categoryId uint) error { 349 | return here.db.DistributeClass(classId, categoryId) 350 | } 351 | 352 | // ChangeClassGet 更改采集类是否允许采集 353 | func (here *Manager) ChangeClassGet(classId uint, get bool) error { 354 | return here.db.ChangeClassGet(classId, get) 355 | } 356 | 357 | // Login 登录函数 358 | func (here *Manager) Login(account string, password string) bool { 359 | return here.db.Login(account, password) 360 | } 361 | 362 | // ContentCount 获取所有影片数目 363 | func (here *Manager) ContentCount() int { 364 | return here.db.ContentCount() 365 | } 366 | 367 | // UpdateAccount 更新用户名 368 | func (here *Manager) UpdateAccount(oldAccount string, newAccount string) error { 369 | return here.db.UpdateAccount(oldAccount, newAccount) 370 | } 371 | 372 | // UpdatePassword 更新密码 373 | func (here *Manager) UpdatePassword(account string, newPassword string) error { 374 | return here.db.UpdatePassword(account, newPassword) 375 | } 376 | 377 | // UpdateCollectInterval 更新采集间隔 378 | func (here *Manager) UpdateCollectInterval(interval int) error { 379 | getters := make([]*getter.Getter, 0) // 创建一个切片存储当前正在采集的采集源 380 | // 尝试关闭所有正在进行的采集源 381 | here.getters_mutex.Lock() 382 | for _, getter := range here.getters { 383 | if getter.JudgeGetting() { 384 | getters = append(getters, getter) // 存储 385 | getter.StopGet() 386 | for getter.JudgeGetting() { 387 | // 自旋等待采集源结束 388 | } 389 | } 390 | } 391 | here.getters_mutex.Unlock() 392 | err := here.db.ChangeCollectInterval(interval) 393 | if err == nil { 394 | // 数据库更改成功后才会修改内存中的值 395 | getter.ChangeInterval(interval) 396 | } 397 | // 复原原本采集现场 398 | for _, getter := range getters { 399 | getter.StartGet() 400 | } 401 | return err 402 | } 403 | 404 | // GetCollectInterval 获取采集间隔 405 | func (here *Manager) GetCollectInterval() int { 406 | return getter.GetInterval() 407 | } 408 | 409 | func (here *Manager) ReGet(SourceId uint) error { 410 | here.getters_mutex.Lock() 411 | getter, ok := here.getters[SourceId] 412 | here.getters_mutex.Unlock() 413 | if !ok { 414 | return errors.New("the source which id is not exist" + strconv.Itoa(int(SourceId))) 415 | } 416 | getter.ReGet() 417 | return nil 418 | } 419 | 420 | func (here *Manager) Rename_content(id uint, name string) error { 421 | return here.db.Rename(id, name) 422 | } 423 | 424 | func (here *Manager) Repic_content(id uint, pic string) error { 425 | return here.db.Repic(id, pic) 426 | } 427 | 428 | func (here *Manager) Reactor_content(id uint, actor string) error { 429 | return here.db.Reactor(id, actor) 430 | } 431 | 432 | func (here *Manager) Redirector_content(id uint, director string) error { 433 | return here.db.Redirector(id, director) 434 | } 435 | 436 | func (here *Manager) Reduration_content(id uint, duration string) error { 437 | return here.db.Reduration(id, duration) 438 | } 439 | 440 | func (here *Manager) Redesc_content(id uint, desc string) error { 441 | return here.db.Redesc(id, desc) 442 | } 443 | 444 | func (here *Manager) Reurl_content(id uint, url string) error { 445 | return here.db.Reurl(id, url) 446 | } 447 | 448 | func (here *Manager) Exports() ([]byte, error) { 449 | var res []byte 450 | 451 | data, err := here.db.Exports() 452 | if err != nil { 453 | return res, err 454 | } 455 | 456 | res, err = json.Marshal(data) 457 | if err != nil { 458 | return res, err 459 | } 460 | 461 | return res, nil 462 | } 463 | 464 | func (here *Manager) Imports(bytes []byte) error { 465 | var data _struct.DATA 466 | err := json.Unmarshal(bytes, &data) 467 | if err != nil { 468 | return err 469 | } 470 | 471 | for i := 0; i < len(data.Sources); i++ { 472 | data.Sources[i].Ok = false 473 | data.Sources[i].Pg = 1 474 | } 475 | 476 | err = here.db.Imports(data) 477 | if err != nil { 478 | util.Logger.Errorf("An error occurred while importing data: %s", err) 479 | return err 480 | } 481 | 482 | for _, source := range data.Sources { 483 | here.getters_mutex.Lock() 484 | here.getters[source.ID] = getter.NewGetter(source.ID, source.Name, source.Url, false, 1) 485 | here.getters_mutex.Unlock() 486 | } 487 | 488 | return nil 489 | } 490 | 491 | type Source struct { 492 | ID uint `json:"id"` 493 | Name string `json:"name"` 494 | Url string `json:"url"` 495 | Get bool `json:"get"` 496 | Ok bool `json:"ok"` 497 | Pg int `json:"pg"` 498 | } 499 | 500 | type Movie struct { 501 | Id int `json:"id"` 502 | Name string `json:"name"` 503 | Pic string `json:"pic"` 504 | Actor string `json:"actor"` 505 | Director string `json:"director"` 506 | Duration string `json:"duration"` 507 | Description string `json:"description"` 508 | Url string `json:"url,omitempty"` 509 | } 510 | 511 | type Class struct { 512 | ID uint `json:"id"` 513 | Name string `json:"name"` // 采集分类名 514 | Get bool `json:"get"` // 是否采集 515 | CategoryId uint `json:"categoryId"` 516 | } 517 | 518 | type Category struct { 519 | ID uint `json:"id"` 520 | Name string `json:"name"` 521 | Main bool `json:"main,omitempty"` 522 | ClassNum int `json:"classNum,omitempty"` 523 | MovieNum int `json:"movieNum,omitempty"` 524 | } 525 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 3 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= 4 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 5 | github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= 6 | github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= 7 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 8 | github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= 9 | github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= 10 | github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= 11 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 12 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 13 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= 14 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= 15 | github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= 16 | github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= 17 | github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= 18 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 19 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 20 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 21 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 22 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 24 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 25 | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= 26 | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= 27 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 28 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 29 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 30 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 31 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 32 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 33 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 34 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 35 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 36 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 37 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 38 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 39 | github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= 40 | github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 41 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 42 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 43 | github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= 44 | github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 45 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 46 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 47 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 48 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 49 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 50 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 51 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 52 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 53 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 54 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 55 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 56 | github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= 57 | github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 58 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= 59 | github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= 60 | github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= 61 | github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= 62 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= 63 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 64 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= 65 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 66 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= 67 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= 68 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= 69 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= 70 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= 71 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= 72 | github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= 73 | github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= 74 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= 75 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= 76 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= 77 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= 78 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= 79 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 80 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 81 | github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= 82 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= 83 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= 84 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= 85 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 86 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 87 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 88 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 89 | github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= 90 | github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 91 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= 92 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 93 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= 94 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= 95 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= 96 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= 97 | github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= 98 | github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= 99 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= 100 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= 101 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= 102 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= 103 | github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= 104 | github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= 105 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 106 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 107 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 108 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 109 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 110 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 111 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 112 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 113 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 114 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 115 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 116 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 117 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 118 | github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= 119 | github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 120 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 121 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 122 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 123 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 124 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 125 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 126 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 127 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 128 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 129 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 130 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 131 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 132 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 133 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 134 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 135 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 136 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 137 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 138 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 139 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 140 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 141 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 142 | github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 143 | github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI= 144 | github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 145 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 146 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 147 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 148 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 149 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 150 | github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= 151 | github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= 152 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 153 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 154 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 155 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 156 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 157 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 158 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 159 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 160 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 161 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 162 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 163 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 164 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 165 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 166 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 167 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 168 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 169 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 170 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 171 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 172 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 173 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 174 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 175 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 176 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 177 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 178 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 179 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 180 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 181 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 182 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 183 | github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= 184 | github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 185 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 186 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 187 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 188 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 189 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 190 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 191 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 192 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 193 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 194 | github.com/wader/gormstore/v2 v2.0.3 h1:/29GWPauY8xZkpLnB8hsp+dZfP3ivA9fiDw1YVNTp6U= 195 | github.com/wader/gormstore/v2 v2.0.3/go.mod h1:sr3N3a8F1+PBc3fHoKaphFqDXLRJ9Oe6Yow0HxKFbbg= 196 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 197 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 198 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 199 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 200 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 201 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 202 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 203 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 204 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 205 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 206 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 207 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 208 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 209 | golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc= 210 | golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 211 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 212 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 213 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 214 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 215 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 216 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 217 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 218 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 219 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 220 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 221 | golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= 222 | golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= 223 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 224 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 225 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 226 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 227 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 228 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 229 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 230 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 231 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 232 | golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= 233 | golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= 234 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 235 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 236 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 237 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 238 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 239 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 240 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 241 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 242 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 243 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 244 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 245 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 246 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 247 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 248 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 249 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 250 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 251 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 252 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 253 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 254 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 255 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 256 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 257 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 258 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 259 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 260 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 261 | golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= 262 | golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 263 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 264 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 265 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 266 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 267 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 268 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 269 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 270 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 271 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 272 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 273 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 274 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 275 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 276 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 277 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 278 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 279 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 280 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 281 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 282 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 283 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 284 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 285 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 286 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 287 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 288 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 289 | gorm.io/driver/mysql v1.4.0/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= 290 | gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= 291 | gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= 292 | gorm.io/driver/postgres v1.4.1 h1:DutsKq2LK2Ag65q/+VygWth0/L4GAVOp+sCtg6WzZjs= 293 | gorm.io/driver/postgres v1.4.1/go.mod h1:whNfh5WhhHs96honoLjBAMwJGYEuA3m1hvgUbNXhPCw= 294 | gorm.io/driver/sqlite v1.4.1/go.mod h1:AKZZCAoFfOWHF7Nd685Iq8Uywc0i9sWJlzpoE/INzsw= 295 | gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= 296 | gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= 297 | gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 298 | gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 299 | gorm.io/gorm v1.23.10/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= 300 | gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= 301 | gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= 302 | gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= 303 | gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= 304 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 305 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 306 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 307 | -------------------------------------------------------------------------------- /router/back.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "io" 5 | mm "movie/manager" 6 | "movie/router/MiddleWare" 7 | "movie/util" 8 | "net/http" 9 | "strconv" 10 | 11 | "github.com/asaskevich/govalidator" 12 | "github.com/gin-gonic/gin" 13 | "github.com/sirupsen/logrus" 14 | ) 15 | 16 | // TODO 该文件log函数未替换 17 | 18 | func back(r *gin.Engine, manager *mm.Manager) { 19 | // 登录函数处理 20 | login(r, manager) 21 | 22 | // 获取版本号 23 | version(r, manager) 24 | 25 | // 路由组定义 26 | user := r.Group("/user") 27 | 28 | // 路由组中间件,用于鉴权 29 | user.Use(MiddleWare.Auth(manager)) 30 | 31 | // 路由组逻辑处理 32 | { 33 | // 关闭全局采集 34 | stop(user, manager) 35 | 36 | // 开启全局采集 37 | start(user, manager) 38 | 39 | // 关闭采集 40 | stop_one(user, manager) 41 | 42 | // 开启采集 43 | start_one(user, manager) 44 | 45 | // 全局影片列表 46 | list(user, manager) 47 | 48 | // 全局搜索影片 49 | back_search(user, manager) 50 | 51 | // 重命名影片名 52 | content_rename(user, manager) 53 | 54 | // 修改影片的海报地址 55 | content_repic(user, manager) 56 | 57 | // 修改影片的主演 58 | content_reactor(user, manager) 59 | 60 | // 修改影片的导演 61 | content_redirector(user, manager) 62 | 63 | // 修改影片的时长 64 | content_reduration(user, manager) 65 | 66 | // 修改影片的描述 67 | content_redesc(user, manager) 68 | 69 | // 修改影片的url 70 | content_reurl(user, manager) 71 | 72 | // 删除影片 73 | delete(user, manager) 74 | 75 | // 获取所有影片数目 76 | count(user, manager) 77 | 78 | // 采集源影片列表 79 | source_list(user, manager) 80 | 81 | // 采集源影片搜索 82 | source_search_content(user, manager) 83 | 84 | // 获取所有的source 85 | source_all(user, manager) 86 | 87 | // 添加source 88 | add_source(user, manager) 89 | 90 | // 重新采集source 91 | source_reget(user, manager) 92 | 93 | // 改名source 94 | source_rename(user, manager) 95 | 96 | // 修改sourceurl 97 | source_reurl(user, manager) 98 | 99 | // 删除source 100 | source_delete(user, manager) 101 | 102 | // 获取某资源库下所有采集类 103 | source_class(user, manager) 104 | 105 | // 自建分类影片列表 106 | category_list(user, manager) 107 | 108 | // 自建分类影片搜索 109 | category_search(user, manager) 110 | 111 | // 获取自建分类 112 | category_all(user, manager) 113 | 114 | // 创建一个分类 115 | category_add(user, manager) 116 | 117 | // 改名分类 118 | category_rename(user, manager) 119 | 120 | // 删除分类 121 | category_delete(user, manager) 122 | 123 | // 采集类影片列表 124 | class_list(user, manager) 125 | 126 | // 采集类影片搜索 127 | class_list_search(user, manager) 128 | 129 | // 改变class的采集状态是否采集 130 | class_chang_get(user, manager) 131 | 132 | // 分配采集类到自建类 133 | class_distribute(user, manager) 134 | 135 | // 更新账户 136 | update_ccount(user, manager) 137 | 138 | // 更新密码 139 | update_password(user, manager) 140 | 141 | // 获取采集间隔 142 | get_collect_interval(user, manager) 143 | 144 | // 更新采集间隔 145 | update_collect_interval(user, manager) 146 | 147 | // 设置当前分类为首页分类 148 | set_category_main(user, manager) 149 | 150 | // 清除缓存 151 | cache_purge(user, manager) 152 | 153 | // 登出操作 154 | logout(user, manager) 155 | 156 | // 导出 157 | exports(user, manager) 158 | 159 | // 导入 160 | imports(user, manager) 161 | 162 | } 163 | } 164 | 165 | // 登录函数处理 166 | func login(r *gin.Engine, manager *mm.Manager) { 167 | r.POST("/user/login", func(c *gin.Context) { 168 | account := c.PostForm("account") 169 | password := c.PostForm("password") 170 | long := c.PostForm("long") // 获取参数是否要长时间无需登录 171 | if manager.SessionGet(c.Request, "login") == nil { 172 | // 验证登录 173 | if !manager.Login(account, password) { 174 | c.Status(http.StatusForbidden) 175 | util.Logger.WithFields(logrus.Fields{ 176 | "account": account, 177 | "password": password, 178 | }).Debug("Someone login failed") 179 | return 180 | } 181 | kv := make(map[interface{}]interface{}) 182 | { 183 | kv["login"] = true 184 | kv["account"] = account 185 | } 186 | manager.SessionInit( 187 | c.Writer, 188 | c.Request, 189 | long == "true", 190 | kv, 191 | ) // 进行相关的初始化操作,并且判断是否需要cookie长期有效 192 | } 193 | 194 | c.Status(http.StatusOK) 195 | }) 196 | } 197 | 198 | // 获取版本号 199 | func version(r *gin.Engine, manager *mm.Manager) { 200 | r.GET("/system/version", func(c *gin.Context) { 201 | err := manager.SessionGet(c.Request, "login") 202 | if err == nil { 203 | c.AbortWithStatus(http.StatusUnauthorized) 204 | return 205 | } 206 | c.Next() 207 | }, func(c *gin.Context) { 208 | c.String(http.StatusOK, util.Version) 209 | }) 210 | } 211 | 212 | // 关闭全局采集 213 | func stop(user *gin.RouterGroup, manager *mm.Manager) { 214 | user.GET("/stop", func(_ *gin.Context) { 215 | manager.GetStop() 216 | util.Logger.Debug("Stop all collection") 217 | }) 218 | } 219 | 220 | // 开启全局采集 221 | func start(user *gin.RouterGroup, manager *mm.Manager) { 222 | user.GET("/start", func(_ *gin.Context) { 223 | manager.GetStart() 224 | util.Logger.Debug("Start all collection") 225 | }) 226 | } 227 | 228 | // 关闭某个采集 229 | func stop_one(user *gin.RouterGroup, manager *mm.Manager) { 230 | user.GET("/stop/:id", func(c *gin.Context) { 231 | idV := c.Param("id") 232 | id, err := strconv.Atoi(idV) 233 | if err != nil { 234 | c.Status(http.StatusBadRequest) 235 | util.Logger.Warn("Stop collection failed, the param idV is not integer") 236 | return 237 | } 238 | manager.GetStopById(uint(id)) 239 | util.Logger.Debug("Stop collection, id:", id) 240 | c.Status(http.StatusOK) 241 | }) 242 | } 243 | 244 | // 开启采集 245 | func start_one(user *gin.RouterGroup, manager *mm.Manager) { 246 | user.GET("/start/:id", func(c *gin.Context) { 247 | idV := c.Param("id") 248 | id, err := strconv.Atoi(idV) 249 | if err != nil { 250 | c.Status(http.StatusBadRequest) 251 | util.Logger.Warn("Start collection failed, the param idV is not integer") 252 | return 253 | } 254 | manager.GetStartById(uint(id)) 255 | util.Logger.Debug("Start collection, id:", id) 256 | c.Status(http.StatusOK) 257 | }) 258 | } 259 | 260 | // 全局影片列表 261 | func list(user *gin.RouterGroup, manager *mm.Manager) { 262 | user.POST("/list", func(c *gin.Context) { 263 | // 获取页码 264 | pgV := c.PostForm("pg") 265 | pg, err := strconv.Atoi(pgV) 266 | if err != nil { 267 | c.Status(http.StatusBadRequest) 268 | util.Logger.Warn("get global movies list failed, the pgV is not integer") 269 | return 270 | } 271 | // 获取数量 272 | numV := c.PostForm("num") 273 | num, err := strconv.Atoi(numV) 274 | if err != nil { 275 | c.Status(http.StatusBadRequest) 276 | util.Logger.Warn("get global movies list failed, the numV is not integer") 277 | return 278 | } 279 | // 执行检索 280 | movies, pgCount, err := manager.ContentList(num, pg) 281 | if err != nil { 282 | c.Status(http.StatusInternalServerError) 283 | util.Logger.Warn( 284 | "get global movies list failed, get the list from database failed, err:", 285 | err, 286 | ) 287 | return 288 | } 289 | movie := Movie{ 290 | Movies: movies, 291 | PgCount: pgCount, 292 | } 293 | // 编码json 294 | util.Logger.Debug("get the global movies list, pg:", pgV, "num:", numV, "get data:") 295 | c.JSON(http.StatusOK, movie) 296 | }) 297 | } 298 | 299 | // 全局搜索影片 300 | func back_search(user *gin.RouterGroup, manager *mm.Manager) { 301 | user.POST("/search", func(c *gin.Context) { 302 | // 获取关键字 303 | keyword := c.PostForm("keyword") 304 | if keyword == "" { 305 | c.Status(http.StatusBadRequest) 306 | util.Logger.Debug("Keyword is empty") 307 | return 308 | } 309 | // 获取页码 310 | pgV := c.PostForm("pg") 311 | pg, err := strconv.Atoi(pgV) 312 | if err != nil { 313 | util.Logger.Warn("pgV is not a integer") 314 | c.Status(http.StatusBadRequest) 315 | return 316 | } 317 | // 获取数量 318 | numV := c.PostForm("num") 319 | num, err := strconv.Atoi(numV) 320 | if err != nil { 321 | util.Logger.Warn("numV is not a integer") 322 | c.Status(http.StatusBadRequest) 323 | return 324 | } 325 | // 执行搜索操作 326 | movies, pgCount, err := manager.SearchContent_bk(keyword, num, pg) 327 | if err != nil { 328 | util.Logger.Error("Search movie failed, err:", err) 329 | c.Status(http.StatusInternalServerError) 330 | return 331 | } 332 | movie := Movie{ 333 | Movies: movies, 334 | PgCount: pgCount, 335 | } 336 | // 编码json 337 | util.Logger.Debug("search movie, keyword:", keyword, "pg:", pg, "num:", num) 338 | c.JSON(http.StatusOK, movie) 339 | }) 340 | } 341 | 342 | // 重命名影片的名字 343 | func content_rename(user *gin.RouterGroup, manager *mm.Manager) { 344 | user.POST("/content/rename", func(c *gin.Context) { 345 | idV := c.PostForm("id") 346 | name := c.PostForm("name") 347 | 348 | id, err := strconv.Atoi(idV) 349 | if err != nil { 350 | util.Logger.Warn("Rename content failed, the id isn't a integer") 351 | c.Status(http.StatusBadRequest) 352 | return 353 | } 354 | err = manager.Rename_content(uint(id), name) 355 | if err != nil { 356 | util.Logger.Errorf("Rename content failed, id is %d, err is %s", id, err) 357 | c.Status(http.StatusInternalServerError) 358 | return 359 | } 360 | util.Logger.Infof("Rename content success, id is %d", id) 361 | c.Status(http.StatusOK) 362 | }) 363 | } 364 | 365 | // 修改影片的海报地址 366 | func content_repic(user *gin.RouterGroup, manager *mm.Manager) { 367 | user.POST("/content/repic", func(c *gin.Context) { 368 | idV := c.PostForm("id") 369 | pic := c.PostForm("pic") 370 | 371 | id, err := strconv.Atoi(idV) 372 | if err != nil { 373 | util.Logger.Warn("Repic content failed, the id isn't a integer") 374 | c.Status(http.StatusBadRequest) 375 | return 376 | } 377 | err = manager.Repic_content(uint(id), pic) 378 | if err != nil { 379 | util.Logger.Errorf("Repic content failed, id is %d, err is %s", id, err) 380 | c.Status(http.StatusInternalServerError) 381 | return 382 | } 383 | util.Logger.Infof("Repic content success, id is %d", id) 384 | c.Status(http.StatusOK) 385 | }) 386 | } 387 | 388 | // 修改影片的主演 389 | func content_reactor(user *gin.RouterGroup, manager *mm.Manager) { 390 | user.POST("/content/reactor", func(c *gin.Context) { 391 | idV := c.PostForm("id") 392 | actor := c.PostForm("actor") 393 | 394 | id, err := strconv.Atoi(idV) 395 | if err != nil { 396 | util.Logger.Warn("Reactor content failed, the id isn't a integer") 397 | c.Status(http.StatusBadRequest) 398 | return 399 | } 400 | err = manager.Reactor_content(uint(id), actor) 401 | if err != nil { 402 | util.Logger.Errorf("Reactor content failed, id is %d, err is %s", id, err) 403 | c.Status(http.StatusInternalServerError) 404 | return 405 | } 406 | util.Logger.Infof("Reactor content success, id is %d", id) 407 | c.Status(http.StatusOK) 408 | }) 409 | } 410 | 411 | // 修改影片的导演 412 | func content_redirector(user *gin.RouterGroup, manager *mm.Manager) { 413 | user.POST("/content/redirector", func(c *gin.Context) { 414 | idV := c.PostForm("id") 415 | director := c.PostForm("director") 416 | 417 | id, err := strconv.Atoi(idV) 418 | if err != nil { 419 | util.Logger.Warn("Redirector content failed, the id isn't a integer") 420 | c.Status(http.StatusBadRequest) 421 | return 422 | } 423 | err = manager.Redirector_content(uint(id), director) 424 | if err != nil { 425 | util.Logger.Errorf("Redirector content failed, id is %d, err is %s", id, err) 426 | c.Status(http.StatusInternalServerError) 427 | return 428 | } 429 | util.Logger.Infof("Redirector content success, id is %d", id) 430 | c.Status(http.StatusOK) 431 | }) 432 | } 433 | 434 | // 修改影片的时长 435 | func content_reduration(user *gin.RouterGroup, manager *mm.Manager) { 436 | user.POST("/content/reduration", func(c *gin.Context) { 437 | idV := c.PostForm("id") 438 | duration := c.PostForm("duration") 439 | 440 | id, err := strconv.Atoi(idV) 441 | if err != nil { 442 | util.Logger.Warn("Reduration content failed, the id isn't a integer") 443 | c.Status(http.StatusBadRequest) 444 | return 445 | } 446 | err = manager.Reduration_content(uint(id), duration) 447 | if err != nil { 448 | util.Logger.Errorf("Reduration content failed, id is %d, err is %s", id, err) 449 | c.Status(http.StatusInternalServerError) 450 | return 451 | } 452 | util.Logger.Infof("Reduration content success, id is %d", id) 453 | c.Status(http.StatusOK) 454 | }) 455 | } 456 | 457 | // 修改影片的描述 458 | func content_redesc(user *gin.RouterGroup, manager *mm.Manager) { 459 | user.POST("/content/redesc", func(c *gin.Context) { 460 | idV := c.PostForm("id") 461 | description := c.PostForm("description") 462 | 463 | id, err := strconv.Atoi(idV) 464 | if err != nil { 465 | util.Logger.Warn("Redesc content failed, the id isn't a integer") 466 | c.Status(http.StatusBadRequest) 467 | return 468 | } 469 | err = manager.Redesc_content(uint(id), description) 470 | if err != nil { 471 | util.Logger.Errorf("Redesc content failed, id is %d, err is %s", id, err) 472 | c.Status(http.StatusInternalServerError) 473 | return 474 | } 475 | util.Logger.Infof("Redesc content success, id is %d", id) 476 | c.Status(http.StatusOK) 477 | }) 478 | } 479 | 480 | // 修改影片url 481 | func content_reurl(user *gin.RouterGroup, manager *mm.Manager) { 482 | user.POST("/content/reurl", func(c *gin.Context) { 483 | idV := c.PostForm("id") 484 | url := c.PostForm("url") 485 | 486 | id, err := strconv.Atoi(idV) 487 | if err != nil { 488 | util.Logger.Warn("Reurl content failed, the id isn't a integer") 489 | c.Status(http.StatusBadRequest) 490 | return 491 | } 492 | err = manager.Reurl_content(uint(id), url) 493 | if err != nil { 494 | util.Logger.Errorf("Reurl content failed, id is %d, err is %s", id, err) 495 | c.Status(http.StatusInternalServerError) 496 | return 497 | } 498 | util.Logger.Infof("Reurl content success, id is %d", id) 499 | c.Status(http.StatusOK) 500 | }) 501 | } 502 | 503 | // 删除影片 504 | func delete(user *gin.RouterGroup, manager *mm.Manager) { 505 | user.POST("/del", func(c *gin.Context) { 506 | // 获取contentId 507 | idV := c.PostForm("id") 508 | id, err := strconv.Atoi(idV) 509 | if err != nil { 510 | c.Status(http.StatusBadRequest) 511 | util.Logger.Error("The id of movie is not a integer") 512 | return 513 | } 514 | if manager.DelContent(uint(id)) != nil { 515 | c.Status(http.StatusInternalServerError) 516 | util.Logger.Error("Del movie failed, err:", err) 517 | return 518 | } 519 | c.Status(http.StatusOK) 520 | }) 521 | } 522 | 523 | // 获取所有影片数目 524 | func count(user *gin.RouterGroup, manager *mm.Manager) { 525 | user.GET("/count", func(c *gin.Context) { 526 | numV := strconv.Itoa(manager.ContentCount()) 527 | util.Logger.Debug("movie counts is ", numV) 528 | c.String(http.StatusOK, numV) 529 | }) 530 | } 531 | 532 | // 采集源影片列表 533 | func source_list(user *gin.RouterGroup, manager *mm.Manager) { 534 | user.POST("/source/list", func(c *gin.Context) { 535 | // 获取sourceId 536 | idV := c.PostForm("id") 537 | id, err := strconv.Atoi(idV) 538 | if err != nil { 539 | util.Logger.Warn("The id of source is not a integer") 540 | c.Status(http.StatusBadRequest) 541 | return 542 | } 543 | 544 | // 获取页码 545 | pgV := c.PostForm("pg") 546 | pg, err := strconv.Atoi(pgV) 547 | if err != nil { 548 | util.Logger.Warn("The id of page is not a integer") 549 | c.Status(http.StatusBadRequest) 550 | return 551 | } 552 | 553 | // 获取数量 554 | numV := c.PostForm("num") 555 | num, err := strconv.Atoi(numV) 556 | if err != nil { 557 | util.Logger.Warn("The numV is not a integer") 558 | c.Status(http.StatusBadRequest) 559 | return 560 | } 561 | 562 | // 执行检索 563 | movies, pgCount, err := manager.ContentList_Source(uint(id), num, pg) 564 | if err != nil { 565 | util.Logger.Error("get content list of source which id is ", id, "failed, err:", err) 566 | c.Status(http.StatusInternalServerError) 567 | return 568 | } 569 | movie := Movie{ 570 | Movies: movies, 571 | PgCount: pgCount, 572 | } 573 | // 编码json 574 | util.Logger.Info("get conteng list of source which id is", id) 575 | c.JSON(http.StatusOK, movie) 576 | }) 577 | } 578 | 579 | // 采集源影片搜索 580 | func source_search_content(user *gin.RouterGroup, manager *mm.Manager) { 581 | user.POST("/source/search", func(c *gin.Context) { 582 | // 获取sourceId 583 | idV := c.PostForm("id") 584 | id, err := strconv.Atoi(idV) 585 | if err != nil { 586 | util.Logger.Warn("The id of source is not a integer") 587 | c.Status(http.StatusBadRequest) 588 | return 589 | } 590 | // 获取关键字 591 | keyword := c.PostForm("keyword") 592 | if keyword == "" { 593 | util.Logger.Warn("The keyword to search from source is blank, id:", id) 594 | c.Status(http.StatusBadRequest) 595 | return 596 | } 597 | // 获取页码 598 | pgV := c.PostForm("pg") 599 | pg, err := strconv.Atoi(pgV) 600 | if err != nil { 601 | util.Logger.Warn("the pgV of search from source is not a integer, id:", id) 602 | c.Status(http.StatusBadRequest) 603 | return 604 | } 605 | // 获取数量 606 | numV := c.PostForm("num") 607 | num, err := strconv.Atoi(numV) 608 | if err != nil { 609 | util.Logger.Warn("The numV of search from source is not a integer, id:", id) 610 | c.Status(http.StatusBadRequest) 611 | return 612 | } 613 | // 执行搜索操作 614 | movies, pgCount, err := manager.SearchContent_bk_Source(uint(id), keyword, num, pg) 615 | if err != nil { 616 | util.Logger.Error("Search content from source which id is ", id, "failed, err:", err) 617 | c.Status(http.StatusInternalServerError) 618 | return 619 | } 620 | movie := Movie{ 621 | Movies: movies, 622 | PgCount: pgCount, 623 | } 624 | // 编码json 625 | util.Logger.Info("Search content from source which id is ", id) 626 | c.JSON(http.StatusOK, movie) 627 | }) 628 | } 629 | 630 | // 获取所有的source 631 | func source_all(user *gin.RouterGroup, manager *mm.Manager) { 632 | user.GET("/source/all", func(c *gin.Context) { 633 | sources, err := manager.GetSource() 634 | if err != nil { 635 | util.Logger.Error("Get all sources from database failed, err:", err) 636 | c.Status(http.StatusInternalServerError) 637 | return 638 | } 639 | util.Logger.Info("Get all sources from database success") 640 | c.JSON(http.StatusOK, sources) 641 | }) 642 | } 643 | 644 | // 添加source 645 | func add_source(user *gin.RouterGroup, manager *mm.Manager) { 646 | user.POST("/source/add", func(c *gin.Context) { 647 | name := c.PostForm("name") 648 | url := c.PostForm("url") 649 | if !govalidator.IsURL(url) { 650 | c.Status(http.StatusBadRequest) 651 | return 652 | } 653 | res := manager.AddSource(name, url) 654 | if !res { 655 | util.Logger.Errorln("add source failed, name:", name, "url:", url) 656 | c.Status(http.StatusInternalServerError) 657 | return 658 | } 659 | util.Logger.Info("Add source, name:", name, "url:", url) 660 | c.Status(http.StatusOK) 661 | }) 662 | } 663 | 664 | // TODO:back的source相关函数需要过滤名字和url 665 | 666 | // 重新采集 667 | func source_reget(user *gin.RouterGroup, manager *mm.Manager) { 668 | user.POST("/source/reGet", func(c *gin.Context) { 669 | idV := c.PostForm("id") 670 | id, err := strconv.Atoi(idV) 671 | if err != nil { 672 | util.Logger.Warn("Rget source failed, the id isn't a integer") 673 | c.Status(http.StatusBadRequest) 674 | return 675 | } 676 | err = manager.ReGet(uint(id)) 677 | if err != nil { 678 | util.Logger.Error("Rget source failed, id is ", id, "err: ", err) 679 | c.Status(http.StatusInternalServerError) 680 | return 681 | } 682 | util.Logger.Info("Rget source success, id is ", id) 683 | c.Status(http.StatusOK) 684 | }) 685 | } 686 | 687 | // 修改source名字 688 | func source_rename(user *gin.RouterGroup, manager *mm.Manager) { 689 | user.POST("/source/reName", func(c *gin.Context) { 690 | idV := c.PostForm("id") 691 | name := c.PostForm("name") 692 | 693 | id, err := strconv.Atoi(idV) 694 | if err != nil { 695 | util.Logger.Warn("Rget source failed, the id isn't a integer") 696 | c.Status(http.StatusBadRequest) 697 | return 698 | } 699 | err = manager.RenameSource(uint(id), name) 700 | if err != nil { 701 | util.Logger.Error("Rename source failed, id is ", id, "err: ", err) 702 | c.Status(http.StatusInternalServerError) 703 | return 704 | } 705 | 706 | util.Logger.Info("Rename source success, id is ", id) 707 | c.Status(http.StatusOK) 708 | }) 709 | } 710 | 711 | // 修改source的地址 712 | func source_reurl(user *gin.RouterGroup, manager *mm.Manager) { 713 | user.POST("/source/reUrl", func(c *gin.Context) { 714 | idV := c.PostForm("id") 715 | url := c.PostForm("url") 716 | if !govalidator.IsURL(url) { 717 | c.Status(http.StatusBadRequest) 718 | return 719 | } 720 | id, err := strconv.Atoi(idV) 721 | if err != nil { 722 | util.Logger.Warn("Rget source failed, the id isn't a integer") 723 | c.Status(http.StatusBadRequest) 724 | return 725 | } 726 | err = manager.ReurlSource(uint(id), url) 727 | if err != nil { 728 | util.Logger.Error("Reurl source failed, id is ", id, "err: ", err) 729 | c.Status(http.StatusInternalServerError) 730 | return 731 | } 732 | util.Logger.Info("Reurl source success, id is ", id) 733 | c.Status(http.StatusOK) 734 | }) 735 | } 736 | 737 | // 删除source 738 | func source_delete(user *gin.RouterGroup, manager *mm.Manager) { 739 | user.POST("/source/del", func(c *gin.Context) { 740 | idV := c.PostForm("id") 741 | id, err := strconv.Atoi(idV) 742 | if err != nil { 743 | c.Status(http.StatusBadRequest) 744 | util.Logger.Warn("Del source failed, the id of source is not a integer") 745 | return 746 | } 747 | err = manager.DelSource(uint(id)) 748 | if err != nil { 749 | util.Logger.Error("Del source failed, err:", err) 750 | c.Status(http.StatusInternalServerError) 751 | return 752 | } 753 | util.Logger.Info("Del source, id is ", id) 754 | c.Status(http.StatusOK) 755 | }) 756 | } 757 | 758 | // 获取某资源库下所有采集类 759 | func source_class(user *gin.RouterGroup, manager *mm.Manager) { 760 | user.GET("/source/all_class/:id", func(c *gin.Context) { 761 | idV := c.Param("id") 762 | id, err := strconv.Atoi(idV) 763 | if err != nil { 764 | util.Logger.Warn("Get source's all class failed, the id of source is not a integer") 765 | c.Status(http.StatusBadRequest) 766 | return 767 | } 768 | classes, err := manager.GetClass(uint(id)) 769 | if err != nil { 770 | util.Logger.Error("Get source's all class failed, err: ", err) 771 | c.Status(http.StatusInternalServerError) 772 | return 773 | } 774 | util.Logger.Info("Get all class of source that id is ", id) 775 | c.JSON(http.StatusOK, classes) 776 | }) 777 | } 778 | 779 | // 自建分类影片列表 780 | func category_list(user *gin.RouterGroup, manager *mm.Manager) { 781 | user.POST("/category/list", func(c *gin.Context) { 782 | // 获取categoryId 783 | idV := c.PostForm("id") 784 | id, err := strconv.Atoi(idV) 785 | if err != nil { 786 | util.Logger.Warn("get category list failed, the id is not a integer") 787 | c.Status(http.StatusBadRequest) 788 | return 789 | } 790 | // 获取页码 791 | pgV := c.PostForm("pg") 792 | pg, err := strconv.Atoi(pgV) 793 | if err != nil { 794 | util.Logger.Warn("get category list failed, the id is not a integer") 795 | c.Status(http.StatusBadRequest) 796 | return 797 | } 798 | // 获取数量 799 | numV := c.PostForm("num") 800 | num, err := strconv.Atoi(numV) 801 | if err != nil { 802 | util.Logger.Warn("get category list failed, the num is not a integer") 803 | c.Status(http.StatusBadRequest) 804 | return 805 | } 806 | // 执行检索 807 | movies, pgCount, err := manager.ContentList_Category(uint(id), num, pg) 808 | if err != nil { 809 | util.Logger.Error("get category list failed, err: ", err) 810 | c.Status(http.StatusInternalServerError) 811 | return 812 | } 813 | movie := Movie{ 814 | Movies: movies, 815 | PgCount: pgCount, 816 | } 817 | // 编码json 818 | util.Logger.Infof("get category list, id: %d, pg: %d, num: %d", id, pg, num) 819 | c.JSON(http.StatusOK, movie) 820 | }) 821 | } 822 | 823 | // 自建分类影片搜索 824 | func category_search(user *gin.RouterGroup, manager *mm.Manager) { 825 | user.POST("/category/search", func(c *gin.Context) { 826 | // 获取categoryId 827 | idV := c.PostForm("id") 828 | id, err := strconv.Atoi(idV) 829 | if err != nil { 830 | util.Logger.Warn("search movie from category failed, the id is not a integer") 831 | c.Status(http.StatusBadRequest) 832 | return 833 | } 834 | // 获取关键字 835 | keyword := c.PostForm("keyword") 836 | if keyword == "" { 837 | util.Logger.Warn("search movie from category failed, the keyword is blank") 838 | c.Status(http.StatusBadRequest) 839 | return 840 | } 841 | // 获取页码 842 | pgV := c.PostForm("pg") 843 | pg, err := strconv.Atoi(pgV) 844 | if err != nil { 845 | util.Logger.Warn("search movie from category failed, the pg is not a integer") 846 | c.Status(http.StatusBadRequest) 847 | return 848 | } 849 | // 获取数量 850 | numV := c.PostForm("num") 851 | num, err := strconv.Atoi(numV) 852 | if err != nil { 853 | util.Logger.Warn("search movie from category failed, the num is not a integer") 854 | c.Status(http.StatusBadRequest) 855 | return 856 | } 857 | // 执行搜索操作 858 | movies, pgCount, err := manager.SearchContent_bk_Category(uint(id), keyword, num, pg) 859 | if err != nil { 860 | util.Logger.Error("search movie from category failed, id: ", id, "err: ", err) 861 | c.Status(http.StatusInternalServerError) 862 | return 863 | } 864 | movie := Movie{ 865 | Movies: movies, 866 | PgCount: pgCount, 867 | } 868 | // 编码json 869 | util.Logger.Info("search movie from category, id is ", id) 870 | c.JSON(http.StatusOK, movie) 871 | }) 872 | } 873 | 874 | func category_all(user *gin.RouterGroup, manager *mm.Manager) { 875 | user.GET("/category/all", func(c *gin.Context) { 876 | categories, err := manager.GetCategory() 877 | if err != nil { 878 | util.Logger.Error("get all category failed, err: ", err) 879 | c.Status(http.StatusInternalServerError) 880 | return 881 | } 882 | util.Logger.Info("get all category") 883 | c.JSON(http.StatusOK, categories) 884 | }) 885 | } 886 | 887 | func category_add(user *gin.RouterGroup, manager *mm.Manager) { 888 | user.POST("/category/add", func(c *gin.Context) { 889 | name := c.PostForm("name") 890 | err := manager.AddCategory(name) 891 | if err == nil { 892 | util.Logger.Info("create a category called ", name) 893 | c.Status(http.StatusOK) 894 | return 895 | } 896 | util.Logger.Error("create category failed, err:", err) 897 | c.Status(http.StatusBadRequest) 898 | }) 899 | } 900 | 901 | // 重命名分类 902 | func category_rename(user *gin.RouterGroup, manager *mm.Manager) { 903 | user.POST("/category/reName", func(c *gin.Context) { 904 | idV := c.PostForm("id") 905 | name := c.PostForm("name") 906 | id, err := strconv.Atoi(idV) 907 | if err != nil { 908 | util.Logger.Warn("Rename category failed, the id is not a integer") 909 | c.Status(http.StatusBadRequest) 910 | return 911 | } 912 | err = manager.UpdateCategory(uint(id), name) 913 | if err != nil { 914 | util.Logger.Errorf("Rename category failed, err:%s", err) 915 | c.Status(http.StatusInternalServerError) 916 | return 917 | } 918 | util.Logger.Info("Rename category, id is ", id) 919 | c.Status(http.StatusOK) 920 | }) 921 | } 922 | 923 | // 删除分类 924 | func category_delete(user *gin.RouterGroup, manager *mm.Manager) { 925 | user.POST("/category/del", func(c *gin.Context) { 926 | idV := c.PostForm("id") 927 | id, err := strconv.Atoi(idV) 928 | if err != nil { 929 | util.Logger.Warn("Del category failed, the id is not a integer") 930 | c.Status(http.StatusBadRequest) 931 | return 932 | } 933 | err = manager.DelCategory(uint(id)) 934 | if err != nil { 935 | util.Logger.Error("Del category failed, err:", err) 936 | c.Status(http.StatusInternalServerError) 937 | return 938 | } 939 | util.Logger.Info("Del category, id is ", id) 940 | c.Status(http.StatusOK) 941 | }) 942 | } 943 | 944 | // 采集类影片列表 945 | func class_list(user *gin.RouterGroup, manager *mm.Manager) { 946 | user.POST("/class/list", func(c *gin.Context) { 947 | // 获取categoryId 948 | idV := c.PostForm("id") 949 | id, err := strconv.Atoi(idV) 950 | if err != nil { 951 | util.Logger.Warn("gat class list failed, the category id is not a integer") 952 | c.Status(http.StatusBadRequest) 953 | return 954 | } 955 | // 获取页码 956 | pgV := c.PostForm("pg") 957 | pg, err := strconv.Atoi(pgV) 958 | if err != nil { 959 | util.Logger.Warn("get class list failed, the pg is not a integer") 960 | c.Status(http.StatusBadRequest) 961 | return 962 | } 963 | // 获取数量 964 | numV := c.PostForm("num") 965 | num, err := strconv.Atoi(numV) 966 | if err != nil { 967 | util.Logger.Warn("get class list failed, the num is not a integer") 968 | c.Status(http.StatusBadRequest) 969 | return 970 | } 971 | // 执行检索 972 | movies, pgCount, err := manager.ContentList_Class(uint(id), num, pg) 973 | if err != nil { 974 | util.Logger.Error("get class list of category id:", id, "failed, err:", err) 975 | c.Status(http.StatusInternalServerError) 976 | return 977 | } 978 | movie := Movie{ 979 | Movies: movies, 980 | PgCount: pgCount, 981 | } 982 | // 编码json 983 | util.Logger.Info("get class list of category id: ", id) 984 | c.JSON(http.StatusOK, movie) 985 | }) 986 | } 987 | 988 | func class_list_search(user *gin.RouterGroup, manager *mm.Manager) { 989 | user.POST("/class/search", func(c *gin.Context) { 990 | // 获取sourceId 991 | idV := c.PostForm("id") 992 | id, err := strconv.Atoi(idV) 993 | if err != nil { 994 | util.Logger.Warn("search in class failed, the id is not a integer") 995 | c.Status(http.StatusBadRequest) 996 | return 997 | } 998 | // 获取关键字 999 | keyword := c.PostForm("keyword") 1000 | if keyword == "" { 1001 | util.Logger.Warn("search in class failed, the keyword is blank") 1002 | c.Status(http.StatusBadRequest) 1003 | return 1004 | } 1005 | // 获取页码 1006 | pgV := c.PostForm("pg") 1007 | pg, err := strconv.Atoi(pgV) 1008 | if err != nil { 1009 | util.Logger.Warn("search in class failed, the pg is not a integer") 1010 | c.Status(http.StatusBadRequest) 1011 | return 1012 | } 1013 | // 获取数量 1014 | numV := c.PostForm("num") 1015 | num, err := strconv.Atoi(numV) 1016 | if err != nil { 1017 | util.Logger.Warn("search in class failed, the num is not a integer") 1018 | c.Status(http.StatusBadRequest) 1019 | return 1020 | } 1021 | // 执行搜索操作 1022 | movies, pgCount, err := manager.SearchContent_bk_Class(uint(id), keyword, num, pg) 1023 | if err != nil { 1024 | util.Logger.Error("search in class failed, err: ", err) 1025 | c.Status(http.StatusInternalServerError) 1026 | return 1027 | } 1028 | movie := Movie{ 1029 | Movies: movies, 1030 | PgCount: pgCount, 1031 | } 1032 | util.Logger.Info("search in class,id: ", id, " keyword: ", keyword) 1033 | // 编码json 1034 | c.JSON(http.StatusOK, movie) 1035 | }) 1036 | } 1037 | 1038 | // 改变class的采集状态是否采集 1039 | func class_chang_get(user *gin.RouterGroup, manager *mm.Manager) { 1040 | user.POST("/class/changeGet", func(c *gin.Context) { 1041 | idV := c.PostForm("id") 1042 | id, err := strconv.Atoi(idV) 1043 | if err != nil { 1044 | util.Logger.Warn("change class get failed, the id is not a integer") 1045 | c.Status(http.StatusBadRequest) 1046 | return 1047 | } 1048 | 1049 | getV := c.PostForm("get") 1050 | get := !(getV == "0") 1051 | err = manager.ChangeClassGet(uint(id), get) 1052 | if err != nil { 1053 | c.Status(http.StatusInternalServerError) 1054 | return 1055 | } 1056 | c.Status(http.StatusOK) 1057 | }) 1058 | } 1059 | 1060 | // 分配采集类到自建类 1061 | func class_distribute(user *gin.RouterGroup, manager *mm.Manager) { 1062 | user.POST("/class/distribute", func(c *gin.Context) { 1063 | classIdV := c.PostForm("classId") 1064 | classId, err := strconv.Atoi(classIdV) 1065 | if err != nil { 1066 | util.Logger.Warn("distribute class to category failed, the classid is not a integer") 1067 | c.Status(http.StatusBadRequest) 1068 | return 1069 | } 1070 | categoryIdV := c.PostForm("categoryId") 1071 | categoryId, err := strconv.Atoi(categoryIdV) 1072 | if err != nil { 1073 | util.Logger.Warn("distribute class to category failed, the categoryId is not a integer") 1074 | c.Status(http.StatusBadRequest) 1075 | return 1076 | } 1077 | err = manager.DistributeClass(uint(classId), uint(categoryId)) 1078 | if err != nil { 1079 | util.Logger.Error("distribute class to category failed, err:", err) 1080 | c.Status(http.StatusInternalServerError) 1081 | return 1082 | } 1083 | 1084 | util.Logger.Info("distribute class ", classId, "to category ", categoryId) 1085 | c.Status(http.StatusOK) 1086 | }) 1087 | } 1088 | 1089 | // 更新账户 1090 | func update_ccount(user *gin.RouterGroup, manager *mm.Manager) { 1091 | user.POST("/updateAccount", func(c *gin.Context) { 1092 | account := c.PostForm("account") 1093 | if account == "" { 1094 | util.Logger.Warn("update account failed, the account is blank") 1095 | c.Status(http.StatusBadRequest) 1096 | return 1097 | } 1098 | oldAccount := manager.SessionGet(c.Request, "account").(string) 1099 | err := manager.UpdateAccount(oldAccount, account) 1100 | if err != nil { 1101 | util.Logger.Error("update account failed, the oldAccount: ", oldAccount, " newAccount: ", account) 1102 | c.Status(http.StatusInternalServerError) 1103 | return 1104 | } 1105 | kv := make(map[interface{}]interface{}) 1106 | kv["account"] = account 1107 | manager.SessionSet(c.Writer, c.Request, kv) 1108 | util.Logger.Info("update account ", account, " success") 1109 | c.Status(http.StatusOK) 1110 | }) 1111 | } 1112 | 1113 | // 更新密码 1114 | func update_password(user *gin.RouterGroup, manager *mm.Manager) { 1115 | user.POST("/updatePassword", func(c *gin.Context) { 1116 | password := c.PostForm("password") 1117 | if password == "" { 1118 | util.Logger.Warn("update password failed, the password is blank") 1119 | c.Status(http.StatusBadRequest) 1120 | return 1121 | } 1122 | account := manager.SessionGet(c.Request, "account").(string) 1123 | err := manager.UpdatePassword(account, password) 1124 | if err != nil { 1125 | util.Logger.Error("update password failed, err:", err) 1126 | c.Status(http.StatusInternalServerError) 1127 | return 1128 | } 1129 | util.Logger.Infof("update password success, account %s new password is %s", account, password) 1130 | c.Status(http.StatusOK) 1131 | }) 1132 | } 1133 | 1134 | // 获取采集间隔 1135 | func get_collect_interval(user *gin.RouterGroup, manager *mm.Manager) { 1136 | user.GET("/getCollectInterval", func(c *gin.Context) { 1137 | util.Logger.Info("get collect interval") 1138 | c.String(http.StatusOK, strconv.Itoa(manager.GetCollectInterval())) 1139 | }) 1140 | } 1141 | 1142 | // 获取采集间隔 1143 | func update_collect_interval(user *gin.RouterGroup, manager *mm.Manager) { 1144 | user.POST("/updateCollectInterval", func(c *gin.Context) { 1145 | intervalV := c.PostForm("interval") 1146 | interval, err := strconv.Atoi(intervalV) 1147 | if err != nil { 1148 | util.Logger.Warn("update collect interval failed, the interval is not a integer") 1149 | c.Status(http.StatusBadRequest) 1150 | return 1151 | } 1152 | if interval <= 0 { 1153 | interval = 24 1154 | } 1155 | err = manager.UpdateCollectInterval(interval) 1156 | if err != nil { 1157 | util.Logger.Error("update collect interval failed, err:", err) 1158 | c.Status(http.StatusInternalServerError) 1159 | return 1160 | } 1161 | util.Logger.Info("update collect interval ", interval) 1162 | c.Status(http.StatusOK) 1163 | }) 1164 | } 1165 | 1166 | // 设置当前分类为首页分类 1167 | func set_category_main(user *gin.RouterGroup, manager *mm.Manager) { 1168 | user.POST("/setCategoryMain", func(c *gin.Context) { 1169 | idV := c.PostForm("id") 1170 | id, err := strconv.Atoi(idV) 1171 | if err != nil { 1172 | util.Logger.Warn("set category main failed, the category id is not integer") 1173 | c.Status(http.StatusBadRequest) 1174 | return 1175 | } 1176 | 1177 | mainV := c.PostForm("main") 1178 | err = manager.SetCategoryMain(uint(id), mainV == "true") 1179 | if err != nil { 1180 | util.Logger.Error("set category main failed, err:", err) 1181 | c.Status(http.StatusInternalServerError) 1182 | return 1183 | } 1184 | util.Logger.Info("set category main, category id is ", id) 1185 | c.Status(http.StatusOK) 1186 | }) 1187 | } 1188 | 1189 | // 清除缓存 1190 | func cache_purge(user *gin.RouterGroup, _ *mm.Manager) { 1191 | user.GET("/cachePurge", func(c *gin.Context) { 1192 | MiddleWare.Pruge() 1193 | c.Status(http.StatusOK) 1194 | }) 1195 | } 1196 | 1197 | // 登出操作 1198 | func logout(user *gin.RouterGroup, manager *mm.Manager) { 1199 | user.GET("/logout", func(c *gin.Context) { 1200 | account := manager.SessionGet(c.Request, "account").(string) 1201 | 1202 | manager.SessionDestroy(c.Writer, c.Request) 1203 | 1204 | util.Logger.Infof("account %s logout", account) 1205 | c.Status(http.StatusOK) 1206 | }) 1207 | } 1208 | 1209 | func exports(user *gin.RouterGroup, manager *mm.Manager) { 1210 | user.GET("/exports", func(c *gin.Context) { 1211 | data, err := manager.Exports() 1212 | if err != nil { 1213 | c.Status(http.StatusInternalServerError) 1214 | return 1215 | } 1216 | c.Header("Content-Disposition", "attachment; filename=\"movie.data\"") 1217 | c.Data(http.StatusOK, "application/octet-stream", data) 1218 | }) 1219 | } 1220 | 1221 | func imports(user *gin.RouterGroup, manager *mm.Manager) { 1222 | user.POST("/imports", func(c *gin.Context) { 1223 | db, err := c.FormFile("db") 1224 | if err != nil { 1225 | c.Status(http.StatusInternalServerError) 1226 | return 1227 | } 1228 | 1229 | dst := "./" + db.Filename 1230 | // 上传文件至指定的完整文件路径 1231 | c.SaveUploadedFile(db, dst) 1232 | 1233 | dfile, err := db.Open() 1234 | if err != nil { 1235 | util.Logger.Errorf("try open the upload dbfile fails,dbfile name: %s, err: %s", db.Filename, err) 1236 | c.Status(http.StatusInternalServerError) 1237 | return 1238 | } 1239 | 1240 | bytes, err := io.ReadAll(dfile) 1241 | // 1242 | if err != nil { 1243 | util.Logger.Errorf("try read the upload dbfile fails,dbfile name: %s, err: %s", db.Filename, err) 1244 | c.Status(http.StatusInternalServerError) 1245 | return 1246 | } 1247 | // 1248 | // fmt.Println("文件内容:", string(bytes)) 1249 | // 1250 | err = manager.Imports(bytes) 1251 | if err != nil { 1252 | util.Logger.Errorf("try restore db fails,dbfile name: %s, err: %s", db.Filename, err) 1253 | c.Status(http.StatusInternalServerError) 1254 | return 1255 | } 1256 | 1257 | c.Status(http.StatusOK) 1258 | }) 1259 | } 1260 | --------------------------------------------------------------------------------