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 := task.NewDevicePersistTask(rdb, deviceRepo, log)
|
||||||
devicePersistTask.Start(5 * time.Minute)
|
devicePersistTask.Start(5 * time.Minute)
|
||||||
|
|
||||||
shareCodeCache := cache.NewShareCodeCache(rdb)
|
shareCodeCache := cache.NewShareCodeCache(rdb, time.Duration(cfg.ShareCodeTTLMin)*time.Minute)
|
||||||
shareCodeRepo := repository.NewShareCodeRepository(db)
|
shareCodeRepo := repository.NewShareCodeRepository(db)
|
||||||
shareCodePersistTask := task.NewShareCodePersistTask(shareCodeCache, shareCodeRepo, log)
|
shareCodePersistTask := task.NewShareCodePersistTask(shareCodeCache, shareCodeRepo, log)
|
||||||
shareCodePersistTask.Start(5 * time.Minute)
|
shareCodePersistTask.Start(5 * time.Minute)
|
||||||
@@ -105,7 +105,7 @@ func main() {
|
|||||||
zap.String("region", cfg.S3.Region),
|
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{
|
srv := &http.Server{
|
||||||
Addr: cfg.Addr(),
|
Addr: cfg.Addr(),
|
||||||
|
|||||||
Vendored
+8
-7
@@ -51,7 +51,6 @@ const (
|
|||||||
shareImportFlushLockPref = "share:import:flush:lock:"
|
shareImportFlushLockPref = "share:import:flush:lock:"
|
||||||
shareMacIndexPrefix = "share:mac:"
|
shareMacIndexPrefix = "share:mac:"
|
||||||
shareCodeLength = 5
|
shareCodeLength = 5
|
||||||
shareCodeTTL = 30 * time.Minute
|
|
||||||
shareMacIndexTTL = 1 * time.Hour
|
shareMacIndexTTL = 1 * time.Hour
|
||||||
shareImportPendingTTL = 12 * time.Hour
|
shareImportPendingTTL = 12 * time.Hour
|
||||||
shareCodeCharset = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
|
shareCodeCharset = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
|
||||||
@@ -102,18 +101,20 @@ type ShareCodeData struct {
|
|||||||
|
|
||||||
type ShareCodeCache struct {
|
type ShareCodeCache struct {
|
||||||
rdb *redis.Client
|
rdb *redis.Client
|
||||||
|
codeTTL time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewShareCodeCache(rdb *redis.Client) *ShareCodeCache {
|
func NewShareCodeCache(rdb *redis.Client, codeTTL time.Duration) *ShareCodeCache {
|
||||||
return &ShareCodeCache{rdb: rdb}
|
return &ShareCodeCache{rdb: rdb, codeTTL: codeTTL}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ShareCodeTTL() time.Duration {
|
// ShareCodeTTL returns the configured share code TTL.
|
||||||
return shareCodeTTL
|
func (c *ShareCodeCache) ShareCodeTTL() time.Duration {
|
||||||
|
return c.codeTTL
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ShareCodeCache) Create(ctx context.Context, macAddr, ipAddr string, eqData []byte) (*ShareCodeData, error) {
|
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)
|
eqJSON := string(eqData)
|
||||||
|
|
||||||
for i := 0; i < shareCodeMaxRetries; i++ {
|
for i := 0; i < shareCodeMaxRetries; i++ {
|
||||||
@@ -125,7 +126,7 @@ func (c *ShareCodeCache) Create(ctx context.Context, macAddr, ipAddr string, eqD
|
|||||||
key := shareCodeKey(code)
|
key := shareCodeKey(code)
|
||||||
macIdxKey := shareMacIndexKey(macAddr)
|
macIdxKey := shareMacIndexKey(macAddr)
|
||||||
ok, err := shareCreateScript.Run(ctx, c.rdb, []string{key, sharePendingSet, macIdxKey},
|
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()
|
).Int()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create share code in redis: %w", err)
|
return nil, fmt.Errorf("create share code in redis: %w", err)
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ type Config struct {
|
|||||||
Equalize EqualizeConfig
|
Equalize EqualizeConfig
|
||||||
S3 S3Config
|
S3 S3Config
|
||||||
EnablePersistTask bool
|
EnablePersistTask bool
|
||||||
|
ShareCodeMaxPerMac int
|
||||||
|
ShareCodeTTLMin int
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load() (*Config, error) {
|
func Load() (*Config, error) {
|
||||||
@@ -57,6 +59,8 @@ func Load() (*Config, error) {
|
|||||||
Equalize: eq,
|
Equalize: eq,
|
||||||
S3: s3cfg,
|
S3: s3cfg,
|
||||||
EnablePersistTask: getEnv("ENABLE_PERSIST_TASK", "false") == "true",
|
EnablePersistTask: getEnv("ENABLE_PERSIST_TASK", "false") == "true",
|
||||||
|
ShareCodeMaxPerMac: getEnvInt("SHARE_CODE_MAX_PER_MAC", 1),
|
||||||
|
ShareCodeTTLMin: getEnvInt("SHARE_CODE_TTL_MIN", 30),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,3 +74,15 @@ func getEnv(key, fallback string) string {
|
|||||||
}
|
}
|
||||||
return fallback
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ import (
|
|||||||
type ShareCodeHandler struct {
|
type ShareCodeHandler struct {
|
||||||
cache *cache.ShareCodeCache
|
cache *cache.ShareCodeCache
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
|
maxPerMac int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewShareCodeHandler(shareCache *cache.ShareCodeCache, log *zap.Logger) *ShareCodeHandler {
|
func NewShareCodeHandler(shareCache *cache.ShareCodeCache, log *zap.Logger, maxPerMac int) *ShareCodeHandler {
|
||||||
return &ShareCodeHandler{cache: shareCache, log: log}
|
return &ShareCodeHandler{cache: shareCache, log: log, maxPerMac: maxPerMac}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportShareCode 导出分享码
|
// ExportShareCode 导出分享码
|
||||||
@@ -69,6 +70,25 @@ func (h *ShareCodeHandler) ExportShareCode(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := c.Request.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))
|
data, err := h.cache.Create(ctx, mac, clientIP, []byte(eqDataRaw))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.log.Error("create share code failed", zap.Error(err))
|
h.log.Error("create share code failed", zap.Error(err))
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/luxsin/app-api/internal/cache"
|
"github.com/luxsin/app-api/internal/cache"
|
||||||
@@ -18,7 +19,7 @@ import (
|
|||||||
ginSwagger "github.com/swaggo/gin-swagger"
|
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 := gin.New()
|
||||||
r.Use(gin.Recovery())
|
r.Use(gin.Recovery())
|
||||||
r.Use(middleware.RequestID())
|
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)
|
otaRepo := repository.NewOTARepository(db)
|
||||||
curveRepo := repository.NewCurveRepository(db)
|
curveRepo := repository.NewCurveRepository(db)
|
||||||
|
|
||||||
shareCodeCache := cache.NewShareCodeCache(rdb)
|
shareCodeCache := cache.NewShareCodeCache(rdb, shareCodeTTL)
|
||||||
|
|
||||||
// Handler
|
// Handler
|
||||||
health := handler.NewHealthHandler()
|
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)
|
ota := handler.NewOTAHandler(otaRepo, log)
|
||||||
curve := handler.NewCurveHandler(curveRepo, curveCache, eqCfg, s3, log)
|
curve := handler.NewCurveHandler(curveRepo, curveCache, eqCfg, s3, log)
|
||||||
modelCSV := handler.NewModelCSVHandler(s3, log)
|
modelCSV := handler.NewModelCSVHandler(s3, log)
|
||||||
shareCode := handler.NewShareCodeHandler(shareCodeCache, log)
|
shareCode := handler.NewShareCodeHandler(shareCodeCache, log, shareCodeMaxPerMac)
|
||||||
|
|
||||||
v1 := r.Group("/api/v1")
|
v1 := r.Group("/api/v1")
|
||||||
{
|
{
|
||||||
|
|||||||
+9
-6
@@ -1,16 +1,15 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
REMOTE_DIR="/data/project/app-api/"
|
REMOTE_DIR="/data/project/app-api"
|
||||||
|
|
||||||
DEFAULT_PATHS=(
|
DEFAULT_PATHS=(
|
||||||
./cmd/
|
./cmd/
|
||||||
./internal/
|
./internal/
|
||||||
./pkg
|
./pkg/
|
||||||
|
./docs/
|
||||||
./go.mod
|
./go.mod
|
||||||
./go.sum
|
./go.sum
|
||||||
./Dockerfile
|
|
||||||
./docker-compose.yml
|
|
||||||
)
|
)
|
||||||
|
|
||||||
show_help() {
|
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 和 api2
|
||||||
upload.sh api1 同步默认路径到 api1
|
upload.sh api1 同步默认路径到 api1
|
||||||
upload.sh api2 ./internal 同步 internal/ 到 api2
|
upload.sh api2 ./internal 同步 internal/ 到 api2
|
||||||
|
upload.sh api1 Dockerfile 单独同步 Dockerfile 到 api1
|
||||||
upload.sh -n 虚拟执行,预览同步到 api1 和 api2
|
upload.sh -n 虚拟执行,预览同步到 api1 和 api2
|
||||||
upload.sh -n api1 ./internal 虚拟执行,预览同步到 api1
|
upload.sh -n api1 ./internal 虚拟执行,预览同步到 api1
|
||||||
EOF
|
EOF
|
||||||
@@ -73,7 +75,8 @@ upload_to() {
|
|||||||
local item rel remote_path
|
local item rel remote_path
|
||||||
for item in "${paths[@]}"; do
|
for item in "${paths[@]}"; do
|
||||||
rel="${item#./}"
|
rel="${item#./}"
|
||||||
remote_path="${REMOTE_DIR}${rel}"
|
rel="${rel%/}" # 剥离尾部斜杠,避免拼接时出现双斜杠
|
||||||
|
remote_path="${REMOTE_DIR}/${rel}"
|
||||||
|
|
||||||
if [[ -d "$item" ]]; then
|
if [[ -d "$item" ]]; then
|
||||||
echo " 同步目录: $item -> ${remote_host}:${remote_path}/"
|
echo " 同步目录: $item -> ${remote_host}:${remote_path}/"
|
||||||
|
|||||||
Reference in New Issue
Block a user