Merge branch 'feat/1.1.2/notification' into test

This commit is contained in:
LinkinStars 2023-08-22 17:40:49 +08:00
commit 119c7ddf6d
26 changed files with 177 additions and 102 deletions

View File

@ -1,6 +1,7 @@
package answercmd package answercmd
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"time" "time"
@ -39,7 +40,7 @@ var (
// @name Authorization // @name Authorization
func Main() { func Main() {
log.SetLogger(zap.NewLogger( log.SetLogger(zap.NewLogger(
log.ParseLevel(logLevel), zap.WithName("answer"), zap.WithPath(logPath), zap.WithCallerFullPath())) log.ParseLevel(logLevel), zap.WithName("answer"), zap.WithPath(logPath)))
Execute() Execute()
} }
@ -59,7 +60,7 @@ func runApp() {
fmt.Println("answer Version:", constant.Version, " Revision:", constant.Revision) fmt.Println("answer Version:", constant.Version, " Revision:", constant.Revision)
defer cleanup() defer cleanup()
if err := app.Run(); err != nil { if err := app.Run(context.Background()); err != nil {
panic(err) panic(err)
} }
} }

View File

@ -178,7 +178,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
collectionController := controller.NewCollectionController(collectionService) collectionController := controller.NewCollectionController(collectionService)
answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo, notificationQueueService) answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo, notificationQueueService)
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, configService) 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) 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) 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) questionController := controller.NewQuestionController(questionService, answerService, rankService, siteInfoCommonService, captchaService)

4
go.mod
View File

