fix(sharecode): 支持分享码TTL配置并限制每MAC分享码数
- 配置增加 ShareCodeMaxPerMac 和 ShareCodeTTLMin 参数 - ShareCodeCache 支持自定义分享码有效期 TTL - 生成分享码时使用配置的 TTL 替代固定30分钟 - 共享码处理添加限制,同一MAC有效分享码数量限制 - 路由层、处理器层和任务调度中增加TTL及限额参数传递 - 同步脚本默认同步路径调整,去除默认同步Docker文件,增加docs目录同步 - 修正上传脚本拼接远程路径时尾部斜杠问题
This commit is contained in:
+2
-2
@@ -88,7 +88,7 @@ func main() {
|
||||
devicePersistTask := task.NewDevicePersistTask(rdb, deviceRepo, log)
|
||||
devicePersistTask.Start(5 * time.Minute)
|
||||
|
||||
shareCodeCache := cache.NewShareCodeCache(rdb)
|
||||
shareCodeCache := cache.NewShareCodeCache(rdb, time.Duration(cfg.ShareCodeTTLMin)*time.Minute)
|
||||
shareCodeRepo := repository.NewShareCodeRepository(db)
|
||||
shareCodePersistTask := task.NewShareCodePersistTask(shareCodeCache, shareCodeRepo, log)
|
||||
shareCodePersistTask.Start(5 * time.Minute)
|
||||
@@ -105,7 +105,7 @@ func main() {
|
||||
zap.String("region", cfg.S3.Region),
|
||||
)
|
||||
|
||||
engine := router.New(log, db, searchClient, rdb, cfg.Equalize, s3Storage)
|
||||
engine := router.New(log, db, searchClient, rdb, cfg.Equalize, s3Storage, cfg.ShareCodeMaxPerMac, time.Duration(cfg.ShareCodeTTLMin)*time.Minute)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: cfg.Addr(),
|
||||
|
||||
Vendored
+9
-8
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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")
|
||||
{
|
||||
|
||||
+9
-6
@@ -1,16 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REMOTE_DIR="/data/project/app-api/"
|
||||
REMOTE_DIR="/data/project/app-api"
|
||||
|
||||
DEFAULT_PATHS=(
|
||||
./cmd/
|
||||
./internal/
|
||||
./pkg
|
||||
./pkg/
|
||||
./docs/
|
||||
./go.mod
|
||||
./go.sum
|
||||
./Dockerfile
|
||||
./docker-compose.yml
|
||||
)
|
||||
|
||||
show_help() {
|
||||
@@ -30,12 +29,15 @@ show_help() {
|
||||
|
||||
路径:
|
||||
指定要同步的文件或目录,可传多个。
|
||||
省略时使用默认路径: cmd/ internal/ pkg go.mod go.sum Dockerfile docker-compose.yml
|
||||
省略时使用默认路径: cmd/ internal/ pkg/ docs/ go.mod go.sum
|
||||
|
||||
注意: Dockerfile 和 docker-compose.yml 未包含在默认路径中(如需同步请手动指定)
|
||||
|
||||
示例:
|
||||
upload.sh 同步默认路径到 api1 和 api2
|
||||
upload.sh api1 同步默认路径到 api1
|
||||
upload.sh api2 ./internal 同步 internal/ 到 api2
|
||||
upload.sh api1 Dockerfile 单独同步 Dockerfile 到 api1
|
||||
upload.sh -n 虚拟执行,预览同步到 api1 和 api2
|
||||
upload.sh -n api1 ./internal 虚拟执行,预览同步到 api1
|
||||
EOF
|
||||
@@ -73,7 +75,8 @@ upload_to() {
|
||||
local item rel remote_path
|
||||
for item in "${paths[@]}"; do
|
||||
rel="${item#./}"
|
||||
remote_path="${REMOTE_DIR}${rel}"
|
||||
rel="${rel%/}" # 剥离尾部斜杠,避免拼接时出现双斜杠
|
||||
remote_path="${REMOTE_DIR}/${rel}"
|
||||
|
||||
if [[ -d "$item" ]]; then
|
||||
echo " 同步目录: $item -> ${remote_host}:${remote_path}/"
|
||||
|
||||
Reference in New Issue
Block a user