From 4cea7e95f64f7f0b10e56003e0bdb78593c7e54e Mon Sep 17 00:00:00 2001 From: mubai <1609539827@qq.com> Date: Fri, 6 Mar 2026 23:10:29 +0800 Subject: [PATCH] Storage optimization --- client/package.json | 4 +- server/config/DataConfig.go | 6 +- server/controller/IndexController.go | 14 +- server/go.mod | 84 ++-- server/go.sum | 338 +++++++------ server/logic/IndexLogic.go | 95 ++-- server/main.go | 3 +- server/model/system/FileUpload.go | 11 +- server/model/system/Movies.go | 606 +++++++++++++++-------- server/model/system/Search.go | 208 ++------ server/model/system/VirtualObject.go | 79 ++- server/plugin/SystemInit/DatabaseInIt.go | 4 + server/plugin/SystemInit/SpiderInit.go | 6 - server/plugin/common/conver/Collect.go | 73 +-- server/plugin/common/util/StringUtil.go | 68 +++ server/plugin/db/mysql.go | 6 +- 16 files changed, 920 insertions(+), 685 deletions(-) diff --git a/client/package.json b/client/package.json index 33d818e..6da7941 100644 --- a/client/package.json +++ b/client/package.json @@ -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" } diff --git a/server/config/DataConfig.go b/server/config/DataConfig.go index 22b548f..8eb18be 100644 --- a/server/config/DataConfig.go +++ b/server/config/DataConfig.go @@ -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 diff --git a/server/controller/IndexController.go b/server/controller/IndexController.go index e6bd1c5..288342c 100644 --- a/server/controller/IndexController.go +++ b/server/controller/IndexController.go @@ -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, diff --git a/server/go.mod b/server/go.mod index 8670738..0ff3fa9 100644 --- a/server/go.mod +++ b/server/go.mod @@ -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 ) diff --git a/server/go.sum b/server/go.sum index 8a0e7a9..64ebdbb 100644 --- a/server/go.sum +++ b/server/go.sum @@ -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= diff --git a/server/logic/IndexLogic.go b/server/logic/IndexLogic.go index 3e70736..24f61c6 100644 --- a/server/logic/IndexLogic.go +++ b/server/logic/IndexLogic.go @@ -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返回当前所属分类下的首页展示数据 diff --git a/server/main.go b/server/main.go index 149e450..259aa64 100644 --- a/server/main.go +++ b/server/main.go @@ -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 { diff --git a/server/model/system/FileUpload.go b/server/model/system/FileUpload.go index 63cdcde..d0f8c32 100644 --- a/server/model/system/FileUpload.go +++ b/server/model/system/FileUpload.go @@ -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) // 替换采集站的图片链接为本地链接 diff --git a/server/model/system/Movies.go b/server/model/system/Movies.go index 526274b..98299ae 100644 --- a/server/model/system/Movies.go +++ b/server/model/system/Movies.go @@ -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() -} diff --git a/server/model/system/Search.go b/server/model/system/Search.go index ceafde0..38f1a41 100644 --- a/server/model/system/Search.go +++ b/server/model/system/Search.go @@ -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) } diff --git a/server/model/system/VirtualObject.go b/server/model/system/VirtualObject.go index cc03eff..b32f858 100644 --- a/server/model/system/VirtualObject.go +++ b/server/model/system/VirtualObject.go @@ -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, + } +} diff --git a/server/plugin/SystemInit/DatabaseInIt.go b/server/plugin/SystemInit/DatabaseInIt.go index 01e9423..b5b530d 100644 --- a/server/plugin/SystemInit/DatabaseInIt.go +++ b/server/plugin/SystemInit/DatabaseInIt.go @@ -10,6 +10,10 @@ func TableInIt() { system.InitAdminAccount() // 创建 Search Table system.CreateSearchTable() + // 创建 MovieDetails Table + system.CreateMovieDetailTable() + // 创建 SlaveMovieInfo Table + system.CreateSlaveMovieInfoTable() // 创建图片信息管理表 system.CreateFileTable() // 创建采集失效记录表 diff --git a/server/plugin/SystemInit/SpiderInit.go b/server/plugin/SystemInit/SpiderInit.go index 824e436..54cb5a4 100644 --- a/server/plugin/SystemInit/SpiderInit.go +++ b/server/plugin/SystemInit/SpiderInit.go @@ -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 { diff --git a/server/plugin/common/conver/Collect.go b/server/plugin/common/conver/Collect.go index ba1ba29..38a55e1 100644 --- a/server/plugin/common/conver/Collect.go +++ b/server/plugin/common/conver/Collect.go @@ -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, }) diff --git a/server/plugin/common/util/StringUtil.go b/server/plugin/common/util/StringUtil.go index 93e53da..ec3f283 100644 --- a/server/plugin/common/util/StringUtil.go +++ b/server/plugin/common/util/StringUtil.go @@ -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) +} diff --git a/server/plugin/db/mysql.go b/server/plugin/db/mysql.go index e522c66..db787a8 100644 --- a/server/plugin/db/mysql.go +++ b/server/plugin/db/mysql.go @@ -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 }