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:
ryan
2026-06-15 16:45:26 +08:00
parent 953af7d8db
commit 239711cea7
17 changed files with 73 additions and 78 deletions
+7 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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: 未登录
+3 -4
View File
@@ -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))
+5 -4
View File
@@ -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()
+10 -11
View File
@@ -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(),
}) })
+3 -4
View File
@@ -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"
+3 -4
View File
@@ -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) {
+2 -2
View File
@@ -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 (
+3 -4
View File
@@ -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
View File
+2 -3
View File
@@ -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
View File
@@ -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"