mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-11 01:36:37 +08:00
refactor(service): adopt feature-based architecture and rename pkg/diskcache
- Moved GORM/Redis CAPTCHA manager from internal/service/cap directly into the cohesive CAPTCHA app folder at internal/apps/cap/. - Moved background system cleanup handler from internal/service/cleanup.go into internal/apps/upload/cleanup.go. - Completely removed the global internal/service directory to keep module logic self-contained. - Renamed the core utility engine pkg/diskcache to pkg/cache/disk to separate underlying utility code from db/config integrations. - Renamed DiskCache struct in pkg/cache/disk to Cache to resolve revive package-name stuttering warning. - Regenerated Swagger API documentation and confirmed all tests compile and pass with 0 linter issues.
This commit is contained in:
+7
-7
@@ -55,7 +55,7 @@ const docTemplate = `{
|
|||||||
"500": {
|
"500": {
|
||||||
"description": "内部服务错误",
|
"description": "内部服务错误",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_service_cap.RedeemResponse"
|
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,19 +89,19 @@ const docTemplate = `{
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "核销成功,返回 X-Cap-Token",
|
"description": "核销成功,返回 X-Cap-Token",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_service_cap.RedeemResponse"
|
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "参数错误或核销失败",
|
"description": "参数错误或核销失败",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_service_cap.RedeemResponse"
|
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "内部服务错误",
|
"description": "内部服务错误",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_service_cap.RedeemResponse"
|
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -620,7 +620,7 @@ const docTemplate = `{
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_diskcache.Status"
|
"$ref": "#/definitions/diskcache.Status"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5648,7 +5648,7 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"github_com_Rain-kl_Wavelet_internal_diskcache.Status": {
|
"diskcache.Status": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"base_path": {
|
"base_path": {
|
||||||
@@ -5671,7 +5671,7 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"github_com_Rain-kl_Wavelet_internal_service_cap.RedeemResponse": {
|
"github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"error": {
|
"error": {
|
||||||
|
|||||||
+7
-7
@@ -48,7 +48,7 @@
|
|||||||
"500": {
|
"500": {
|
||||||
"description": "内部服务错误",
|
"description": "内部服务错误",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_service_cap.RedeemResponse"
|
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,19 +82,19 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "核销成功,返回 X-Cap-Token",
|
"description": "核销成功,返回 X-Cap-Token",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_service_cap.RedeemResponse"
|
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "参数错误或核销失败",
|
"description": "参数错误或核销失败",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_service_cap.RedeemResponse"
|
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "内部服务错误",
|
"description": "内部服务错误",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_service_cap.RedeemResponse"
|
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -613,7 +613,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_diskcache.Status"
|
"$ref": "#/definitions/diskcache.Status"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5641,7 +5641,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"github_com_Rain-kl_Wavelet_internal_diskcache.Status": {
|
"diskcache.Status": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"base_path": {
|
"base_path": {
|
||||||
@@ -5664,7 +5664,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"github_com_Rain-kl_Wavelet_internal_service_cap.RedeemResponse": {
|
"github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"error": {
|
"error": {
|
||||||
|
|||||||
+7
-7
@@ -117,7 +117,7 @@ definitions:
|
|||||||
description: '"select" 或 "exec"'
|
description: '"select" 或 "exec"'
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
github_com_Rain-kl_Wavelet_internal_diskcache.Status:
|
diskcache.Status:
|
||||||
properties:
|
properties:
|
||||||
base_path:
|
base_path:
|
||||||
type: string
|
type: string
|
||||||
@@ -132,7 +132,7 @@ definitions:
|
|||||||
ttl_minutes:
|
ttl_minutes:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
github_com_Rain-kl_Wavelet_internal_service_cap.RedeemResponse:
|
github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse:
|
||||||
properties:
|
properties:
|
||||||
error:
|
error:
|
||||||
type: string
|
type: string
|
||||||
@@ -1431,7 +1431,7 @@ paths:
|
|||||||
"500":
|
"500":
|
||||||
description: 内部服务错误
|
description: 内部服务错误
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/github_com_Rain-kl_Wavelet_internal_service_cap.RedeemResponse'
|
$ref: '#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse'
|
||||||
summary: 生成人机验证难题
|
summary: 生成人机验证难题
|
||||||
tags:
|
tags:
|
||||||
- cap
|
- cap
|
||||||
@@ -1453,15 +1453,15 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: 核销成功,返回 X-Cap-Token
|
description: 核销成功,返回 X-Cap-Token
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/github_com_Rain-kl_Wavelet_internal_service_cap.RedeemResponse'
|
$ref: '#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse'
|
||||||
"400":
|
"400":
|
||||||
description: 参数错误或核销失败
|
description: 参数错误或核销失败
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/github_com_Rain-kl_Wavelet_internal_service_cap.RedeemResponse'
|
$ref: '#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse'
|
||||||
"500":
|
"500":
|
||||||
description: 内部服务错误
|
description: 内部服务错误
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/github_com_Rain-kl_Wavelet_internal_service_cap.RedeemResponse'
|
$ref: '#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse'
|
||||||
summary: 校验人机验证解答
|
summary: 校验人机验证解答
|
||||||
tags:
|
tags:
|
||||||
- cap
|
- cap
|
||||||
@@ -1773,7 +1773,7 @@ paths:
|
|||||||
- $ref: '#/definitions/response.Any'
|
- $ref: '#/definitions/response.Any'
|
||||||
- properties:
|
- properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/github_com_Rain-kl_Wavelet_internal_diskcache.Status'
|
$ref: '#/definitions/diskcache.Status'
|
||||||
type: object
|
type: object
|
||||||
"401":
|
"401":
|
||||||
description: 未登录
|
description: 未登录
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import ("bytes"
|
|||||||
"github.com/Rain-kl/Wavelet/internal/apps/upload"
|
"github.com/Rain-kl/Wavelet/internal/apps/upload"
|
||||||
"github.com/Rain-kl/Wavelet/internal/apps/user"
|
"github.com/Rain-kl/Wavelet/internal/apps/user"
|
||||||
"github.com/Rain-kl/Wavelet/internal/model"
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
"github.com/Rain-kl/Wavelet/internal/service"
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/task"
|
"github.com/Rain-kl/Wavelet/internal/task"
|
||||||
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||||
"github.com/Rain-kl/Wavelet/internal/util"
|
"github.com/Rain-kl/Wavelet/internal/util"
|
||||||
@@ -92,7 +91,7 @@ func TestListTaskTypes(t *testing.T) {
|
|||||||
foundCleanup := false
|
foundCleanup := false
|
||||||
foundWarmImageCache := false
|
foundWarmImageCache := false
|
||||||
for _, m := range taskMetas {
|
for _, m := range taskMetas {
|
||||||
if m.Type == service.TaskTypeSystemCleanup {
|
if m.Type == upload.TaskTypeSystemCleanup {
|
||||||
foundCleanup = true
|
foundCleanup = true
|
||||||
}
|
}
|
||||||
if m.Type == upload.TaskTypeWarmImageCache {
|
if m.Type == upload.TaskTypeWarmImageCache {
|
||||||
@@ -100,7 +99,7 @@ func TestListTaskTypes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !foundCleanup {
|
if !foundCleanup {
|
||||||
t.Errorf("expected task type %s to be listed", service.TaskTypeSystemCleanup)
|
t.Errorf("expected task type %s to be listed", upload.TaskTypeSystemCleanup)
|
||||||
}
|
}
|
||||||
if !foundWarmImageCache {
|
if !foundWarmImageCache {
|
||||||
t.Errorf("expected task type %s to be listed", upload.TaskTypeWarmImageCache)
|
t.Errorf("expected task type %s to be listed", upload.TaskTypeWarmImageCache)
|
||||||
@@ -116,7 +115,7 @@ func TestDispatchTask(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("dispatch valid task successfully", func(t *testing.T) {
|
t.Run("dispatch valid task successfully", func(t *testing.T) {
|
||||||
payload := DispatchTaskRequest{
|
payload := DispatchTaskRequest{
|
||||||
TaskType: service.TaskTypeSystemCleanup,
|
TaskType: upload.TaskTypeSystemCleanup,
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(payload)
|
body, _ := json.Marshal(payload)
|
||||||
req, _ := http.NewRequest("POST", "/api/v1/admin/tasks/dispatch", bytes.NewBuffer(body))
|
req, _ := http.NewRequest("POST", "/api/v1/admin/tasks/dispatch", bytes.NewBuffer(body))
|
||||||
|
|||||||
@@ -3,16 +3,17 @@
|
|||||||
|
|
||||||
package cap
|
package cap
|
||||||
|
|
||||||
import ("net/http"
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
caputil "github.com/Rain-kl/Wavelet/internal/service/cap"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
"github.com/Rain-kl/Wavelet/internal/common/response"
|
||||||
|
)
|
||||||
|
|
||||||
// VerifyMiddleware returns a Gin middleware that checks and consumes the X-Cap-Token header.
|
// VerifyMiddleware returns a Gin middleware that checks and consumes the X-Cap-Token header.
|
||||||
// enabledFunc is an optional callback allowing dynamic check of whether captcha protection is turned on.
|
// enabledFunc is an optional callback allowing dynamic check of whether captcha protection is turned on.
|
||||||
func VerifyMiddleware(mgr *caputil.Manager, scope string, enabledFunc func() bool) gin.HandlerFunc {
|
func VerifyMiddleware(mgr *Manager, scope string, enabledFunc func() bool) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
if enabledFunc != nil && !enabledFunc() {
|
if enabledFunc != nil && !enabledFunc() {
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ package cap
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
capService "github.com/Rain-kl/Wavelet/internal/service/cap"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type challengeRequest struct {
|
type challengeRequest struct {
|
||||||
@@ -28,7 +27,7 @@ type redeemRequest struct {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param request body challengeRequest false "可选范围限制参数"
|
// @Param request body challengeRequest false "可选范围限制参数"
|
||||||
// @Success 200 {object} cap.ChallengeResponse "成功返回 PoW 难题"
|
// @Success 200 {object} cap.ChallengeResponse "成功返回 PoW 难题"
|
||||||
// @Failure 500 {object} capService.RedeemResponse "内部服务错误"
|
// @Failure 500 {object} RedeemResponse "内部服务错误"
|
||||||
// @Router /api/cap/challenge [post]
|
// @Router /api/cap/challenge [post]
|
||||||
func Challenge(c *gin.Context) {
|
func Challenge(c *gin.Context) {
|
||||||
var req challengeRequest
|
var req challengeRequest
|
||||||
@@ -38,10 +37,10 @@ func Challenge(c *gin.Context) {
|
|||||||
req.Scope = "login"
|
req.Scope = "login"
|
||||||
}
|
}
|
||||||
|
|
||||||
mgr := capService.GetDefaultManager()
|
mgr := GetDefaultManager()
|
||||||
resp, err := mgr.Generate(c.Request.Context(), req.Scope)
|
resp, err := mgr.Generate(c.Request.Context(), req.Scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, capService.RedeemResponse{
|
c.JSON(http.StatusInternalServerError, RedeemResponse{
|
||||||
Success: false,
|
Success: false,
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
@@ -58,14 +57,14 @@ func Challenge(c *gin.Context) {
|
|||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param request body redeemRequest true "难题 Token 与解答 solutions 数组"
|
// @Param request body redeemRequest true "难题 Token 与解答 solutions 数组"
|
||||||
// @Success 200 {object} capService.RedeemResponse "核销成功,返回 X-Cap-Token"
|
// @Success 200 {object} RedeemResponse "核销成功,返回 X-Cap-Token"
|
||||||
// @Failure 400 {object} capService.RedeemResponse "参数错误或核销失败"
|
// @Failure 400 {object} RedeemResponse "参数错误或核销失败"
|
||||||
// @Failure 500 {object} capService.RedeemResponse "内部服务错误"
|
// @Failure 500 {object} RedeemResponse "内部服务错误"
|
||||||
// @Router /api/cap/redeem [post]
|
// @Router /api/cap/redeem [post]
|
||||||
func Redeem(c *gin.Context) {
|
func Redeem(c *gin.Context) {
|
||||||
var req redeemRequest
|
var req redeemRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, capService.RedeemResponse{
|
c.JSON(http.StatusBadRequest, RedeemResponse{
|
||||||
Success: false,
|
Success: false,
|
||||||
Error: "无效的参数",
|
Error: "无效的参数",
|
||||||
})
|
})
|
||||||
@@ -76,10 +75,10 @@ func Redeem(c *gin.Context) {
|
|||||||
req.Scope = "login"
|
req.Scope = "login"
|
||||||
}
|
}
|
||||||
|
|
||||||
mgr := capService.GetDefaultManager()
|
mgr := GetDefaultManager()
|
||||||
resp, err := mgr.Redeem(c.Request.Context(), req.Token, req.Solutions, req.Scope)
|
resp, err := mgr.Redeem(c.Request.Context(), req.Token, req.Solutions, req.Scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, capService.RedeemResponse{
|
c.JSON(http.StatusInternalServerError, RedeemResponse{
|
||||||
Success: false,
|
Success: false,
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ import ("bytes"
|
|||||||
"github.com/Rain-kl/Wavelet/internal/db"
|
"github.com/Rain-kl/Wavelet/internal/db"
|
||||||
"github.com/Rain-kl/Wavelet/internal/model"
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||||
capUtil "github.com/Rain-kl/Wavelet/internal/service/cap"
|
pkgcap "github.com/Rain-kl/Wavelet/pkg/cap"
|
||||||
pkgcap "github.com/Rain-kl/Wavelet/pkg/cap"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||||
@@ -34,7 +33,7 @@ func TestCapEndpointsAndMiddleware(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Login endpoint with CAPTCHA middleware
|
// Login endpoint with CAPTCHA middleware
|
||||||
r.POST("/api/v1/user/login", VerifyMiddleware(capUtil.GetDefaultManager(), "login", func() bool {
|
r.POST("/api/v1/user/login", VerifyMiddleware(GetDefaultManager(), "login", func() bool {
|
||||||
enabled, err := model.GetBoolByKey(context.Background(), model.ConfigKeyCapLoginEnabled)
|
enabled, err := model.GetBoolByKey(context.Background(), model.ConfigKeyCapLoginEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
@@ -106,7 +105,7 @@ func TestCapEndpointsAndMiddleware(t *testing.T) {
|
|||||||
t.Fatalf("expected 200 OK for redeem, got %d. Body: %s", w.Code, w.Body.String())
|
t.Fatalf("expected 200 OK for redeem, got %d. Body: %s", w.Code, w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
var redeemResp capUtil.RedeemResponse
|
var redeemResp RedeemResponse
|
||||||
if err := json.Unmarshal(w.Body.Bytes(), &redeemResp); err != nil {
|
if err := json.Unmarshal(w.Body.Bytes(), &redeemResp); err != nil {
|
||||||
t.Fatalf("failed to unmarshal redeem response: %v", err)
|
t.Fatalf("failed to unmarshal redeem response: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// Copyright 2026 Arctel.net
|
// Copyright 2026 Arctel.net
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
// Package service implements Wavelet's core background service processes.
|
// Package upload implements upload tasks and file cleanup services.
|
||||||
package service
|
package upload
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
"github.com/Rain-kl/Wavelet/internal/db"
|
"github.com/Rain-kl/Wavelet/internal/db"
|
||||||
"github.com/Rain-kl/Wavelet/internal/diskcache"
|
"github.com/Rain-kl/Wavelet/internal/diskcache"
|
||||||
"github.com/Rain-kl/Wavelet/internal/model"
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
"github.com/Rain-kl/Wavelet/internal/service"
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/storage"
|
"github.com/Rain-kl/Wavelet/internal/storage"
|
||||||
"github.com/Rain-kl/Wavelet/internal/task"
|
"github.com/Rain-kl/Wavelet/internal/task"
|
||||||
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||||
@@ -111,7 +110,7 @@ func TestSystemCleanupHandler_Execute(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// 执行 handler
|
// 执行 handler
|
||||||
handler := &service.SystemCleanupHandler{}
|
handler := &SystemCleanupHandler{}
|
||||||
result, err := handler.Execute(ctx, nil)
|
result, err := handler.Execute(ctx, nil)
|
||||||
|
|
||||||
// 验证结果
|
// 验证结果
|
||||||
@@ -162,7 +161,7 @@ func TestSystemCleanupHandler_ExecuteNoFiles(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// 没有任何上传记录
|
// 没有任何上传记录
|
||||||
handler := &service.SystemCleanupHandler{}
|
handler := &SystemCleanupHandler{}
|
||||||
result, err := handler.Execute(ctx, nil)
|
result, err := handler.Execute(ctx, nil)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -172,7 +171,7 @@ func TestSystemCleanupHandler_ExecuteNoFiles(t *testing.T) {
|
|||||||
|
|
||||||
func TestSystemCleanupHandler_ImplementsTaskHandler(t *testing.T) {
|
func TestSystemCleanupHandler_ImplementsTaskHandler(t *testing.T) {
|
||||||
// 编译期验证 SystemCleanupHandler 实现了 TaskHandler 接口
|
// 编译期验证 SystemCleanupHandler 实现了 TaskHandler 接口
|
||||||
var _ task.TaskHandler = (*service.SystemCleanupHandler)(nil)
|
var _ task.TaskHandler = (*SystemCleanupHandler)(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWarmImageCacheHandlerValidatePayload(t *testing.T) {
|
func TestWarmImageCacheHandlerValidatePayload(t *testing.T) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/db"
|
"github.com/Rain-kl/Wavelet/internal/db"
|
||||||
"github.com/Rain-kl/Wavelet/internal/model"
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
pkgcache "github.com/Rain-kl/Wavelet/pkg/diskcache"
|
pkgcache "github.com/Rain-kl/Wavelet/pkg/cache/disk"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Status represents the runtime cache statistics.
|
// Status represents the runtime cache statistics.
|
||||||
@@ -35,7 +35,7 @@ var ErrCacheMiss = pkgcache.ErrCacheMiss
|
|||||||
|
|
||||||
// DiskCache is a wrapper around the generic pkg/diskcache that integrates with the DB for configs.
|
// DiskCache is a wrapper around the generic pkg/diskcache that integrates with the DB for configs.
|
||||||
type DiskCache struct {
|
type DiskCache struct {
|
||||||
*pkgcache.DiskCache
|
*pkgcache.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import (
|
|||||||
"github.com/Rain-kl/Wavelet/internal/apps/upload"
|
"github.com/Rain-kl/Wavelet/internal/apps/upload"
|
||||||
"github.com/Rain-kl/Wavelet/internal/apps/user"
|
"github.com/Rain-kl/Wavelet/internal/apps/user"
|
||||||
"github.com/Rain-kl/Wavelet/internal/model"
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
capUtil "github.com/Rain-kl/Wavelet/internal/service/cap"
|
|
||||||
|
|
||||||
// Swagger 文档生成
|
// Swagger 文档生成
|
||||||
_ "github.com/Rain-kl/Wavelet/docs"
|
_ "github.com/Rain-kl/Wavelet/docs"
|
||||||
@@ -208,21 +207,21 @@ func registerOAuthRoutes(apiV1Router *gin.RouterGroup) {
|
|||||||
func registerUserRoutes(apiV1Router *gin.RouterGroup) {
|
func registerUserRoutes(apiV1Router *gin.RouterGroup) {
|
||||||
userRouter := apiV1Router.Group("/user")
|
userRouter := apiV1Router.Group("/user")
|
||||||
{
|
{
|
||||||
userRouter.POST("/login", capApp.VerifyMiddleware(capUtil.GetDefaultManager(), "login", func() bool {
|
userRouter.POST("/login", capApp.VerifyMiddleware(capApp.GetDefaultManager(), "login", func() bool {
|
||||||
enabled, err := model.GetBoolByKey(context.Background(), model.ConfigKeyCapLoginEnabled)
|
enabled, err := model.GetBoolByKey(context.Background(), model.ConfigKeyCapLoginEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return enabled
|
return enabled
|
||||||
}), user.Login)
|
}), user.Login)
|
||||||
userRouter.POST("/register", capApp.VerifyMiddleware(capUtil.GetDefaultManager(), "register", func() bool {
|
userRouter.POST("/register", capApp.VerifyMiddleware(capApp.GetDefaultManager(), "register", func() bool {
|
||||||
enabled, err := model.GetBoolByKey(context.Background(), model.ConfigKeyCapLoginEnabled)
|
enabled, err := model.GetBoolByKey(context.Background(), model.ConfigKeyCapLoginEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return enabled
|
return enabled
|
||||||
}), user.Register)
|
}), user.Register)
|
||||||
userRouter.POST("/send-email-code", capApp.VerifyMiddleware(capUtil.GetDefaultManager(), "send_email_code", func() bool {
|
userRouter.POST("/send-email-code", capApp.VerifyMiddleware(capApp.GetDefaultManager(), "send_email_code", func() bool {
|
||||||
enabled, err := model.GetBoolByKey(context.Background(), model.ConfigKeyCapLoginEnabled)
|
enabled, err := model.GetBoolByKey(context.Background(), model.ConfigKeyCapLoginEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/Rain-kl/Wavelet/internal/apps/admin/push"
|
"github.com/Rain-kl/Wavelet/internal/apps/admin/push"
|
||||||
"github.com/Rain-kl/Wavelet/internal/apps/upload"
|
"github.com/Rain-kl/Wavelet/internal/apps/upload"
|
||||||
"github.com/Rain-kl/Wavelet/internal/apps/user"
|
"github.com/Rain-kl/Wavelet/internal/apps/user"
|
||||||
"github.com/Rain-kl/Wavelet/internal/service"
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/task"
|
"github.com/Rain-kl/Wavelet/internal/task"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,8 +18,8 @@ func Register() {
|
|||||||
task.RegisterTaskMeta(upload.StorageMigrationMeta)
|
task.RegisterTaskMeta(upload.StorageMigrationMeta)
|
||||||
|
|
||||||
// system cleanup
|
// system cleanup
|
||||||
task.RegisterHandler(service.SystemCleanupTask, &service.SystemCleanupHandler{})
|
task.RegisterHandler(upload.SystemCleanupTask, &upload.SystemCleanupHandler{})
|
||||||
task.RegisterTaskMeta(service.SystemCleanupMeta)
|
task.RegisterTaskMeta(upload.SystemCleanupMeta)
|
||||||
|
|
||||||
// upload
|
// upload
|
||||||
task.RegisterHandler(upload.WarmImageCacheTask, &upload.WarmImageCacheHandler{})
|
task.RegisterHandler(upload.WarmImageCacheTask, &upload.WarmImageCacheHandler{})
|
||||||
|
|||||||
+18
-18
@@ -1,8 +1,8 @@
|
|||||||
// Copyright 2026 Arctel.net
|
// Copyright 2026 Arctel.net
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
// Package diskcache implements a platform-level disk-backed cache with size limit, TTL, and LRU eviction.
|
// Package disk implements a platform-level disk-backed cache with size limit, TTL, and LRU eviction.
|
||||||
package diskcache
|
package disk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
@@ -44,8 +44,8 @@ type Status struct {
|
|||||||
BasePath string `json:"base_path"`
|
BasePath string `json:"base_path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiskCache implements the disk-backed cache with size limits, TTL, and LRU eviction.
|
// Cache implements the disk-backed cache with size limits, TTL, and LRU eviction.
|
||||||
type DiskCache struct {
|
type Cache struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
d *diskv.Diskv
|
d *diskv.Diskv
|
||||||
basePath string
|
basePath string
|
||||||
@@ -65,15 +65,15 @@ type cacheItem struct {
|
|||||||
expiredAt time.Time
|
expiredAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new DiskCache instance.
|
// New creates a new Cache instance.
|
||||||
func New(basePath string) *DiskCache {
|
func New(basePath string) *Cache {
|
||||||
d := diskv.New(diskv.Options{
|
d := diskv.New(diskv.Options{
|
||||||
BasePath: basePath,
|
BasePath: basePath,
|
||||||
Transform: func(_ string) []string { return []string{} }, // flat structure for easy walk
|
Transform: func(_ string) []string { return []string{} }, // flat structure for easy walk
|
||||||
CacheSizeMax: 1024 * 1024, // 1MB in-memory cache size for diskv itself
|
CacheSizeMax: 1024 * 1024, // 1MB in-memory cache size for diskv itself
|
||||||
})
|
})
|
||||||
|
|
||||||
c := &DiskCache{
|
c := &Cache{
|
||||||
d: d,
|
d: d,
|
||||||
basePath: basePath,
|
basePath: basePath,
|
||||||
maxSize: defaultMaxSizeMB * 1024 * 1024, // 100MB default
|
maxSize: defaultMaxSizeMB * 1024 * 1024, // 100MB default
|
||||||
@@ -91,7 +91,7 @@ func New(basePath string) *DiskCache {
|
|||||||
// Set stores a key-value pair in the cache.
|
// Set stores a key-value pair in the cache.
|
||||||
// Use DefaultExpiration for the configured default TTL, NoExpiration for no
|
// Use DefaultExpiration for the configured default TTL, NoExpiration for no
|
||||||
// TTL, or a positive duration for a business-specific TTL.
|
// TTL, or a positive duration for a business-specific TTL.
|
||||||
func (c *DiskCache) Set(key string, value []byte, ttl time.Duration) error {
|
func (c *Cache) Set(key string, value []byte, ttl time.Duration) error {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ func (c *DiskCache) Set(key string, value []byte, ttl time.Duration) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get retrieves a key's value from the cache.
|
// Get retrieves a key's value from the cache.
|
||||||
func (c *DiskCache) Get(key string) ([]byte, error) {
|
func (c *Cache) Get(key string) ([]byte, error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
@@ -185,13 +185,13 @@ func (c *DiskCache) Get(key string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete removes a key-value pair from the cache.
|
// Delete removes a key-value pair from the cache.
|
||||||
func (c *DiskCache) Delete(key string) error {
|
func (c *Cache) Delete(key string) error {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
return c.deleteUnlocked(key)
|
return c.deleteUnlocked(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DiskCache) deleteUnlocked(key string) error {
|
func (c *Cache) deleteUnlocked(key string) error {
|
||||||
if elem, ok := c.items[key]; ok {
|
if elem, ok := c.items[key]; ok {
|
||||||
item := elem.Value.(*cacheItem)
|
item := elem.Value.(*cacheItem)
|
||||||
c.currentSize -= item.size
|
c.currentSize -= item.size
|
||||||
@@ -202,7 +202,7 @@ func (c *DiskCache) deleteUnlocked(key string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear flushes all cached elements.
|
// Clear flushes all cached elements.
|
||||||
func (c *DiskCache) Clear() error {
|
func (c *Cache) Clear() error {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
@@ -214,7 +214,7 @@ func (c *DiskCache) Clear() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Status returns the cache status.
|
// Status returns the cache status.
|
||||||
func (c *DiskCache) Status() Status {
|
func (c *Cache) Status() Status {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
defer c.mu.RUnlock()
|
defer c.mu.RUnlock()
|
||||||
|
|
||||||
@@ -229,7 +229,7 @@ func (c *DiskCache) Status() Status {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePolicy dynamically updates policies.
|
// UpdatePolicy dynamically updates policies.
|
||||||
func (c *DiskCache) UpdatePolicy(maxSizeMB int64, ttlMinutes int64, lruEnabled bool) {
|
func (c *Cache) UpdatePolicy(maxSizeMB int64, ttlMinutes int64, lruEnabled bool) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ func (c *DiskCache) UpdatePolicy(maxSizeMB int64, ttlMinutes int64, lruEnabled b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// evict evicts oldest items if current size exceeds maxSize and LRU is enabled.
|
// evict evicts oldest items if current size exceeds maxSize and LRU is enabled.
|
||||||
func (c *DiskCache) evict() {
|
func (c *Cache) evict() {
|
||||||
if !c.lruEnabled {
|
if !c.lruEnabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -256,7 +256,7 @@ func (c *DiskCache) evict() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// loadTracker scans the cache directory on startup to rebuild memory state.
|
// loadTracker scans the cache directory on startup to rebuild memory state.
|
||||||
func (c *DiskCache) loadTracker() error {
|
func (c *Cache) loadTracker() error {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
@@ -325,7 +325,7 @@ func (c *DiskCache) loadTracker() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StartCleanupWorker periodically cleans up expired cache items.
|
// StartCleanupWorker periodically cleans up expired cache items.
|
||||||
func (c *DiskCache) StartCleanupWorker(interval time.Duration) {
|
func (c *Cache) StartCleanupWorker(interval time.Duration) {
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
c.cleanExpired()
|
c.cleanExpired()
|
||||||
@@ -333,7 +333,7 @@ func (c *DiskCache) StartCleanupWorker(interval time.Duration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// cleanExpired scans memory for expired items and removes them.
|
// cleanExpired scans memory for expired items and removes them.
|
||||||
func (c *DiskCache) cleanExpired() {
|
func (c *Cache) cleanExpired() {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright 2026 Arctel.net
|
// Copyright 2026 Arctel.net
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package diskcache
|
package disk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
Reference in New Issue
Block a user