feat(notification): add notification limit

This commit is contained in:
LinkinStars 2023-08-22 17:07:45 +08:00
parent 8a037f00db
commit e4a77367a4
15 changed files with 93 additions and 50 deletions

View File

@ -178,7 +178,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
collectionController := controller.NewCollectionController(collectionService)
answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo, notificationQueueService)
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, configService)
externalNotificationService := notification.NewExternalNotificationService(userNotificationConfigRepo, followRepo, emailService, userRepo, externalNotificationQueueService)
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)

4
go.mod
View File

@ -29,8 +29,8 @@ require (
github.com/ory/dockertest/v3 v3.9.1
github.com/robfig/cron/v3 v3.0.1
github.com/scottleedavis/go-exif-remove v0.0.0-20230314195146-7e059d593405
github.com/segmentfault/pacman v1.0.5-0.20230822075009-309985fb8700
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20230822075009-309985fb8700
github.com/segmentfault/pacman v1.0.5-0.20230822083413-c0075a2d401f
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20230822083413-c0075a2d401f
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20221018072427-a15dd1434e05
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20230516093754-b76aef1c1150
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20221018072427-a15dd1434e05

6
go.sum
View File

@ -89,6 +89,7 @@ github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZw
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@ -644,8 +645,12 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
github.com/segmentfault/pacman v1.0.5-0.20230822075009-309985fb8700 h1:VqxiuNGQg86GEKxnzmJegZR2Ufr7EVXo68mdKaf+/MQ=
github.com/segmentfault/pacman v1.0.5-0.20230822075009-309985fb8700/go.mod h1:5lNp5REd8QMThmBUvR3Fi9Y3AsOB4GRq7soCB4QLqOs=
github.com/segmentfault/pacman v1.0.5-0.20230822083413-c0075a2d401f h1:9f2Bjf6bdMvNyUop32wAGJCdp+Jdm/d6nKBYvFvkRo0=
github.com/segmentfault/pacman v1.0.5-0.20230822083413-c0075a2d401f/go.mod h1:5lNp5REd8QMThmBUvR3Fi9Y3AsOB4GRq7soCB4QLqOs=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20230822075009-309985fb8700 h1:VZpexPTcr7sOxxYUGa/9cneyCESfisAhCNbaEuTpcwI=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20230822075009-309985fb8700/go.mod h1:rmf1TCwz67dyM+AmTwSd1BxTo2AOYHj262lP93bOZbs=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20230822083413-c0075a2d401f h1:1KHe0uN6p798E7XJZPhZkgm/hXk5CTjisCvFMqaZSKI=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20230822083413-c0075a2d401f/go.mod h1:rmf1TCwz67dyM+AmTwSd1BxTo2AOYHj262lP93bOZbs=
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20221018072427-a15dd1434e05 h1:BlqTgc3/MYKG6vMI2MI+6o+7P4Gy5PXlawu185wPXAk=
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20221018072427-a15dd1434e05/go.mod h1:prPjFam7MyZ5b3S9dcDOt2tMPz6kf7C9c243s9zSwPY=
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20230516093754-b76aef1c1150 h1:OEuW1D7RGDE0CZDr0oGMw9Eiq7fAbD9C4WMrvSixamk=
@ -774,6 +779,7 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=

View File

@ -3,20 +3,23 @@ package constant
import "time"
const (
UserStatusChangedCacheKey = "answer:user:status:"
UserStatusChangedCacheTime = 7 * 24 * time.Hour
UserTokenCacheKey = "answer:user:token:"
UserTokenCacheTime = 7 * 24 * time.Hour
AdminTokenCacheKey = "answer:admin:token:"
AdminTokenCacheTime = 7 * 24 * time.Hour
UserTokenMappingCacheKey = "answer:user-token:mapping:"
SiteInfoCacheKey = "answer:site-info:"
SiteInfoCacheTime = 1 * time.Hour
ConfigID2KEYCacheKeyPrefix = "answer:config:id:"
ConfigKEY2ContentCacheKeyPrefix = "answer:config:key:"
ConnectorUserExternalInfoCacheKey = "answer:connector:"
ConnectorUserExternalInfoCacheTime = 10 * time.Minute
SiteMapQuestionCacheKeyPrefix = "answer:sitemap:question:%d"
SiteMapQuestionCacheTime = time.Hour
SitemapMaxSize = 50000
UserStatusChangedCacheKey = "answer:user:status:"
UserStatusChangedCacheTime = 7 * 24 * time.Hour
UserTokenCacheKey = "answer:user:token:"
UserTokenCacheTime = 7 * 24 * time.Hour
AdminTokenCacheKey = "answer:admin:token:"
AdminTokenCacheTime = 7 * 24 * time.Hour
UserTokenMappingCacheKey = "answer:user-token:mapping:"
SiteInfoCacheKey = "answer:site-info:"
SiteInfoCacheTime = 1 * time.Hour
ConfigID2KEYCacheKeyPrefix = "answer:config:id:"
ConfigKEY2ContentCacheKeyPrefix = "answer:config:key:"
ConnectorUserExternalInfoCacheKey = "answer:connector:"
ConnectorUserExternalInfoCacheTime = 10 * time.Minute
SiteMapQuestionCacheKeyPrefix = "answer:sitemap:question:%d"
SiteMapQuestionCacheTime = time.Hour
SitemapMaxSize = 50000
NewQuestionNotificationLimitCacheKeyPrefix = "answer:new-question-notification-limit:"
NewQuestionNotificationLimitCacheTime = 7 * 24 * time.Hour
NewQuestionNotificationLimitMax = 50
)

View File

@ -71,7 +71,7 @@ var migrations = []Migration{
NewMigration("v1.1.0-beta.2", "update question post time", updateQuestionPostTime, true),
NewMigration("v1.1.0", "add gravatar base url", updateCount, true),
NewMigration("v1.1.1", "update the length of revision content", updateTheLengthOfRevisionContent, false),
NewMigration("v1.1.2", "add notification config", addNoticeConfig, false),
NewMigration("v1.1.2", "add notification config", addNoticeConfig, true),
}
func GetMigrations() []Migration {

View File

@ -31,10 +31,10 @@ func (ar *authRepo) GetUserCacheInfo(ctx context.Context, accessToken string) (u
if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
userInfo = &entity.UserCacheInfo{}
if !exist {
return nil, nil
}
userInfo = &entity.UserCacheInfo{}
_ = json.Unmarshal([]byte(userInfoCache), userInfo)
return userInfo, nil
}

View File

@ -50,10 +50,11 @@ func (cr *captchaRepo) GetActionType(ctx context.Context, unit, actionType strin
if err != nil {
return nil, err
}
actionInfo = &entity.ActionRecordInfo{}
if exist {
_ = json.Unmarshal([]byte(res), actionInfo)
if !exist {
return nil, nil
}
actionInfo = &entity.ActionRecordInfo{}
_ = json.Unmarshal([]byte(res), actionInfo)
return actionInfo, nil
}

View File

@ -53,6 +53,7 @@ func (sr *siteInfoRepo) GetByType(ctx context.Context, siteType string) (siteInf
exist, err = sr.data.DB.Context(ctx).Where(builder.Eq{"type": siteType}).Get(siteInfo)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return nil, false, err
}
if exist {
sr.setCache(ctx, siteType, siteInfo)
@ -65,10 +66,11 @@ func (sr *siteInfoRepo) getCache(ctx context.Context, siteType string) (siteInfo
if err != nil {
return nil
}
siteInfo = &entity.SiteInfo{}
if exist {
_ = json.Unmarshal([]byte(siteInfoCache), siteInfo)
if !exist {
return nil
}
siteInfo = &entity.SiteInfo{}
_ = json.Unmarshal([]byte(siteInfoCache), siteInfo)
return siteInfo
}

View File

@ -89,9 +89,10 @@ func (ur *userExternalLoginRepo) GetCacheUserExternalLoginInfo(
if err != nil {
return info, err
}
info = &schema.ExternalLoginUserInfoCache{}
if exist {
_ = json.Unmarshal([]byte(res), &info)
if !exist {
return nil, nil
}
info = &schema.ExternalLoginUserInfoCache{}
_ = json.Unmarshal([]byte(res), &info)
return info, nil
}

View File

@ -111,17 +111,20 @@ func (cs *CaptchaService) ActionRecordVerifyCaptcha(
}
func (cs *CaptchaService) ActionRecordAdd(ctx context.Context, actionType string, unit string) (int, error) {
var err error
info, cahceErr := cs.captchaRepo.GetActionType(ctx, unit, actionType)
if cahceErr != nil {
info, err := cs.captchaRepo.GetActionType(ctx, unit, actionType)
if err != nil {
log.Error(err)
return 0, err
}
info.Num++
err = cs.captchaRepo.SetActionType(ctx, unit, actionType, "", info.Num)
amount := 1
if info != nil {
amount = info.Num + 1
}
err = cs.captchaRepo.SetActionType(ctx, unit, actionType, "", amount)
if err != nil {
return 0, err
}
return info.Num, nil
return amount, nil
}
func (cs *CaptchaService) ActionRecordDel(ctx context.Context, actionType string, unit string) {

View File

@ -2,6 +2,7 @@ package action
import (
"context"
"github.com/segmentfault/pacman/log"
"time"
"github.com/answerdev/answer/internal/entity"
@ -13,8 +14,14 @@ import (
func (cs *CaptchaService) ValidationStrategy(ctx context.Context, unit, actionType string) bool {
info, err := cs.captchaRepo.GetActionType(ctx, unit, actionType)
if err != nil {
//No record, no processing
//
log.Error(err)
return false
}
if info == nil {
info = &entity.ActionRecordInfo{
LastTime: time.Now().Unix(),
Num: 1,
}
}
switch actionType {
case entity.CaptchaActionEmail:

View File

@ -70,8 +70,8 @@ type DashboardService interface {
}
func (ds *dashboardService) Statistical(ctx context.Context) (*schema.DashboardInfo, error) {
dashboardInfo, err := ds.getFromCache(ctx)
if err != nil {
dashboardInfo := ds.getFromCache(ctx)
if dashboardInfo == nil {
dashboardInfo = &schema.DashboardInfo{}
dashboardInfo.QuestionCount = ds.questionCount(ctx)
dashboardInfo.AnswerCount = ds.answerCount(ctx)
@ -95,19 +95,20 @@ func (ds *dashboardService) Statistical(ctx context.Context) (*schema.DashboardI
return dashboardInfo, nil
}
func (ds *dashboardService) getFromCache(ctx context.Context) (dashboardInfo *schema.DashboardInfo, err error) {
func (ds *dashboardService) getFromCache(ctx context.Context) (dashboardInfo *schema.DashboardInfo) {
infoStr, exist, err := ds.data.Cache.GetString(ctx, schema.DashboardCacheKey)
if err != nil {
return nil, err
log.Errorf("get dashboard statistical from cache failed: %s", err)
return nil
}
if !exist {
return nil
}
dashboardInfo = &schema.DashboardInfo{}
if !exist {
return dashboardInfo, nil
}
if err = json.Unmarshal([]byte(infoStr), dashboardInfo); err != nil {
return nil, err
return nil
}
return dashboardInfo, nil
return dashboardInfo
}
func (ds *dashboardService) setCache(ctx context.Context, info *schema.DashboardInfo) {

View File

@ -22,6 +22,7 @@ type ExternalNotificationService struct {
}
func NewExternalNotificationService(
data *data.Data,
userNotificationConfigRepo user_notification_config.UserNotificationConfigRepo,
followRepo activity_common.FollowRepo,
emailService *export.EmailService,
@ -29,6 +30,7 @@ func NewExternalNotificationService(
notificationQueueService notice_queue.ExternalNotificationQueueService,
) *ExternalNotificationService {
n := &ExternalNotificationService{
data: data,
userNotificationConfigRepo: userNotificationConfigRepo,
followRepo: followRepo,
emailService: emailService,

View File

@ -105,7 +105,24 @@ func (ns *ExternalNotificationService) getNewQuestionSubscribers(ctx context.Con
}
func (ns *ExternalNotificationService) checkSendNewQuestionNotificationEmailLimit(ctx context.Context, userID string) bool {
// TODO: check if reach send limit
key := constant.NewQuestionNotificationLimitCacheKeyPrefix + userID
old, exist, err := ns.data.Cache.GetInt64(ctx, key)
if err != nil {
log.Error(err)
return false
}
if exist && old >= constant.NewQuestionNotificationLimitMax {
log.Debugf("%s user reach new question notification limit", userID)
return true
}
if !exist {
err = ns.data.Cache.SetInt64(ctx, key, 1, constant.NewQuestionNotificationLimitCacheTime)
} else {
_, err = ns.data.Cache.Increase(ctx, key, 1)
}
if err != nil {
log.Error(err)
}
return false
}

View File

@ -252,7 +252,7 @@ func (us *UserExternalLoginService) ExternalLoginBindingUserSendEmail(
}
resp = &schema.ExternalLoginBindingUserSendEmailResp{}
externalLoginInfo, err := us.userExternalLoginRepo.GetCacheUserExternalLoginInfo(ctx, req.BindingKey)
if err != nil || len(externalLoginInfo.ExternalID) == 0 {
if err != nil || externalLoginInfo == nil {
return nil, errors.BadRequest(reason.UserNotFound)
}
if len(externalLoginInfo.Email) > 0 {
@ -308,7 +308,7 @@ func (us *UserExternalLoginService) ExternalLoginBindingUserSendEmail(
func (us *UserExternalLoginService) ExternalLoginBindingUser(
ctx context.Context, bindingKey string, oldUserInfo *entity.User) (err error) {
externalLoginInfo, err := us.userExternalLoginRepo.GetCacheUserExternalLoginInfo(ctx, bindingKey)
if err != nil || len(externalLoginInfo.ExternalID) == 0 {
if err != nil || externalLoginInfo == nil {
return errors.BadRequest(reason.UserNotFound)
}
return us.bindOldUser(ctx, externalLoginInfo, oldUserInfo)