Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feat/1.2.0/img' into test
Browse files Browse the repository at this point in the history
  • Loading branch information
LinkinStars committed Oct 16, 2023
2 parents 359cb32 + 5ca5120 commit 06d9658
Show file tree
Hide file tree
Showing 12 changed files with 137 additions and 26 deletions.
9 changes: 6 additions & 3 deletions cmd/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions i18n/en_US.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ backend:
other: Data server error.
forbidden_error:
other: Forbidden.
duplicate_request_error:
other: Duplicate submission.
action:
report:
other: Flag
Expand Down
2 changes: 2 additions & 0 deletions internal/base/constant/cache_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ const (
NewQuestionNotificationLimitCacheKeyPrefix = "answer:new-question-notification-limit:"
NewQuestionNotificationLimitCacheTime = 7 * 24 * time.Hour
NewQuestionNotificationLimitMax = 50
RateLimitCacheKeyPrefix = "answer:rate-limit:"
RateLimitCacheTime = 5 * time.Minute
)
1 change: 1 addition & 0 deletions internal/base/middleware/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ var ProviderSetMiddleware = wire.NewSet(
NewAuthUserMiddleware,
NewAvatarMiddleware,
NewShortIDMiddleware,
NewRateLimitMiddleware,
)
44 changes: 44 additions & 0 deletions internal/base/middleware/rate_limit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package middleware

import (
"encoding/json"
"fmt"
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/repo/limit"
"github.com/answerdev/answer/pkg/encryption"
"github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log"
)

type RateLimitMiddleware struct {
limitRepo *limit.LimitRepo
}

// NewRateLimitMiddleware new rate limit middleware
func NewRateLimitMiddleware(limitRepo *limit.LimitRepo) *RateLimitMiddleware {
return &RateLimitMiddleware{
limitRepo: limitRepo,
}
}

// DuplicateRequestRejection detects and rejects duplicate requests
// It only works for the requests that post content. Such as add question, add answer, comment etc.
func (rm *RateLimitMiddleware) DuplicateRequestRejection(ctx *gin.Context, req any) bool {
userID := GetLoginUserIDFromContext(ctx)
fullPath := ctx.FullPath()
reqJson, _ := json.Marshal(req)
key := encryption.MD5(fmt.Sprintf("%s:%s:%s", userID, fullPath, string(reqJson)))
reject, err := rm.limitRepo.CheckAndRecord(ctx, key)
if err != nil {
log.Errorf("check and record rate limit error: %s", err.Error())
return false
}
if !reject {
return false
}
log.Debugf("duplicate request: [%s] %s", fullPath, string(reqJson))
handler.HandleResponse(ctx, errors.BadRequest(reason.DuplicateRequestError), nil)
return true
}
2 changes: 2 additions & 0 deletions internal/base/reason/reason.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const (
DatabaseError = "base.database_error"
// ForbiddenError forbidden error
ForbiddenError = "base.forbidden_error"
// DuplicateRequestError duplicate request error
DuplicateRequestError = "base.duplicate_request_error"
)

