fix(sharecode): 支持分享码TTL配置并限制每MAC分享码数

- 配置增加 ShareCodeMaxPerMac 和 ShareCodeTTLMin 参数
- ShareCodeCache 支持自定义分享码有效期 TTL
- 生成分享码时使用配置的 TTL 替代固定30分钟
- 共享码处理添加限制,同一MAC有效分享码数量限制
- 路由层、处理器层和任务调度中增加TTL及限额参数传递
- 同步脚本默认同步路径调整,去除默认同步Docker文件,增加docs目录同步
- 修正上传脚本拼接远程路径时尾部斜杠问题
This commit is contained in:
eafonyang
2026-06-12 18:29:08 +08:00
parent 28fb798926
commit 598bbd4998
6 changed files with 82 additions and 41 deletions
+9 -8
View File
@@ -51,7 +51,6 @@ const (
shareImportFlushLockPref = "share:import:flush:lock:"
shareMacIndexPrefix = "share:mac:"
shareCodeLength = 5
shareCodeTTL = 30 * time.Minute
shareMacIndexTTL = 1 * time.Hour
shareImportPendingTTL = 12 * time.Hour
shareCodeCharset = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
@@ -101,19 +100,21 @@ type ShareCodeData struct {
}
type ShareCodeCache struct {
rdb *redis.Client
rdb *redis.Client
codeTTL time.Duration
}
func NewShareCodeCache(rdb *redis.Client) *ShareCodeCache {
return &ShareCodeCache{rdb: rdb}
func NewShareCodeCache(rdb *redis.Client, codeTTL time.Duration) *ShareCodeCache {
return &ShareCodeCache{rdb: rdb, codeTTL: codeTTL}
}
func ShareCodeTTL() time.Duration {
return shareCodeTTL
// ShareCodeTTL returns the configured share code TTL.
func (c *ShareCodeCache) ShareCodeTTL() time.Duration {
return c.codeTTL
}
func (c *ShareCodeCache) Create(ctx context.Context, macAddr, ipAddr string, eqData []byte) (*ShareCodeData, error) {
expireAt := time.Now().Add(shareCodeTTL)
expireAt := time.Now().Add(c.codeTTL)
eqJSON := string(eqData)
for i := 0; i < shareCodeMaxRetries; i++ {
@@ -125,7 +126,7 @@ func (c *ShareCodeCache) Create(ctx context.Context, macAddr, ipAddr string, eqD
key := shareCodeKey(code)
macIdxKey := shareMacIndexKey(macAddr)
ok, err := shareCreateScript.Run(ctx, c.rdb, []string{key, sharePendingSet, macIdxKey},
macAddr, ipAddr, eqJSON, expireAt.Format(time.RFC3339), int(shareCodeTTL.Seconds()), code, expireAt.Unix(), int(shareMacIndexTTL.Seconds()),
macAddr, ipAddr, eqJSON, expireAt.Format(time.RFC3339), int(c.codeTTL.Seconds()), code, expireAt.Unix(), int(shareMacIndexTTL.Seconds()),
).Int()
if err != nil {
return nil, fmt.Errorf("create share code in redis: %w", err)
+34 -18
View File
@@ -7,15 +7,17 @@ import (
)
type Config struct {
Env string
Host string
Port int
Database DatabaseConfig
Meilisearch MeilisearchConfig
Redis RedisConfig
Equalize EqualizeConfig
S3 S3Config
EnablePersistTask bool
Env string
Host string
Port int
Database DatabaseConfig
Meilisearch MeilisearchConfig
Redis RedisConfig
Equalize EqualizeConfig
S3 S3Config
EnablePersistTask bool
ShareCodeMaxPerMac int
ShareCodeTTLMin int
}
func Load() (*Config, error) {
@@ -48,15 +50,17 @@ func Load() (*Config, error) {
s3cfg := loadS3(env)
return &Config{
Env: env,
Host: getEnv("APP_HOST", "0.0.0.0"),
Port: port,
Database: db,
Meilisearch: ms,
Redis: rd,
Equalize: eq,
S3: s3cfg,
EnablePersistTask: getEnv("ENABLE_PERSIST_TASK", "false") == "true",
Env: env,
Host: getEnv("APP_HOST", "0.0.0.0"),
Port: port,
Database: db,
Meilisearch: ms,
Redis: rd,
Equalize: eq,
S3: s3cfg,
EnablePersistTask: getEnv("ENABLE_PERSIST_TASK", "false") == "true",
ShareCodeMaxPerMac: getEnvInt("SHARE_CODE_MAX_PER_MAC", 1),
ShareCodeTTLMin: getEnvInt("SHARE_CODE_TTL_MIN", 30),
}, nil
}
@@ -70,3 +74,15 @@ func getEnv(key, fallback string) string {
}
return fallback
}
func getEnvInt(key string, fallback int) int {
v := os.Getenv(key)
if v == "" {
return fallback
}
n, err := strconv.Atoi(v)
if err != nil {
return fallback
}
return n
}
+24 -4
View File
@@ -12,12 +12,13 @@ import (
)
type ShareCodeHandler struct {
cache *cache.ShareCodeCache
log *zap.Logger
cache *cache.ShareCodeCache
log *zap.Logger
maxPerMac int
}
func NewShareCodeHandler(shareCache *cache.ShareCodeCache, log *zap.Logger) *ShareCodeHandler {
return &ShareCodeHandler{cache: shareCache, log: log}
func NewShareCodeHandler(shareCache *cache.ShareCodeCache, log *zap.Logger, maxPerMac int) *ShareCodeHandler {
return &ShareCodeHandler{cache: shareCache, log: log, maxPerMac: maxPerMac}
}
// ExportShareCode 导出分享码
@@ -69,6 +70,25 @@ func (h *ShareCodeHandler) ExportShareCode(c *gin.Context) {
}
ctx := c.Request.Context()
// 限制同一 MAC 在有效期内的分享码数量
existing, err := h.cache.ListByMac(ctx, mac)
if err != nil {
h.log.Error("check existing share codes by mac failed", zap.String("mac", mac), zap.Error(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "系统错误",
})
return
}
if len(existing) >= h.maxPerMac {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "已有未过期的分享码,请稍后再试",
})
return
}
data, err := h.cache.Create(ctx, mac, clientIP, []byte(eqDataRaw))
if err != nil {
h.log.Error("create share code failed", zap.Error(err))
+4 -3
View File
@@ -2,6 +2,7 @@ package router
import (
"database/sql"
"time"
"github.com/gin-gonic/gin"
"github.com/luxsin/app-api/internal/cache"
@@ -18,7 +19,7 @@ import (
ginSwagger "github.com/swaggo/gin-swagger"
)
func New(log *zap.Logger, db *sql.DB, searchClient *search.Client, rdb *redis.Client, eqCfg config.EqualizeConfig, s3 *storage.S3Storage) *gin.Engine {
func New(log *zap.Logger, db *sql.DB, searchClient *search.Client, rdb *redis.Client, eqCfg config.EqualizeConfig, s3 *storage.S3Storage, shareCodeMaxPerMac int, shareCodeTTL time.Duration) *gin.Engine {
r := gin.New()
r.Use(gin.Recovery())
r.Use(middleware.RequestID())
@@ -36,7 +37,7 @@ func New(log *zap.Logger, db *sql.DB, searchClient *search.Client, rdb *redis.Cl
otaRepo := repository.NewOTARepository(db)
curveRepo := repository.NewCurveRepository(db)
shareCodeCache := cache.NewShareCodeCache(rdb)
shareCodeCache := cache.NewShareCodeCache(rdb, shareCodeTTL)
// Handler
health := handler.NewHealthHandler()
@@ -47,7 +48,7 @@ func New(log *zap.Logger, db *sql.DB, searchClient *search.Client, rdb *redis.Cl
ota := handler.NewOTAHandler(otaRepo, log)
curve := handler.NewCurveHandler(curveRepo, curveCache, eqCfg, s3, log)
modelCSV := handler.NewModelCSVHandler(s3, log)
shareCode := handler.NewShareCodeHandler(shareCodeCache, log)
shareCode := handler.NewShareCodeHandler(shareCodeCache, log, shareCodeMaxPerMac)
v1 := r.Group("/api/v1")
{