Skip to content

Commit 65cd313

Browse files
committed
feat(ecache): add an in-memory cache implementation
1 parent 6547eb4 commit 65cd313

4 files changed

Lines changed: 104 additions & 8 deletions

File tree

ecache/entity_cache.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"time"
88

99
"github.com/pkg/errors"
10-
"golang.org/x/exp/constraints"
1110
"golang.org/x/sync/singleflight"
1211
)
1312

@@ -29,12 +28,17 @@ var (
2928
type Cache interface {
3029
Del(ctx context.Context, keys ...string) (affected int64, err error)
3130
Get(ctx context.Context, key string) (val []byte, err error)
32-
GetAsUint64(ctx context.Context, key string) (val uint64, err error)
3331
Set(ctx context.Context, key string, val []byte, expire time.Duration) error
32+
GetAsUint64(ctx context.Context, key string) (val uint64, err error)
33+
SetUint64(ctx context.Context, key string, val uint64, expire time.Duration) error
3434
IsKeyNotFound(err error) bool
3535
}
3636

37-
type CacheableEntity[INT constraints.Integer] interface {
37+
type UnsignedInt interface {
38+
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
39+
}
40+
41+
type CacheableEntity[INT UnsignedInt] interface {
3842
ID() INT
3943
}
4044

@@ -54,7 +58,7 @@ func DelCachedEntity(ctx context.Context, cache Cache, key string) (affected int
5458
// 查找顺序:主键ID --> 缓存中实体对象 --> 数据库中实体对象。
5559
// 局限:
5660
// 1、缓存 key格式无法自定义且不够内聚
57-
func GetEntityByID[T CacheableEntity[INT], INT constraints.Integer](
61+
func GetEntityByID[T CacheableEntity[INT], INT UnsignedInt](
5862
ctx context.Context,
5963
cache Cache,
6064
entityKeyPrefix string,
@@ -108,7 +112,7 @@ func GetEntityByID[T CacheableEntity[INT], INT constraints.Integer](
108112
}
109113

110114
// GetEntitiesByID 从Redis缓存中读取指定实体列表。若缓存中不存在,则从指定方法中读取并存入Redis缓存。
111-
func GetEntitiesByID[T CacheableEntity[INT], INT constraints.Integer](
115+
func GetEntitiesByID[T CacheableEntity[INT], INT UnsignedInt](
112116
ctx context.Context,
113117
cache Cache,
114118
entityKeyPrefix string,
@@ -168,7 +172,7 @@ func GetEntitiesByID[T CacheableEntity[INT], INT constraints.Integer](
168172

169173
// UniqueKey 唯一索引约束
170174
type UniqueKey interface {
171-
~string | constraints.Integer
175+
~string | UnsignedInt
172176
}
173177

174178
// GetEntityByUniqueKey 返回唯一索引值对应的实体对象。
@@ -177,7 +181,7 @@ type UniqueKey interface {
177181
// 1、Redis key格式无法自定义
178182
// 2、不支持联合唯一索引
179183
// 3、多一次查询
180-
func GetEntityByUniqueKey[T CacheableEntity[INT], INT constraints.Integer, UK UniqueKey](
184+
func GetEntityByUniqueKey[T CacheableEntity[INT], INT UnsignedInt, UK UniqueKey](
181185
ctx context.Context,
182186
cache Cache,
183187
entityKeyPrefix string,
@@ -218,7 +222,7 @@ func GetEntityByUniqueKey[T CacheableEntity[INT], INT constraints.Integer, UK Un
218222
return nil, errors.WithStack(err)
219223
}
220224
// 3、保存唯一索引值(键)与主键ID(值)关系
221-
if err = cache.Set(ctx, ukKey, fmt.Appendf(nil, "%d", id), 0); err != nil {
225+
if err = cache.SetUint64(ctx, ukKey, uint64(id), -1); err != nil {
222226
return nil, errors.WithStack(err)
223227
}
224228
// 4、根据主键ID尝试返回实体对象

ecache/memory/memory_cache.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package memory
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
gocache "github.com/patrickmn/go-cache"
8+
"github.com/pkg/errors"
9+
)
10+
11+
var (
12+
// ErrKeyNotFound indicates the requested key does not exist in the cache
13+
ErrKeyNotFound = errors.New("key not found")
14+
15+
// ErrUnexpectedType occurs when attempting to retrieve data with unexpected type
16+
ErrUnexpectedType = errors.New("unknown data type")
17+
)
18+
19+
type cache struct {
20+
db *gocache.Cache
21+
}
22+
23+
// NewCache creates an in-memory cache instance with configurable expiration.
24+
// defaultExpiration: default TTL for cache entries (use time.Duration(0) for no expiration)
25+
// cleanupInterval: interval for automatic removal of expired entries (use time.Duration(0) to disable)
26+
func NewCache(defaultExpiration, cleanupInterval time.Duration) *cache {
27+
return &cache{
28+
db: gocache.New(defaultExpiration, cleanupInterval),
29+
}
30+
}
31+
32+
// Del removes multiple entries from the cache.
33+
// Returns number of deleted items and any potential error (currently always nil)
34+
func (c *cache) Del(ctx context.Context, keys ...string) (affected int64, err error) {
35+
for _, key := range keys {
36+
c.db.Delete(key)
37+
}
38+
return int64(len(keys)), nil
39+
}
40+
41+
// Get retrieves a value as byte slice from the cache.
42+
// Returns ErrKeyNotFound if the key doesn't exist
43+
// Returns ErrUnexpectedType if value cannot be cast to []byte
44+
func (c *cache) Get(ctx context.Context, key string) (val []byte, err error) {
45+
v, ok := c.db.Get(key)
46+
if !ok {
47+
return nil, errors.WithStack(ErrKeyNotFound)
48+
}
49+
item := v.(gocache.Item)
50+
if val, ok = item.Object.([]byte); !ok {
51+
return nil, errors.WithStack(ErrUnexpectedType)
52+
}
53+
return val, nil
54+
}
55+
56+
// GetAsUint64 retrieves a numeric value from the cache as uint64.
57+
// Returns ErrKeyNotFound if the key doesn't exist
58+
// Returns ErrUnexpectedType if value cannot be cast to uint64
59+
func (c *cache) GetAsUint64(ctx context.Context, key string) (val uint64, err error) {
60+
v, ok := c.db.Get(key)
61+
if !ok {
62+
return 0, errors.WithStack(ErrKeyNotFound)
63+
}
64+
item := v.(gocache.Item)
65+
if val, ok = item.Object.(uint64); !ok {
66+
return 0, errors.WithStack(ErrUnexpectedType)
67+
}
68+
return val, nil
69+
}
70+
71+
// Set stores a byte slice in the cache with expiration.
72+
// expire: 0 uses default expiration, <0 means no expiration
73+
func (c *cache) Set(ctx context.Context, key string, val []byte, expire time.Duration) error {
74+
c.db.Set(key, val, expire)
75+
return nil
76+
}
77+
78+
// SetUint64 stores a numeric value in the cache with expiration.
79+
// expire: 0 uses default expiration, <0 means no expiration
80+
func (c *cache) SetUint64(ctx context.Context, key string, val uint64, expire time.Duration) error {
81+
c.db.Set(key, val, expire)
82+
return nil
83+
}
84+
85+
// IsKeyNotFound checks if an error indicates missing key
86+
// Helps determine error type without direct dependency on package errors
87+
func (c *cache) IsKeyNotFound(err error) bool {
88+
return errors.Is(err, ErrKeyNotFound)
89+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/dromara/carbon/v2 v2.5.2
1010
github.com/guregu/null/v5 v5.0.0
1111
github.com/nicksnyder/go-i18n/v2 v2.5.1
12+
github.com/patrickmn/go-cache v2.1.0+incompatible
1213
github.com/pkg/errors v0.9.1
1314
github.com/redis/go-redis/v9 v9.7.3
1415
github.com/stretchr/testify v1.10.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ github.com/guregu/null/v5 v5.0.0 h1:PRxjqyOekS11W+w/7Vfz6jgJE/BCwELWtgvOJzddimw=
2020
github.com/guregu/null/v5 v5.0.0/go.mod h1:SjupzNy+sCPtwQTKWhUCqjhVCO69hpsl2QsZrWHjlwU=
2121
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
2222
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
23+
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
24+
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
2325
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
2426
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
2527
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

0 commit comments

Comments
 (0)