support periodically cleaning up expired tokens

This commit is contained in:
MaysWind
2024-08-12 00:49:07 +08:00
parent 80b8b9afdd
commit 52dfee9ca6
22 changed files with 506 additions and 7 deletions
+97
View File
@@ -0,0 +1,97 @@
package cmd
import (
"fmt"
"github.com/urfave/cli/v2"
"github.com/mayswind/ezbookkeeping/pkg/cron"
"github.com/mayswind/ezbookkeeping/pkg/log"
)
// CronJobs represents the cron command
var CronJobs = &cli.Command{
Name: "cron",
Usage: "ezBookkeeping cron job utilities",
Subcommands: []*cli.Command{
{
Name: "list",
Usage: "List all enabled cron jobs",
Action: listAllCronJobs,
},
{
Name: "run",
Usage: "Run specified cron job",
Action: runCronJob,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Required: true,
Usage: "Cron job name",
},
},
},
},
}
func listAllCronJobs(c *cli.Context) error {
config, err := initializeSystem(c)
if err != nil {
return err
}
err = cron.InitializeCronJobSchedulerContainer(config, false)
if err != nil {
log.BootErrorf("[cron_jobs.listAllCronJobs] initializes cron job scheduler failed, because %s", err.Error())
return err
}
cronJobs := cron.Container.GetAllJobs()
if len(cronJobs) < 1 {
log.BootErrorf("[cron_jobs.listAllCronJobs] there are no enabled cron jobs")
return err
}
for i := 0; i < len(cronJobs); i++ {
if i > 0 {
fmt.Printf("---\n")
}
cronJob := cronJobs[i]
fmt.Printf("[Name] %s\n", cronJob.Name)
fmt.Printf("[Description] %s\n", cronJob.Description)
fmt.Printf("[Interval] Every %s\n", cronJob.Interval)
}
return nil
}
func runCronJob(c *cli.Context) error {
config, err := initializeSystem(c)
if err != nil {
return err
}
err = cron.InitializeCronJobSchedulerContainer(config, false)
if err != nil {
log.BootErrorf("[cron_jobs.runCronJob] initializes cron job scheduler failed, because %s", err.Error())
return err
}
jobName := c.String("name")
err = cron.Container.SyncRunJobNow(jobName)
if err != nil {
log.BootErrorf("[cron_jobs.runCronJob] failed to run cron job \"%s\", because %s", jobName, err.Error())
return err
}
return nil
}
+8
View File
@@ -15,6 +15,7 @@ import (
"github.com/mayswind/ezbookkeeping/pkg/api"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/cron"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/log"
"github.com/mayswind/ezbookkeeping/pkg/middlewares"
@@ -62,6 +63,13 @@ func startWebServer(c *cli.Context) error {
return err
}
err = cron.InitializeCronJobSchedulerContainer(config, true)
if err != nil {
log.BootErrorf("[webserver.startWebServer] initializes cron job scheduler failed, because %s", err.Error())
return err
}
serverInfo := fmt.Sprintf("current server id is %d, current instance id is %d", requestid.Container.Current.GetCurrentServerUniqId(), requestid.Container.Current.GetCurrentInstanceUniqId())
uuidServerInfo := ""
if config.UuidGeneratorType == settings.InternalUuidGeneratorType {
+4
View File
@@ -150,6 +150,10 @@ cleanup_interval = 60
# Set to 0 to disable duplicate checker for new data submissions, default is 300 (5 minutes)
duplicate_submissions_interval = 300
[cron]
# Set to true to clean up expired tokens periodically
enable_remove_expired_tokens = true
[security]
# Used for signing, you must change it to keep your user data safe before you first run ezBookkeeping
secret_key =
+1
View File
@@ -36,6 +36,7 @@ func main() {
cmd.WebServer,
cmd.Database,
cmd.UserData,
cmd.CronJobs,
cmd.SecurityUtils,
cmd.Utilities,
},
+4
View File
@@ -7,6 +7,7 @@ require (
github.com/gin-contrib/cache v1.3.0
github.com/gin-contrib/gzip v1.0.1
github.com/gin-gonic/gin v1.10.0
github.com/go-co-op/gocron/v2 v2.11.0
github.com/go-playground/validator/v10 v10.22.0
github.com/go-sql-driver/mysql v1.8.1
github.com/golang-jwt/jwt/v5 v5.2.1
@@ -49,6 +50,7 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/gomodule/redigo v1.8.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jonboulle/clockwork v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
@@ -61,6 +63,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
@@ -69,6 +72,7 @@ require (
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
+8
View File
@@ -43,6 +43,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-co-op/gocron/v2 v2.11.0 h1:IOowNA6SzwdRFnD4/Ol3Kj6G2xKfsoiiGq2Jhhm9bvE=
github.com/go-co-op/gocron/v2 v2.11.0/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -67,6 +69,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
@@ -109,6 +113,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 h1:pyecQtsPmlkCsMkYhT5iZ+sUXuwee+OvfuJjinEA3ko=
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62/go.mod h1:65XQgovT59RWatovFwnwocoUxiI/eENTnOY5GK3STuY=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
@@ -146,6 +152,8 @@ golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
+101
View File
@@ -0,0 +1,101 @@
package cron
import (
"time"
"github.com/go-co-op/gocron/v2"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/log"
"github.com/mayswind/ezbookkeeping/pkg/settings"
)
// CronJobSchedulerContainer contains the current cron job scheduler
type CronJobSchedulerContainer struct {
scheduler gocron.Scheduler
allJobs []*CronJob
allJobsMap map[string]*CronJob
allGocronJobsMap map[string]gocron.Job
}
// Initialize a cron job scheduler container singleton instance
var (
Container = &CronJobSchedulerContainer{
allJobsMap: make(map[string]*CronJob),
allGocronJobsMap: make(map[string]gocron.Job),
}
)
// InitializeCronJobSchedulerContainer initializes the cron job scheduler according to the config
func InitializeCronJobSchedulerContainer(config *settings.Config, startScheduler bool) error {
var err error
Container.scheduler, err = gocron.NewScheduler(
gocron.WithLocation(time.Local),
gocron.WithLogger(NewGocronLoggerAdapter()),
)
if err != nil {
return err
}
Container.registerAllJobs(config)
if startScheduler {
Container.scheduler.Start()
}
return nil
}
// GetAllJobs returns all the cron jobs
func (c *CronJobSchedulerContainer) GetAllJobs() []*CronJob {
return c.allJobs
}
// SyncRunJobNow runs the specified cron job synchronously now
func (c *CronJobSchedulerContainer) SyncRunJobNow(jobName string) error {
if jobName == "" {
return errs.ErrCronJobNameIsEmpty
}
job := c.allJobsMap[jobName]
if job == nil {
return errs.ErrCronJobNotExistsOrNotEnabled
}
gocronJob := c.allGocronJobsMap[jobName]
if gocronJob == nil {
return errs.ErrCronJobNotExistsOrNotEnabled
}
job.doRun()
return nil
}
func (c *CronJobSchedulerContainer) registerAllJobs(config *settings.Config) {
if config.EnableRemoveExpiredTokens {
Container.registerIntervalJob(RemoveExpiredTokensJob)
}
}
func (c *CronJobSchedulerContainer) registerIntervalJob(job *CronJob) {
gocronJob, err := c.scheduler.NewJob(
gocron.DurationJob(job.Interval),
gocron.NewTask(job.doRun),
gocron.WithName(job.Name),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
)
if err == nil {
c.allJobs = append(c.allJobs, job)
c.allJobsMap[job.Name] = job
c.allGocronJobsMap[job.Name] = gocronJob
log.Infof("[cron_container.registerJob] job \"%s\" has been registered", job.Name)
log.Debugf("[cron_container.registerJob] job \"%s\" gocron id is %s", job.Name, gocronJob.ID())
} else {
log.Errorf("[cron_container.registerJob] job \"%s\" cannot be been registered, because %s", job.Name, err.Error())
}
}
+49
View File
@@ -0,0 +1,49 @@
package cron
import (
"fmt"
"time"
"github.com/mayswind/ezbookkeeping/pkg/duplicatechecker"
"github.com/mayswind/ezbookkeeping/pkg/log"
"github.com/mayswind/ezbookkeeping/pkg/utils"
)
type CronJob struct {
Name string
Description string
Interval time.Duration
Run func() error
}
func (c *CronJob) doRun() {
start := time.Now()
localAddr, err := utils.GetLocalIPAddressesString()
if err != nil {
log.Warnf("[cron_job.doRun] job \"%s\" cannot get local ipv4 address, because %s", c.Name, err.Error())
return
}
currentInfo := fmt.Sprintf("ip: %s, startTime: %d", localAddr, time.Now().Unix())
found, runningInfo := duplicatechecker.Container.GetOrSetCronJobRunningInfo(c.Name, currentInfo, c.Interval)
if found {
log.Warnf("[cron_job.doRun] job \"%s\" is already running (%s)", c.Name, runningInfo)
return
}
err = c.Run()
duplicatechecker.Container.Current.RemoveCronJobRunningInfo(c.Name)
now := time.Now()
if err != nil {
log.Errorf("[cron_job.doRun] failed to run job \"%s\", because %s", c.Name, err.Error())
return
}
cost := now.Sub(start).Nanoseconds() / 1e6
log.Infof("[cron_job.doRun] run job \"%s\" successfully, cost %dms", c.Name, cost)
}
+16
View File
@@ -0,0 +1,16 @@
package cron
import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/services"
)
var RemoveExpiredTokensJob = &CronJob{
Name: "RemoveExpiredTokens",
Description: "Periodically remove expired user tokens from the database.",
Interval: 24 * time.Hour,
Run: func() error {
return services.Tokens.DeleteAllExpiredTokens(nil)
},
}
+36
View File
@@ -0,0 +1,36 @@
package cron
import (
"github.com/go-co-op/gocron/v2"
"github.com/mayswind/ezbookkeeping/pkg/log"
)
// GocronLoggerAdapter represents the logger adapter for gocron
type GocronLoggerAdapter struct {
}
// Debug logs debug log
func (logger GocronLoggerAdapter) Debug(msg string, args ...any) {
log.Debugf(msg, args...)
}
// Info logs info log
func (logger GocronLoggerAdapter) Info(msg string, args ...any) {
log.Infof(msg, args...)
}
// Warn logs warn log
func (logger GocronLoggerAdapter) Warn(msg string, args ...any) {
log.Warnf(msg, args...)
}
// Error logs error log
func (logger GocronLoggerAdapter) Error(msg string, args ...any) {
log.Errorf(msg, args...)
}
// NewGocronLoggerAdapter returns a new GocronLoggerAdapter instance
func NewGocronLoggerAdapter() gocron.Logger {
return GocronLoggerAdapter{}
}
+10
View File
@@ -12,6 +12,16 @@ type DataStore struct {
databases []*Database
}
// Count returns total count of database instances
func (s *DataStore) Count() int {
return len(s.databases)
}
// Get returns a database instance by index
func (s *DataStore) Get(index int) *Database {
return s.databases[index]
}
// Choose returns a database instance by sharding key
func (s *DataStore) Choose(key int64) *Database {
return s.databases[0]
@@ -1,7 +1,11 @@
package duplicatechecker
import "time"
// DuplicateChecker is common duplicate checker interface
type DuplicateChecker interface {
GetSubmissionRemark(checkerType DuplicateCheckerType, uid int64, identification string) (bool, string)
SetSubmissionRemark(checkerType DuplicateCheckerType, uid int64, identification string, remark string)
GetOrSetCronJobRunningInfo(jobName string, runningInfo string, runningInterval time.Duration) (bool, string)
RemoveCronJobRunningInfo(jobName string)
}
@@ -1,6 +1,8 @@
package duplicatechecker
import (
"time"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/settings"
)
@@ -36,3 +38,13 @@ func (c *DuplicateCheckerContainer) GetSubmissionRemark(checkerType DuplicateChe
func (c *DuplicateCheckerContainer) SetSubmissionRemark(checkerType DuplicateCheckerType, uid int64, identification string, remark string) {
c.Current.SetSubmissionRemark(checkerType, uid, identification, remark)
}
// GetOrSetCronJobRunningInfo returns the running info when the cron job is running or saves the running info by the current duplicate checker
func (c *DuplicateCheckerContainer) GetOrSetCronJobRunningInfo(jobName string, runningInfo string, runningInterval time.Duration) (bool, string) {
return c.Current.GetOrSetCronJobRunningInfo(jobName, runningInfo, runningInterval)
}
// RemoveCronJobRunningInfo removes the running info of the cron job by the current duplicate checker
func (c *DuplicateCheckerContainer) RemoveCronJobRunningInfo(jobName string) {
c.Current.RemoveCronJobRunningInfo(jobName)
}
@@ -5,9 +5,9 @@ type DuplicateCheckerType uint8
// Types of uuid
const (
DUPLICATE_CHECKER_TYPE_DEFAULT DuplicateCheckerType = 0
DUPLICATE_CHECKER_TYPE_NEW_ACCOUNT DuplicateCheckerType = 1
DUPLICATE_CHECKER_TYPE_NEW_CATEGORY DuplicateCheckerType = 2
DUPLICATE_CHECKER_TYPE_NEW_TRANSACTION DuplicateCheckerType = 3
DUPLICATE_CHECKER_TYPE_NEW_TEMPLATE DuplicateCheckerType = 4
DUPLICATE_CHECKER_TYPE_BACKGROUND_CRON_JOB DuplicateCheckerType = 0
DUPLICATE_CHECKER_TYPE_NEW_ACCOUNT DuplicateCheckerType = 1
DUPLICATE_CHECKER_TYPE_NEW_CATEGORY DuplicateCheckerType = 2
DUPLICATE_CHECKER_TYPE_NEW_TRANSACTION DuplicateCheckerType = 3
DUPLICATE_CHECKER_TYPE_NEW_TEMPLATE DuplicateCheckerType = 4
)
@@ -2,6 +2,8 @@ package duplicatechecker
import (
"fmt"
"sync"
"time"
"github.com/patrickmn/go-cache"
@@ -11,6 +13,8 @@ import (
// InMemoryDuplicateChecker represents in-memory duplicate checker
type InMemoryDuplicateChecker struct {
cache *cache.Cache
mutex sync.Mutex
}
// NewInMemoryDuplicateChecker returns a new in-memory duplicate checker
@@ -38,6 +42,33 @@ func (c *InMemoryDuplicateChecker) SetSubmissionRemark(checkerType DuplicateChec
c.cache.Set(c.getCacheKey(checkerType, uid, identification), remark, cache.DefaultExpiration)
}
// GetOrSetCronJobRunningInfo returns the running info when the cron job is running or saves the running info by the current duplicate checker
func (c *InMemoryDuplicateChecker) GetOrSetCronJobRunningInfo(jobName string, runningInfo string, runningInterval time.Duration) (bool, string) {
c.mutex.Lock()
defer c.mutex.Unlock()
existedRunningInfo, found := c.cache.Get(c.getCacheKey(DUPLICATE_CHECKER_TYPE_BACKGROUND_CRON_JOB, 0, jobName))
if found {
return true, existedRunningInfo.(string)
}
expiration := runningInterval
if expiration > 1*time.Second {
expiration = expiration - 1*time.Second
}
c.cache.Set(c.getCacheKey(DUPLICATE_CHECKER_TYPE_BACKGROUND_CRON_JOB, 0, jobName), runningInfo, expiration)
return false, ""
}
// RemoveCronJobRunningInfo removes the running info of the cron job by the current duplicate checker
func (c *InMemoryDuplicateChecker) RemoveCronJobRunningInfo(jobName string) {
c.cache.Delete(c.getCacheKey(DUPLICATE_CHECKER_TYPE_BACKGROUND_CRON_JOB, 0, jobName))
}
func (c *InMemoryDuplicateChecker) getCacheKey(checkerType DuplicateCheckerType, uid int64, identification string) string {
return fmt.Sprintf("%d|%d|%s", checkerType, uid, identification)
}
+9
View File
@@ -0,0 +1,9 @@
package errs
import "net/http"
// Error codes related to cron jobs
var (
ErrCronJobNameIsEmpty = NewSystemError(SystemSubcategoryCron, 0, http.StatusInternalServerError, "cron job name is empty")
ErrCronJobNotExistsOrNotEnabled = NewSystemError(SystemSubcategoryCron, 1, http.StatusInternalServerError, "cron job not exists or not enabled")
)
+52
View File
@@ -1,5 +1,9 @@
package errs
import (
"strings"
)
// ErrorCategory represents error category
type ErrorCategory int32
@@ -16,6 +20,7 @@ const (
SystemSubcategoryDatabase = 2
SystemSubcategoryMail = 3
SystemSubcategoryLogging = 4
SystemSubcategoryCron = 5
)
// Sub categories of normal error
@@ -44,6 +49,10 @@ type Error struct {
Context any
}
type MultiErrors struct {
errors []error
}
// Error returns the error message
func (err *Error) Error() string {
return err.Message
@@ -66,6 +75,34 @@ func New(category ErrorCategory, subCategory int32, index int32, httpStatusCode
}
}
// Error returns the error message
func (err *MultiErrors) Error() string {
if len(err.errors) == 1 {
return err.errors[0].Error()
}
var ret strings.Builder
var lastErrorChar byte
ret.WriteString("multi errors: ")
for i := 0; i < len(err.errors); i++ {
if i > 0 {
if lastErrorChar == '.' {
ret.WriteString(" ")
} else {
ret.WriteString(", ")
}
}
errorContent := err.errors[i].Error()
lastErrorChar = errorContent[len(errorContent)-1]
ret.WriteString(errorContent)
}
return ret.String()
}
// NewSystemError returns a new system error instance
func NewSystemError(subCategory int32, index int32, httpStatusCode int, message string) *Error {
return New(CATEGORY_SYSTEM, subCategory, index, httpStatusCode, message)
@@ -107,6 +144,21 @@ func NewErrorWithContext(baseError *Error, context any) *Error {
}
}
// NewMultiErrorOrNil returns a new multi error instance
func NewMultiErrorOrNil(errors ...error) error {
count := len(errors)
if count < 1 {
return nil
} else if count == 1 {
return errors[0]
}
return &MultiErrors{
errors: errors,
}
}
// Or would return the error from err parameter if the this error is defined in this project,
// or return the default error
func Or(err error, defaultErr *Error) *Error {
+2 -2
View File
@@ -7,13 +7,13 @@ const TokenMaxUserAgentLength = 255
// TokenRecord represents token data stored in database
type TokenRecord struct {
Uid int64 `xorm:"PK INDEX(IDX_token_record_uid_type_expired_time)"`
Uid int64 `xorm:"PK INDEX(IDX_token_record_uid_type_expired_time) INDEX(IDX_token_record_expired_time)"`
UserTokenId int64 `xorm:"PK"`
TokenType core.TokenType `xorm:"INDEX(IDX_token_record_uid_type_expired_time) TINYINT NOT NULL"`
Secret string `xorm:"VARCHAR(10) NOT NULL"`
UserAgent string `xorm:"VARCHAR(255)"`
CreatedUnixTime int64 `xorm:"PK"`
ExpiredUnixTime int64 `xorm:"INDEX(IDX_token_record_uid_type_expired_time)"`
ExpiredUnixTime int64 `xorm:"INDEX(IDX_token_record_uid_type_expired_time) INDEX(IDX_token_record_expired_time)"`
LastSeenUnixTime int64
}
+10
View File
@@ -23,6 +23,16 @@ func (s *ServiceUsingDB) TokenDB(uid int64) *datastore.Database {
return s.container.TokenStore.Choose(uid)
}
// TokenDBByIndex returns the datastore by index
func (s *ServiceUsingDB) TokenDBByIndex(index int) *datastore.Database {
return s.container.TokenStore.Get(index)
}
// TokenDBCount returns the count of datastores which contains user token
func (s *ServiceUsingDB) TokenDBCount() int {
return s.container.TokenStore.Count()
}
// UserDataDB returns the datastore which contains user data
func (s *ServiceUsingDB) UserDataDB(uid int64) *datastore.Database {
return s.container.UserDataStore.Choose(uid)
+26
View File
@@ -207,6 +207,32 @@ func (s *TokenService) DeleteTokensByType(c *core.Context, uid int64, tokenType
})
}
// DeleteAllExpiredTokens deletes all expired tokens
func (s *TokenService) DeleteAllExpiredTokens(c *core.Context) error {
var errors []error
totalCount := int64(0)
for i := 0; i < s.TokenDBCount(); i++ {
err := s.TokenDBByIndex(i).DoTransaction(c, func(sess *xorm.Session) error {
count, err := sess.Where("expired_unix_time<=?", time.Now().Unix()).Delete(&models.TokenRecord{})
totalCount += count
return err
})
if err != nil {
errors = append(errors, err)
}
}
if totalCount > 0 {
log.Infof("[tokens.DeleteAllExpiredTokens] %d expired tokens have been deleted", totalCount)
} else if len(errors) == 0 {
log.Infof("[tokens.DeleteAllExpiredTokens] no expired tokens have been deleted")
}
return errs.NewMultiErrorOrNil(errors...)
}
// ExistsValidTokenByType returns whether the given token type exists
func (s *TokenService) ExistsValidTokenByType(c *core.Context, uid int64, tokenType core.TokenType) (bool, error) {
if uid <= 0 {
+15
View File
@@ -252,6 +252,9 @@ type Config struct {
DuplicateSubmissionsInterval uint32
DuplicateSubmissionsIntervalDuration time.Duration
// Cron
EnableRemoveExpiredTokens bool
// Secret
SecretKeyNoSet bool
SecretKey string
@@ -373,6 +376,12 @@ func LoadConfiguration(configFilePath string) (*Config, error) {
return nil, err
}
err = loadCronConfiguration(config, cfgFile, "cron")
if err != nil {
return nil, err
}
err = loadSecurityConfiguration(config, cfgFile, "security")
if err != nil {
@@ -674,6 +683,12 @@ func loadDuplicateCheckerConfiguration(config *Config, configFile *ini.File, sec
return nil
}
func loadCronConfiguration(config *Config, configFile *ini.File, sectionName string) error {
config.EnableRemoveExpiredTokens = getConfigItemBoolValue(configFile, sectionName, "enable_remove_expired_tokens", false)
return nil
}
func loadSecurityConfiguration(config *Config, configFile *ini.File, sectionName string) error {
config.SecretKeyNoSet = !getConfigItemIsSet(configFile, sectionName, "secret_key")
config.SecretKey = getConfigItemStringValue(configFile, sectionName, "secret_key", defaultSecretKey)
+6
View File
@@ -99,6 +99,12 @@
"url": "https://github.com/minio/minio-go",
"licenseUrl": "https://github.com/minio/minio-go/blob/v7.0.74/LICENSE"
},
{
"name": "gocron",
"copyright": "Copyright (c) 2014, 辣椒面",
"url": "https://github.com/go-co-op/gocron",
"licenseUrl": "https://github.com/go-co-op/gocron/blob/v2.11.0/LICENSE"
},
{
"name": "barcode",
"copyright": "Copyright (c) 2014 Florian Sundermann",