diff --git a/backend/config/config.go b/backend/config/config.go index ce2f724..df1f3d2 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -28,6 +28,13 @@ func SetDefault() { viper.SetDefault("oss.minio.secret_key", "minio123") viper.SetDefault("oss.minio.bucket", "campux") viper.SetDefault("oss.minio.use_ssl", false) + + // redis + viper.SetDefault("mq.redis.addr", "localhost:6379") + viper.SetDefault("mq.redis.password", "") + viper.SetDefault("mq.redis.db", 0) + viper.SetDefault("mq.redis.stream.publish_post", "campux_publish_post") + } // 创建配置文件对象 diff --git a/backend/controller/accapi.go b/backend/controller/accapi.go index c8bf86c..77d685b 100644 --- a/backend/controller/accapi.go +++ b/backend/controller/accapi.go @@ -31,6 +31,14 @@ func NewAccountRouter(rg *gin.RouterGroup, as service.AccountService) *AccountRo // 创建账户 func (ar *AccountRouter) CreateAccount(c *gin.Context) { + + _, err := ar.Auth(c, ServiceOnly) + + if err != nil { + ar.StatusCode(c, 401, err.Error()) + return + } + // 取body的json里的uin var body AccountCreateBody @@ -105,6 +113,14 @@ func (ar *AccountRouter) LoginAccount(c *gin.Context) { // 重置密码 func (ar *AccountRouter) ResetPassword(c *gin.Context) { + + _, err := ar.Auth(c, ServiceOnly) + + if err != nil { + ar.StatusCode(c, 401, err.Error()) + return + } + // 取body的json里的uin var body AccountCreateBody diff --git a/backend/controller/api.go b/backend/controller/api.go index 7197bdb..5c9adf5 100644 --- a/backend/controller/api.go +++ b/backend/controller/api.go @@ -1,6 +1,7 @@ package controller import ( + "errors" "net/http" "strings" "time" @@ -9,6 +10,7 @@ import ( "github.com/RockChinQ/Campux/backend/util" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" + "github.com/spf13/viper" ) type APIController struct { @@ -40,6 +42,8 @@ func NewApiController( ) } + // 鉴权中间件 + r.Use(func(c *gin.Context) { if strings.HasPrefix(c.Request.URL.Path, "/v1") { c.Next() @@ -68,6 +72,45 @@ func NewApiController( type APIRouter struct { } +type AuthenticationType int + +const ( + UserOnly AuthenticationType = 1 + ServiceOnly AuthenticationType = 2 + Both AuthenticationType = 3 +) + +// 鉴权 +// 如果是服务鉴权,则拿Authorization头对比service.token +// 其他的都是用户鉴权,直接尝试从GetUin取uin +func (ar *APIRouter) Auth(c *gin.Context, authType AuthenticationType) (int64, error) { + serviceToken := viper.GetString("service.token") + + uin, err := int64(-1), errors.New("authentication failed") + + if authType&ServiceOnly == ServiceOnly { + bearer := c.GetHeader("Authorization") + if bearer != "" { + bearer = bearer[7:] + + if bearer == serviceToken { + uin = 0 + err = nil + } + } + } + + if err == nil { + return uin, err + } + + if authType&UserOnly == UserOnly { + uin, err = ar.GetUin(c) + } + + return uin, err +} + // 从jwt取uin func (ar *APIRouter) GetUin(c *gin.Context) (int64, error) { diff --git a/backend/controller/postapi.go b/backend/controller/postapi.go index 6c14fb9..47d381f 100644 --- a/backend/controller/postapi.go +++ b/backend/controller/postapi.go @@ -98,7 +98,8 @@ func (pr *PostRouter) PostNew(c *gin.Context) { // 下载图片 func (pr *PostRouter) DownloadImage(c *gin.Context) { - _, err := pr.GetUin(c) + + _, err := pr.Auth(c, Both) if err != nil { pr.StatusCode(c, 401, err.Error()) @@ -162,6 +163,16 @@ func (pr *PostRouter) GetSelfPosts(c *gin.Context) { // 获取稿件列表 func (pr *PostRouter) GetPosts(c *gin.Context) { + + _, err := pr.Auth(c, Both) + + if err != nil { + pr.StatusCode(c, 401, err.Error()) + return + } + + // TODO 检查用户权限 + var body GetPostsBody if err := c.ShouldBindJSON(&body); err != nil { @@ -189,13 +200,15 @@ func (pr *PostRouter) GetPosts(c *gin.Context) { } func (pr *PostRouter) GetPostInfo(c *gin.Context) { - _, err := pr.GetUin(c) + _, err := pr.Auth(c, Both) if err != nil { pr.StatusCode(c, 401, err.Error()) return } + // TODO 检查用户权限 + id := c.Param("id") idInt, err := strconv.Atoi(id) @@ -256,6 +269,8 @@ func (pr *PostRouter) ReviewPost(c *gin.Context) { return } + // TODO 检查用户权限 + // 取body的json里的id, status, comment var body PostReviewBody diff --git a/backend/core/app.go b/backend/core/app.go index d489ce9..d2371fb 100644 --- a/backend/core/app.go +++ b/backend/core/app.go @@ -1,10 +1,15 @@ package core import ( + "time" + "github.com/RockChinQ/Campux/backend/controller" "github.com/RockChinQ/Campux/backend/database" + "github.com/RockChinQ/Campux/backend/mq" "github.com/RockChinQ/Campux/backend/oss" "github.com/RockChinQ/Campux/backend/service" + "github.com/RockChinQ/Campux/backend/service/routine" + gocron "github.com/go-co-op/gocron/v2" ) type Application struct { @@ -14,18 +19,51 @@ type Application struct { func NewApplication() *Application { db := database.NewMongoDBManager() - as := service.NewAccountService(*db) - fs := oss.NewMinioClient() - ps := service.NewPostService(*db, *fs) + msq := mq.NewRedisStreamMQ() + as := service.NewAccountService(*db) + ps := service.NewPostService(*db, *fs) ms := service.NewMiscService(*db) + err := ScheduleRoutines(*db, *msq) + if err != nil { + panic(err) + } + return &Application{ API: controller.NewApiController(*as, *ps, *ms), } } +func ScheduleRoutines( + db database.MongoDBManager, + msq mq.RedisStreamMQ, +) error { + s, err := gocron.NewScheduler() + if err != nil { + return err + } + + _, err = s.NewJob( + gocron.DurationJob( + 20*time.Second, + ), + gocron.NewTask( + routine.SchedulePublishing, + db, + msq, + ), + ) + if err != nil { + return err + } + + s.Start() + + return nil +} + func (a *Application) Run() { a.API.R.Run() } diff --git a/backend/mq/redis.go b/backend/mq/redis.go new file mode 100644 index 0000000..e5b6293 --- /dev/null +++ b/backend/mq/redis.go @@ -0,0 +1,35 @@ +package mq + +import ( + "context" + + "github.com/redis/go-redis/v9" + "github.com/spf13/viper" +) + +type RedisStreamMQ struct { + Client *redis.Client + PublishPostStream string +} + +func NewRedisStreamMQ() *RedisStreamMQ { + client := redis.NewClient(&redis.Options{ + Addr: viper.GetString("mq.redis.addr"), + Password: viper.GetString("mq.redis.password"), + DB: viper.GetInt("mq.redis.db"), + }) + return &RedisStreamMQ{ + Client: client, + PublishPostStream: viper.GetString("mq.redis.stream.publish_post"), + } +} + +func (r *RedisStreamMQ) PublishPost(postID int) error { + _, err := r.Client.XAdd(context.Background(), &redis.XAddArgs{ + Stream: r.PublishPostStream, + Values: map[string]interface{}{ + "post_id": postID, + }, + }).Result() + return err +} diff --git a/backend/service/routine/schedule_publishing.go b/backend/service/routine/schedule_publishing.go new file mode 100644 index 0000000..1e0e1e2 --- /dev/null +++ b/backend/service/routine/schedule_publishing.go @@ -0,0 +1,47 @@ +package routine + +import ( + "fmt" + + "github.com/RockChinQ/Campux/backend/database" + "github.com/RockChinQ/Campux/backend/mq" + "github.com/RockChinQ/Campux/backend/util" +) + +func SchedulePublishing(db database.MongoDBManager, msq mq.RedisStreamMQ) { + // 从数据库中查询出所有待发布的稿件 + // 遍历稿件, 发布到消息队列 + // 更新稿件状态 + approvedPosts, err := db.GetPosts(-1, database.POST_STATUS_APPROVED, 1, 1, 10) + if err != nil { + return + } + + for _, post := range approvedPosts { + err = msq.PublishPost(post.ID) + if err != nil { + fmt.Println(err) + continue + } + + // 加日志 + err = db.AddPostLog(&database.PostLogPO{ + PostID: post.ID, + Op: -1, + OldStat: database.POST_STATUS_APPROVED, + NewStat: database.POST_STATUS_IN_QUEUE, + Comment: "发布到消息队列", + CreatedAt: util.GetCSTTime(), + }) + if err != nil { + fmt.Println(err) + continue + } + + err = db.UpdatePostStatus(post.ID, database.POST_STATUS_IN_QUEUE) + if err != nil { + fmt.Println(err) + continue + } + } +} diff --git a/go.mod b/go.mod index 005920e..f961845 100644 --- a/go.mod +++ b/go.mod @@ -5,17 +5,21 @@ go 1.22.1 require ( github.com/gin-contrib/cors v1.7.1 github.com/gin-gonic/gin v1.9.1 + github.com/go-co-op/gocron/v2 v2.2.9 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/minio/minio-go/v7 v7.0.69 + github.com/redis/go-redis/v9 v9.5.1 github.com/spf13/viper v1.18.2 go.mongodb.org/mongo-driver v1.14.0 ) require ( github.com/bytedance/sonic v1.11.3 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect @@ -26,6 +30,7 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/golang/snappy v0.0.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jonboulle/clockwork v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.6 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect @@ -39,6 +44,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/pelletier/go-toml/v2 v2.2.0 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect github.com/rs/xid v1.5.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -57,7 +63,7 @@ require ( go.uber.org/multierr v1.9.0 // indirect golang.org/x/arch v0.7.0 // indirect golang.org/x/crypto v0.21.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 // indirect golang.org/x/net v0.22.0 // indirect golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.18.0 // indirect diff --git a/go.sum b/go.sum index ba747cf..98e4b9d 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,13 @@ +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/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +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/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -13,6 +19,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -27,6 +35,8 @@ 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.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-co-op/gocron/v2 v2.2.9 h1:aoKosYWSSdXFLecjFWX1i8+R6V7XdZb8sB2ZKAY5Yis= +github.com/go-co-op/gocron/v2 v2.2.9/go.mod h1:mZx3gMSlFnb97k3hRqX3+GdlG3+DUwTh6B8fnsTScXg= 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= @@ -48,6 +58,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= +github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= 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/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= @@ -87,6 +99,10 @@ github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= +github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= @@ -136,6 +152,8 @@ go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -145,8 +163,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE= +golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 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=