From d8a9bec180ad59f49b1946104244732a1a503e2b Mon Sep 17 00:00:00 2001 From: zztrans <2438362589@qq.com> Date: Mon, 26 Aug 2024 00:06:29 +0800 Subject: [PATCH 1/8] clear redis --- cmd/clean/main.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/cmd/clean/main.go b/cmd/clean/main.go index 49a906c..6df4c40 100644 --- a/cmd/clean/main.go +++ b/cmd/clean/main.go @@ -4,13 +4,13 @@ import ( "context" "github.com/minio/minio-go/v7" - casbin_agent "github.com/oj-lab/oj-lab-platform/modules/agent/casbin" - judge_model "github.com/oj-lab/oj-lab-platform/models/judge" problem_model "github.com/oj-lab/oj-lab-platform/models/problem" user_model "github.com/oj-lab/oj-lab-platform/models/user" + casbin_agent "github.com/oj-lab/oj-lab-platform/modules/agent/casbin" gorm_agent "github.com/oj-lab/oj-lab-platform/modules/agent/gorm" minio_agent "github.com/oj-lab/oj-lab-platform/modules/agent/minio" + redis_agent "github.com/oj-lab/oj-lab-platform/modules/agent/redis" log_module "github.com/oj-lab/oj-lab-platform/modules/log" ) @@ -44,6 +44,16 @@ func removeMinioObjects() { log_module.AppLogger().Info("Remove Minio Objects success") } +func clearRedis() { + ctx := context.Background() + redis_agent := redis_agent.GetDefaultRedisClient() + err := redis_agent.FlushDB(ctx).Err() + if err != nil { + log_module.AppLogger().WithError(err).Error("Failed to clear redis") + } + + log_module.AppLogger().Info("Clear Redis success") +} func clearDB() { db := gorm_agent.GetDefaultDB() @@ -53,6 +63,8 @@ func clearDB() { &problem_model.ProblemTag{}, &judge_model.Judge{}, &judge_model.JudgeResult{}, + &judge_model.ScoreCache{}, + "problem_tags", "problem_problem_tags", "casbin_rule", ) @@ -61,12 +73,13 @@ func clearDB() { panic("failed to drop tables") } - log_module.AppLogger().Info("Clear db success") - + log_module.AppLogger().Info("Clear DB success") } + func main() { removeMinioObjects() clearCasbin() + clearRedis() clearDB() println("data clean success") From 89b56475a9374e8ab72ac84104ace9c8c9fc77c1 Mon Sep 17 00:00:00 2001 From: zztrans <2438362589@qq.com> Date: Mon, 26 Aug 2024 00:07:00 +0800 Subject: [PATCH 2/8] feat: judge score cache impl & rank cache model --- cmd/init/db.go | 1 + cmd/web_server/handler/judge_result.go | 6 +++ models/judge/judge_db.go | 14 ++++++ models/judge/judge_rank_cache.go | 15 +++++++ models/judge/judge_score_cache.go | 30 +++++++++++++ models/judge/judge_score_cache_db.go | 27 ++++++++++++ services/judge/judge_score_cache.go | 60 ++++++++++++++++++++++++++ 7 files changed, 153 insertions(+) create mode 100644 models/judge/judge_rank_cache.go create mode 100644 models/judge/judge_score_cache.go create mode 100644 models/judge/judge_score_cache_db.go create mode 100644 services/judge/judge_score_cache.go diff --git a/cmd/init/db.go b/cmd/init/db.go index 70769b7..50f2ef6 100644 --- a/cmd/init/db.go +++ b/cmd/init/db.go @@ -17,6 +17,7 @@ func initDB() { &problem_model.Problem{}, &judge_model.Judge{}, &judge_model.JudgeResult{}, + &judge_model.ScoreCache{}, ) if err != nil { panic("failed to migrate database") diff --git a/cmd/web_server/handler/judge_result.go b/cmd/web_server/handler/judge_result.go index 9223e3a..a2cc2ca 100644 --- a/cmd/web_server/handler/judge_result.go +++ b/cmd/web_server/handler/judge_result.go @@ -80,4 +80,10 @@ func postReportJudgeResult(ginCtx *gin.Context) { gin_utils.NewInternalError(ginCtx, err.Error()) return } + + _, err = judge_service.UpdateScoreCache(ginCtx, judgeUID, verdict) + if err != nil { + gin_utils.NewInternalError(ginCtx, err.Error()) + return + } } diff --git a/models/judge/judge_db.go b/models/judge/judge_db.go index c5db8bf..165b50d 100644 --- a/models/judge/judge_db.go +++ b/models/judge/judge_db.go @@ -21,6 +21,20 @@ func CreateJudge(tx *gorm.DB, judge Judge) (*Judge, error) { return &judge, tx.Create(&judge).Error } +// only count if when accept && accept time < SolvedTime +func GetBeforeSubmission(tx *gorm.DB, judge Judge) (int64, error) { + var count int64 + err := tx.Model(&Judge{}). + Where("create_at < ?", judge.CreateAt). + Where("status = ?", JudgeStatusFinished). + Count(&count).Error + + if err != nil { + return 0, err + } + return count + 1, err +} + func GetJudge(tx *gorm.DB, uid uuid.UUID) (*Judge, error) { judge := Judge{} err := tx.Model(&Judge{}). diff --git a/models/judge/judge_rank_cache.go b/models/judge/judge_rank_cache.go new file mode 100644 index 0000000..3863f38 --- /dev/null +++ b/models/judge/judge_rank_cache.go @@ -0,0 +1,15 @@ +package judge_model + +import ( + "github.com/oj-lab/oj-lab-platform/models" + user_model "github.com/oj-lab/oj-lab-platform/models/user" +) + +// user contest summary rank info +type RankCache struct { + models.MetaFields + UserAccount string `json:"userAccount" gorm:"primaryKey"` + User user_model.User `json:"user"` + Points uint `json:"points"` + TotalSubmissions uint `json:"totalSubmissions"` +} diff --git a/models/judge/judge_score_cache.go b/models/judge/judge_score_cache.go new file mode 100644 index 0000000..5b71e9c --- /dev/null +++ b/models/judge/judge_score_cache.go @@ -0,0 +1,30 @@ +package judge_model + +import ( + "time" + + "github.com/oj-lab/oj-lab-platform/models" + problem_model "github.com/oj-lab/oj-lab-platform/models/problem" + user_model "github.com/oj-lab/oj-lab-platform/models/user" +) + +// user contest problem summary score info +type ScoreCache struct { + models.MetaFields + UserAccount string `json:"userAccount" gorm:"primaryKey"` + User user_model.User `json:"user"` + ProblemSlug string `json:"problemSlug" gorm:"primaryKey"` + Problem problem_model.Problem `json:"problem"` + SubmissionCount int64 `json:"submissionCount" gorm:"default:1"` // judge create time < solvetime will be count + IsCorrect bool `json:"isCorrect" gorm:"default:false"` + SolveTime *time.Time `json:"SolveAt" gorm:"default:null"` // ac time < solveTime, update submissionCount +} + +func NewScoreCache(userAccount string, problemSlug string) ScoreCache { + return ScoreCache{ + UserAccount: userAccount, + ProblemSlug: problemSlug, + SubmissionCount: 1, + IsCorrect: false, + } +} diff --git a/models/judge/judge_score_cache_db.go b/models/judge/judge_score_cache_db.go new file mode 100644 index 0000000..fccb0d9 --- /dev/null +++ b/models/judge/judge_score_cache_db.go @@ -0,0 +1,27 @@ +package judge_model + +import ( + "github.com/oj-lab/oj-lab-platform/models" + "gorm.io/gorm" +) + +func CreateJudgeScoreCache(tx *gorm.DB, scoreCache ScoreCache) (*ScoreCache, error) { + scoreCache.MetaFields = models.NewMetaFields() + return &scoreCache, tx.Create(&scoreCache).Error +} + +func GetJudgeScoreCache(tx *gorm.DB, userAccount string, problemSlug string) (*ScoreCache, error) { + scoreCache := ScoreCache{} + err := tx.Model(&ScoreCache{}). + Where("user_account = ?", userAccount). + Where("problem_slug = ?", problemSlug). + First(&scoreCache).Error + if err != nil { + return nil, err + } + return &scoreCache, nil +} + +func UpdateJudgeScoreCache(tx *gorm.DB, scoreCache ScoreCache) error { + return tx.Model(&scoreCache).Updates(scoreCache).Error +} diff --git a/services/judge/judge_score_cache.go b/services/judge/judge_score_cache.go new file mode 100644 index 0000000..fd10dfe --- /dev/null +++ b/services/judge/judge_score_cache.go @@ -0,0 +1,60 @@ +package judge_service + +import ( + "context" + + "github.com/google/uuid" + judge_model "github.com/oj-lab/oj-lab-platform/models/judge" + gorm_agent "github.com/oj-lab/oj-lab-platform/modules/agent/gorm" +) + +func UpdateScoreCache(ctx context.Context, uid uuid.UUID, verdict judge_model.JudgeVerdict) (*judge_model.ScoreCache, error) { + db := gorm_agent.GetDefaultDB() + judge, err := judge_model.GetJudge(db, uid) + if err != nil { + return nil, err + } + // log_module.AppLogger().WithField("judge", judge).Debug("getjudge") + scoreCache, err := judge_model.GetJudgeScoreCache(db, judge.UserAccount, judge.ProblemSlug) + if err != nil { + // previous empty + // log_module.AppLogger().Debug("previous empty") + + scoreCache := judge_model.NewScoreCache(judge.UserAccount, judge.ProblemSlug) + if verdict == judge_model.JudgeVerdictAccepted { + scoreCache.IsCorrect = true + scoreCache.SolveTime = judge.CreateAt + } + newScoreCache, err := judge_model.CreateJudgeScoreCache(db, scoreCache) + if err != nil { + return nil, err + } + return newScoreCache, nil + } + + // log_module.AppLogger().WithField("scoreCache", scoreCache).Debug("get scoreCache") + + // previous no ac || current more early + // need to update + if !scoreCache.IsCorrect || judge.CreateAt.Before(*scoreCache.SolveTime) { + if verdict == judge_model.JudgeVerdictAccepted { + scoreCache.SubmissionCount, err = judge_model.GetBeforeSubmission(db, *judge) // rescan to count previous finished + if err != nil { + return nil, err + } + scoreCache.IsCorrect = true + scoreCache.SolveTime = judge.CreateAt + } else { + scoreCache.SubmissionCount += 1 + } + + // log_module.AppLogger().WithField("scoreCache", scoreCache).Debug("update scoreCache") + + err = judge_model.UpdateJudgeScoreCache(db, *scoreCache) + if err != nil { + return nil, err + } + } + // if no early, no need update, just a query + return scoreCache, nil +} From 39fb58c32992a37ea808620c1c80a0b0627e928c Mon Sep 17 00:00:00 2001 From: zztrans <2438362589@qq.com> Date: Wed, 28 Aug 2024 22:54:52 +0800 Subject: [PATCH 3/8] fix: Rename scorecache --- cmd/clean/main.go | 1 - models/judge/judge_score_cache.go | 12 ++++++------ models/judge/judge_score_cache_db.go | 10 +++++----- services/judge/judge_score_cache.go | 11 ++++++----- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/cmd/clean/main.go b/cmd/clean/main.go index 6df4c40..772bc0c 100644 --- a/cmd/clean/main.go +++ b/cmd/clean/main.go @@ -64,7 +64,6 @@ func clearDB() { &judge_model.Judge{}, &judge_model.JudgeResult{}, &judge_model.ScoreCache{}, - "problem_tags", "problem_problem_tags", "casbin_rule", ) diff --git a/models/judge/judge_score_cache.go b/models/judge/judge_score_cache.go index 5b71e9c..bf17f70 100644 --- a/models/judge/judge_score_cache.go +++ b/models/judge/judge_score_cache.go @@ -9,22 +9,22 @@ import ( ) // user contest problem summary score info -type ScoreCache struct { +type JudgeScoreCache struct { models.MetaFields UserAccount string `json:"userAccount" gorm:"primaryKey"` User user_model.User `json:"user"` ProblemSlug string `json:"problemSlug" gorm:"primaryKey"` Problem problem_model.Problem `json:"problem"` SubmissionCount int64 `json:"submissionCount" gorm:"default:1"` // judge create time < solvetime will be count - IsCorrect bool `json:"isCorrect" gorm:"default:false"` - SolveTime *time.Time `json:"SolveAt" gorm:"default:null"` // ac time < solveTime, update submissionCount + IsAccepted bool `json:"isAccepted" gorm:"default:false"` + SolveTime *time.Time `json:"solveAt" gorm:"default:null"` // ac time < solveTime, update submissionCount } -func NewScoreCache(userAccount string, problemSlug string) ScoreCache { - return ScoreCache{ +func NewJudgeScoreCache(userAccount string, problemSlug string) JudgeScoreCache { + return JudgeScoreCache{ UserAccount: userAccount, ProblemSlug: problemSlug, SubmissionCount: 1, - IsCorrect: false, + IsAccepted: false, } } diff --git a/models/judge/judge_score_cache_db.go b/models/judge/judge_score_cache_db.go index fccb0d9..acf0932 100644 --- a/models/judge/judge_score_cache_db.go +++ b/models/judge/judge_score_cache_db.go @@ -5,14 +5,14 @@ import ( "gorm.io/gorm" ) -func CreateJudgeScoreCache(tx *gorm.DB, scoreCache ScoreCache) (*ScoreCache, error) { +func CreateJudgeScoreCache(tx *gorm.DB, scoreCache JudgeScoreCache) (*JudgeScoreCache, error) { scoreCache.MetaFields = models.NewMetaFields() return &scoreCache, tx.Create(&scoreCache).Error } -func GetJudgeScoreCache(tx *gorm.DB, userAccount string, problemSlug string) (*ScoreCache, error) { - scoreCache := ScoreCache{} - err := tx.Model(&ScoreCache{}). +func GetJudgeScoreCache(tx *gorm.DB, userAccount string, problemSlug string) (*JudgeScoreCache, error) { + scoreCache := JudgeScoreCache{} + err := tx.Model(&JudgeScoreCache{}). Where("user_account = ?", userAccount). Where("problem_slug = ?", problemSlug). First(&scoreCache).Error @@ -22,6 +22,6 @@ func GetJudgeScoreCache(tx *gorm.DB, userAccount string, problemSlug string) (*S return &scoreCache, nil } -func UpdateJudgeScoreCache(tx *gorm.DB, scoreCache ScoreCache) error { +func UpdateJudgeScoreCache(tx *gorm.DB, scoreCache JudgeScoreCache) error { return tx.Model(&scoreCache).Updates(scoreCache).Error } diff --git a/services/judge/judge_score_cache.go b/services/judge/judge_score_cache.go index fd10dfe..95a019b 100644 --- a/services/judge/judge_score_cache.go +++ b/services/judge/judge_score_cache.go @@ -8,21 +8,22 @@ import ( gorm_agent "github.com/oj-lab/oj-lab-platform/modules/agent/gorm" ) -func UpdateScoreCache(ctx context.Context, uid uuid.UUID, verdict judge_model.JudgeVerdict) (*judge_model.ScoreCache, error) { +func UpsertJudgeScoreCache(ctx context.Context, uid uuid.UUID, verdict judge_model.JudgeVerdict) (*judge_model.JudgeScoreCache, error) { db := gorm_agent.GetDefaultDB() judge, err := judge_model.GetJudge(db, uid) if err != nil { return nil, err } // log_module.AppLogger().WithField("judge", judge).Debug("getjudge") + scoreCache, err := judge_model.GetJudgeScoreCache(db, judge.UserAccount, judge.ProblemSlug) if err != nil { // previous empty // log_module.AppLogger().Debug("previous empty") - scoreCache := judge_model.NewScoreCache(judge.UserAccount, judge.ProblemSlug) + scoreCache := judge_model.NewJudgeScoreCache(judge.UserAccount, judge.ProblemSlug) if verdict == judge_model.JudgeVerdictAccepted { - scoreCache.IsCorrect = true + scoreCache.IsAccepted = true scoreCache.SolveTime = judge.CreateAt } newScoreCache, err := judge_model.CreateJudgeScoreCache(db, scoreCache) @@ -36,13 +37,13 @@ func UpdateScoreCache(ctx context.Context, uid uuid.UUID, verdict judge_model.Ju // previous no ac || current more early // need to update - if !scoreCache.IsCorrect || judge.CreateAt.Before(*scoreCache.SolveTime) { + if !scoreCache.IsAccepted || judge.CreateAt.Before(*scoreCache.SolveTime) { if verdict == judge_model.JudgeVerdictAccepted { scoreCache.SubmissionCount, err = judge_model.GetBeforeSubmission(db, *judge) // rescan to count previous finished if err != nil { return nil, err } - scoreCache.IsCorrect = true + scoreCache.IsAccepted = true scoreCache.SolveTime = judge.CreateAt } else { scoreCache.SubmissionCount += 1 From b53b390e40fbe6e9108e4052b983483fa6b2667c Mon Sep 17 00:00:00 2001 From: zztrans <2438362589@qq.com> Date: Wed, 28 Aug 2024 23:12:26 +0800 Subject: [PATCH 4/8] fix: create Concurrency conflict --- cmd/clean/main.go | 2 +- cmd/init/db.go | 2 +- cmd/web_server/handler/judge_result.go | 2 +- services/judge/judge_score_cache.go | 32 +++++++++++++++----------- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/cmd/clean/main.go b/cmd/clean/main.go index 772bc0c..d6ea1f0 100644 --- a/cmd/clean/main.go +++ b/cmd/clean/main.go @@ -63,7 +63,7 @@ func clearDB() { &problem_model.ProblemTag{}, &judge_model.Judge{}, &judge_model.JudgeResult{}, - &judge_model.ScoreCache{}, + &judge_model.JudgeScoreCache{}, "problem_problem_tags", "casbin_rule", ) diff --git a/cmd/init/db.go b/cmd/init/db.go index 50f2ef6..b3a9a29 100644 --- a/cmd/init/db.go +++ b/cmd/init/db.go @@ -17,7 +17,7 @@ func initDB() { &problem_model.Problem{}, &judge_model.Judge{}, &judge_model.JudgeResult{}, - &judge_model.ScoreCache{}, + &judge_model.JudgeScoreCache{}, ) if err != nil { panic("failed to migrate database") diff --git a/cmd/web_server/handler/judge_result.go b/cmd/web_server/handler/judge_result.go index a2cc2ca..978078f 100644 --- a/cmd/web_server/handler/judge_result.go +++ b/cmd/web_server/handler/judge_result.go @@ -81,7 +81,7 @@ func postReportJudgeResult(ginCtx *gin.Context) { return } - _, err = judge_service.UpdateScoreCache(ginCtx, judgeUID, verdict) + _, err = judge_service.UpsertJudgeScoreCache(ginCtx, judgeUID, verdict) if err != nil { gin_utils.NewInternalError(ginCtx, err.Error()) return diff --git a/services/judge/judge_score_cache.go b/services/judge/judge_score_cache.go index 95a019b..1518026 100644 --- a/services/judge/judge_score_cache.go +++ b/services/judge/judge_score_cache.go @@ -15,22 +15,26 @@ func UpsertJudgeScoreCache(ctx context.Context, uid uuid.UUID, verdict judge_mod return nil, err } // log_module.AppLogger().WithField("judge", judge).Debug("getjudge") - - scoreCache, err := judge_model.GetJudgeScoreCache(db, judge.UserAccount, judge.ProblemSlug) - if err != nil { - // previous empty - // log_module.AppLogger().Debug("previous empty") - - scoreCache := judge_model.NewJudgeScoreCache(judge.UserAccount, judge.ProblemSlug) - if verdict == judge_model.JudgeVerdictAccepted { - scoreCache.IsAccepted = true - scoreCache.SolveTime = judge.CreateAt - } - newScoreCache, err := judge_model.CreateJudgeScoreCache(db, scoreCache) + var scoreCache *judge_model.JudgeScoreCache + for { + scoreCache, err = judge_model.GetJudgeScoreCache(db, judge.UserAccount, judge.ProblemSlug) if err != nil { - return nil, err + // previous empty + // log_module.AppLogger().Debug("previous empty") + + scoreCache := judge_model.NewJudgeScoreCache(judge.UserAccount, judge.ProblemSlug) + if verdict == judge_model.JudgeVerdictAccepted { + scoreCache.IsAccepted = true + scoreCache.SolveTime = judge.CreateAt + } + newScoreCache, err := judge_model.CreateJudgeScoreCache(db, scoreCache) + if err != nil { // create fail, get data again and continue the update logic. + continue + } + return newScoreCache, nil + } else { + break } - return newScoreCache, nil } // log_module.AppLogger().WithField("scoreCache", scoreCache).Debug("get scoreCache") From d8c90b12061131bff3697fe05d88a19416d17860 Mon Sep 17 00:00:00 2001 From: zztrans <2438362589@qq.com> Date: Thu, 29 Aug 2024 00:33:36 +0800 Subject: [PATCH 5/8] feat: rankCache update --- cmd/clean/main.go | 1 + cmd/init/db.go | 13 +++++ cmd/web_server/handler/judge_result.go | 2 +- models/judge/judge_rank_cache.go | 14 ++++-- models/judge/judge_rank_cache_db.go | 26 ++++++++++ models/judge/judge_test.go | 49 +++++++++++++++++++ .../{judge_score_cache.go => judge_cache.go} | 43 ++++++++++++---- services/user/user.go | 6 ++- 8 files changed, 140 insertions(+), 14 deletions(-) create mode 100644 models/judge/judge_rank_cache_db.go rename services/judge/{judge_score_cache.go => judge_cache.go} (64%) diff --git a/cmd/clean/main.go b/cmd/clean/main.go index d6ea1f0..312f5ee 100644 --- a/cmd/clean/main.go +++ b/cmd/clean/main.go @@ -64,6 +64,7 @@ func clearDB() { &judge_model.Judge{}, &judge_model.JudgeResult{}, &judge_model.JudgeScoreCache{}, + &judge_model.JudgeRankCache{}, "problem_problem_tags", "casbin_rule", ) diff --git a/cmd/init/db.go b/cmd/init/db.go index b3a9a29..9f1c7b1 100644 --- a/cmd/init/db.go +++ b/cmd/init/db.go @@ -18,6 +18,7 @@ func initDB() { &judge_model.Judge{}, &judge_model.JudgeResult{}, &judge_model.JudgeScoreCache{}, + &judge_model.JudgeRankCache{}, ) if err != nil { panic("failed to migrate database") @@ -37,8 +38,20 @@ func initDB() { Account: "anonymous", Password: func() *string { s := ""; return &s }(), }) + if err != nil { panic(fmt.Sprintf("failed to create anonymous user: %v", err)) } + + _, err = judge_model.CreateJudgeRankCache(db, judge_model.NewJudgeRankCache("root")) + if err != nil { + panic(fmt.Sprintf("failed to create root rankcache: %v", err)) + } + + _, err = judge_model.CreateJudgeRankCache(db, judge_model.NewJudgeRankCache("anonymous")) + if err != nil { + panic(fmt.Sprintf("failed to create anonymous rankcache: %v", err)) + } + log_module.AppLogger().Info("migrate tables ans users success") } diff --git a/cmd/web_server/handler/judge_result.go b/cmd/web_server/handler/judge_result.go index 978078f..e792b65 100644 --- a/cmd/web_server/handler/judge_result.go +++ b/cmd/web_server/handler/judge_result.go @@ -81,7 +81,7 @@ func postReportJudgeResult(ginCtx *gin.Context) { return } - _, err = judge_service.UpsertJudgeScoreCache(ginCtx, judgeUID, verdict) + err = judge_service.UpsertJudgeCache(ginCtx, judgeUID, verdict) if err != nil { gin_utils.NewInternalError(ginCtx, err.Error()) return diff --git a/models/judge/judge_rank_cache.go b/models/judge/judge_rank_cache.go index 3863f38..66f33f4 100644 --- a/models/judge/judge_rank_cache.go +++ b/models/judge/judge_rank_cache.go @@ -6,10 +6,18 @@ import ( ) // user contest summary rank info -type RankCache struct { +type JudgeRankCache struct { models.MetaFields UserAccount string `json:"userAccount" gorm:"primaryKey"` User user_model.User `json:"user"` - Points uint `json:"points"` - TotalSubmissions uint `json:"totalSubmissions"` + Points int64 `json:"points"` + TotalSubmissions int64 `json:"totalSubmissions"` +} + +func NewJudgeRankCache(userAccount string) JudgeRankCache { + return JudgeRankCache{ + UserAccount: userAccount, + Points: 0, + TotalSubmissions: 0, + } } diff --git a/models/judge/judge_rank_cache_db.go b/models/judge/judge_rank_cache_db.go new file mode 100644 index 0000000..5594a9d --- /dev/null +++ b/models/judge/judge_rank_cache_db.go @@ -0,0 +1,26 @@ +package judge_model + +import ( + "github.com/oj-lab/oj-lab-platform/models" + "gorm.io/gorm" +) + +func CreateJudgeRankCache(tx *gorm.DB, rankCache JudgeRankCache) (*JudgeRankCache, error) { + rankCache.MetaFields = models.NewMetaFields() + return &rankCache, tx.Create(&rankCache).Error +} + +func GetJudgeRankCache(tx *gorm.DB, userAccount string) (*JudgeRankCache, error) { + rankCache := JudgeRankCache{} + err := tx.Model(&JudgeRankCache{}). + Where("user_account = ?", userAccount). + First(&rankCache).Error + if err != nil { + return nil, err + } + return &rankCache, nil +} + +func UpdateJudgeRankCache(tx *gorm.DB, rankCache JudgeRankCache) error { + return tx.Model(&rankCache).Updates(rankCache).Error +} diff --git a/models/judge/judge_test.go b/models/judge/judge_test.go index 5688b25..01dda7f 100644 --- a/models/judge/judge_test.go +++ b/models/judge/judge_test.go @@ -3,8 +3,10 @@ package judge_model import ( "encoding/json" "testing" + "time" problem_model "github.com/oj-lab/oj-lab-platform/models/problem" + user_model "github.com/oj-lab/oj-lab-platform/models/user" gorm_agent "github.com/oj-lab/oj-lab-platform/modules/agent/gorm" ) @@ -46,3 +48,50 @@ func TestJudgeDB(t *testing.T) { } t.Logf("%+v\n", string(judgeJson)) } + +func TestJudgeScoreCacheDB(t *testing.T) { + db := gorm_agent.GetDefaultDB() + + problem := &problem_model.Problem{ + Slug: "test-judgeScoreCache-db-problem", + } + var err error + err = problem_model.CreateProblem(db, *problem) + if err != nil { + t.Error(err) + } + + user := &user_model.User{ + Account: "test-judgeScoreCache-db-user", + } + _, err = user_model.CreateUser(db, *user) + if err != nil { + t.Error(err) + } + + judgeScoreCache := NewJudgeScoreCache(user.Account, problem.Slug) + _, err = CreateJudgeScoreCache(db, judgeScoreCache) + if err != nil { + t.Error(err) + } + + now := time.Now() + judgeScoreCache.IsAccepted = true + judgeScoreCache.SolveTime = &now + judgeScoreCache.SubmissionCount = 10 + err = UpdateJudgeScoreCache(db, judgeScoreCache) + if err != nil { + t.Error(err) + } + + newjudgeScoreCache, err := GetJudgeScoreCache(db, user.Account, problem.Slug) + if err != nil { + t.Error(err) + } + + judgeScoreCacheJson, err := json.MarshalIndent(newjudgeScoreCache, "", "\t") + if err != nil { + t.Error(err) + } + t.Logf("%+v\n", string(judgeScoreCacheJson)) +} diff --git a/services/judge/judge_score_cache.go b/services/judge/judge_cache.go similarity index 64% rename from services/judge/judge_score_cache.go rename to services/judge/judge_cache.go index 1518026..0a1407b 100644 --- a/services/judge/judge_score_cache.go +++ b/services/judge/judge_cache.go @@ -8,30 +8,45 @@ import ( gorm_agent "github.com/oj-lab/oj-lab-platform/modules/agent/gorm" ) -func UpsertJudgeScoreCache(ctx context.Context, uid uuid.UUID, verdict judge_model.JudgeVerdict) (*judge_model.JudgeScoreCache, error) { +func UpsertJudgeCache(ctx context.Context, uid uuid.UUID, verdict judge_model.JudgeVerdict) error { db := gorm_agent.GetDefaultDB() judge, err := judge_model.GetJudge(db, uid) if err != nil { - return nil, err + return err } // log_module.AppLogger().WithField("judge", judge).Debug("getjudge") var scoreCache *judge_model.JudgeScoreCache + var rankCache *judge_model.JudgeRankCache + rankCache, err = judge_model.GetJudgeRankCache(db, judge.UserAccount) + if err != nil { + return err + } + + extraPoint := 0 for { scoreCache, err = judge_model.GetJudgeScoreCache(db, judge.UserAccount, judge.ProblemSlug) if err != nil { // previous empty // log_module.AppLogger().Debug("previous empty") - scoreCache := judge_model.NewJudgeScoreCache(judge.UserAccount, judge.ProblemSlug) if verdict == judge_model.JudgeVerdictAccepted { + extraPoint = 1 scoreCache.IsAccepted = true scoreCache.SolveTime = judge.CreateAt } - newScoreCache, err := judge_model.CreateJudgeScoreCache(db, scoreCache) + _, err := judge_model.CreateJudgeScoreCache(db, scoreCache) if err != nil { // create fail, get data again and continue the update logic. continue } - return newScoreCache, nil + + // create success + rankCache.Points += int64(extraPoint) + rankCache.TotalSubmissions += 1 + err = judge_model.UpdateJudgeRankCache(db, *rankCache) + if err != nil { + return err + } + return nil } else { break } @@ -42,24 +57,34 @@ func UpsertJudgeScoreCache(ctx context.Context, uid uuid.UUID, verdict judge_mod // previous no ac || current more early // need to update if !scoreCache.IsAccepted || judge.CreateAt.Before(*scoreCache.SolveTime) { + if !scoreCache.IsAccepted { + extraPoint = 1 + } + preSubmissionCount := scoreCache.SubmissionCount if verdict == judge_model.JudgeVerdictAccepted { scoreCache.SubmissionCount, err = judge_model.GetBeforeSubmission(db, *judge) // rescan to count previous finished if err != nil { - return nil, err + return err } scoreCache.IsAccepted = true + rankCache.Points = rankCache.Points + int64(extraPoint) scoreCache.SolveTime = judge.CreateAt } else { scoreCache.SubmissionCount += 1 } - + rankCache.TotalSubmissions += scoreCache.SubmissionCount - preSubmissionCount // log_module.AppLogger().WithField("scoreCache", scoreCache).Debug("update scoreCache") err = judge_model.UpdateJudgeScoreCache(db, *scoreCache) if err != nil { - return nil, err + return err + } + + err = judge_model.UpdateJudgeRankCache(db, *rankCache) + if err != nil { + return err } } // if no early, no need update, just a query - return scoreCache, nil + return nil } diff --git a/services/user/user.go b/services/user/user.go index d0ee9a5..7fabcb6 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + judge_model "github.com/oj-lab/oj-lab-platform/models/judge" user_model "github.com/oj-lab/oj-lab-platform/models/user" casbin_agent "github.com/oj-lab/oj-lab-platform/modules/agent/casbin" gorm_agent "github.com/oj-lab/oj-lab-platform/modules/agent/gorm" @@ -13,7 +14,10 @@ import ( func CreateUser(ctx context.Context, user user_model.User) (*user_model.User, error) { db := gorm_agent.GetDefaultDB() - + _, err := judge_model.CreateJudgeRankCache(db, judge_model.NewJudgeRankCache(user.Account)) + if err != nil { + return nil, err + } return user_model.CreateUser(db, user) } From f6c9f499b0a4e03a0edb1b513caf070b8a706c54 Mon Sep 17 00:00:00 2001 From: zztrans <2438362589@qq.com> Date: Wed, 4 Sep 2024 17:00:01 +0800 Subject: [PATCH 6/8] feat: ranklist - simplify upsert logic - judge_rank api service model db - some test --- cmd/init/db.go | 10 -- cmd/web_server/handler/rank.go | 60 ++++++++++++ cmd/web_server/main.go | 1 + models/judge/judge_db.go | 7 +- models/judge/judge_rank.go | 14 +++ models/judge/judge_rank_cache.go | 4 +- models/judge/judge_rank_cache_db.go | 17 ++++ models/judge/judge_rank_db.go | 43 +++++++++ models/judge/judge_score_cache.go | 4 +- models/judge/judge_test.go | 118 ++++++++++++++++++++++++ services/judge/judge_cache.go | 43 ++++----- services/judge/judge_rank.go | 40 ++++++++ services/judge/judge_test.go | 137 ++++++++++++++++++++++++++++ 13 files changed, 456 insertions(+), 42 deletions(-) create mode 100644 cmd/web_server/handler/rank.go create mode 100644 models/judge/judge_rank.go create mode 100644 models/judge/judge_rank_db.go create mode 100644 services/judge/judge_rank.go diff --git a/cmd/init/db.go b/cmd/init/db.go index 9f1c7b1..4ab2602 100644 --- a/cmd/init/db.go +++ b/cmd/init/db.go @@ -43,15 +43,5 @@ func initDB() { panic(fmt.Sprintf("failed to create anonymous user: %v", err)) } - _, err = judge_model.CreateJudgeRankCache(db, judge_model.NewJudgeRankCache("root")) - if err != nil { - panic(fmt.Sprintf("failed to create root rankcache: %v", err)) - } - - _, err = judge_model.CreateJudgeRankCache(db, judge_model.NewJudgeRankCache("anonymous")) - if err != nil { - panic(fmt.Sprintf("failed to create anonymous rankcache: %v", err)) - } - log_module.AppLogger().Info("migrate tables ans users success") } diff --git a/cmd/web_server/handler/rank.go b/cmd/web_server/handler/rank.go new file mode 100644 index 0000000..963423d --- /dev/null +++ b/cmd/web_server/handler/rank.go @@ -0,0 +1,60 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + gin_utils "github.com/oj-lab/oj-lab-platform/modules/utils/gin" + judge_service "github.com/oj-lab/oj-lab-platform/services/judge" +) + +func SetupRankRouter(baseRoute *gin.RouterGroup) { + g := baseRoute.Group("/rank") + { + g.GET("", getRankList) + // g.GET("/:account", getUserRank) + } +} + +// getUserRank +// +// @Router /rank/{account} [get] +// @Summary Get a user rank +// @Description Get a rank for user +// @Tags problem +// @Accept json +// @Success 200 +// func getUserRank(ginCtx *gin.Context) { + +// } + +// getRankList +// +// @Router /rank [get] +// @Summary Get rank list +// @Description Get rank list +// @Tags rank +// @Accept json +// @Success 200 +func getRankList(ginCtx *gin.Context) { + limit, err := gin_utils.QueryInt(ginCtx, "limit", 100) + if err != nil { + gin_utils.NewInvalidParamError(ginCtx, "limit", err.Error()) + return + } + offset, err := gin_utils.QueryInt(ginCtx, "offset", 0) + if err != nil { + gin_utils.NewInvalidParamError(ginCtx, "offset", err.Error()) + return + } + + rankInfoList, err := judge_service.GetRankList( + ginCtx, + nil, + &limit, &offset, + ) + if err != nil { + gin_utils.NewInternalError(ginCtx, err.Error()) + return + } + + ginCtx.JSON(200, rankInfoList) +} diff --git a/cmd/web_server/main.go b/cmd/web_server/main.go index 6b040d4..9f16e4e 100644 --- a/cmd/web_server/main.go +++ b/cmd/web_server/main.go @@ -79,6 +79,7 @@ func main() { handler.SetupJudgeRouter(apiRouter) handler.SetupJudgeTaskRouter(apiRouter) handler.SetupJudgeResultRouter(apiRouter) + handler.SetupRankRouter(apiRouter) err := r.Run(fmt.Sprintf(":%d", servicePort)) if err != nil { diff --git a/models/judge/judge_db.go b/models/judge/judge_db.go index 165b50d..f75cdd1 100644 --- a/models/judge/judge_db.go +++ b/models/judge/judge_db.go @@ -22,7 +22,7 @@ func CreateJudge(tx *gorm.DB, judge Judge) (*Judge, error) { } // only count if when accept && accept time < SolvedTime -func GetBeforeSubmission(tx *gorm.DB, judge Judge) (int64, error) { +func GetBeforeSubmission(tx *gorm.DB, judge Judge) (int, error) { var count int64 err := tx.Model(&Judge{}). Where("create_at < ?", judge.CreateAt). @@ -32,7 +32,7 @@ func GetBeforeSubmission(tx *gorm.DB, judge Judge) (int64, error) { if err != nil { return 0, err } - return count + 1, err + return int(count + 1), err } func GetJudge(tx *gorm.DB, uid uuid.UUID) (*Judge, error) { @@ -154,6 +154,9 @@ func UpdateJudge(tx *gorm.DB, judge Judge) error { if judge.Verdict != "" { updatingJudge.Verdict = judge.Verdict } + if judge.MetaFields.CreateAt != nil { + updatingJudge.MetaFields = judge.MetaFields + } return tx.Model(&updatingJudge).Updates(updatingJudge).Error } diff --git a/models/judge/judge_rank.go b/models/judge/judge_rank.go new file mode 100644 index 0000000..dc06f01 --- /dev/null +++ b/models/judge/judge_rank.go @@ -0,0 +1,14 @@ +package judge_model + +import "github.com/oj-lab/oj-lab-platform/models" + +type JudgeRank struct { + Rank int + AvatarURL string + Name string + Points int + TotalSubmissions int + AcceptRate float32 +} + +var RankInfoSelection = append([]string{"user_account", "points", "total_submissions"}, models.MetaFieldsSelection...) diff --git a/models/judge/judge_rank_cache.go b/models/judge/judge_rank_cache.go index 66f33f4..7fa34e8 100644 --- a/models/judge/judge_rank_cache.go +++ b/models/judge/judge_rank_cache.go @@ -10,8 +10,8 @@ type JudgeRankCache struct { models.MetaFields UserAccount string `json:"userAccount" gorm:"primaryKey"` User user_model.User `json:"user"` - Points int64 `json:"points"` - TotalSubmissions int64 `json:"totalSubmissions"` + Points int `json:"points"` + TotalSubmissions int `json:"totalSubmissions"` } func NewJudgeRankCache(userAccount string) JudgeRankCache { diff --git a/models/judge/judge_rank_cache_db.go b/models/judge/judge_rank_cache_db.go index 5594a9d..3e04070 100644 --- a/models/judge/judge_rank_cache_db.go +++ b/models/judge/judge_rank_cache_db.go @@ -1,6 +1,8 @@ package judge_model import ( + "errors" + "github.com/oj-lab/oj-lab-platform/models" "gorm.io/gorm" ) @@ -10,6 +12,14 @@ func CreateJudgeRankCache(tx *gorm.DB, rankCache JudgeRankCache) (*JudgeRankCach return &rankCache, tx.Create(&rankCache).Error } +func DeleteJudgeRankCache(tx *gorm.DB, userAccount string) error { + var judgeRankCache JudgeRankCache + if err := tx.Where("user_account = ?", userAccount).First(&judgeRankCache).Error; err != nil { + return err + } + return tx.Delete(&judgeRankCache).Error +} + func GetJudgeRankCache(tx *gorm.DB, userAccount string) (*JudgeRankCache, error) { rankCache := JudgeRankCache{} err := tx.Model(&JudgeRankCache{}). @@ -24,3 +34,10 @@ func GetJudgeRankCache(tx *gorm.DB, userAccount string) (*JudgeRankCache, error) func UpdateJudgeRankCache(tx *gorm.DB, rankCache JudgeRankCache) error { return tx.Model(&rankCache).Updates(rankCache).Error } + +func (rankCache *JudgeRankCache) BeforeSave(tx *gorm.DB) (err error) { + if rankCache.Points > rankCache.TotalSubmissions { + return errors.New("TotalSubmissions must >= Points") + } + return nil +} diff --git a/models/judge/judge_rank_db.go b/models/judge/judge_rank_db.go new file mode 100644 index 0000000..8b065dd --- /dev/null +++ b/models/judge/judge_rank_db.go @@ -0,0 +1,43 @@ +package judge_model + +import "gorm.io/gorm" + +type GetRankOptions struct { + Selection []string + UserAccount *string + Offset *int + Limit *int +} + +func buildGetRankCacheTXByOptions(tx *gorm.DB, options GetRankOptions, isCount bool) *gorm.DB { + tx = tx.Model(&JudgeRankCache{}) + if len(options.Selection) > 0 { + tx = tx.Select(options.Selection) + } + if options.UserAccount != nil { + tx = tx.Where("user_account = ?", *options.UserAccount) + } + + tx = tx.Distinct().Preload("User") + if !isCount { + if options.Offset != nil { + tx = tx.Offset(*options.Offset) + } + if options.Limit != nil { + tx = tx.Limit(*options.Limit) + } + } + + tx = tx.Order("points DESC, total_submissions ASC") + return tx +} + +func GetRankCacheListByOptions(tx *gorm.DB, options GetRankOptions) ([]JudgeRankCache, error) { + rankInfoList := []JudgeRankCache{} + tx = buildGetRankCacheTXByOptions(tx, options, false) + err := tx.Find(&rankInfoList).Error + if err != nil { + return nil, err + } + return rankInfoList, nil +} diff --git a/models/judge/judge_score_cache.go b/models/judge/judge_score_cache.go index bf17f70..f463792 100644 --- a/models/judge/judge_score_cache.go +++ b/models/judge/judge_score_cache.go @@ -15,7 +15,7 @@ type JudgeScoreCache struct { User user_model.User `json:"user"` ProblemSlug string `json:"problemSlug" gorm:"primaryKey"` Problem problem_model.Problem `json:"problem"` - SubmissionCount int64 `json:"submissionCount" gorm:"default:1"` // judge create time < solvetime will be count + SubmissionCount int `json:"submissionCount" gorm:"default:0"` // judge create time < solvetime will be counted IsAccepted bool `json:"isAccepted" gorm:"default:false"` SolveTime *time.Time `json:"solveAt" gorm:"default:null"` // ac time < solveTime, update submissionCount } @@ -24,7 +24,7 @@ func NewJudgeScoreCache(userAccount string, problemSlug string) JudgeScoreCache return JudgeScoreCache{ UserAccount: userAccount, ProblemSlug: problemSlug, - SubmissionCount: 1, + SubmissionCount: 0, IsAccepted: false, } } diff --git a/models/judge/judge_test.go b/models/judge/judge_test.go index 01dda7f..7b63566 100644 --- a/models/judge/judge_test.go +++ b/models/judge/judge_test.go @@ -2,12 +2,15 @@ package judge_model import ( "encoding/json" + "fmt" + "strconv" "testing" "time" problem_model "github.com/oj-lab/oj-lab-platform/models/problem" user_model "github.com/oj-lab/oj-lab-platform/models/user" gorm_agent "github.com/oj-lab/oj-lab-platform/modules/agent/gorm" + "github.com/stretchr/testify/assert" ) func TestJudgeDB(t *testing.T) { @@ -95,3 +98,118 @@ func TestJudgeScoreCacheDB(t *testing.T) { } t.Logf("%+v\n", string(judgeScoreCacheJson)) } + +func TestJudgeRank(t *testing.T) { + db := gorm_agent.GetDefaultDB() + + var names []string + var users []user_model.User + + for i := 0; i < 4; i++ { + names = append(names, "test"+strconv.Itoa(i)) + users = append(users, user_model.User{ + Account: names[i], + Password: func() *string { s := ""; return &s }(), + }) + } + + _, err := CreateJudgeRankCache(db, JudgeRankCache{ + UserAccount: names[0], + User: users[0], + Points: 10, + TotalSubmissions: 10, + }) + if err != nil { + t.Error(err) + } + + _, err = CreateJudgeRankCache(db, JudgeRankCache{ + UserAccount: names[1], + User: users[1], + Points: 10, + TotalSubmissions: 11, + }) + if err != nil { + t.Error(err) + } + + _, err = CreateJudgeRankCache(db, JudgeRankCache{ + UserAccount: names[2], + User: users[2], + Points: 10, + TotalSubmissions: 12, + }) + if err != nil { + t.Error(err) + } + + _, err = CreateJudgeRankCache(db, JudgeRankCache{ + UserAccount: names[3], + User: users[3], + Points: 9, + TotalSubmissions: 10, + }) + if err != nil { + t.Error(err) + } + + err = UpdateJudgeRankCache(db, JudgeRankCache{ + UserAccount: names[3], + User: users[3], + Points: 9, + TotalSubmissions: 8, + }) + assert.Error(t, err) + assert.Equal(t, "TotalSubmissions must >= Points", err.Error()) + rankCache, err := GetJudgeRankCache(db, names[3]) + if err != nil { + t.Error(err) + } + fmt.Printf("rankCache: %v\n", rankCache) + + rankOption := GetRankOptions{ + Selection: RankInfoSelection, + } + rankList, err := GetRankCacheListByOptions(db, rankOption) + if err != nil { + t.Error(err) + } + fmt.Printf("rankList: %v\n", rankList) + if len(rankList) != 4 { + t.Error("rankCount should be 4") + } + + offset := 2 + rankOption = GetRankOptions{ + Selection: RankInfoSelection, + Offset: &offset, + } + rankList, err = GetRankCacheListByOptions(db, rankOption) + if err != nil { + t.Error(err) + } + fmt.Printf("rankList: %v\n", rankList) + if len(rankList) != 2 { + t.Error("rankCount should be 2") + } + + rankListJson, err := json.MarshalIndent(rankList, "", "\t") + if err != nil { + t.Error(err) + } + fmt.Printf("%+v\n", string(rankListJson)) + + for i := 0; i < 4; i++ { + err = DeleteJudgeRankCache(db, names[i]) + if err != nil { + t.Error(err) + } + } + + for i := 0; i < 4; i++ { + err = user_model.DeleteUser(db, users[i]) + if err != nil { + t.Error(err) + } + } +} diff --git a/services/judge/judge_cache.go b/services/judge/judge_cache.go index 0a1407b..f1df1b1 100644 --- a/services/judge/judge_cache.go +++ b/services/judge/judge_cache.go @@ -17,36 +17,26 @@ func UpsertJudgeCache(ctx context.Context, uid uuid.UUID, verdict judge_model.Ju // log_module.AppLogger().WithField("judge", judge).Debug("getjudge") var scoreCache *judge_model.JudgeScoreCache var rankCache *judge_model.JudgeRankCache - rankCache, err = judge_model.GetJudgeRankCache(db, judge.UserAccount) - if err != nil { - return err + for { + rankCache, err = judge_model.GetJudgeRankCache(db, judge.UserAccount) + if err != nil { // previous empty + _, err = judge_model.CreateJudgeRankCache(db, judge_model.NewJudgeRankCache(judge.UserAccount)) + if err != nil { // create fail, exists -> get data again and continue the update logic. + continue + } + } else { + break + } } - extraPoint := 0 for { scoreCache, err = judge_model.GetJudgeScoreCache(db, judge.UserAccount, judge.ProblemSlug) - if err != nil { - // previous empty - // log_module.AppLogger().Debug("previous empty") - scoreCache := judge_model.NewJudgeScoreCache(judge.UserAccount, judge.ProblemSlug) - if verdict == judge_model.JudgeVerdictAccepted { - extraPoint = 1 - scoreCache.IsAccepted = true - scoreCache.SolveTime = judge.CreateAt - } - _, err := judge_model.CreateJudgeScoreCache(db, scoreCache) - if err != nil { // create fail, get data again and continue the update logic. + if err != nil { // previous empty + _, err := judge_model.CreateJudgeScoreCache(db, judge_model.NewJudgeScoreCache(judge.UserAccount, judge.ProblemSlug)) + if err != nil { // create fail, exists -> get data again and continue the update logic. continue } - // create success - rankCache.Points += int64(extraPoint) - rankCache.TotalSubmissions += 1 - err = judge_model.UpdateJudgeRankCache(db, *rankCache) - if err != nil { - return err - } - return nil } else { break } @@ -57,17 +47,18 @@ func UpsertJudgeCache(ctx context.Context, uid uuid.UUID, verdict judge_model.Ju // previous no ac || current more early // need to update if !scoreCache.IsAccepted || judge.CreateAt.Before(*scoreCache.SolveTime) { + extraPoint := 0 if !scoreCache.IsAccepted { extraPoint = 1 } preSubmissionCount := scoreCache.SubmissionCount if verdict == judge_model.JudgeVerdictAccepted { - scoreCache.SubmissionCount, err = judge_model.GetBeforeSubmission(db, *judge) // rescan to count previous finished + scoreCache.SubmissionCount, err = judge_model.GetBeforeSubmission(db, *judge) // rescan to count previous finished judge if err != nil { return err } scoreCache.IsAccepted = true - rankCache.Points = rankCache.Points + int64(extraPoint) + rankCache.Points = rankCache.Points + extraPoint scoreCache.SolveTime = judge.CreateAt } else { scoreCache.SubmissionCount += 1 @@ -79,12 +70,12 @@ func UpsertJudgeCache(ctx context.Context, uid uuid.UUID, verdict judge_model.Ju if err != nil { return err } - err = judge_model.UpdateJudgeRankCache(db, *rankCache) if err != nil { return err } } + // if no early, no need update, just a query return nil } diff --git a/services/judge/judge_rank.go b/services/judge/judge_rank.go new file mode 100644 index 0000000..d3d3504 --- /dev/null +++ b/services/judge/judge_rank.go @@ -0,0 +1,40 @@ +package judge_service + +import ( + "context" + + judge_model "github.com/oj-lab/oj-lab-platform/models/judge" + gorm_agent "github.com/oj-lab/oj-lab-platform/modules/agent/gorm" +) + +func GetRankList( + _ context.Context, + account *string, + limit, offset *int, +) ([]judge_model.JudgeRank, error) { + db := gorm_agent.GetDefaultDB() + getOptions := judge_model.GetRankOptions{ + Selection: judge_model.RankInfoSelection, + Limit: limit, + Offset: offset, + } + + rankCacheList, err := judge_model.GetRankCacheListByOptions(db, getOptions) + if err != nil { + return nil, err + } + + var rankList []judge_model.JudgeRank + + for i, rankCache := range rankCacheList { + rankList = append(rankList, judge_model.JudgeRank{ + Rank: i + *offset + 1, + AvatarURL: rankCache.User.AvatarURL, + Name: rankCache.User.Name, + Points: rankCache.Points, + TotalSubmissions: rankCache.TotalSubmissions, + AcceptRate: float32(rankCache.Points) / float32(rankCache.TotalSubmissions), + }) + } + return rankList, nil +} diff --git a/services/judge/judge_test.go b/services/judge/judge_test.go index a3ac187..7c46bf4 100644 --- a/services/judge/judge_test.go +++ b/services/judge/judge_test.go @@ -1,15 +1,19 @@ package judge_service import ( + "fmt" "net/http/httptest" "testing" + "time" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/gin-gonic/gin" "github.com/oj-lab/oj-lab-platform/models" judge_model "github.com/oj-lab/oj-lab-platform/models/judge" problem_model "github.com/oj-lab/oj-lab-platform/models/problem" + user_model "github.com/oj-lab/oj-lab-platform/models/user" gorm_agent "github.com/oj-lab/oj-lab-platform/modules/agent/gorm" ) @@ -67,3 +71,136 @@ func TestCreateJudge(t *testing.T) { asserts.Equal(judge.ProblemSlug, insert_judge.ProblemSlug) asserts.Equal(judge.Language, insert_judge.Language) } + +func TestUpsertJudgeCache(t *testing.T) { + ctx, _ := gin.CreateTestContext(httptest.NewRecorder()) + db := gorm_agent.GetDefaultDB() + + user := user_model.User{ + Account: "test-upserJudgeCache-user", + Password: func() *string { s := ""; return &s }(), + } + + problem := problem_model.Problem{ + Slug: "test-upserJudgeCache-problem", + } + + judge1 := &judge_model.Judge{ + UserAccount: user.Account, + User: user, + ProblemSlug: problem.Slug, + Problem: problem, + Verdict: judge_model.JudgeVerdictAccepted, + } + judge1.UID = uuid.New() + judge1, err := CreateJudge(ctx, *judge1) + if err != nil { + t.Error(err) + } + time1 := time.Unix(int64(1000), 0) + judge1.MetaFields.CreateAt = &time1 + err = judge_model.UpdateJudge(db, *judge1) + if err != nil { + t.Error(err) + } + err = UpsertJudgeCache(ctx, judge1.UID, judge_model.JudgeVerdictAccepted) + if err != nil { + t.Error(err) + } + + judge2 := &judge_model.Judge{ + UserAccount: user.Account, + User: user, + ProblemSlug: problem.Slug, + Problem: problem, + Verdict: judge_model.JudgeVerdictWrongAnswer, + } + judge2, err = CreateJudge(ctx, *judge2) + if err != nil { + t.Error(err) + } + time2 := time.Unix(int64(999), 0) + judge2.MetaFields.CreateAt = &time2 + err = judge_model.UpdateJudge(db, *judge2) + if err != nil { + t.Error(err) + } + err = UpsertJudgeCache(ctx, judge2.UID, judge_model.JudgeVerdictWrongAnswer) + if err != nil { + t.Error(err) + } + + judge3 := &judge_model.Judge{ + UserAccount: user.Account, + User: user, + ProblemSlug: problem.Slug, + Problem: problem, + Verdict: judge_model.JudgeVerdictWrongAnswer, + } + judge3, err = CreateJudge(ctx, *judge3) + if err != nil { + t.Error(err) + } + time3 := time.Unix(int64(1001), 0) + judge3.MetaFields.CreateAt = &time3 + err = judge_model.UpdateJudge(db, *judge3) + if err != nil { + t.Error(err) + } + err = UpsertJudgeCache(ctx, judge3.UID, judge_model.JudgeVerdictWrongAnswer) + if err != nil { + t.Error(err) + } + + rankCache, err := judge_model.GetJudgeRankCache(db, user.Account) + if err != nil { + t.Error(err) + } + fmt.Println(rankCache) + scoreCacheCache, err := judge_model.GetJudgeScoreCache(db, user.Account, problem.Slug) + if err != nil { + t.Error(err) + } + fmt.Println(scoreCacheCache) + + asserts := assert.New(t) + asserts.Equal(rankCache.Points, 1) + asserts.Equal(rankCache.TotalSubmissions, 2) + asserts.Equal(scoreCacheCache.SubmissionCount, 2) + + judge4 := &judge_model.Judge{ + UserAccount: user.Account, + User: user, + ProblemSlug: problem.Slug, + Problem: problem, + Verdict: judge_model.JudgeVerdictAccepted, + } + judge4, err = CreateJudge(ctx, *judge4) + if err != nil { + t.Error(err) + } + time4 := time.Unix(int64(998), 0) + judge4.MetaFields.CreateAt = &time4 + err = judge_model.UpdateJudge(db, *judge4) + if err != nil { + t.Error(err) + } + err = UpsertJudgeCache(ctx, judge4.UID, judge_model.JudgeVerdictAccepted) + if err != nil { + t.Error(err) + } + + rankCache, err = judge_model.GetJudgeRankCache(db, user.Account) + if err != nil { + t.Error(err) + } + fmt.Println(rankCache) + scoreCacheCache, err = judge_model.GetJudgeScoreCache(db, user.Account, problem.Slug) + if err != nil { + t.Error(err) + } + fmt.Println(scoreCacheCache) + asserts.Equal(rankCache.Points, 1) + asserts.Equal(rankCache.TotalSubmissions, 1) + asserts.Equal(scoreCacheCache.SubmissionCount, 1) +} From 8faf5d5f71daea699dd0df7ce35c1100d5f470e4 Mon Sep 17 00:00:00 2001 From: zztrans <2438362589@qq.com> Date: Wed, 4 Sep 2024 17:38:23 +0800 Subject: [PATCH 7/8] add test coverage --- models/judge/judge_db.go | 2 +- services/judge/judge_test.go | 92 +++++++++++++++++++++--------------- 2 files changed, 56 insertions(+), 38 deletions(-) diff --git a/models/judge/judge_db.go b/models/judge/judge_db.go index f75cdd1..117738e 100644 --- a/models/judge/judge_db.go +++ b/models/judge/judge_db.go @@ -21,7 +21,7 @@ func CreateJudge(tx *gorm.DB, judge Judge) (*Judge, error) { return &judge, tx.Create(&judge).Error } -// only count if when accept && accept time < SolvedTime +// include self, only count if when accept && accept time < SolvedTime func GetBeforeSubmission(tx *gorm.DB, judge Judge) (int, error) { var count int64 err := tx.Model(&Judge{}). diff --git a/services/judge/judge_test.go b/services/judge/judge_test.go index 7c46bf4..595c630 100644 --- a/services/judge/judge_test.go +++ b/services/judge/judge_test.go @@ -73,6 +73,7 @@ func TestCreateJudge(t *testing.T) { } func TestUpsertJudgeCache(t *testing.T) { + // previous WA || later WA || previous AC || later AC || GetBeforeSubmission ctx, _ := gin.CreateTestContext(httptest.NewRecorder()) db := gorm_agent.GetDefaultDB() @@ -80,74 +81,69 @@ func TestUpsertJudgeCache(t *testing.T) { Account: "test-upserJudgeCache-user", Password: func() *string { s := ""; return &s }(), } - problem := problem_model.Problem{ Slug: "test-upserJudgeCache-problem", } - judge1 := &judge_model.Judge{ + baseACJudge := &judge_model.Judge{ UserAccount: user.Account, User: user, ProblemSlug: problem.Slug, Problem: problem, Verdict: judge_model.JudgeVerdictAccepted, + Status: judge_model.JudgeStatusFinished, } - judge1.UID = uuid.New() - judge1, err := CreateJudge(ctx, *judge1) + baseACJudge.UID = uuid.New() + baseACJudge, err := CreateJudge(ctx, *baseACJudge) if err != nil { t.Error(err) } time1 := time.Unix(int64(1000), 0) - judge1.MetaFields.CreateAt = &time1 - err = judge_model.UpdateJudge(db, *judge1) + baseACJudge.MetaFields.CreateAt = &time1 + err = judge_model.UpdateJudge(db, *baseACJudge) if err != nil { t.Error(err) } - err = UpsertJudgeCache(ctx, judge1.UID, judge_model.JudgeVerdictAccepted) + err = UpsertJudgeCache(ctx, baseACJudge.UID, judge_model.JudgeVerdictAccepted) if err != nil { t.Error(err) } - judge2 := &judge_model.Judge{ + preWAJudge := &judge_model.Judge{ UserAccount: user.Account, User: user, ProblemSlug: problem.Slug, Problem: problem, Verdict: judge_model.JudgeVerdictWrongAnswer, + Status: judge_model.JudgeStatusFinished, } - judge2, err = CreateJudge(ctx, *judge2) + preWAJudge, err = CreateJudge(ctx, *preWAJudge) if err != nil { t.Error(err) } - time2 := time.Unix(int64(999), 0) - judge2.MetaFields.CreateAt = &time2 - err = judge_model.UpdateJudge(db, *judge2) + time2 := time.Unix(int64(998), 0) + preWAJudge.MetaFields.CreateAt = &time2 + err = judge_model.UpdateJudge(db, *preWAJudge) if err != nil { t.Error(err) } - err = UpsertJudgeCache(ctx, judge2.UID, judge_model.JudgeVerdictWrongAnswer) + err = UpsertJudgeCache(ctx, preWAJudge.UID, judge_model.JudgeVerdictWrongAnswer) if err != nil { t.Error(err) } - judge3 := &judge_model.Judge{ - UserAccount: user.Account, - User: user, - ProblemSlug: problem.Slug, - Problem: problem, - Verdict: judge_model.JudgeVerdictWrongAnswer, - } - judge3, err = CreateJudge(ctx, *judge3) + laterWAJudge := preWAJudge + laterWAJudge, err = CreateJudge(ctx, *laterWAJudge) if err != nil { t.Error(err) } time3 := time.Unix(int64(1001), 0) - judge3.MetaFields.CreateAt = &time3 - err = judge_model.UpdateJudge(db, *judge3) + laterWAJudge.MetaFields.CreateAt = &time3 + err = judge_model.UpdateJudge(db, *laterWAJudge) if err != nil { t.Error(err) } - err = UpsertJudgeCache(ctx, judge3.UID, judge_model.JudgeVerdictWrongAnswer) + err = UpsertJudgeCache(ctx, laterWAJudge.UID, judge_model.JudgeVerdictWrongAnswer) if err != nil { t.Error(err) } @@ -167,25 +163,35 @@ func TestUpsertJudgeCache(t *testing.T) { asserts.Equal(rankCache.Points, 1) asserts.Equal(rankCache.TotalSubmissions, 2) asserts.Equal(scoreCacheCache.SubmissionCount, 2) + asserts.Equal(scoreCacheCache.SolveTime, baseACJudge.CreateAt) - judge4 := &judge_model.Judge{ - UserAccount: user.Account, - User: user, - ProblemSlug: problem.Slug, - Problem: problem, - Verdict: judge_model.JudgeVerdictAccepted, + preACJudge := baseACJudge + preACJudge, err = CreateJudge(ctx, *preACJudge) + if err != nil { + t.Error(err) } - judge4, err = CreateJudge(ctx, *judge4) + time4 := time.Unix(int64(999), 0) + preACJudge.MetaFields.CreateAt = &time4 + err = judge_model.UpdateJudge(db, *preACJudge) if err != nil { t.Error(err) } - time4 := time.Unix(int64(998), 0) - judge4.MetaFields.CreateAt = &time4 - err = judge_model.UpdateJudge(db, *judge4) + err = UpsertJudgeCache(ctx, preACJudge.UID, judge_model.JudgeVerdictAccepted) if err != nil { t.Error(err) } - err = UpsertJudgeCache(ctx, judge4.UID, judge_model.JudgeVerdictAccepted) + laterACJudge := baseACJudge + laterACJudge, err = CreateJudge(ctx, *laterACJudge) + if err != nil { + t.Error(err) + } + time5 := time.Unix(int64(1002), 0) + laterACJudge.MetaFields.CreateAt = &time5 + err = judge_model.UpdateJudge(db, *laterACJudge) + if err != nil { + t.Error(err) + } + err = UpsertJudgeCache(ctx, laterACJudge.UID, judge_model.JudgeVerdictAccepted) if err != nil { t.Error(err) } @@ -201,6 +207,18 @@ func TestUpsertJudgeCache(t *testing.T) { } fmt.Println(scoreCacheCache) asserts.Equal(rankCache.Points, 1) - asserts.Equal(rankCache.TotalSubmissions, 1) - asserts.Equal(scoreCacheCache.SubmissionCount, 1) + asserts.Equal(rankCache.TotalSubmissions, 2) + asserts.Equal(scoreCacheCache.SubmissionCount, 2) + asserts.Equal(scoreCacheCache.SolveTime, preACJudge.CreateAt) + + submissionCount, err := judge_model.GetBeforeSubmission(db, *preACJudge) + if err != nil { + t.Error(err) + } + asserts.Equal(submissionCount, 2) + submissionCount, err = judge_model.GetBeforeSubmission(db, *laterACJudge) + if err != nil { + t.Error(err) + } + asserts.Equal(submissionCount, 5) } From 34b5f46e2b7341682177c99d357f9118bf3f012e Mon Sep 17 00:00:00 2001 From: zztrans <2438362589@qq.com> Date: Wed, 4 Sep 2024 17:52:28 +0800 Subject: [PATCH 8/8] fix: CI test fail, time local --- services/judge/judge_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/judge/judge_test.go b/services/judge/judge_test.go index 595c630..988b77b 100644 --- a/services/judge/judge_test.go +++ b/services/judge/judge_test.go @@ -163,7 +163,7 @@ func TestUpsertJudgeCache(t *testing.T) { asserts.Equal(rankCache.Points, 1) asserts.Equal(rankCache.TotalSubmissions, 2) asserts.Equal(scoreCacheCache.SubmissionCount, 2) - asserts.Equal(scoreCacheCache.SolveTime, baseACJudge.CreateAt) + asserts.Equal(scoreCacheCache.SolveTime.In(time.Local), baseACJudge.CreateAt.In(time.Local)) preACJudge := baseACJudge preACJudge, err = CreateJudge(ctx, *preACJudge) @@ -209,7 +209,7 @@ func TestUpsertJudgeCache(t *testing.T) { asserts.Equal(rankCache.Points, 1) asserts.Equal(rankCache.TotalSubmissions, 2) asserts.Equal(scoreCacheCache.SubmissionCount, 2) - asserts.Equal(scoreCacheCache.SolveTime, preACJudge.CreateAt) + asserts.Equal(scoreCacheCache.SolveTime.In(time.Local), preACJudge.CreateAt.In(time.Local)) submissionCount, err := judge_model.GetBeforeSubmission(db, *preACJudge) if err != nil {