mirror of https://gitee.com/answerdev/answer.git
Merge remote-tracking branch 'origin/feat/1.2.0/img' into test
This commit is contained in:
commit
06d965822a
|
@ -25,6 +25,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"
|
||||
notification2 "github.com/answerdev/answer/internal/repo/notification"
|
||||
"github.com/answerdev/answer/internal/repo/plugin_config"
|
||||
|
@ -154,7 +155,9 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
|||
rolePowerRelRepo := role.NewRolePowerRelRepo(dataData)
|
||||
rolePowerRelService := role2.NewRolePowerRelService(rolePowerRelRepo, userRoleRelService)
|
||||
rankService := rank2.NewRankService(userCommon, userRankRepo, objService, userRoleRelService, rolePowerRelService, configService)
|
||||
commentController := controller.NewCommentController(commentService, rankService, captchaService)
|
||||
limitRepo := limit.NewRateLimitRepo(dataData)
|
||||
rateLimitMiddleware := middleware.NewRateLimitMiddleware(limitRepo)
|
||||
commentController := controller.NewCommentController(commentService, rankService, captchaService, rateLimitMiddleware)
|
||||
reportRepo := report.NewReportRepo(dataData, uniqueIDRepo)
|
||||
reportService := report2.NewReportService(reportRepo, objService)
|
||||
reportController := controller.NewReportController(reportService, rankService, captchaService)
|
||||
|
@ -181,8 +184,8 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
|||
externalNotificationService := notification.NewExternalNotificationService(dataData, userNotificationConfigRepo, followRepo, emailService, userRepo, externalNotificationQueueService)
|
||||
questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, userRepo, revisionService, metaService, collectionCommon, answerActivityService, emailService, notificationQueueService, externalNotificationQueueService, activityQueueService, siteInfoCommonService, externalNotificationService)
|
||||
answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService, notificationQueueService, externalNotificationQueueService, activityQueueService)
|
||||
questionController := controller.NewQuestionController(questionService, answerService, rankService, siteInfoCommonService, captchaService)
|
||||
answerController := controller.NewAnswerController(answerService, rankService, captchaService)
|
||||
questionController := controller.NewQuestionController(questionService, answerService, rankService, siteInfoCommonService, captchaService, rateLimitMiddleware)
|
||||
answerController := controller.NewAnswerController(answerService, rankService, captchaService, rateLimitMiddleware)
|
||||
searchParser := search_parser.NewSearchParser(tagCommonService, userCommon)
|
||||
searchRepo := search_common.NewSearchRepo(dataData, uniqueIDRepo, userCommon, tagCommonService)
|
||||
searchService := service.NewSearchService(searchParser, searchRepo)
|
||||
|
|
|
@ -14,6 +14,8 @@ backend:
|
|||
other: Data server error.
|
||||
forbidden_error:
|
||||
other: Forbidden.
|
||||
duplicate_request_error:
|
||||
other: Duplicate submission.
|
||||
action:
|
||||
report:
|
||||
other: Flag
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -9,4 +9,5 @@ var ProviderSetMiddleware = wire.NewSet(
|
|||
NewAuthUserMiddleware,
|
||||
NewAvatarMiddleware,
|
||||
NewShortIDMiddleware,
|
||||
NewRateLimitMiddleware,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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 (
|
||||
|
|
|
@ -21,9 +21,10 @@ 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
|
||||
|
@ -31,11 +32,13 @@ 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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
|
|
@ -19,9 +19,10 @@ 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
|
||||
|
@ -29,11 +30,13 @@ 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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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{
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
|
@ -75,4 +76,5 @@ var ProviderSetRepo = wire.NewSet(
|
|||
user_external_login.NewUserExternalLoginRepo,
|
||||
plugin_config.NewPluginConfigRepo,
|
||||
user_notification_config.NewUserNotificationConfigRepo,
|
||||
limit.NewRateLimitRepo,
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue