Storage optimization

This commit is contained in:
mubai
2026-03-06 23:10:29 +08:00
parent 24aa240ab2
commit 4cea7e95f6
16 changed files with 920 additions and 685 deletions

View File

@@ -23,8 +23,8 @@
"typescript": "^4.9.3",
"unplugin-auto-import": "^0.15.1",
"unplugin-vue-components": "^0.24.1",
"vite": "^4.2.0",
"vite-plugin-ssr": "^0.4.109",
"vite": "^7.3.1",
"vite-plugin-ssr": "^0.4.85",
"vue-router": "^4.1.6",
"vue-tsc": "^1.2.0"
}

View File

@@ -86,13 +86,15 @@ const (
// SearchTableName 存放检索信息的数据表名
SearchTableName = "search"
UserTableName = "users"
MovieDetailName = "movie_details"
SlaveMovieInfo = "slave_infos"
UserIdInitialVal = 10000
FileTableName = "files"
FailureRecordTableName = "failure_records"
//mysql服务配置信息 root:root 设置mysql账户的用户名和密码
MysqlDsn = "root:root@(192.168.20.5:3306)/FilmSite?charset=utf8mb4&parseTime=True&loc=Local"
MysqlDsn = "root:root@(192.168.20.5:3601)/FilmSite?charset=utf8mb4&parseTime=True&loc=Local"
//MysqlDsn = "root:MuBai0916$@(113.44.5.201:3610)/FilmSite?charset=utf8mb4&parseTime=True&loc=Local"
// MysqlDsn docker compose 环境下的链接信息 mysql:3306 为 docker compose 中 mysql服务对应的网络名称和端口
@@ -108,7 +110,7 @@ const (
//RedisPassword = `MuBai0916$`
//RedisDBNo = 0
RedisAddr = `192.168.20.5:6379`
RedisAddr = `192.168.20.5:3602`
RedisPassword = `root`
RedisDBNo = 0

View File

@@ -1,11 +1,12 @@
package controller
import (
"github.com/gin-gonic/gin"
"server/logic"
"server/model/system"
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
const (
@@ -40,10 +41,10 @@ func FilmDetail(c *gin.Context) {
return
}
// 获取影片详情信息
detail := logic.IL.GetFilmDetail(id)
detail := logic.IL.GetFilmDetail(int64(id))
// 获取相关推荐影片数据
page := system.Page{Current: 0, PageSize: 14}
relateMovie := logic.IL.RelateMovie(detail.MovieDetail, &page)
relateMovie := logic.IL.RelateMovie(detail, &page)
system.Success(gin.H{
"detail": detail,
"relate": relateMovie,
@@ -61,23 +62,22 @@ func FilmPlayInfo(c *gin.Context) {
return
}
// 获取影片详情信息
detail := logic.IL.GetFilmDetail(id)
detail := logic.IL.GetFilmDetail(int64(id))
// 如果 playFrom 为空, 则设置默认播放源和默认影片数据
if len(playFrom) <= 1 && len(detail.List) > 0 {
playFrom = detail.List[0].Id
}
// 获取当前影片播放信息
var currentPlay system.MovieUrlInfo
var currentPlay system.PlayItem
for _, v := range detail.List {
if v.Id == playFrom {
currentPlay = v.LinkList[episode]
}
}
// 推荐影片信息
page := system.Page{Current: 0, PageSize: 14}
relateMovie := logic.IL.RelateMovie(detail.MovieDetail, &page)
relateMovie := logic.IL.RelateMovie(detail, &page)
system.Success(gin.H{
"detail": detail,
"current": currentPlay,

View File

@@ -1,56 +1,66 @@
module server
go 1.20
go 1.25.0
require (
github.com/gin-gonic/gin v1.9.0
github.com/gocolly/colly/v2 v2.1.0
github.com/golang-jwt/jwt/v5 v5.1.0
github.com/redis/go-redis/v9 v9.0.2
github.com/robfig/cron/v3 v3.0.0
gorm.io/driver/mysql v1.4.7
gorm.io/gorm v1.25.5
github.com/gin-gonic/gin v1.12.0
github.com/gocolly/colly/v2 v2.3.0
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/redis/go-redis/v9 v9.18.0
github.com/robfig/cron/v3 v3.0.1
gorm.io/driver/mysql v1.6.0
gorm.io/gorm v1.31.1
)
require (
github.com/PuerkitoBio/goquery v1.5.1 // indirect
github.com/andybalholm/cascadia v1.2.0 // indirect
github.com/antchfx/htmlquery v1.2.3 // indirect
github.com/antchfx/xmlquery v1.2.4 // indirect
github.com/antchfx/xpath v1.1.8 // indirect
github.com/bytedance/sonic v1.8.5 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/PuerkitoBio/goquery v1.11.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/antchfx/htmlquery v1.3.5 // indirect
github.com/antchfx/xmlquery v1.5.0 // indirect
github.com/antchfx/xpath v1.3.5 // indirect
github.com/bits-and-blooms/bitset v1.24.4 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.12.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kennygrant/sanitize v1.2.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.2 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
github.com/temoto/robotstxt v1.1.1 // indirect
github.com/nlnwa/whatwg-url v0.6.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/temoto/robotstxt v1.1.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
google.golang.org/appengine v1.6.6 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/arch v0.22.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.36.10 // indirect
)

View File

@@ -1,211 +1,227 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE=
github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
github.com/antchfx/htmlquery v1.2.3 h1:sP3NFDneHx2stfNXCKbhHFo8XgNjCACnU/4AO5gWz6M=
github.com/antchfx/htmlquery v1.2.3/go.mod h1:B0ABL+F5irhhMWg54ymEZinzMSi0Kt3I2if0BLYa3V0=
github.com/antchfx/xmlquery v1.2.4 h1:T/SH1bYdzdjTMoz2RgsfVKbM5uWh3gjDYYepFqQmFv4=
github.com/antchfx/xmlquery v1.2.4/go.mod h1:KQQuESaxSlqugE2ZBcM/qn+ebIpt+d+4Xx7YcSGAIrM=
github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/antchfx/xpath v1.1.8 h1:PcL6bIX42Px5usSx6xRYw/wjB3wYGkj0MJ9MBzEKVgk=
github.com/antchfx/xpath v1.1.8/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/bsm/ginkgo/v2 v2.5.0 h1:aOAnND1T40wEdAtkGSkvSICWeQ8L3UASX7YVCqQx+eQ=
github.com/bsm/gomega v1.20.0 h1:JhAwLmtRzXFTx2AkALSLa8ijZafntmhSoU63Ok18Uq8=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.8.5 h1:kjX0/vo5acEQ/sinD/18SkA/lDDUk23F0RcaHvI7omc=
github.com/bytedance/sonic v1.8.5/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/antchfx/htmlquery v1.3.5 h1:aYthDDClnG2a2xePf6tys/UyyM/kRcsFRm+ifhFKoU0=
github.com/antchfx/htmlquery v1.3.5/go.mod h1:5oyIPIa3ovYGtLqMPNjBF2Uf25NPCKsMjCnQ8lvjaoA=
github.com/antchfx/xmlquery v1.5.0 h1:uAi+mO40ZWfyU6mlUBxRVvL6uBNZ6LMU4M3+mQIBV4c=
github.com/antchfx/xmlquery v1.5.0/go.mod h1:lJfWRXzYMK1ss32zm1GQV3gMIW/HFey3xDZmkP1SuNc=
github.com/antchfx/xpath v1.3.5 h1:PqbXLC3TkfeZyakF5eeh3NTWEbYl4VHNVeufANzDbKQ=
github.com/antchfx/xpath v1.3.5/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA=
github.com/gocolly/colly/v2 v2.1.0 h1:k0DuZkDoCsx51bKpRJNEmcxcp+W5N8ziuwGaSDuFoGs=
github.com/gocolly/colly/v2 v2.1.0/go.mod h1:I2MuhsLjQ+Ex+IzK3afNS8/1qP3AedHOusRPcRdC5o0=
github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU=
github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gocolly/colly/v2 v2.3.0 h1:HSFh0ckbgVd2CSGRE+Y/iA4goUhGROJwyQDCMXGFBWM=
github.com/gocolly/colly/v2 v2.3.0/go.mod h1:Qp54s/kQbwCQvFVx8KzKCSTXVJ1wWT4QeAKEu33x1q8=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jawher/mow.cli v1.1.0/go.mod h1:aNaQlc7ozF3vw6IJ2dHjp2ZFiA4ozMIYY6PyuRJwlUg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4=
github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/nlnwa/whatwg-url v0.6.2 h1:jU61lU2ig4LANydbEJmA2nPrtCGiKdtgT0rmMd2VZ/Q=
github.com/nlnwa/whatwg-url v0.6.2/go.mod h1:x0FPXJzzOEieQtsBT/AKvbiBbQ46YlL6Xa7m02M1ECk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/redis/go-redis/v9 v9.0.2 h1:BA426Zqe/7r56kCcvxYLWe1mkaz71LKF77GwgFzSxfE=
github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps=
github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/temoto/robotstxt v1.1.1 h1:Gh8RCs8ouX3hRSxxK7B1mO5RFByQ4CmJZDwgom++JaA=
github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg=
github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.4.7 h1:rY46lkCspzGHn7+IYsNpSfEv9tA+SU4SkkB+GFX125Y=
gorm.io/driver/mysql v1.4.7/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=

View File

@@ -1,14 +1,13 @@
package logic
import (
"fmt"
"github.com/gin-gonic/gin"
"regexp"
"server/config"
"server/model/system"
"server/plugin/db"
"server/plugin/common/util"
"server/plugin/spider"
"strings"
"github.com/gin-gonic/gin"
)
/*
@@ -82,16 +81,13 @@ func (i *IndexLogic) ClearIndexCache() {
}
// GetFilmDetail 影片详情信息页面处理
func (i *IndexLogic) GetFilmDetail(id int) system.MovieDetailVo {
// 通过Id 获取影片search信息
search := system.SearchInfo{}
db.Mdb.Where("mid", id).First(&search)
// 获取redis中的完整影视信息 MovieDetail:Cid11:Id24676
movieDetail := system.GetDetailByKey(fmt.Sprintf(config.MovieDetailKey, search.Cid, search.Mid))
var res = system.MovieDetailVo{MovieDetail: movieDetail}
func (i *IndexLogic) GetFilmDetail(id int64) system.MovieDetailVo {
// 通过mid获取影片的详情信息
detail := system.GetDetailByMid(id)
//查找其他站点是否存在影片对应的播放源
res.List = multipleSource(&movieDetail)
return res
ml := multipleSource(&detail)
// 转换组合主次站点信息
return system.ConvertMovieDetailVo(detail, ml)
}
// GetCategoryInfo 分类信息获取, 组装导航栏需要的信息
@@ -135,13 +131,9 @@ func (i *IndexLogic) GetNavCategory() []*system.Category {
// SearchFilmInfo 获取关键字匹配的影片信息
func (i *IndexLogic) SearchFilmInfo(key string, page *system.Page) []system.MovieBasicInfo {
// 1. 从mysql中获取满足条件的数据, 每页10条
sl := system.SearchFilmKeyword(key, page)
// 2. 获取redis中的basicMovieInfo信息
var bl []system.MovieBasicInfo
for _, s := range sl {
bl = append(bl, system.GetBasicInfoByKey(fmt.Sprintf(config.MovieBasicInfoKey, s.Cid, s.Mid)))
}
return bl
ids := system.SearchFilmKeyword(key, page)
// 2. 通过ids获取对应的影片信息
return system.GetBasicInfoByIds(ids)
}
// GetFilmCategory 根据Pid或Cid获取指定的分页数据
@@ -169,7 +161,7 @@ func (i *IndexLogic) GetPidCategory(pid int64) *system.CategoryTree {
}
// RelateMovie 根据当前影片信息匹配相关的影片
func (i *IndexLogic) RelateMovie(detail system.MovieDetail, page *system.Page) []system.MovieBasicInfo {
func (i *IndexLogic) RelateMovie(detail system.MovieDetailVo, page *system.Page) []system.MovieBasicInfo {
/*
根据当前影片信息匹配相关的影片
1. 分类Cid,
@@ -203,53 +195,40 @@ func (i *IndexLogic) SearchTags(pid int64) map[string]interface{} {
func multipleSource(detail *system.MovieDetail) []system.PlayLinkVo {
// 生成多站点的播放源信息
master := system.GetCollectSourceListByGrade(system.MasterCollect)
var playList = []system.PlayLinkVo{{master[0].Id, master[0].Name, detail.PlayList[0]}}
// 整合多播放源, 初始化存储key map
names := make(map[string]int)
// 1. 判断detail的dbId是否存在, 存在则添加到names中作为匹配条件
if detail.DbId > 0 {
names[system.GenerateHashKey(detail.DbId)] = 0
}
// 2. 对name进行去除特殊格式处理
names[system.GenerateHashKey(detail.Name)] = 0
// 3. 对包含第一季的name进行处理
names[system.GenerateHashKey(regexp.MustCompile(`第一季$`).ReplaceAllString(detail.Name, ""))] = 0
// 4. 将subtitle进行切分,放入names中
if len(detail.SubTitle) > 0 && strings.Contains(detail.SubTitle, ",") {
for _, v := range strings.Split(detail.SubTitle, ",") {
names[system.GenerateHashKey(v)] = 0
var l = []system.PlayLinkVo{{master[0].Id, master[0].Name, detail.PlayList[0]}}
// 通过 name 以及 subTitle 生成 hash id 和 dbID 匹配次级站点播放信息
// 使用map 防止清洗后的id重复
idMap := make(map[string]int)
idMap[system.GenerateHashKey(detail.Mid)] = 0
// 将subTitle进行切割
if len(detail.SubTitle) > 0 {
for _, s := range strings.Split(util.FormatSpecialChar(detail.SubTitle), ",") {
idMap[system.GenerateHashKey(s)] = 0
}
}
if len(detail.SubTitle) > 0 && strings.Contains(detail.SubTitle, "/") {
for _, v := range strings.Split(detail.SubTitle, "/") {
names[system.GenerateHashKey(v)] = 0
}
// 遍历idMqp整合ids
var ids []string
for id, _ := range idMap {
ids = append(ids, id)
}
// 遍历所有附属站点列表
sc := system.GetCollectSourceListByGrade(system.SlaveCollect)
for _, s := range sc {
for k, _ := range names {
pl := system.GetMultiplePlay(s.Id, k)
if len(pl) > 0 {
// 如果当前站点已经匹配到数据则直接退出当前循环
//detail.PlayList = append(detail.PlayList, pl)
playList = append(playList, system.PlayLinkVo{Id: s.Id, Name: s.Name, LinkList: pl})
break
}
}
// 获取附属站点的基本信息
sMap := make(map[string]system.FilmSource)
for _, c := range system.GetCollectSourceListByGrade(system.SlaveCollect) {
sMap[c.Id] = c
}
return playList
// 获取满足条件的次级站点播放数据
for _, s := range system.GetMultiplePlay(ids, detail.DbId) {
l = append(l, system.PlayLinkVo{Id: s.Mid, Name: sMap[s.Mid].Name, LinkList: s.PlayList[0]})
}
return l
}
// GetFilmsByTags 通过searchTag 返回满足条件的分页影片信息
func (i *IndexLogic) GetFilmsByTags(st system.SearchTagsVO, page *system.Page) []system.MovieBasicInfo {
// 获取满足条件的影片id 列表
sl := system.GetSearchInfosByTags(st, page)
ids := system.GetSearchInfosByTags(st, page)
// 通过key 获取对应影片的基本信息
return system.GetBasicInfoBySearchInfos(sl...)
return system.GetBasicInfoByIds(ids)
}
// GetFilmClassify 通过Pid返回当前所属分类下的首页展示数据

View File

@@ -7,12 +7,11 @@ import (
"server/plugin/SystemInit"
"server/plugin/db"
"server/router"
"time"
)
func init() {
// 执行初始化前等待20s , 让mysql服务完成初始化指令
time.Sleep(time.Second * 20)
//time.Sleep(time.Second * 20)
//初始化redis客户端
err := db.InitRedisConn()
if err != nil {

View File

@@ -3,8 +3,6 @@ package system
import (
"encoding/json"
"fmt"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
"log"
"path/filepath"
"regexp"
@@ -12,6 +10,9 @@ import (
"server/plugin/common/util"
"server/plugin/db"
"strings"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
// FileInfo 图片信息对象
@@ -173,8 +174,10 @@ func SyncFilmPicture() {
// ReplaceDetailPic 将影片详情中的图片地址替换为自己的
func ReplaceDetailPic(d *MovieDetail) {
// 查询影片对应的本地图片信息
if ExistFileInfoByRid(d.Id) {
// 获取主站点信息, 如果未开启图片同步功能则直接返回
s := GetCollectSourceListByGrade(MasterCollect)[0]
// 如果开启了图片同步 且存在对应图片信息 则查询影片对应的本地图片信息,
if s.SyncPictures && ExistFileInfoByRid(d.Id) {
// 如果存在关联的本地图片, 则查询对应的图片信息
f := GetFileInfoByRid(d.Id)
// 替换采集站的图片链接为本地链接

View File

@@ -1,16 +1,21 @@
package system
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"github.com/redis/go-redis/v9"
"hash/fnv"
"log"
"regexp"
"server/config"
"server/plugin/common/util"
"server/plugin/db"
"strconv"
"strings"
"time"
"gorm.io/gorm"
)
// Movie 影片基本信息
@@ -25,31 +30,6 @@ type Movie struct {
PlayFrom string `json:"playFrom"` // 播放来源
}
// MovieDescriptor 影片详情介绍信息
type MovieDescriptor struct {
SubTitle string `json:"subTitle"` //子标题
CName string `json:"cName"` //分类名称
EnName string `json:"enName"` //英文名
Initial string `json:"initial"` //首字母
ClassTag string `json:"classTag"` //分类标签
Actor string `json:"actor"` //主演
Director string `json:"director"` //导演
Writer string `json:"writer"` //作者
Blurb string `json:"blurb"` //简介, 残缺,不建议使用
Remarks string `json:"remarks"` // 更新情况
ReleaseDate string `json:"releaseDate"` //上映时间
Area string `json:"area"` // 地区
Language string `json:"language"` //语言
Year string `json:"year"` //年份
State string `json:"state"` //影片状态 正片|预告...
UpdateTime string `json:"updateTime"` //更新时间
AddTime int64 `json:"addTime"` //资源添加时间戳
DbId int64 `json:"dbId"` //豆瓣id
DbScore string `json:"dbScore"` // 豆瓣评分
Hits int64 `json:"hits"` //影片热度
Content string `json:"content"` //内容简介
}
// MovieBasicInfo 影片基本信息
type MovieBasicInfo struct {
Id int64 `json:"id"` //影片Id
@@ -68,203 +48,420 @@ type MovieBasicInfo struct {
Year string `json:"year"` //年份
}
// MovieUrlInfo 影视资源url信息
type MovieUrlInfo struct {
// PlayItem 影视资源url信息
type PlayItem struct {
Episode string `json:"episode"` // 集数
Link string `json:"link"` // 播放地址
}
// MoviePlayList 播放列表信息, 二维切片
type MoviePlayList [][]PlayItem
// FromList 播放来源切片
type FromList []string
// MovieDetail 影片详情信息
type MovieDetail struct {
Id int64 `json:"id"` //影片Id
Cid int64 `json:"cid"` //分类ID
Pid int64 `json:"pid"` //一级分类ID
Name string `json:"name"` //片名
Picture string `json:"picture"` //简介图片
PlayFrom []string `json:"playFrom"` // 播放来源
DownFrom string `json:"DownFrom"` //下载来源 例: http
Id int64 `json:"id" gorm:"primaryKey"` //影片Id
Mid int64 `json:"mid"` //影片Id
Cid int64 `json:"cid"` //分类ID
Pid int64 `json:"pid"` //一级分类ID
Name string `json:"name"` //片名
Picture string `json:"picture"` //简介图片
SubTitle string `json:"subTitle"` //子标题
CName string `json:"cName"` //分类名称
EnName string `json:"enName"` //英文名
Initial string `json:"initial"` //首字母
ClassTag string `json:"classTag"` //分类标签
Actor string `json:"actor"` //主演
Director string `json:"director"` //导演
Writer string `json:"writer"` //作者
Blurb string `json:"blurb"` //简介, 残缺,不建议使用
Remarks string `json:"remarks"` // 更新情况
ReleaseDate string `json:"releaseDate"` //上映时间
Area string `json:"area"` // 地区
Language string `json:"language"` //语言
Year string `json:"year"` //年份
State string `json:"state"` //影片状态 正片|预告...
UpdateTime string `json:"updateTime"` //更新时间
AddTime int64 `json:"addTime"` //资源添加时间戳
DbId int64 `json:"dbId"` //豆瓣id
DbScore string `json:"dbScore"` // 豆瓣评分
Hits int64 `json:"hits"` //影片热度
Content string `json:"content"` //内容简介
PlayFrom FromList `json:"playFrom" gorm:"type:json"` // 播放来源
DownFrom string `json:"DownFrom"` //下载来源 例: http
//PlaySeparator string `json:"playSeparator"` // 播放信息分隔符
PlayList [][]MovieUrlInfo `json:"playList"` //播放地址url
DownloadList [][]MovieUrlInfo `json:"downloadList"` // 下载url地址
MovieDescriptor `json:"descriptor"` //影片描述信息
PlayList MoviePlayList `json:"playList" gorm:"type:json"` //播放地址url
DownloadList MoviePlayList `json:"downloadList" gorm:"type:json"` // 下载url地址
}
// ===================================Redis数据交互========================================================
type SlaveMovieInfo struct {
Id int64 `json:"id" gorm:"primaryKey"` // 自增ID
Sid string `json:"sid"` // 采集站标识ID
//Name string `json:"name"` // 影片名称
Mid string `json:"mid"` // 归一匹配ID
DbId int64 `json:"dbId"` //豆瓣ID 可能为空
PlayList MoviePlayList `json:"playList" gorm:"type:json"`
}
// TableName 设置MovieDetail表的表名
func (m *MovieDetail) TableName() string {
return config.MovieDetailName
}
// TableName 设置slaveMovieInfo 表名
func (m *SlaveMovieInfo) TableName() string {
return config.SlaveMovieInfo
}
// CreateMovieDetailTable 创建存储检索信息的数据表
func CreateMovieDetailTable() {
// 如果不存在则创建表 并设置自增ID初始值为10000
if !ExistMovieDetailTable() {
err := db.Mdb.AutoMigrate(&MovieDetail{})
if err != nil {
log.Println("Create Table MovieDetailsTable Failed: ", err)
}
}
}
// ExistMovieDetailTable 检测是否存在 MovieDetails表
func ExistMovieDetailTable() bool {
return db.Mdb.Migrator().HasTable(&MovieDetail{})
}
// CreateSlaveMovieInfoTable 创建存储检索信息的数据表
func CreateSlaveMovieInfoTable() {
// 如果不存在则创建表 并设置自增ID初始值为10000
if !ExistSlaveMovieInfoTable() {
err := db.Mdb.AutoMigrate(&SlaveMovieInfo{})
if err != nil {
log.Println("Create Table SlaveMovieInfoTable Failed: ", err)
}
}
}
// ExistSlaveMovieInfoTable 检测是否存在 MovieDetails表
func ExistSlaveMovieInfoTable() bool {
return db.Mdb.Migrator().HasTable(&SlaveMovieInfo{})
}
// =================================== column序列化 接口========================================================
func (m *MoviePlayList) Scan(value interface{}) error {
if value == nil {
*m = nil
return nil
}
b, ok := value.([]byte)
if !ok {
return errors.New("MoviePlayList serialization failed, value is not []byte")
}
return json.Unmarshal(b, m)
}
func (m MoviePlayList) Value() (driver.Value, error) {
if m == nil {
return nil, nil
}
return json.Marshal(m)
}
func (fl *FromList) Scan(value interface{}) error {
if value == nil {
*fl = nil
return nil
}
b, ok := value.([]byte)
if !ok {
return errors.New("FromList serialization failed, value is not []byte")
}
return json.Unmarshal(b, fl)
}
func (fl FromList) Value() (driver.Value, error) {
if fl == nil {
return nil, nil
}
return json.Marshal(fl)
}
// =================================== Spider数据处理 ========================================================
// SaveDetails 保存影片详情信息到redis中 格式: MovieDetail:Cid?:Id?
func SaveDetails(list []MovieDetail) (err error) {
// 遍历list中的信息
for _, detail := range list {
// 序列化影片详情信息
data, _ := json.Marshal(detail)
// 1. 原使用Zset存储, 但是不便于单个检索 db.Rdb.ZAdd(db.Cxt, fmt.Sprintf("%s:Cid%d", config.MovieDetailKey, detail.Cid), redis.Z{Score: float64(detail.Id), Member: member}).Err()
// 改为普通 k v 存储, k-> id关键字, v json序列化的结果
err = db.Rdb.Set(db.Cxt, fmt.Sprintf(config.MovieDetailKey, detail.Cid, detail.Id), data, config.FilmExpired).Err()
// 2. 同步保存简略信息到redis中
SaveMovieBasicInfo(detail)
// 3. 保存 Search tag redis中
if err == nil {
// 转换 detail信息
searchInfo := ConvertSearchInfo(detail)
// 只存储用于检索对应影片的关键字信息
SaveSearchTag(searchInfo)
}
func SaveDetails(ml []MovieDetail) (err error) {
// 1. 先将详情信息存入 MovieDetail表中
if err = db.Mdb.Create(&ml).Error; err != nil {
log.Println("影片详情信息保存失败: ", err)
}
// 保存一份search信息到mysql, 批量存储
BatchSaveSearchInfo(list)
// 2. 将详情信息转化为SearchInfo并保存
BatchSaveSearchInfo(ml)
return err
}
// SaveDetail 保存单部影片信息
func SaveDetail(detail MovieDetail) (err error) {
// 序列化影片详情信息
data, _ := json.Marshal(detail)
// 保存影片信息到Redis
err = db.Rdb.Set(db.Cxt, fmt.Sprintf(config.MovieDetailKey, detail.Cid, detail.Id), data, config.FilmExpired).Err()
if err != nil {
return err
}
// 2. 同步保存简略信息到redis中
SaveMovieBasicInfo(detail)
// 转换 detail信息
searchInfo := ConvertSearchInfo(detail)
// 3. 保存 Search tag redis中
// 只存储用于检索对应影片的关键字信息
func SaveDetail(m MovieDetail) (err error) {
// 1. 转换 detail信息 searchInfo
searchInfo := ConvertSearchInfo(m)
// 2. 保存 Search tag 到 redis中 只存储用于检索对应影片的关键字信息
SaveSearchTag(searchInfo)
// 3. 将影片详情信息保存到 MovieDetails表中
// 4. 先查询数据库中是否存在对应记录 ,如果不存在对应记录则 保存当前记录
tx := db.Mdb.Begin()
if !ExistMovieDetailByMid(m.Mid) {
// 执行插入操作
if err := tx.Create(&m).Error; err != nil {
tx.Rollback()
return err
}
} else {
// 只对会变化的字段进行更新
err := tx.Model(&MovieDetail{}).Where("mid", m.Mid).Updates(MovieDetail{PlayList: m.PlayList, DownloadList: m.DownloadList,
Remarks: m.Remarks, State: m.State, UpdateTime: m.UpdateTime, AddTime: m.AddTime, DbScore: m.DbScore, Hits: m.Hits}).Error
if err != nil {
tx.Rollback()
return err
}
}
// 提交事务
tx.Commit()
// 保存影片检索信息到searchTable
err = SaveSearchInfo(searchInfo)
return err
}
// SaveMovieBasicInfo 摘取影片的详情部分信息转存为影视基本信息
func SaveMovieBasicInfo(detail MovieDetail) {
basicInfo := MovieBasicInfo{
Id: detail.Id,
Cid: detail.Cid,
Pid: detail.Pid,
Name: detail.Name,
SubTitle: detail.SubTitle,
CName: detail.CName,
State: detail.State,
Picture: detail.Picture,
Actor: detail.Actor,
Director: detail.Director,
Blurb: detail.Blurb,
Remarks: detail.Remarks,
Area: detail.Area,
Year: detail.Year,
}
data, _ := json.Marshal(basicInfo)
_ = db.Rdb.Set(db.Cxt, fmt.Sprintf(config.MovieBasicInfoKey, detail.Cid, detail.Id), data, config.FilmExpired).Err()
// ExistMovieDetailByMid 通过mid判断是否存在对应信息
func ExistMovieDetailByMid(mid int64) bool {
var count int64
db.Mdb.Model(&SearchInfo{}).Where("mid", mid).Count(&count)
return count > 0
}
// SaveSitePlayList 仅保存播放url列表信息到当前站点
func SaveSitePlayList(id string, list []MovieDetail) (err error) {
// 如果list 为空则直接返回
if len(list) <= 0 {
func SaveSitePlayList(id string, ml []MovieDetail) (err error) {
// 如果ml 为空则直接返回
if len(ml) <= 0 {
return nil
}
res := make(map[string]string)
for _, d := range list {
if len(d.PlayList) > 0 {
data, _ := json.Marshal(d.PlayList[0])
// 不保存电影解说类
if strings.Contains(d.CName, "解说") {
continue
var sl []SlaveMovieInfo
for _, m := range ml {
s := SlaveMovieInfo{Sid: id, Mid: GenerateHashKey(m.Name), DbId: m.DbId, PlayList: m.PlayList}
// 查询表中是否已经存在对应的数据记录, 如果有则更新, 无则追加到切片中统一处理
if id := ExistSlaveMovieInfo(s); id <= 0 {
if err = db.Mdb.Model(&s).Where("id", id).Updates(s).Error; err != nil {
log.Println("附属站点影片信息更新失败: ", err)
}
// 如果DbId不为0, 则以dbID作为key进行hash额外存储一次
if d.DbId != 0 {
res[GenerateHashKey(d.DbId)] = string(data)
}
res[GenerateHashKey(d.Name)] = string(data)
continue
}
sl = append(sl, s)
}
// 如果结果不为空,则将数据保存到redis
if len(res) > 0 {
// 保存形式 key: MultipleSource:siteName Hash[hash(movieName)]list
err = db.Rdb.HMSet(db.Cxt, fmt.Sprintf(config.MultipleSiteDetail, id), res).Err()
// 将处理后的结果存储到 SalveMovieInfo表
if err = db.Mdb.Create(&sl).Error; err != nil {
log.Println("附属站点影片信息保存失败: ", err)
}
return
}
// BatchSaveSearchInfo 批量保存Search信息
func BatchSaveSearchInfo(list []MovieDetail) {
var infoList []SearchInfo
for _, v := range list {
infoList = append(infoList, ConvertSearchInfo(v))
// ExistSlaveMovieInfo 查询对应记录, 如果存在则返还id, 不存在则返还 -1
func ExistSlaveMovieInfo(s SlaveMovieInfo) int64 {
var id int64
if err := db.Mdb.Model(&SlaveMovieInfo{}).Select("id").Where("sid = ? AND (mid = ? OR db_id = ?)", s.Sid, s.Mid, s.DbId).First(&id).Error; err != nil {
// 如果错误类型为gorm.ErrRecordNotFound, 直接返回 0
if errors.Is(err, gorm.ErrRecordNotFound) {
return 0
}
// 如果是其他异常则输出异常信息并返回 -1
log.Println("Find SlaveMovieInfo Failed: ", err)
return -1
}
// 将检索信息存入redis中做一次转存
RdbSaveSearchInfo(infoList)
return id
}
// ============================ APi接口 ==================================================
// GetDetailByMid 获取影片对应的详情信息
func GetDetailByMid(mid int64) MovieDetail {
var d MovieDetail
// 查询mid对应的影片详情信息, 只查询部分字段
if err := db.Mdb.Model(&MovieDetail{}).Where("mid = ?", mid).First(&d).Error; err != nil {
log.Println("Find MovieDetail Failed: ", err)
return d
}
// 执行本地图片匹配
ReplaceDetailPic(&d)
return d
}
// GetBasicInfoByMid 获取Id对应的影片基本信息
func GetBasicInfoByMid(mid int64) MovieBasicInfo {
// 通过id查询满足条件的影片基本信息
var basic MovieBasicInfo
var d MovieDetail
// 查询mid对应的影片详情信息, 只查询部分字段
if err := db.Mdb.Model(&MovieDetail{}).Select("id, mid, cid, pid, name, sub_title, c_name, state, picture, actor, director,"+
" content, remarks, area, year").Where("mid = ?", mid).First(&d).Error; err != nil {
log.Println("Find MovieDetail Failed: ", err)
return basic
}
// 匹配本地图片
ReplaceDetailPic(&d)
// 将 MovieDetail转化为 BasicInfo
basic = ConvertBasicInfo(d)
return basic
}
// GetBasicInfoByIds 通过searchInfo 获取影片的基本信息
func GetBasicInfoByIds(ids []int64) []MovieBasicInfo {
var ml []MovieDetail
var l []MovieBasicInfo
// 使用in查询, 一次性拿到满足条件的数据
if err := db.Mdb.Model(&MovieDetail{}).Select("id, mid, cid, pid, name, sub_title, c_name, state, picture, actor, director,"+
" content, remarks, area, year").Where("mid IN (?)", ids).Find(&ml).Error; err != nil {
log.Println("BatchFind BasicInfo Failed: ", err)
return nil
}
// 将查询到的结果批量转化为BasicInfo
for _, m := range ml {
// 执行本地图片匹配
ReplaceDetailPic(&m)
l = append(l, ConvertBasicInfo(m))
}
return l
}
// GetMovieListByPid 通过Pid 分类ID 获取对应影片的数据信息
func GetMovieListByPid(pid int64, page *Page) []MovieBasicInfo {
// 返回分页参数
var count int64
db.Mdb.Model(&SearchInfo{}).Where("pid", pid).Count(&count)
page.Total = int(count)
page.PageCount = int((page.Total + page.PageSize - 1) / page.PageSize)
// 通过Search表查询
var ids []int64
if err := db.Mdb.Limit(page.PageSize).Offset((page.Current-1)*page.PageSize).Select("mid").Where("pid", pid).Order("update_stamp DESC").Find(&ids).Error; err != nil {
log.Println(err)
return nil
}
// 通过ids查询影片基本信息并返回
return GetBasicInfoByIds(ids)
}
// GetMovieListByCid 通过Cid查找对应的影片分页数据, 不适合GetMovieListByPid 糅合
func GetMovieListByCid(cid int64, page *Page) []MovieBasicInfo {
// 返回分页参数
var count int64
db.Mdb.Model(&SearchInfo{}).Where("cid", cid).Count(&count)
page.Total = int(count)
page.PageCount = int((page.Total + page.PageSize - 1) / page.PageSize)
// 进行具体的信息查询
var ids []int64
if err := db.Mdb.Limit(page.PageSize).Offset((page.Current-1)*page.PageSize).Select("mid").Where("cid", cid).Order("update_stamp DESC").Find(&ids).Error; err != nil {
log.Println(err)
return nil
}
// 通过影片ID去redis中获取id对应数据信息
return GetBasicInfoByIds(ids)
}
// GetRelateMovieBasicInfo GetRelateMovie 根据 name, cid, pid, classtag 获取相关影片
func GetRelateMovieBasicInfo(search SearchInfo, page *Page) []MovieBasicInfo {
/*
根据当前影片信息匹配相关的影片
1. 分类Cid,
2. 如果影片名称含有第x季 则根据影片名进行模糊匹配
3. class_tag 剧情内容匹配, 切分后使用 or 进行匹配
4. area 地区
5. 语言 Language
*/
// sql 拼接查询条件
sql := ""
// 优先进行名称相似匹配, 先对影片名称进行精简, 只保留主体用于匹配同系列影片
name := util.CleanFilmName(search.Name)
sql = fmt.Sprintf(`select mid from %s where (name LIKE "%%%s%%" or sub_title LIKE "%%%[2]s%%") AND cid=%d AND search.deleted_at IS NULL union`, search.TableName(), name, search.Cid)
// 添加其他相似匹配规则 同属二级分类
sql = fmt.Sprintf(`%s (select * from %s where cid=%d AND `, sql, search.TableName(), search.Cid)
// 根据剧情标签查找相似影片, classTag 使用的分隔符为 , | /首先去除 classTag 中包含的所有空格
search.ClassTag = strings.ReplaceAll(search.ClassTag, " ", "")
// 如果 classTag 中包含分割符则进行拆分匹配
cl := strings.Split(util.FormatSpecialChar(search.ClassTag), ",")
if len(cl) > 0 {
s := "("
for _, c := range cl {
s = fmt.Sprintf(`%s class_tag like "%%%s%%" OR`, s, c)
}
sql = fmt.Sprintf("%s %s)", sql, strings.TrimSuffix(s, "OR"))
} else {
sql = fmt.Sprintf(`%s class_tag like "%%%s%%"`, sql, search.ClassTag)
}
// 除名称外的相似影片使用随机排序
//sql = fmt.Sprintf("%s ORDER BY RAND() limit %d,%d)", sql, page.Current, page.PageSize)
sql = fmt.Sprintf("%s AND search.deleted_at IS NULL limit %d,%d)", sql, page.Current, page.PageSize)
// 条件拼接完成后加上limit参数
sql = fmt.Sprintf("(%s) limit %d,%d", sql, page.Current, page.PageSize)
// 执行sql, 获取满足条件的影片mid切片
var ids []int64
db.Mdb.Raw(sql).Scan(&ids)
// 通过 ids 获取影片基本信息,并返回
return GetBasicInfoByIds(ids)
}
// GetMultiplePlay 通过影片名的ID值匹配播放源, 不区分站点
func GetMultiplePlay(mIds []string, dbIds int64) []SlaveMovieInfo {
// 初始化返回值
var l []SlaveMovieInfo
// 通过siteId, mIds, dbIds 检索满足条件的数据
if err := db.Mdb.Model(&SlaveMovieInfo{}).Select("play_list").Where("mid IN (?) OR db_id = ?", mIds, dbIds).Find(&l).Error; err != nil {
log.Println("GetMultiplePlay Failed: ", err)
return nil
}
return l
}
// ============================ 数据处理 ==================================================
// ConvertSearchInfo 将detail信息处理成 searchInfo
func ConvertSearchInfo(detail MovieDetail) SearchInfo {
score, _ := strconv.ParseFloat(detail.DbScore, 64)
stamp, _ := time.ParseInLocation(time.DateTime, detail.UpdateTime, time.Local)
func ConvertSearchInfo(m MovieDetail) SearchInfo {
score, _ := strconv.ParseFloat(m.DbScore, 64)
stamp, _ := time.ParseInLocation(time.DateTime, m.UpdateTime, time.Local)
// detail中的年份信息并不准确, 因此采用 ReleaseDate中的年份
year, err := strconv.ParseInt(regexp.MustCompile(`[1-9][0-9]{3}`).FindString(detail.ReleaseDate), 10, 64)
year, err := strconv.ParseInt(regexp.MustCompile(`[1-9][0-9]{3}`).FindString(m.ReleaseDate), 10, 64)
if err != nil {
year = 0
}
return SearchInfo{
Mid: detail.Id,
Cid: detail.Cid,
Pid: detail.Pid,
Name: detail.Name,
SubTitle: detail.SubTitle,
CName: detail.CName,
ClassTag: detail.ClassTag,
Area: detail.Area,
Language: detail.Language,
Mid: m.Id,
Cid: m.Cid,
Pid: m.Pid,
Name: m.Name,
SubTitle: m.SubTitle,
CName: m.CName,
ClassTag: m.ClassTag,
Area: m.Area,
Language: m.Language,
Year: year,
Initial: detail.Initial,
Initial: m.Initial,
Score: score,
Hits: detail.Hits,
Hits: m.Hits,
UpdateStamp: stamp.Unix(),
State: detail.State,
Remarks: detail.Remarks,
State: m.State,
Remarks: m.Remarks,
// ReleaseDate 部分影片缺失该参数, 所以使用添加时间作为上映时间排序
ReleaseStamp: detail.AddTime,
ReleaseStamp: m.AddTime,
}
}
// GetBasicInfoByKey 获取Id对应的影片基本信息
func GetBasicInfoByKey(key string) MovieBasicInfo {
// 反序列化得到的结果
data := []byte(db.Rdb.Get(db.Cxt, key).Val())
basic := MovieBasicInfo{}
_ = json.Unmarshal(data, &basic)
// 执行本地图片匹配
ReplaceBasicDetailPic(&basic)
return basic
}
// GetDetailByKey 获取影片对应的详情信息
func GetDetailByKey(key string) MovieDetail {
// 反序列化得到的结果
data := []byte(db.Rdb.Get(db.Cxt, key).Val())
detail := MovieDetail{}
_ = json.Unmarshal(data, &detail)
// 执行本地图片匹配
ReplaceDetailPic(&detail)
return detail
}
// GetBasicInfoBySearchInfos 通过searchInfo 获取影片的基本信息
func GetBasicInfoBySearchInfos(infos ...SearchInfo) []MovieBasicInfo {
var list []MovieBasicInfo
for _, s := range infos {
data := []byte(db.Rdb.Get(db.Cxt, fmt.Sprintf(config.MovieBasicInfoKey, s.Cid, s.Mid)).Val())
basic := MovieBasicInfo{}
_ = json.Unmarshal(data, &basic)
// 执行本地图片匹配
ReplaceBasicDetailPic(&basic)
list = append(list, basic)
}
return list
// ConvertBasicInfo 将Detail信息转化为basic信息
func ConvertBasicInfo(m MovieDetail) MovieBasicInfo {
return MovieBasicInfo{Id: m.Mid, Cid: m.Cid, Pid: m.Pid, Name: m.Name, SubTitle: m.SubTitle,
CName: m.CName, State: m.State, Picture: m.Picture, Actor: m.Actor, Director: m.Director, Blurb: m.Content,
Remarks: m.Remarks, Area: m.Area, Year: m.Year}
}
/*
@@ -274,18 +471,45 @@ func GetBasicInfoBySearchInfos(infos ...SearchInfo) []MovieBasicInfo {
3. 去除name首尾的标点符号
4. 将处理完成后的name转化为hash值作为存储时的key
*/
// GenerateHashKey 存储播放源信息时对影片名称进行处理, 提高各站点间同一影片的匹配度
// GenerateHashKey 存储播放源信息时对影片名称进行处理-生成id, 提高各站点间同一影片的匹配度
func GenerateHashKey[K string | ~int | int64](key K) string {
mName := fmt.Sprint(key)
//1. 去除name中的所有空格
mName = regexp.MustCompile(`\s`).ReplaceAllString(mName, "")
//2. 去除name中含有的别名.*
mName = regexp.MustCompile(`.*$`).ReplaceAllString(mName, "")
//2. 添加常用的名称标准化替换规则
rules := []string{
// 中文季数标签统一
"season", "s", "第", "s", "季", "", "期", "", "画", "",
// --- 3. 剧场版标准化 ---
"剧场版", "ovo", "映画", "ovo", "电影版", "ovo", "The Movie", "ovo", "Movie", "ovo", "(Movie)", "ovo", "〔映画〕", "ovo",
// 特殊数学符号 (用户常用来代替数字,如 ∬ 代表 2)
"", "1", "Ⅱ", "2", "Ⅲ",
"∫", "1", "∬", "2", "∮", "3", "Ⅳ", "4", "", "5", "Ⅵ", "6", "Ⅶ", "7", "Ⅷ", "8", "Ⅸ", "9", "", "10", // 用户可能用积分号代表季数
"一", "1", "二", "2", "三", "3", "四", "4", "五", "5", "六", "6", "七", "7", "八", "8", "九", "9",
// 移除或替换无意义的装饰符号,这些符号在搜索中通常不仅无用还会阻碍匹配
"★", "", "☆", "", "◆", "", "◇", "", "●", "", "○", "",
"【", "", "】", "", "〖", "", "〗", "", "", "", "", "",
"「", "", "」", "", "『", "", "』", "",
"|", "", "", "", // 竖线分隔符
"~", "", "", "", // 波浪号
"...", "", "……", "", // 省略号
"!", "", "", "", "?", "", "", "",
"(", "", ")", "", "", "", "", "",
"[", "", "]", "", "", "", "", "",
"{", "", "}", "", "", "", "", "",
"", "&", "", "+",
"-", "", "", "", "—", "", "", "", // 策略:通常移除所有标点,让 "A-B" 变成 "AB"
"_", "", "_", "",
".", "", "", "", "。", "",
",", "", "", "",
":", "", ":", "", ":", "",
";", "", "", "",
"'", "", "", "", "\"", "", "“", "", "”", "",
"`", "", "", "",
}
mName = strings.NewReplacer(rules...).Replace(mName)
//3. 去除name首尾的标点符号
mName = regexp.MustCompile(`^[[:punct:]]+|[[:punct:]]+$`).ReplaceAllString(mName, "")
// 部分站点包含 动画版, 特殊别名 等字符, 需进行删除
//mName = regexp.MustCompile(`动画版`).ReplaceAllString(mName, "")
mName = regexp.MustCompile(`季.*`).ReplaceAllString(mName, "季")
//4. 将处理完成后的name转化为hash值作为存储时的key
h := fnv.New32a()
_, err := h.Write([]byte(mName))
@@ -294,27 +518,3 @@ func GenerateHashKey[K string | ~int | int64](key K) string {
}
return fmt.Sprint(h.Sum32())
}
// ============================采集方案.v1 遗留==================================================
// SaveMoves 保存影片分页请求list
func SaveMoves(list []Movie) (err error) {
// 整合数据
for _, m := range list {
//score, _ := time.ParseInLocation(time.DateTime, m.Time, time.Local)
movie, _ := json.Marshal(m)
// 以Cid为目录为集合进行存储, 便于后续搜索, 以影片id为分值进行存储 例 MovieList:Cid%d
err = db.Rdb.ZAdd(db.Cxt, fmt.Sprintf(config.MovieListInfoKey, m.Cid), redis.Z{Score: float64(m.Id), Member: movie}).Err()
}
return err
}
// AllMovieInfoKey 获取redis中所有的影视列表信息key MovieList:Cid
func AllMovieInfoKey() []string {
return db.Rdb.Keys(db.Cxt, fmt.Sprint("MovieList:Cid*")).Val()
}
// GetMovieListByKey 获取指定分类的影片列表数据
func GetMovieListByKey(key string) []string {
return db.Rdb.ZRange(db.Cxt, key, 0, -1).Val()
}

View File

@@ -3,10 +3,7 @@ package system
import (
"encoding/json"
"fmt"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
"log"
"math"
"reflect"
"regexp"
"server/config"
@@ -15,6 +12,9 @@ import (
"strconv"
"strings"
"time"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
// SearchInfo 存储用于检索的信息
@@ -51,18 +51,6 @@ func (s *SearchInfo) TableName() string {
// ================================= Spider 数据处理(redis) =================================
// RdbSaveSearchInfo 批量保存检索信息到redis
func RdbSaveSearchInfo(list []SearchInfo) {
// 1.整合一下zset数据集
var members []redis.Z
for _, s := range list {
member, _ := json.Marshal(s)
members = append(members, redis.Z{Score: float64(s.Mid), Member: member})
}
// 2.批量保存到zset集合中
db.Rdb.ZAdd(db.Cxt, config.SearchInfoTemp, members...)
}
// FilmZero 删除所有库存数据
func FilmZero() {
// 删除redis中当前库存储的所有数据
@@ -95,6 +83,15 @@ func DelMtPlay(keys []string) {
db.Rdb.Del(db.Cxt, keys...)
}
// TunCateSearchTable 截断SearchInfo数据表
func TunCateSearchTable() {
var searchInfo SearchInfo
err := db.Mdb.Exec(fmt.Sprintf("TRUNCATE TABLE %s", searchInfo.TableName())).Error
if err != nil {
log.Println("TRUNCATE TABLE Error: ", err)
}
}
/*
SearchKeyword 设置search关键字集合(影片分类检索类型数据)
类型, 剧情 , 地区, 语言, 年份, 首字母, 排序
@@ -251,21 +248,13 @@ func AddSearchIndex() {
}
// BatchSave 批量保存影片search信息
func BatchSave(list []SearchInfo) {
tx := db.Mdb.Begin()
// 防止程序异常终止
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.CreateInBatches(list, len(list)).Error; err != nil {
// 插入失败则回滚事务, 重新进行插入
tx.Rollback()
func BatchSave(sl []SearchInfo) {
if err := db.Mdb.Model(&SearchInfo{}).Create(&sl).Error; err != nil {
log.Println("BatchSaveSearchInfo Failed: ", err)
return
}
// 保存成功后将相应tag数据缓存到redis中
BatchHandleSearchTag(list...)
tx.Commit()
BatchHandleSearchTag(sl...)
}
// BatchSaveOrUpdate 判断数据库中是否存在对应mid的数据, 如果存在则更新, 否则插入
@@ -330,12 +319,21 @@ func ExistSearchInfo(mid int64) bool {
return count > 0
}
// TunCateSearchTable 截断SearchInfo数据表
func TunCateSearchTable() {
var searchInfo SearchInfo
err := db.Mdb.Exec(fmt.Sprintf("TRUNCATE TABLE %s", searchInfo.TableName())).Error
if err != nil {
log.Println("TRUNCATE TABLE Error: ", err)
// 777777777
// BatchSaveSearchInfo 批量保存Search信息
func BatchSaveSearchInfo(ml []MovieDetail) {
var sl []SearchInfo
for _, m := range ml {
s := ConvertSearchInfo(m)
// 保存一份tag信息到redis
SaveSearchTag(s)
// 追加数据到转化后的切片中
sl = append(sl, s)
}
// 批量保存影片检索信息
if err := db.Mdb.Create(&sl).Error; err != nil {
log.Println("影片详情信息保存失败: ", err)
}
}
@@ -391,50 +389,6 @@ func SearchInfoToMdb(model int) {
// ================================= API 数据接口信息处理 =================================
// GetMovieListByPid 通过Pid 分类ID 获取对应影片的数据信息
func GetMovieListByPid(pid int64, page *Page) []MovieBasicInfo {
// 返回分页参数
var count int64
db.Mdb.Model(&SearchInfo{}).Where("pid", pid).Count(&count)
page.Total = int(count)
page.PageCount = int((page.Total + page.PageSize - 1) / page.PageSize)
// 进行具体的信息查询
var s []SearchInfo
if err := db.Mdb.Limit(page.PageSize).Offset((page.Current-1)*page.PageSize).Where("pid", pid).Order("update_stamp DESC").Find(&s).Error; err != nil {
log.Println(err)
return nil
}
// 通过影片ID去redis中获取id对应数据信息
var list []MovieBasicInfo
for _, v := range s {
// 通过key搜索指定的影片信息 , MovieDetail:Cid6:Id15441
list = append(list, GetBasicInfoByKey(fmt.Sprintf(config.MovieBasicInfoKey, v.Cid, v.Mid)))
}
return list
}
// GetMovieListByCid 通过Cid查找对应的影片分页数据, 不适合GetMovieListByPid 糅合
func GetMovieListByCid(cid int64, page *Page) []MovieBasicInfo {
// 返回分页参数
var count int64
db.Mdb.Model(&SearchInfo{}).Where("cid", cid).Count(&count)
page.Total = int(count)
page.PageCount = int((page.Total + page.PageSize - 1) / page.PageSize)
// 进行具体的信息查询
var s []SearchInfo
if err := db.Mdb.Limit(page.PageSize).Offset((page.Current-1)*page.PageSize).Where("cid", cid).Order("update_stamp DESC").Find(&s).Error; err != nil {
log.Println(err)
return nil
}
// 通过影片ID去redis中获取id对应数据信息
var list []MovieBasicInfo
for _, v := range s {
// 通过key搜索指定的影片信息 , MovieDetail:Cid6:Id15441
list = append(list, GetBasicInfoByKey(fmt.Sprintf(config.MovieBasicInfoKey, v.Cid, v.Mid)))
}
return list
}
// GetHotMovieByPid 获取Pid指定类别的热门影片
func GetHotMovieByPid(pid int64, page *Page) []SearchInfo {
// 返回分页参数
@@ -472,89 +426,17 @@ func GetHotMovieByCid(cid int64, page *Page) []SearchInfo {
}
// SearchFilmKeyword 通过关键字搜索库存中满足条件的影片名
func SearchFilmKeyword(keyword string, page *Page) []SearchInfo {
var searchList []SearchInfo
func SearchFilmKeyword(keyword string, page *Page) []int64 {
var ids []int64
// 1. 先统计搜索满足条件的数据量
var count int64
db.Mdb.Model(&SearchInfo{}).Where("name LIKE ?", fmt.Sprint(`%`, keyword, `%`)).Or("sub_title LIKE ?", fmt.Sprint(`%`, keyword, `%`)).Count(&count)
page.Total = int(count)
page.PageCount = int((page.Total + page.PageSize - 1) / page.PageSize)
// 2. 获取满足条件的数据
db.Mdb.Limit(page.PageSize).Offset((page.Current-1)*page.PageSize).
Where("name LIKE ?", fmt.Sprintf(`%%%s%%`, keyword)).Or("sub_title LIKE ?", fmt.Sprintf(`%%%s%%`, keyword)).Order("year DESC, update_stamp DESC").Find(&searchList)
return searchList
}
// GetRelateMovieBasicInfo GetRelateMovie 根据SearchInfo获取相关影片
func GetRelateMovieBasicInfo(search SearchInfo, page *Page) []MovieBasicInfo {
/*
根据当前影片信息匹配相关的影片
1. 分类Cid,
2. 如果影片名称含有第x季 则根据影片名进行模糊匹配
3. class_tag 剧情内容匹配, 切分后使用 or 进行匹配
4. area 地区
5. 语言 Language
*/
// sql 拼接查询条件
sql := ""
// 优先进行名称相似匹配
//search.Name = regexp.MustCompile("第.{1,3}季").ReplaceAllString(search.Name, "")
name := regexp.MustCompile(`(第.{1,3}季.*)|([0-9]{1,3})|(剧场版)|(\s\S*$)|(之.*)|([\p{P}\p{S}].*)`).ReplaceAllString(search.Name, "")
// 如果处理后的影片名称依旧没有改变 且具有一定长度 则截取部分内容作为搜索条件
if len(name) == len(search.Name) && len(name) > 10 {
// 中文字符需截取3的倍数,否则可能乱码
name = name[:int(math.Ceil(float64(len(name))/5)*3)]
}
sql = fmt.Sprintf(`select * from %s where (name LIKE "%%%s%%" or sub_title LIKE "%%%[2]s%%") AND cid=%d AND search.deleted_at IS NULL union`, search.TableName(), name, search.Cid)
// 执行后续匹配内容, 匹配结果过少,减少过滤条件
//sql = fmt.Sprintf(`%s select * from %s where cid=%d AND area="%s" AND language="%s" AND`, sql, search.TableName(), search.Cid, search.Area, search.Language)
// 添加其他相似匹配规则
sql = fmt.Sprintf(`%s (select * from %s where cid=%d AND `, sql, search.TableName(), search.Cid)
// 根据剧情标签查找相似影片, classTag 使用的分隔符为 , | /
// 首先去除 classTag 中包含的所有空格
search.ClassTag = strings.ReplaceAll(search.ClassTag, " ", "")
// 如果 classTag 中包含分割符则进行拆分匹配
if strings.Contains(search.ClassTag, ",") {
s := "("
for _, t := range strings.Split(search.ClassTag, ",") {
s = fmt.Sprintf(`%s class_tag like "%%%s%%" OR`, s, t)
}
sql = fmt.Sprintf("%s %s)", sql, strings.TrimSuffix(s, "OR"))
} else if strings.Contains(search.ClassTag, "/") {
s := "("
for _, t := range strings.Split(search.ClassTag, "/") {
s = fmt.Sprintf(`%s class_tag like "%%%s%%" OR`, s, t)
}
sql = fmt.Sprintf("%s %s)", sql, strings.TrimSuffix(s, "OR"))
} else {
sql = fmt.Sprintf(`%s class_tag like "%%%s%%"`, sql, search.ClassTag)
}
// 除名称外的相似影片使用随机排序
//sql = fmt.Sprintf("%s ORDER BY RAND() limit %d,%d)", sql, page.Current, page.PageSize)
sql = fmt.Sprintf("%s AND search.deleted_at IS NULL limit %d,%d)", sql, page.Current, page.PageSize)
// 条件拼接完成后加上limit参数
sql = fmt.Sprintf("(%s) limit %d,%d", sql, page.Current, page.PageSize)
// 执行sql
var list []SearchInfo
db.Mdb.Raw(sql).Scan(&list)
// 根据list 获取对应的BasicInfo
var basicList []MovieBasicInfo
for _, s := range list {
// 通过key获取对应的影片基本数据
basicList = append(basicList, GetBasicInfoByKey(fmt.Sprintf(config.MovieBasicInfoKey, s.Cid, s.Mid)))
}
return basicList
}
// GetMultiplePlay 通过影片名hash值匹配播放源
func GetMultiplePlay(siteId, key string) []MovieUrlInfo {
data := db.Rdb.HGet(db.Cxt, fmt.Sprintf(config.MultipleSiteDetail, siteId), key).Val()
var playList []MovieUrlInfo
_ = json.Unmarshal([]byte(data), &playList)
return playList
db.Mdb.Limit(page.PageSize).Offset((page.Current-1)*page.PageSize).Select("mid").
Where("name LIKE ?", fmt.Sprintf(`%%%s%%`, keyword)).Or("sub_title LIKE ?", fmt.Sprintf(`%%%s%%`, keyword)).Order("year DESC, update_stamp DESC").Find(&ids)
return ids
}
// GetSearchTag 通过影片分类 Pid 返回对应分类的tag信息
@@ -629,7 +511,7 @@ func HandleTagStr(title string, tags ...string) []map[string]string {
}
// GetSearchInfosByTags 查询满足searchTag条件的影片分页数据
func GetSearchInfosByTags(st SearchTagsVO, page *Page) []SearchInfo {
func GetSearchInfosByTags(st SearchTagsVO, page *Page) []int64 {
// 准备查询语句的条件
qw := db.Mdb.Model(&SearchInfo{})
// 通过searchTags的非空属性值, 拼接对应的查询条件
@@ -679,19 +561,19 @@ func GetSearchInfosByTags(st SearchTagsVO, page *Page) []SearchInfo {
// 返回分页参数
GetPage(qw, page)
// 查询具体的searchInfo 分页数据
var sl []SearchInfo
if err := qw.Limit(page.PageSize).Offset((page.Current - 1) * page.PageSize).Find(&sl).Error; err != nil {
var ids []int64
if err := qw.Select("mid").Limit(page.PageSize).Offset((page.Current - 1) * page.PageSize).Find(&ids).Error; err != nil {
log.Println(err)
return nil
}
return sl
return ids
}
// GetMovieListBySort 通过排序类型返回对应的影片基本信息
func GetMovieListBySort(t int, pid int64, page *Page) []MovieBasicInfo {
var sl []SearchInfo
qw := db.Mdb.Model(&SearchInfo{}).Where("pid", pid).Limit(page.PageSize).Offset((page.Current) - 10*page.PageSize)
var ids []int64
qw := db.Mdb.Model(&SearchInfo{}).Select("mid").Where("pid", pid).Limit(page.PageSize).Offset((page.Current) - 10*page.PageSize)
// 针对不同排序类型返回对应的分页数据
switch t {
case 0:
@@ -704,11 +586,11 @@ func GetMovieListBySort(t int, pid int64, page *Page) []MovieBasicInfo {
// 最近更新 (更新时间)
qw.Order("update_stamp DESC")
}
if err := qw.Find(&sl).Error; err != nil {
if err := qw.Find(&ids).Error; err != nil {
log.Println(err)
return nil
}
return GetBasicInfoBySearchInfos(sl...)
return GetBasicInfoByIds(ids)
}

View File

@@ -112,15 +112,46 @@ type UserInfoVo struct {
// PlayLinkVo 多站点播放链接数据列表
type PlayLinkVo struct {
Id string `json:"id"`
Name string `json:"name"`
LinkList []MovieUrlInfo `json:"linkList"`
Id string `json:"id"`
Name string `json:"name"`
LinkList []PlayItem `json:"linkList"`
}
// MovieDetailVo 影片详情数据, 播放源合并版
type MovieDetailVo struct {
MovieDetail
List []PlayLinkVo `json:"list"`
Id int64 `json:"id" gorm:"primaryKey"` //影片Id
Mid int64 `json:"mid"` //影片Id
Cid int64 `json:"cid"` //分类ID
Pid int64 `json:"pid"` //一级分类ID
Name string `json:"name"` //片名
Picture string `json:"picture"` //简介图片
SubTitle string `json:"subTitle"` //子标题
CName string `json:"cName"` //分类名称
EnName string `json:"enName"` //英文名
Initial string `json:"initial"` //首字母
ClassTag string `json:"classTag"` //分类标签
Actor string `json:"actor"` //主演
Director string `json:"director"` //导演
Writer string `json:"writer"` //作者
Blurb string `json:"blurb"` //简介, 残缺,不建议使用
Remarks string `json:"remarks"` // 更新情况
ReleaseDate string `json:"releaseDate"` //上映时间
Area string `json:"area"` // 地区
Language string `json:"language"` //语言
Year string `json:"year"` //年份
State string `json:"state"` //影片状态 正片|预告...
UpdateTime string `json:"updateTime"` //更新时间
AddTime int64 `json:"addTime"` //资源添加时间戳
DbId int64 `json:"dbId"` //豆瓣id
DbScore string `json:"dbScore"` // 豆瓣评分
Hits int64 `json:"hits"` //影片热度
Content string `json:"content"` //内容简介
PlayFrom FromList `json:"playFrom" gorm:"type:json"` // 播放来源
DownFrom string `json:"DownFrom"` //下载来源 例: http
List []PlayLinkVo `json:"list"` // 播放信息切片组
//PlaySeparator string `json:"playSeparator"` // 播放信息分隔符
//PlayList MoviePlayList `json:"playList" gorm:"type:json"` //播放地址url
DownloadList MoviePlayList `json:"downloadList" gorm:"type:json"` // 下载url地址
}
type RecordRequestVo struct {
@@ -132,3 +163,41 @@ type RecordRequestVo struct {
EndTime time.Time `json:"endTime"` // 结束时间
Paging *Page `json:"paging"` // 分页参数
}
// ----------------------------------- vo type convert --------------------------------------------------------------
// ConvertMovieDetailVo 整合详情信息
func ConvertMovieDetailVo(d MovieDetail, l []PlayLinkVo) MovieDetailVo {
return MovieDetailVo{
Id: d.Id,
Mid: d.Mid,
Cid: d.Cid,
Pid: d.Pid,
Name: d.Name,
Picture: d.Picture,
SubTitle: d.SubTitle,
CName: d.CName,
EnName: d.EnName,
Initial: d.Initial,
ClassTag: d.ClassTag,
Actor: d.Actor,
Director: d.Director,
Writer: d.Writer,
Blurb: "", // blurb 和 content 内容重复度过高, 且内存占用过高, 所以舍弃简介字段
Remarks: d.Remarks,
ReleaseDate: d.ReleaseDate,
Area: d.Area,
Language: d.Language,
Year: d.Year,
State: d.State,
UpdateTime: d.UpdateTime,
AddTime: d.AddTime,
DbId: d.DbId,
DbScore: d.DbScore,
Hits: d.Hits,
Content: d.Content,
PlayFrom: d.PlayFrom,
DownFrom: d.DownFrom,
List: l,
}
}

View File

@@ -10,6 +10,10 @@ func TableInIt() {
system.InitAdminAccount()
// 创建 Search Table
system.CreateSearchTable()
// 创建 MovieDetails Table
system.CreateMovieDetailTable()
// 创建 SlaveMovieInfo Table
system.CreateSlaveMovieInfoTable()
// 创建图片信息管理表
system.CreateFileTable()
// 创建采集失效记录表

View File

@@ -30,12 +30,6 @@ func FilmSourceInit() {
{Id: util.GenerateSalt(), Name: "HD(SN)", Uri: `https://suoniapi.com/api.php/provide/vod/from/snm3u8/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: false, Interval: 2000},
{Id: util.GenerateSalt(), Name: "HD(DB)", Uri: `https://caiji.dbzy.tv/api.php/provide/vod/from/dbm3u8/at/josn/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: false},
{Id: util.GenerateSalt(), Name: "HD(IK)", Uri: `https://ikunzyapi.com/api.php/provide/vod/at/json`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: false},
//{Id: util.GenerateSalt(), Name: "WX(T2)", Uri: `https://api.wuxianzy.net/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: false},
//{Id: util.GenerateSalt(), Name: "OK(BK)", Uri: `https://api.okzy.org/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: false},
//{Id: util.GenerateSalt(), Name: "HD(HW)", Uri: `https://cjhwba.com/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: false, Interval: 3000},
//{Id: util.GenerateSalt(), Name: "HD(lzBk)", Uri: `https://cj.lzcaiji.com/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: false},
//{Id: util.GenerateSalt(), Name: "HD(fs)", Uri: `https://www.feisuzyapi.com/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: false},
//{Id: util.GenerateSalt(), Name: "HD(bfBk)", Uri: `http://app.bfzyapi.com/api.php/provide/vod/`, ResultModel: system.JsonResult, Grade: system.SlaveCollect, SyncPictures: false, CollectType: system.CollectVideo, State: false},
}
err := system.SaveCollectSourceList(l)
if err != nil {

View File

@@ -3,9 +3,11 @@ package conver
import (
"encoding/xml"
"log"
"regexp"
"server/config"
"server/model/collect"
"server/model/system"
"server/plugin/common/util"
"strings"
)
@@ -70,36 +72,42 @@ func ConvertFilmDetails(details []collect.FilmDetail) []system.MovieDetail {
// ConvertFilmDetail 将影片详情数据处理转化为 system.MovieDetail
func ConvertFilmDetail(detail collect.FilmDetail) system.MovieDetail {
/*
对需数据进行相应的简化处理
1.对常见分割符进行统一化处理
2.如果演员和导演名单过长,则进行截断, 最多只保留3个
*/
detail.VodActor = regexp.MustCompile(`[$&#%]`).ReplaceAllString(detail.VodActor, ",")
md := system.MovieDetail{
Id: detail.VodID,
Mid: detail.VodID,
Cid: detail.TypeID,
Pid: detail.TypeID1,
Name: detail.VodName,
Picture: detail.VodPic,
DownFrom: detail.VodDownFrom,
MovieDescriptor: system.MovieDescriptor{
SubTitle: detail.VodSub,
CName: detail.TypeName,
EnName: detail.VodEn,
Initial: detail.VodLetter,
ClassTag: detail.VodClass,
Actor: detail.VodActor,
Director: detail.VodDirector,
Writer: detail.VodWriter,
Blurb: detail.VodBlurb,
Remarks: detail.VodRemarks,
ReleaseDate: detail.VodPubDate,
Area: detail.VodArea,
Language: detail.VodLang,
Year: detail.VodYear,
State: detail.VodState,
UpdateTime: detail.VodTime,
AddTime: detail.VodTimeAdd,
DbId: detail.VodDouBanID,
DbScore: detail.VodDouBanScore,
Hits: detail.VodHits,
Content: detail.VodContent,
},
SubTitle: detail.VodSub,
CName: detail.TypeName,
EnName: detail.VodEn,
Initial: detail.VodLetter,
ClassTag: detail.VodClass,
Actor: util.TruncateBySep(detail.VodActor, 3),
Director: util.TruncateBySep(detail.VodDirector, 2),
Writer: util.TruncateBySep(detail.VodWriter, 2),
//Blurb: detail.VodBlurb,
Blurb: "", // blurb 和 content 内容重复度过高, 且内存占用过高, 所以舍弃简介字段
Remarks: detail.VodRemarks,
ReleaseDate: detail.VodPubDate,
Area: detail.VodArea,
Language: detail.VodLang,
Year: detail.VodYear,
State: detail.VodState,
UpdateTime: detail.VodTime,
AddTime: detail.VodTimeAdd,
DbId: detail.VodDouBanID,
DbScore: detail.VodDouBanScore,
Hits: detail.VodHits,
Content: detail.VodContent,
}
// 通过分割符切分播放源信息 PlaySeparator $$$
md.PlayFrom = strings.Split(detail.VodPlayFrom, detail.VodPlayNote)
@@ -111,8 +119,8 @@ func ConvertFilmDetail(detail collect.FilmDetail) system.MovieDetail {
}
// GenFilmPlayList 处理影片播放地址数据, 只保留m3u8与mp4格式的链接,生成playList
func GenFilmPlayList(playUrl, separator string) [][]system.MovieUrlInfo {
var res [][]system.MovieUrlInfo
func GenFilmPlayList(playUrl, separator string) system.MoviePlayList {
var res system.MoviePlayList
if separator != "" {
// 1. 通过分隔符切分播放源地址
for _, l := range strings.Split(playUrl, separator) {
@@ -120,7 +128,6 @@ func GenFilmPlayList(playUrl, separator string) [][]system.MovieUrlInfo {
if strings.Contains(l, ".m3u8") || strings.Contains(l, ".mp4") {
// 2. 将每组播放源对应的播放列表信息存储到列表中
res = append(res, ConvertPlayUrl(l))
}
}
} else {
@@ -134,8 +141,8 @@ func GenFilmPlayList(playUrl, separator string) [][]system.MovieUrlInfo {
}
// GenAllFilmPlayList 处理影片播放地址数据, 保留全部播放链接,生成playList
func GenAllFilmPlayList(playUrl, separator string) [][]system.MovieUrlInfo {
var res [][]system.MovieUrlInfo
func GenAllFilmPlayList(playUrl, separator string) system.MoviePlayList {
var res system.MoviePlayList
if separator != "" {
// 1. 通过分隔符切分播放源地址
for _, l := range strings.Split(playUrl, separator) {
@@ -150,18 +157,18 @@ func GenAllFilmPlayList(playUrl, separator string) [][]system.MovieUrlInfo {
}
// ConvertPlayUrl 将单个playFrom的播放地址字符串处理成列表形式
func ConvertPlayUrl(playUrl string) []system.MovieUrlInfo {
func ConvertPlayUrl(playUrl string) []system.PlayItem {
// 对每个片源的集数和播放地址进行分割 Episode$Link#Episode$Link
var l []system.MovieUrlInfo
var l []system.PlayItem
for _, p := range strings.Split(playUrl, "#") {
// 处理 Episode$Link 形式的播放信息
if strings.Contains(p, "$") {
l = append(l, system.MovieUrlInfo{
l = append(l, system.PlayItem{
Episode: strings.Split(p, "$")[0],
Link: strings.Split(p, "$")[1],
})
} else {
l = append(l, system.MovieUrlInfo{
l = append(l, system.PlayItem{
Episode: "(`・ω・´)",
Link: p,
})

View File

@@ -12,6 +12,7 @@ import (
"log"
"net/url"
"regexp"
"strings"
)
// GenerateUUID 生成UUID
@@ -101,6 +102,7 @@ func ValidURL(s string) bool {
return true
}
// ValidPwd 校验密码
func ValidPwd(s string) error {
if len(s) < 8 || len(s) > 12 {
return fmt.Errorf("密码长度不符合规范, 必须为8-10位")
@@ -124,3 +126,69 @@ func ValidPwd(s string) error {
}
return nil
}
// TruncateBySep 截断字符串,保留指定数量的结果
func TruncateBySep(s string, limit int) string {
// 如果保留数量小于等于0则返回空值
if limit <= 0 {
return ""
}
// 先强制对不同的分割符进行统一替换为 ,
s = regexp.MustCompile(`[$&#%]`).ReplaceAllString(s, ",")
// 使用 strings.Split 分割字符串
// Split 会在分隔符连续出现或出现在首尾时产生空字符串,这通常符合预期
parts := strings.Split(s, ",")
// 片段数量小于或等于限制,直接返回原字符串
// 返回原字符串是为了保留原始的格式(比如末尾是否有分隔符)
// 即使不截断也重新 Join 一遍(去除多余的空片段等)
return strings.Join(parts[:limit], ",")
}
// CleanFilmName 清洗影片名称,只保留主体
func CleanFilmName(name string) string {
if name == "" {
return ""
}
// 1. 去除常见的前缀 (方括号、圆括号内的内容) 匹配 [xxx], 【xxx】, (xxx), xxx
//rePrefix := regexp.MustCompile(`^\s*[\[【\(][^\]】\)]*[\]】\)]\s*`)
//for rePrefix.MatchString(name) {
// name = rePrefix.ReplaceAllString(name, "")
//}
// 2.定义需要清洗的特殊标识关键字集合
var noisePatterns = []string{
`第 [零一二三四五六七八九十\d]+ 季`, `第 [零一二三四五六七八九十\d]+ 话`, `第 [零一二三四五六七八九十\d]+ 集`,
`Season\s*\d+`, `S\d+`, `Ep\d+`, `\d{1,3}\s*(话 | 集)`,
`\s+(II|III|IV|V|VI|VII|VIII|IX|X)\s*$`,
`剧场版`, `电影版`, `OVA`, `OAD`, `SP`, `特别篇`, `总集篇`, `外传`, ``, ``, ``, `终章`,
`\d{3,4}[Pp]`, `HD`, `FHD`, `UHD`, `4K`, `BD`, `BluRay`, `BDRip`, `HEVC`, `H264`, `H265`,
`GB`, `MB`, `MP4`, `MKV`, `AVI`, `RMVB`,
`字幕组`, `动漫`, `动画`, `新版`, `重制版`, `连载`, `更新`, `全集`, `合集`,
`Uncensored`, `NoCen`, `Dubbed`, `Subbed`, `Raw`, `生肉`, `熟肉`,
}
// 3. 处理拼接完整的正则表达式
fullPattern := `(?i)(?:\s+|\.+|_+|-+) (` + strings.Join(noisePatterns, "|") + `).*$`
cutRegex := regexp.MustCompile(fullPattern)
// 去除满足匹配集的子串
name = cutRegex.ReplaceAllString(name, "")
// 特殊处理 "之" 字结构 (仅当 "之" 后紧跟噪音词时切除) 之\s*(噪音词)
reRegex := regexp.MustCompile(`(?i) 之\s* (` + strings.Join(noisePatterns, "|") + `).*$`)
name = reRegex.ReplaceAllString(name, "")
// 修剪 - 去除末尾残留的符号和空白
name = strings.TrimRight(name, " \t\n\r._-]):")
return name
}
// FormatSpecialChar 格式化特殊字符, 统一替换为逗号
func FormatSpecialChar(src string) string {
// 执行替换
return strings.Map(func(r rune) rune {
switch r {
case '#', '/', '$', '&', '%', '^', '*', '-':
return ','
default:
return r
}
}, src)
}

View File

@@ -1,11 +1,12 @@
package db
import (
"server/config"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"server/config"
)
var Mdb *gorm.DB
@@ -25,7 +26,8 @@ func InitMysql() (err error) {
SingularTable: true, //是否使用 结构体名称作为表名 (关闭自动变复数)
//NameReplacer: strings.NewReplacer("spider_", ""), // 替表名和字段中的 Me 为 空
},
Logger: logger.Default.LogMode(logger.Info), //设置日志级别为Info
Logger: logger.Default.LogMode(logger.Warn), //设置日志级别为Info
//Logger: logger.Default.LogMode(logger.Info), //设置日志级别为Info
})
return
}