新增分享码功能
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/luxsin/app-api/internal/cache"
|
||||
"github.com/luxsin/app-api/internal/model"
|
||||
"github.com/luxsin/app-api/internal/repository"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ShareCodePersistTask struct {
|
||||
cache *cache.ShareCodeCache
|
||||
repo *repository.ShareCodeRepository
|
||||
log *zap.Logger
|
||||
}
|
||||
|
||||
func NewShareCodePersistTask(shareCache *cache.ShareCodeCache, repo *repository.ShareCodeRepository, log *zap.Logger) *ShareCodePersistTask {
|
||||
return &ShareCodePersistTask{cache: shareCache, repo: repo, log: log}
|
||||
}
|
||||
|
||||
func (t *ShareCodePersistTask) Start(interval time.Duration) {
|
||||
go func() {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
t.Persist()
|
||||
}
|
||||
}()
|
||||
t.log.Info("share code persist task started", zap.String("interval", interval.String()))
|
||||
}
|
||||
|
||||
func (t *ShareCodePersistTask) Persist() {
|
||||
t.persistExports()
|
||||
t.persistImports()
|
||||
}
|
||||
|
||||
func (t *ShareCodePersistTask) persistExports() {
|
||||
ctx := context.Background()
|
||||
|
||||
codes, err := t.cache.ListPending(ctx)
|
||||
if err != nil {
|
||||
t.log.Error("list pending share codes failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if len(codes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
t.log.Info("persisting share code export logs", zap.Int("count", len(codes)))
|
||||
|
||||
var persisted, skipped, failed int
|
||||
|
||||
for _, code := range codes {
|
||||
ok := t.persistExportOne(ctx, code)
|
||||
switch ok {
|
||||
case 1:
|
||||
persisted++
|
||||
case 0:
|
||||
skipped++
|
||||
default:
|
||||
failed++
|
||||
}
|
||||
}
|
||||
|
||||
if persisted > 0 || failed > 0 {
|
||||
t.log.Info("share code export logs persist finished",
|
||||
zap.Int("persisted", persisted),
|
||||
zap.Int("skipped", skipped),
|
||||
zap.Int("failed", failed),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ShareCodePersistTask) persistImports() {
|
||||
ctx := context.Background()
|
||||
|
||||
logs, err := t.cache.ListPendingImports(ctx)
|
||||
if err != nil {
|
||||
t.log.Error("list pending share import logs failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if len(logs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
t.log.Info("persisting share code import logs", zap.Int("count", len(logs)))
|
||||
|
||||
var persisted, skipped, failed int
|
||||
|
||||
for field, payload := range logs {
|
||||
ok := t.persistImportOne(ctx, field, payload)
|
||||
switch ok {
|
||||
case 1:
|
||||
persisted++
|
||||
case 0:
|
||||
skipped++
|
||||
default:
|
||||
failed++
|
||||
}
|
||||
}
|
||||
|
||||
if persisted > 0 || failed > 0 {
|
||||
t.log.Info("share code import logs persist finished",
|
||||
zap.Int("persisted", persisted),
|
||||
zap.Int("skipped", skipped),
|
||||
zap.Int("failed", failed),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// persistExportOne 返回值:1=成功刷入,0=跳过,-1=失败
|
||||
func (t *ShareCodePersistTask) persistExportOne(ctx context.Context, shareCode string) int {
|
||||
acquired, err := t.cache.AcquireFlushLock(ctx, shareCode)
|
||||
if err != nil {
|
||||
t.log.Error("acquire share flush lock failed", zap.String("share_code", shareCode), zap.Error(err))
|
||||
return -1
|
||||
}
|
||||
if !acquired {
|
||||
return 0
|
||||
}
|
||||
defer func() {
|
||||
if err := t.cache.ReleaseFlushLock(ctx, shareCode); err != nil {
|
||||
t.log.Warn("release share flush lock failed", zap.String("share_code", shareCode), zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
data, err := t.cache.Get(ctx, shareCode)
|
||||
if err != nil {
|
||||
t.log.Error("get share code from redis failed", zap.String("share_code", shareCode), zap.Error(err))
|
||||
return -1
|
||||
}
|
||||
if data == nil {
|
||||
_ = t.cache.MarkPersisted(ctx, shareCode)
|
||||
return 0
|
||||
}
|
||||
if data.Persisted {
|
||||
return 0
|
||||
}
|
||||
|
||||
exists, err := t.repo.HasExportLog(ctx, shareCode)
|
||||
if err != nil {
|
||||
t.log.Error("check share export log failed", zap.String("share_code", shareCode), zap.Error(err))
|
||||
return -1
|
||||
}
|
||||
if exists {
|
||||
if err := t.cache.MarkPersisted(ctx, shareCode); err != nil {
|
||||
t.log.Error("mark share code persisted failed", zap.String("share_code", shareCode), zap.Error(err))
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
logEntry := model.ShareCodeLog{
|
||||
MacAddr: data.MacAddr,
|
||||
ShareCode: data.ShareCode,
|
||||
Action: "export",
|
||||
IpAddr: data.IpAddr,
|
||||
EqData: []byte(data.EqData),
|
||||
ExpireAt: repository.ParseShareExpireAt(data.ExpireAt),
|
||||
}
|
||||
if err := t.repo.InsertLog(ctx, logEntry); err != nil {
|
||||
t.log.Error("insert share export log failed", zap.String("share_code", shareCode), zap.Error(err))
|
||||
return -1
|
||||
}
|
||||
|
||||
if err := t.cache.MarkPersisted(ctx, shareCode); err != nil {
|
||||
t.log.Error("mark share code persisted failed", zap.String("share_code", shareCode), zap.Error(err))
|
||||
return -1
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// persistImportOne 返回值:1=成功刷入,0=跳过,-1=失败
|
||||
func (t *ShareCodePersistTask) persistImportOne(ctx context.Context, field, payload string) int {
|
||||
acquired, err := t.cache.AcquireImportFlushLock(ctx, field)
|
||||
if err != nil {
|
||||
t.log.Error("acquire share import flush lock failed", zap.String("field", field), zap.Error(err))
|
||||
return -1
|
||||
}
|
||||
if !acquired {
|
||||
return 0
|
||||
}
|
||||
defer func() {
|
||||
if err := t.cache.ReleaseImportFlushLock(ctx, field); err != nil {
|
||||
t.log.Warn("release share import flush lock failed", zap.String("field", field), zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
var pending cache.ShareImportPendingLog
|
||||
if err := json.Unmarshal([]byte(payload), &pending); err != nil {
|
||||
t.log.Error("unmarshal share import pending log failed", zap.String("field", field), zap.Error(err))
|
||||
return -1
|
||||
}
|
||||
|
||||
expireAt, err := time.Parse(time.RFC3339, pending.ExpireAt)
|
||||
if err != nil {
|
||||
t.log.Error("parse share import expire_at failed", zap.String("field", field), zap.Error(err))
|
||||
return -1
|
||||
}
|
||||
|
||||
logEntry := model.ShareCodeLog{
|
||||
MacAddr: pending.MacAddr,
|
||||
ShareCode: pending.ShareCode,
|
||||
Action: "import",
|
||||
IpAddr: pending.IpAddr,
|
||||
EqData: []byte(pending.EqData),
|
||||
ExpireAt: repository.ParseShareExpireAt(expireAt),
|
||||
}
|
||||
if err := t.repo.InsertLog(ctx, logEntry); err != nil {
|
||||
t.log.Error("insert share import log failed",
|
||||
zap.String("share_code", pending.ShareCode),
|
||||
zap.String("field", field),
|
||||
zap.Error(err),
|
||||
)
|
||||
return -1
|
||||
}
|
||||
|
||||
if err := t.cache.RemovePendingImport(ctx, field); err != nil {
|
||||
t.log.Error("remove pending share import log failed", zap.String("field", field), zap.Error(err))
|
||||
return -1
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
Reference in New Issue
Block a user