const (
Expand Down
18 changes: 12 additions & 6 deletions internal/controller/answer_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,24 @@ import (

// AnswerController answer controller
type AnswerController struct {
answerService *service.AnswerService
rankService *rank.RankService
actionService *action.CaptchaService
answerService *service.AnswerService
rankService *rank.RankService
actionService *action.CaptchaService
rateLimitMiddleware *middleware.RateLimitMiddleware
}

// NewAnswerController new controller
func NewAnswerController(
answerService *service.AnswerService,
rankService *rank.RankService,
actionService *action.CaptchaService,
rateLimitMiddleware *middleware.RateLimitMiddleware,
) *AnswerController {
return &AnswerController{
answerService: answerService,
rankService: rankService,
actionService: actionService,
answerService: answerService,
rankService: rankService,
actionService: actionService,
rateLimitMiddleware: rateLimitMiddleware,
}
}

Expand Down Expand Up @@ -168,6 +171,9 @@ func (ac *AnswerController) Add(ctx *gin.Context) {
if handler.BindAndCheck(ctx, req) {
return
}
if ac.rateLimitMiddleware.DuplicateRequestRejection(ctx, req) {
return
}
req.QuestionID = uid.DeShortID(req.QuestionID)
req.UserID = middleware.GetLoginUserIDFromContext(ctx)

Expand Down
18 changes: 12 additions & 6 deletions internal/controller/comment_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,24 @@ import (

// CommentController comment controller
type CommentController struct {
commentService *comment.CommentService
rankService *rank.RankService
actionService *action.CaptchaService
commentService *comment.CommentService
rankService *rank.RankService
actionService *action.CaptchaService
rateLimitMiddleware *middleware.RateLimitMiddleware
}

// NewCommentController new controller
func NewCommentController(
commentService *comment.CommentService,
rankService *rank.RankService,
actionService *action.CaptchaService,
rateLimitMiddleware *middleware.RateLimitMiddleware,
) *CommentController {
return &CommentController{
commentService: commentService,
rankService: rankService,
actionService: actionService,
commentService: commentService,
rankService: rankService,
actionService: actionService,
rateLimitMiddleware: rateLimitMiddleware,
}
}

Expand All @@ -52,6 +55,9 @@ func (cc *CommentController) AddComment(ctx *gin.Context) {
if handler.BindAndCheck(ctx, req) {
return
}
if cc.rateLimitMiddleware.DuplicateRequestRejection(ctx, req) {
return
}
req.ObjectID = uid.DeShortID(req.ObjectID)
req.UserID = middleware.GetLoginUserIDFromContext(ctx)

Expand Down
26 changes: 16 additions & 10 deletions internal/controller/question_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ import (

// QuestionController question controller
type QuestionController struct {
questionService *service.QuestionService
answerService *service.AnswerService
rankService *rank.RankService
siteInfoService siteinfo_common.SiteInfoCommonService
actionService *action.CaptchaService
questionService *service.QuestionService
answerService *service.AnswerService
rankService *rank.RankService
siteInfoService siteinfo_common.SiteInfoCommonService
actionService *action.CaptchaService
rateLimitMiddleware *middleware.RateLimitMiddleware
}

// NewQuestionController new controller
Expand All @@ -36,13 +37,15 @@ func NewQuestionController(
rankService *rank.RankService,
siteInfoService siteinfo_common.SiteInfoCommonService,
actionService *action.CaptchaService,
rateLimitMiddleware *middleware.RateLimitMiddleware,
) *QuestionController {
return &QuestionController{
questionService: questionService,
answerService: answerService,
rankService: rankService,
siteInfoService: siteInfoService,
actionService: actionService,
questionService: questionService,
answerService: answerService,
rankService: rankService,
siteInfoService: siteInfoService,
actionService: actionService,
rateLimitMiddleware: rateLimitMiddleware,
}
}

Expand Down Expand Up @@ -332,6 +335,9 @@ func (qc *QuestionController) AddQuestion(ctx *gin.Context) {
if ctx.IsAborted() {
return
}
if qc.rateLimitMiddleware.DuplicateRequestRejection(ctx, req) {
return
}

req.UserID = middleware.GetLoginUserIDFromContext(ctx)
canList, requireRanks, err := qc.rankService.CheckOperationPermissionsForRanks(ctx, req.UserID, []string{
Expand Down
2 changes: 1 addition & 1 deletion internal/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ var migrations = []Migration{
NewMigration("v1.1.1", "update the length of revision content", updateTheLengthOfRevisionContent, false),
NewMigration("v1.1.2", "add notification config", addNoticeConfig, true),
NewMigration("v1.1.3", "set default user notification config", setDefaultUserNotificationConfig, false),
NewMigration("v1.2.0", "add recover answer permission", addRecoverPermission, false),
NewMigration("v1.2.0", "add recover answer permission", addRecoverPermission, true),
}

func GetMigrations() []Migration {
Expand Down
37 changes: 37 additions & 0 deletions internal/repo/limit/limit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package limit

import (
"context"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/data"
"github.com/answerdev/answer/internal/base/reason"
"github.com/segmentfault/pacman/errors"
)

// LimitRepo auth repository
type LimitRepo struct {
data *data.Data
}

// NewRateLimitRepo new repository
func NewRateLimitRepo(data *data.Data) *LimitRepo {
return &LimitRepo{
data: data,
}
}

// CheckAndRecord check
func (lr *LimitRepo) CheckAndRecord(ctx context.Context, key string) (limit bool, err error) {
_, exist, err := lr.data.Cache.GetString(ctx, constant.RateLimitCacheKeyPrefix+key)
if err != nil {
return false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
if exist {
return true, nil
}
err = lr.data.Cache.SetString(ctx, constant.RateLimitCacheKeyPrefix+key, "1", constant.RateLimitCacheTime)
if err != nil {
return false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return false, nil
}
2 changes: 2 additions & 0 deletions internal/repo/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/answerdev/answer/internal/repo/comment"
"github.com/answerdev/answer/internal/repo/config"
"github.com/answerdev/answer/internal/repo/export"
"github.com/answerdev/answer/internal/repo/limit"
"github.com/answerdev/answer/internal/repo/meta"
"github.com/answerdev/answer/internal/repo/notification"
"github.com/answerdev/answer/internal/repo/plugin_config"
Expand Down Expand Up @@ -75,4 +76,5 @@ var ProviderSetRepo = wire.NewSet(
user_external_login.NewUserExternalLoginRepo,
plugin_config.NewPluginConfigRepo,
user_notification_config.NewUserNotificationConfigRepo,
limit.NewRateLimitRepo,
)

0 comments on commit 06d9658

Please sign in to comment.