@ -28,8 +28,8 @@ require (
github.com/ory/dockertest/v3 v3.9.1 github.com/ory/dockertest/v3 v3.9.1
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/scottleedavis/go-exif-remove v0.0.0-20230314195146-7e059d593405 github.com/scottleedavis/go-exif-remove v0.0.0-20230314195146-7e059d593405
github.com/segmentfault/pacman v1.0.4 github.com/segmentfault/pacman v1.0.5-0.20230822083413-c0075a2d401f
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20221219081300-f734f4a16aa0 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/conf/viper v0.0.0-20221018072427-a15dd1434e05
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20230516093754-b76aef1c1150 github.com/segmentfault/pacman/contrib/i18n v0.0.0-20230516093754-b76aef1c1150
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20221018072427-a15dd1434e05 github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20221018072427-a15dd1434e05

14
go.sum
View File

@ -91,6 +91,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 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 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 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 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.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@ -640,10 +641,14 @@ github.com/scottleedavis/go-exif-remove v0.0.0-20230314195146-7e059d593405 h1:2i
github.com/scottleedavis/go-exif-remove v0.0.0-20230314195146-7e059d593405/go.mod h1:rIxVzVLKlBwLxO+lC+k/I4HJfRQcemg/f/76Xmmzsec= github.com/scottleedavis/go-exif-remove v0.0.0-20230314195146-7e059d593405/go.mod h1:rIxVzVLKlBwLxO+lC+k/I4HJfRQcemg/f/76Xmmzsec=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
github.com/segmentfault/pacman v1.0.4 h1:6UIXuMHUeYMWe5toflV9SXZQizRny1RczjZJLj9kul0= github.com/segmentfault/pacman v1.0.5-0.20230822075009-309985fb8700 h1:VqxiuNGQg86GEKxnzmJegZR2Ufr7EVXo68mdKaf+/MQ=
github.com/segmentfault/pacman v1.0.4/go.mod h1:5lNp5REd8QMThmBUvR3Fi9Y3AsOB4GRq7soCB4QLqOs= github.com/segmentfault/pacman v1.0.5-0.20230822075009-309985fb8700/go.mod h1:5lNp5REd8QMThmBUvR3Fi9Y3AsOB4GRq7soCB4QLqOs=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20221219081300-f734f4a16aa0 h1:4x0qG7H2M3qH7Yo2BhGrVlji1iTmRAWgINY/JyENeHs= github.com/segmentfault/pacman v1.0.5-0.20230822083413-c0075a2d401f h1:9f2Bjf6bdMvNyUop32wAGJCdp+Jdm/d6nKBYvFvkRo0=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20221219081300-f734f4a16aa0/go.mod h1:rmf1TCwz67dyM+AmTwSd1BxTo2AOYHj262lP93bOZbs= 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 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/conf/viper v0.0.0-20221018072427-a15dd1434e05/go.mod h1:prPjFam7MyZ5b3S9dcDOt2tMPz6kf7C9c243s9zSwPY=
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20230516093754-b76aef1c1150 h1:OEuW1D7RGDE0CZDr0oGMw9Eiq7fAbD9C4WMrvSixamk= github.com/segmentfault/pacman/contrib/i18n v0.0.0-20230516093754-b76aef1c1150 h1:OEuW1D7RGDE0CZDr0oGMw9Eiq7fAbD9C4WMrvSixamk=
@ -772,6 +777,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 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 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 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.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=

View File

@ -19,4 +19,7 @@ const (
SiteMapQuestionCacheKeyPrefix = "answer:sitemap:question:%d" SiteMapQuestionCacheKeyPrefix = "answer:sitemap:question:%d"
SiteMapQuestionCacheTime = time.Hour SiteMapQuestionCacheTime = time.Hour
SitemapMaxSize = 50000 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-beta.2", "update question post time", updateQuestionPostTime, true),
NewMigration("v1.1.0", "add gravatar base url", updateCount, 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.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 { func GetMigrations() []Migration {

View File

@ -27,15 +27,15 @@ func NewAuthRepo(data *data.Data) auth.AuthRepo {
// GetUserCacheInfo get user cache info // GetUserCacheInfo get user cache info
func (ar *authRepo) GetUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) { func (ar *authRepo) GetUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) {
userInfoCache, err := ar.data.Cache.GetString(ctx, constant.UserTokenCacheKey+accessToken) userInfoCache, exist, err := ar.data.Cache.GetString(ctx, constant.UserTokenCacheKey+accessToken)
if err != nil { if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
} }
if !exist {
return nil, nil
}
userInfo = &entity.UserCacheInfo{} userInfo = &entity.UserCacheInfo{}
err = json.Unmarshal([]byte(userInfoCache), userInfo) _ = json.Unmarshal([]byte(userInfoCache), userInfo)
if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return userInfo, nil return userInfo, nil
} }
@ -81,15 +81,15 @@ func (ar *authRepo) SetUserStatus(ctx context.Context, userID string, userInfo *
// GetUserStatus get user status // GetUserStatus get user status
func (ar *authRepo) GetUserStatus(ctx context.Context, userID string) (userInfo *entity.UserCacheInfo, err error) { func (ar *authRepo) GetUserStatus(ctx context.Context, userID string) (userInfo *entity.UserCacheInfo, err error) {
userInfoCache, err := ar.data.Cache.GetString(ctx, constant.UserStatusChangedCacheKey+userID) userInfoCache, exist, err := ar.data.Cache.GetString(ctx, constant.UserStatusChangedCacheKey+userID)
if err != nil { if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
} }
if !exist {
return nil, nil
}
userInfo = &entity.UserCacheInfo{} userInfo = &entity.UserCacheInfo{}
err = json.Unmarshal([]byte(userInfoCache), userInfo) _ = json.Unmarshal([]byte(userInfoCache), userInfo)
if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return userInfo, nil return userInfo, nil
} }
@ -104,16 +104,16 @@ func (ar *authRepo) RemoveUserStatus(ctx context.Context, userID string) (err er
// GetAdminUserCacheInfo get admin user cache info // GetAdminUserCacheInfo get admin user cache info
func (ar *authRepo) GetAdminUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) { func (ar *authRepo) GetAdminUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) {
userInfoCache, err := ar.data.Cache.GetString(ctx, constant.AdminTokenCacheKey+accessToken) userInfoCache, exist, err := ar.data.Cache.GetString(ctx, constant.AdminTokenCacheKey+accessToken)
if err != nil { if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return return
} }
userInfo = &entity.UserCacheInfo{} if !exist {
err = json.Unmarshal([]byte(userInfoCache), userInfo) return nil, nil
if err != nil {
return nil, err
} }
userInfo = &entity.UserCacheInfo{}
_ = json.Unmarshal([]byte(userInfoCache), userInfo)
return userInfo, nil return userInfo, nil
} }
@ -144,7 +144,10 @@ func (ar *authRepo) RemoveAdminUserCacheInfo(ctx context.Context, accessToken st
// AddUserTokenMapping add user token mapping // AddUserTokenMapping add user token mapping
func (ar *authRepo) AddUserTokenMapping(ctx context.Context, userID, accessToken string) (err error) { func (ar *authRepo) AddUserTokenMapping(ctx context.Context, userID, accessToken string) (err error) {
key := constant.UserTokenMappingCacheKey + userID key := constant.UserTokenMappingCacheKey + userID
resp, _ := ar.data.Cache.GetString(ctx, key) resp, _, err := ar.data.Cache.GetString(ctx, key)
if err != nil {
return err
}
mapping := make(map[string]bool, 0) mapping := make(map[string]bool, 0)
if len(resp) > 0 { if len(resp) > 0 {
_ = json.Unmarshal([]byte(resp), &mapping) _ = json.Unmarshal([]byte(resp), &mapping)
@ -157,7 +160,10 @@ func (ar *authRepo) AddUserTokenMapping(ctx context.Context, userID, accessToken
// RemoveUserTokens Log out all users under this user id // RemoveUserTokens Log out all users under this user id
func (ar *authRepo) RemoveUserTokens(ctx context.Context, userID string, remainToken string) { func (ar *authRepo) RemoveUserTokens(ctx context.Context, userID string, remainToken string) {
key := constant.UserTokenMappingCacheKey + userID key := constant.UserTokenMappingCacheKey + userID
resp, _ := ar.data.Cache.GetString(ctx, key) resp, _, err := ar.data.Cache.GetString(ctx, key)
if err != nil {
return
}
mapping := make(map[string]bool, 0) mapping := make(map[string]bool, 0)
if len(resp) > 0 { if len(resp) > 0 {
_ = json.Unmarshal([]byte(resp), &mapping) _ = json.Unmarshal([]byte(resp), &mapping)

View File

@ -43,19 +43,19 @@ func (cr *captchaRepo) SetActionType(ctx context.Context, unit, actionType, conf
return return
} }
func (cr *captchaRepo) GetActionType(ctx context.Context, unit, actionType string) (actioninfo *entity.ActionRecordInfo, err error) { func (cr *captchaRepo) GetActionType(ctx context.Context, unit, actionType string) (actionInfo *entity.ActionRecordInfo, err error) {
now := time.Now() now := time.Now()
cacheKey := fmt.Sprintf("ActionRecord:%s@%s@%s", unit, actionType, now.Format("2006-1-02")) cacheKey := fmt.Sprintf("ActionRecord:%s@%s@%s", unit, actionType, now.Format("2006-1-02"))
actioninfo = &entity.ActionRecordInfo{} res, exist, err := cr.data.Cache.GetString(ctx, cacheKey)
res, err := cr.data.Cache.GetString(ctx, cacheKey)
if err != nil { if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() return nil, err
} }
err = json.Unmarshal([]byte(res), actioninfo) if !exist {
if err != nil { return nil, nil
return actioninfo, nil
} }
return actioninfo, nil actionInfo = &entity.ActionRecordInfo{}
_ = json.Unmarshal([]byte(res), actionInfo)
return actionInfo, nil
} }
func (cr *captchaRepo) DelActionType(ctx context.Context, unit, actionType string) (err error) { func (cr *captchaRepo) DelActionType(ctx context.Context, unit, actionType string) (err error) {
@ -79,9 +79,12 @@ func (cr *captchaRepo) SetCaptcha(ctx context.Context, key, captcha string) (err
// GetCaptcha get captcha from cache // GetCaptcha get captcha from cache
func (cr *captchaRepo) GetCaptcha(ctx context.Context, key string) (captcha string, err error) { func (cr *captchaRepo) GetCaptcha(ctx context.Context, key string) (captcha string, err error) {
captcha, err = cr.data.Cache.GetString(ctx, key) captcha, exist, err := cr.data.Cache.GetString(ctx, key)
if err != nil { if err != nil {
log.Debug(err) return "", err
}
if !exist {
return "", fmt.Errorf("captcha not exist")
} }
return captcha, nil return captcha, nil
} }

View File

@ -28,7 +28,7 @@ func NewConfigRepo(data *data.Data) config.ConfigRepo {
func (cr configRepo) GetConfigByID(ctx context.Context, id int) (c *entity.Config, err error) { func (cr configRepo) GetConfigByID(ctx context.Context, id int) (c *entity.Config, err error) {
cacheKey := fmt.Sprintf("%s%d", constant.ConfigID2KEYCacheKeyPrefix, id) cacheKey := fmt.Sprintf("%s%d", constant.ConfigID2KEYCacheKeyPrefix, id)
if cacheData, err := cr.data.Cache.GetString(ctx, cacheKey); err == nil && len(cacheData) > 0 { if cacheData, exist, err := cr.data.Cache.GetString(ctx, cacheKey); err == nil && exist {
c = &entity.Config{} c = &entity.Config{}
c.BuildByJSON([]byte(cacheData)) c.BuildByJSON([]byte(cacheData))
if c.ID > 0 { if c.ID > 0 {
@ -54,7 +54,7 @@ func (cr configRepo) GetConfigByID(ctx context.Context, id int) (c *entity.Confi
func (cr configRepo) GetConfigByKey(ctx context.Context, key string) (c *entity.Config, err error) { func (cr configRepo) GetConfigByKey(ctx context.Context, key string) (c *entity.Config, err error) {
cacheKey := constant.ConfigKEY2ContentCacheKeyPrefix + key cacheKey := constant.ConfigKEY2ContentCacheKeyPrefix + key
if cacheData, err := cr.data.Cache.GetString(ctx, cacheKey); err == nil && len(cacheData) > 0 { if cacheData, exist, err := cr.data.Cache.GetString(ctx, cacheKey); err == nil && exist {
c = &entity.Config{} c = &entity.Config{}
c.BuildByJSON([]byte(cacheData)) c.BuildByJSON([]byte(cacheData))
if c.ID > 0 { if c.ID > 0 {

View File

@ -33,9 +33,12 @@ func (e *emailRepo) SetCode(ctx context.Context, code, content string, duration
// VerifyCode verify the code if out of date // VerifyCode verify the code if out of date
func (e *emailRepo) VerifyCode(ctx context.Context, code string) (content string, err error) { func (e *emailRepo) VerifyCode(ctx context.Context, code string) (content string, err error) {
content, err = e.data.Cache.GetString(ctx, code) content, exist, err := e.data.Cache.GetString(ctx, code)
if err != nil { if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() return "", err
} }
return if !exist {
return "", nil
}
return content, nil
} }

View File

@ -259,8 +259,8 @@ func (qr *questionRepo) SitemapQuestions(ctx context.Context, page, pageSize int
// try to get sitemap data from cache // try to get sitemap data from cache
cacheKey := fmt.Sprintf(constant.SiteMapQuestionCacheKeyPrefix, page) cacheKey := fmt.Sprintf(constant.SiteMapQuestionCacheKeyPrefix, page)
cacheData, err := qr.data.Cache.GetString(ctx, cacheKey) cacheData, exist, err := qr.data.Cache.GetString(ctx, cacheKey)
if err == nil && len(cacheKey) > 0 { if err == nil && exist {
_ = json.Unmarshal([]byte(cacheData), &questionIDList) _ = json.Unmarshal([]byte(cacheData), &questionIDList)
return questionIDList, nil return questionIDList, 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) exist, err = sr.data.DB.Context(ctx).Where(builder.Eq{"type": siteType}).Get(siteInfo)
if err != nil { if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return nil, false, err
} }
if exist { if exist {
sr.setCache(ctx, siteType, siteInfo) sr.setCache(ctx, siteType, siteInfo)
@ -61,11 +62,14 @@ func (sr *siteInfoRepo) GetByType(ctx context.Context, siteType string) (siteInf
} }
func (sr *siteInfoRepo) getCache(ctx context.Context, siteType string) (siteInfo *entity.SiteInfo) { func (sr *siteInfoRepo) getCache(ctx context.Context, siteType string) (siteInfo *entity.SiteInfo) {
siteInfo = &entity.SiteInfo{} siteInfoCache, exist, err := sr.data.Cache.GetString(ctx, constant.SiteInfoCacheKey+siteType)
siteInfoCache, err := sr.data.Cache.GetString(ctx, constant.SiteInfoCacheKey+siteType)
if err != nil { if err != nil {
return nil return nil
} }
if !exist {
return nil
}
siteInfo = &entity.SiteInfo{}
_ = json.Unmarshal([]byte(siteInfoCache), siteInfo) _ = json.Unmarshal([]byte(siteInfoCache), siteInfo)
return siteInfo return siteInfo
} }

View File

@ -85,10 +85,14 @@ func (ur *userExternalLoginRepo) SetCacheUserExternalLoginInfo(
// GetCacheUserExternalLoginInfo cache user info for external login // GetCacheUserExternalLoginInfo cache user info for external login
func (ur *userExternalLoginRepo) GetCacheUserExternalLoginInfo( func (ur *userExternalLoginRepo) GetCacheUserExternalLoginInfo(
ctx context.Context, key string) (info *schema.ExternalLoginUserInfoCache, err error) { ctx context.Context, key string) (info *schema.ExternalLoginUserInfoCache, err error) {
res, err := ur.data.Cache.GetString(ctx, constant.ConnectorUserExternalInfoCacheKey+key) res, exist, err := ur.data.Cache.GetString(ctx, constant.ConnectorUserExternalInfoCacheKey+key)
if err != nil { if err != nil {
return info, err return info, err
} }
if !exist {
return nil, nil
}
info = &schema.ExternalLoginUserInfoCache{}
_ = json.Unmarshal([]byte(res), &info) _ = json.Unmarshal([]byte(res), &info)
return info, nil return info, nil
} }

View File

@ -87,6 +87,7 @@ type NewCommentTemplateData struct {
} }
type NewQuestionTemplateRawData struct { type NewQuestionTemplateRawData struct {
QuestionAuthorUserID string
QuestionTitle string QuestionTitle string
QuestionID string QuestionID string
UnsubscribeCode string UnsubscribeCode string

View File

@ -16,10 +16,12 @@ type ExternalNotificationMsg struct {
NewQuestionTemplateRawData *NewQuestionTemplateRawData `json:"new_question_template_raw_data,omitempty"` NewQuestionTemplateRawData *NewQuestionTemplateRawData `json:"new_question_template_raw_data,omitempty"`
} }
func CreateNewQuestionNotificationMsg(questionID, questionTitle string, tags []*entity.Tag) *ExternalNotificationMsg { func CreateNewQuestionNotificationMsg(
questionID, questionTitle, questionAuthorUserID string, tags []*entity.Tag) *ExternalNotificationMsg {
questionID = uid.DeShortID(questionID) questionID = uid.DeShortID(questionID)
msg := &ExternalNotificationMsg{ msg := &ExternalNotificationMsg{
NewQuestionTemplateRawData: &NewQuestionTemplateRawData{ NewQuestionTemplateRawData: &NewQuestionTemplateRawData{
QuestionAuthorUserID: questionAuthorUserID,
QuestionID: questionID, QuestionID: questionID,
QuestionTitle: questionTitle, QuestionTitle: questionTitle,
}, },

View File

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

View File

@ -2,6 +2,7 @@ package action
import ( import (
"context" "context"
"github.com/segmentfault/pacman/log"
"time" "time"
"github.com/answerdev/answer/internal/entity" "github.com/answerdev/answer/internal/entity"
@ -13,8 +14,14 @@ import (
func (cs *CaptchaService) ValidationStrategy(ctx context.Context, unit, actionType string) bool { func (cs *CaptchaService) ValidationStrategy(ctx context.Context, unit, actionType string) bool {
info, err := cs.captchaRepo.GetActionType(ctx, unit, actionType) info, err := cs.captchaRepo.GetActionType(ctx, unit, actionType)
if err != nil { 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 { switch actionType {
case entity.CaptchaActionEmail: case entity.CaptchaActionEmail:

View File

@ -41,6 +41,9 @@ func (as *AuthService) GetUserCacheInfo(ctx context.Context, accessToken string)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if userCacheInfo == nil {
return nil, nil
}
cacheInfo, _ := as.authRepo.GetUserStatus(ctx, userCacheInfo.UserID) cacheInfo, _ := as.authRepo.GetUserStatus(ctx, userCacheInfo.UserID)
if cacheInfo != nil { if cacheInfo != nil {
userCacheInfo.UserStatus = cacheInfo.UserStatus userCacheInfo.UserStatus = cacheInfo.UserStatus

View File

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

View File

@ -145,7 +145,7 @@ func (es *EmailService) Send(ctx context.Context, toEmailAddr, subject, body str
func (es *EmailService) VerifyUrlExpired(ctx context.Context, code string) (content string) { func (es *EmailService) VerifyUrlExpired(ctx context.Context, code string) (content string) {
content, err := es.emailRepo.VerifyCode(ctx, code) content, err := es.emailRepo.VerifyCode(ctx, code)
if err != nil { if err != nil {
log.Warn(err) log.Error(err)
} }
return content return content
} }

View File

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

View File

@ -78,7 +78,6 @@ func (ns *ExternalNotificationService) getNewQuestionSubscribers(ctx context.Con
UserID: userNotificationConfig.UserID, UserID: userNotificationConfig.UserID,
Channels: schema.NewNotificationChannelsFormJson(userNotificationConfig.Channels), Channels: schema.NewNotificationChannelsFormJson(userNotificationConfig.Channels),
} }
subscribers = append(subscribers, subscribersMapping[userNotificationConfig.UserID])
} }
log.Debugf("get %d subscribers from tags", len(subscribersMapping)) log.Debugf("get %d subscribers from tags", len(subscribersMapping))
@ -98,14 +97,36 @@ func (ns *ExternalNotificationService) getNewQuestionSubscribers(ctx context.Con
UserID: notificationConfig.UserID, UserID: notificationConfig.UserID,
Channels: schema.NewNotificationChannelsFormJson(notificationConfig.Channels), Channels: schema.NewNotificationChannelsFormJson(notificationConfig.Channels),
} }
subscribers = append(subscribers, subscribersMapping[notificationConfig.UserID]) }
// 3. remove question owner
delete(subscribersMapping, msg.NewQuestionTemplateRawData.QuestionAuthorUserID)
for _, subscriber := range subscribersMapping {
subscribers = append(subscribers, subscriber)
} }
log.Debugf("get %d subscribers from all new question config", len(subscribers)) log.Debugf("get %d subscribers from all new question config", len(subscribers))
return subscribers, nil return subscribers, nil
} }
func (ns *ExternalNotificationService) checkSendNewQuestionNotificationEmailLimit(ctx context.Context, userID string) bool { 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 return false
} }

View File

@ -46,13 +46,13 @@ func (ns *NotificationService) GetRedDot(ctx context.Context, req *schema.GetRed
redBot := &schema.RedDot{} redBot := &schema.RedDot{}
inboxKey := fmt.Sprintf("answer_RedDot_%d_%s", schema.NotificationTypeInbox, req.UserID) inboxKey := fmt.Sprintf("answer_RedDot_%d_%s", schema.NotificationTypeInbox, req.UserID)
achievementKey := fmt.Sprintf("answer_RedDot_%d_%s", schema.NotificationTypeAchievement, req.UserID) achievementKey := fmt.Sprintf("answer_RedDot_%d_%s", schema.NotificationTypeAchievement, req.UserID)
inboxValue, err := ns.data.Cache.GetInt64(ctx, inboxKey) inboxValue, _, err := ns.data.Cache.GetInt64(ctx, inboxKey)
if err != nil { if err != nil {
redBot.Inbox = 0 redBot.Inbox = 0
} else { } else {
redBot.Inbox = inboxValue redBot.Inbox = inboxValue
} }
achievementValue, err := ns.data.Cache.GetInt64(ctx, achievementKey) achievementValue, _, err := ns.data.Cache.GetInt64(ctx, achievementKey)
if err != nil { if err != nil {
redBot.Achievement = 0 redBot.Achievement = 0
} else { } else {

View File

@ -333,7 +333,7 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
}) })
qs.externalNotificationQueueService.Send(ctx, qs.externalNotificationQueueService.Send(ctx,
schema.CreateNewQuestionNotificationMsg(question.ID, question.Title, tags)) schema.CreateNewQuestionNotificationMsg(question.ID, question.Title, question.UserID, tags))
questionInfo, err = qs.GetQuestion(ctx, question.ID, question.UserID, req.QuestionPermission) questionInfo, err = qs.GetQuestion(ctx, question.ID, question.UserID, req.QuestionPermission)
return return

View File

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

View File

@ -8,12 +8,14 @@ import (
type Cache interface { type Cache interface {
Base Base
GetString(ctx context.Context, key string) (string, error) GetString(ctx context.Context, key string) (data string, exist bool, err error)
SetString(ctx context.Context, key, value string, ttl time.Duration) error SetString(ctx context.Context, key, value string, ttl time.Duration) (err error)
GetInt64(ctx context.Context, key string) (int64, error) GetInt64(ctx context.Context, key string) (data int64, exist bool, err error)
SetInt64(ctx context.Context, key string, value int64, ttl time.Duration) error SetInt64(ctx context.Context, key string, value int64, ttl time.Duration) (err error)
Del(ctx context.Context, key string) error Increase(ctx context.Context, key string, value int64) (data int64, err error)
Flush(ctx context.Context) error Decrease(ctx context.Context, key string, value int64) (data int64, err error)
Del(ctx context.Context, key string) (err error)
Flush(ctx context.Context) (err error)
} }
var ( var (