diff --git a/.github/workflows/cache.yml b/.github/workflows/cache.yml deleted file mode 100644 index ead760f4..00000000 --- a/.github/workflows/cache.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Cache -on: [push, pull_request] - -jobs: - - cache: - name: Cache - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: [ubuntu-latest] - go: ['1.21.x'] - - services: - memcached: - image: memcached:latest - ports: - - 11211:11211 - redis: - image: redis:latest - ports: - - 6379:6379 - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - - - name: Set git to use LF - run: | - git config --global core.autocrlf false - git config --global core.eol lf - - - name: Check out code into the Go module directory - uses: actions/checkout@v4 - - - name: Set up Go ${{ matrix.go }} - uses: actions/setup-go@v4 - with: - go-version: ${{ matrix.go }} - id: go - - - name: Vet - run: | - cd cache/caches - go vet -v ./... - - - name: Test - env: - LANG: en - run: | - cd cache/caches - go test -v ./... diff --git a/app/cache.go b/app/cache.go index e90fbd66..c411a8e0 100644 --- a/app/cache.go +++ b/app/cache.go @@ -6,12 +6,12 @@ import ( "strings" "time" + "github.com/issue9/cache" "github.com/issue9/scheduled" "github.com/issue9/web" - "github.com/issue9/web/cache" - "github.com/issue9/web/cache/caches" "github.com/issue9/web/locales" + "github.com/issue9/web/server" ) var cacheFactory = map[string]CacheBuilder{} @@ -91,16 +91,16 @@ func init() { return nil, nil, err } - drv, job := caches.NewMemory() + drv, job := server.NewMemory() return drv, &Job{Ticker: d, Job: job}, nil }, "", "memory") RegisterCache(func(dsn string) (cache.Driver, *Job, error) { - return caches.NewMemcache(strings.Split(dsn, ";")...), nil, nil + return server.NewMemcache(strings.Split(dsn, ";")...), nil, nil }, "memcached", "memcache") RegisterCache(func(dsn string) (cache.Driver, *Job, error) { - drv, err := caches.NewRedisFromURL(dsn) + drv, err := server.NewRedisFromURL(dsn) if err != nil { return nil, nil, err } diff --git a/app/config.go b/app/config.go index 07f1a41c..190ef399 100644 --- a/app/config.go +++ b/app/config.go @@ -7,11 +7,11 @@ import ( "runtime/debug" "time" + "github.com/issue9/cache" "github.com/issue9/config" "golang.org/x/text/language" "github.com/issue9/web" - "github.com/issue9/web/cache" "github.com/issue9/web/locales" "github.com/issue9/web/logs" "github.com/issue9/web/server" diff --git a/cache/cache.go b/cache/cache.go deleted file mode 100644 index b8b63dd7..00000000 --- a/cache/cache.go +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: MIT - -// Package cache 统一的缓存系统接口 -package cache - -import ( - "time" - - "github.com/issue9/localeutil" -) - -var ( - errCacheMiss = localeutil.Error("cache miss") - errInvalidKey = localeutil.Error("invalid cache key") -) - -const Forever = 0 // 永不过时 - -// Cache 缓存内容的访问接口 -type Cache interface { - // Get 获取缓存项 - // - // 当前不存在时,返回 [ErrCacheMiss] 错误。 - // key 为缓存项的唯一 ID; - // v 为缓存写入的地址,应该始终为指针类型; - // - // NOTE: 不能正确获取由 [Cache.Counter] 设置的值,[Cache.Counter] - // 的实现是基于缓存系统原生的功能,存储方式与当前的实现可能是不同的。 - Get(key string, v any) error - - // Set 设置或是添加缓存项 - // - // key 表示保存该数据的唯一 ID; - // val 表示保存的数据对象,如果是结构体,需要所有的字段都是公开的或是实现了 - // [Serializer] 接口,否则在缓存过程中将失去这些非公开的字段。 - // ttl 表示过了该时间,缓存项将被回收。如果该值为 [Forever],该值永远不会回收。 - Set(key string, val any, ttl time.Duration) error - - // Delete 删除一个缓存项 - Delete(string) error - - // Exists 判断一个缓存项是否存在 - Exists(string) bool - - // Counter 返回计数器操作接口 - // - // val 和 ttl 表示在该计数器不存在时,初始化的值以及回收时间。 - Counter(key string, val uint64, ttl time.Duration) Counter -} - -// Counter 计数器需要实现的接口 -// -// [Cache] 支持自定义的序列化接口,但是对于自增等纯数值操作, -// 各个缓存服务都实现自有的快捷操作,无法适用自定义的序列化。 -// -// 由 Counter 设置的值,可能无法由 [Cache.Get] 读取到正确的值, -// 但是可以由 [Cache.Exists]、[Cache.Delete] 和 [Cache.Set] 进行相应的操作。 -// -// 各个驱动对自增值的类型定义是不同的, -// 只有在 [0, math.MaxInt32] 范围内的数值是安全的。 -type Counter interface { - // Incr 增加计数并返回增加后的值 - Incr(uint64) (uint64, error) - - // Decr 减少数值并返回减少后的值 - Decr(uint64) (uint64, error) - - // Value 返回该计数器的当前值 - // - // 如果出错将返回默认值。 - Value() (uint64, error) - - // Delete 删除当前的计数器 - // - // 当计数器不存在时,不应该返回错误。 - Delete() error -} - -// Cleanable 可清除所有缓存内容的接口 -type Cleanable interface { - Cache - - // Clean 清除所有的缓存内容 - Clean() error -} - -// Driver 所有缓存驱动需要实现的接口 -// -// 对于数据的序列化相关操作可直接调用 [caches.Marshal] 和 [caches.Unmarshal] -// 进行处理,如果需要自行处理,需要对实现 [Serializer] 接口的数据进行处理。 -// -// 新的驱动可以采用 [cachetest] 对接口进行测试,看是否符合要求。 -// -// [cachetest]: https://pkg.go.dev/github.com/issue9/web/cache/cachetest -type Driver interface { - Cleanable - - // Close 关闭客户端 - Close() error - - // Driver 关联的底层驱动实例 - Driver() any -} - -// ErrCacheMiss 当不存在缓存项时返回的错误 -func ErrCacheMiss() error { return errCacheMiss } - -// ErrInvalidKey key 的格式无效 -// -// 部分驱动对 key 可能是有特殊要求的, -// 比如在文件系统中,可能会不允许在 key 中包含 .. 或是 / 等,碰到此类情况,可返回此错误信息。 -func ErrInvalidKey() error { return errInvalidKey } diff --git a/cache/caches/README.md b/cache/caches/README.md deleted file mode 100644 index 3fbaa12b..00000000 --- a/cache/caches/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# caches - -当前包在 github action 上只能跑在 linux 系统,作为独立的包,不影响其它包在所有系统上运行。 diff --git a/cache/caches/caches.go b/cache/caches/caches.go deleted file mode 100644 index b29ce8b6..00000000 --- a/cache/caches/caches.go +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT - -// Package caches 内置的缓存接口实现 -package caches - -import ( - "bytes" - "encoding/gob" - - "github.com/issue9/web/cache" -) - -// Marshal 序列化对象 -// -// 这是 [cache.Cache] 存储对象时的转换方法, -// 除了判断 [cache.Serializer] 之外,还提供了默认的编码方式。 -// -// 大部分时候 [cache.Driver] 的实现者直接调用此方法即可, -// 如果需要自己实现,需要注意 [cache.Serializer] 接口的判断。 -func Marshal(v any) ([]byte, error) { - if m, ok := v.(cache.Serializer); ok { - return m.MarshalCache() - } - - var buf bytes.Buffer - enc := gob.NewEncoder(&buf) - - if err := enc.Encode(v); err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -func Unmarshal(bs []byte, v any) error { - if u, ok := v.(cache.Serializer); ok { - return u.UnmarshalCache(bs) - } - return gob.NewDecoder(bytes.NewBuffer(bs)).Decode(v) -} diff --git a/cache/caches/memcache.go b/cache/caches/memcache.go deleted file mode 100644 index d52c9a43..00000000 --- a/cache/caches/memcache.go +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-License-Identifier: MIT - -package caches - -import ( - "errors" - "strconv" - "time" - - "github.com/bradfitz/gomemcache/memcache" - - "github.com/issue9/web/cache" -) - -type memcacheDriver struct { - client *memcache.Client -} - -type memcacheCounter struct { - driver *memcacheDriver - key string - val []byte - originVal uint64 - ttl int32 -} - -// NewMemcache 声明基于 [memcached] 的缓存系统 -// -// [cache.Driver.Driver] 的返回类型为 [memcache.Client]。 -// -// [memcached]: https://memcached.org/ -func NewMemcache(addr ...string) cache.Driver { - return &memcacheDriver{ - client: memcache.New(addr...), - } -} - -func (d *memcacheDriver) Get(key string, val any) error { - item, err := d.client.Get(key) - if errors.Is(err, memcache.ErrCacheMiss) { - return cache.ErrCacheMiss() - } else if err != nil { - return err - } - return Unmarshal(item.Value, val) -} - -func (d *memcacheDriver) Set(key string, val any, ttl time.Duration) error { - bs, err := Marshal(val) - if err != nil { - return err - } - - return d.client.Set(&memcache.Item{ - Key: key, - Value: bs, - Expiration: int32(ttl.Seconds()), - }) -} - -func (d *memcacheDriver) Delete(key string) error { return d.client.Delete(key) } - -func (d *memcacheDriver) Exists(key string) bool { - _, err := d.client.Get(key) - return err == nil || !errors.Is(err, memcache.ErrCacheMiss) -} - -func (d *memcacheDriver) Clean() error { return d.client.DeleteAll() } - -func (d *memcacheDriver) Close() error { return d.client.Close() } - -func (d *memcacheDriver) Driver() any { return d.client } - -func (d *memcacheDriver) Counter(key string, val uint64, ttl time.Duration) cache.Counter { - return &memcacheCounter{ - driver: d, - key: key, - val: []byte(strconv.FormatUint(val, 10)), - originVal: val, - ttl: int32(ttl.Seconds()), - } -} - -func (c *memcacheCounter) Incr(n uint64) (uint64, error) { - if err := c.init(); err != nil { - return 0, err - } - - v, err := c.driver.client.Increment(c.key, n) - if err == nil { - err = c.driver.client.Touch(c.key, c.ttl) - } - - if err != nil { - return 0, err - } - return v, nil -} - -func (c *memcacheCounter) Decr(n uint64) (uint64, error) { - if err := c.init(); err != nil { - return 0, err - } - - v, err := c.driver.client.Decrement(c.key, n) - if err == nil { - err = c.driver.client.Touch(c.key, c.ttl) - } - - if err != nil { - return 0, err - } - return v, nil -} - -func (c *memcacheCounter) init() error { - err := c.driver.client.Add(&memcache.Item{ - Key: c.key, - Value: c.val, - Expiration: c.ttl, - }) - if errors.Is(err, memcache.ErrNotStored) { - return nil - } - return err -} - -func (c *memcacheCounter) Value() (uint64, error) { - item, err := c.driver.client.Get(c.key) - if errors.Is(err, memcache.ErrCacheMiss) { - return c.originVal, cache.ErrCacheMiss() - } else if err != nil { - return c.originVal, err - } - - v := string(item.Value) - if v == "0 " { // 零值? - return 0, nil - } - return strconv.ParseUint(v, 10, 64) -} - -func (c *memcacheCounter) Delete() error { - err := c.driver.client.Delete(c.key) - if errors.Is(err, memcache.ErrCacheMiss) { - return nil - } - return err -} diff --git a/cache/caches/memcache_test.go b/cache/caches/memcache_test.go deleted file mode 100644 index d7d34ea2..00000000 --- a/cache/caches/memcache_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MIT - -package caches - -import ( - "testing" - - "github.com/issue9/assert/v3" - - "github.com/issue9/web/cache" - "github.com/issue9/web/cache/cachetest" -) - -var _ cache.Cache = &memcacheDriver{} - -func TestMemcache(t *testing.T) { - a := assert.New(t, false) - - c := NewMemcache("localhost:11211") - a.NotNil(c) - - cachetest.Basic(a, c) - cachetest.Object(a, c) - cachetest.Counter(a, c) - - a.NotError(c.Close()) -} - -func TestMemcache_Close(t *testing.T) { - a := assert.New(t, false) - - c := NewMemcache("localhost:11211") - a.NotNil(c) - a.NotError(c.Set("key", "val", cache.Forever)) - a.NotError(c.Close()) - - c = NewMemcache("localhost:11211") - a.NotNil(c) - var val string - a.NotError(c.Get("key", &val)).Equal(val, "val") -} diff --git a/cache/caches/memory.go b/cache/caches/memory.go deleted file mode 100644 index 4e659cbd..00000000 --- a/cache/caches/memory.go +++ /dev/null @@ -1,197 +0,0 @@ -// SPDX-License-Identifier: MIT - -package caches - -import ( - "strconv" - "sync" - "time" - - "github.com/issue9/scheduled" - - "github.com/issue9/web/cache" -) - -type memoryDriver struct { - items *sync.Map -} - -type memoryCounter struct { - driver *memoryDriver - key string - val []byte - originVal uint64 - expires time.Duration - locker sync.RWMutex -} - -type item struct { - val []byte - dur time.Duration - expire time.Time // 过期的时间 -} - -func (i *item) update(val any) error { - bs, err := Marshal(val) - if err != nil { - return err - } - i.val = bs - i.expire = time.Now().Add(i.dur) - return nil -} - -func (i *item) isExpired(now time.Time) bool { - return i.dur != 0 && i.expire.Before(now) -} - -// NewMemory 声明一个内存缓存 -// -// JobFunc 表示执行内存的操作。 -// [cache.Driver.Driver] 的返回类型为 [sync.Map]。 -func NewMemory() (cache.Driver, scheduled.JobFunc) { - mem := &memoryDriver{ - items: &sync.Map{}, - } - return mem, mem.gc -} - -func (d *memoryDriver) Get(key string, v any) error { - if item, found := d.findItem(key); found { - return Unmarshal(item.val, v) - } - return cache.ErrCacheMiss() -} - -func (d *memoryDriver) findItem(key string) (*item, bool) { - i, found := d.items.Load(key) - if !found { - return nil, false - } - return i.(*item), true -} - -func (d *memoryDriver) Set(key string, val any, ttl time.Duration) error { - i, found := d.findItem(key) - if !found { - bs, err := Marshal(val) - if err != nil { - return err - } - - d.items.Store(key, &item{ - val: bs, - dur: ttl, - expire: time.Now().Add(ttl), - }) - return nil - } - - return i.update(val) -} - -func (d *memoryDriver) Delete(key string) error { - d.items.Delete(key) - return nil -} - -func (d *memoryDriver) Exists(key string) bool { - _, found := d.items.Load(key) - return found -} - -func (d *memoryDriver) Clean() error { - d.items.Range(func(key, val any) bool { - d.items.Delete(key) - return true - }) - return nil -} - -func (d *memoryDriver) Close() error { return d.Clean() } - -func (d *memoryDriver) Driver() any { return d.items } - -func (d *memoryDriver) gc(now time.Time) error { - d.items.Range(func(key, val any) bool { - if v := val.(*item); v.isExpired(now) { - d.items.Delete(key) - } - return true - }) - return nil -} - -func (d *memoryDriver) Counter(key string, val uint64, ttl time.Duration) cache.Counter { - return &memoryCounter{ - driver: d, - key: key, - val: []byte(strconv.FormatUint(val, 10)), - originVal: val, - expires: ttl, - } -} - -func (c *memoryCounter) Incr(n uint64) (uint64, error) { - c.locker.Lock() - defer c.locker.Unlock() - - v, err := c.init() - if err != nil { - return 0, err - } - - v += n - c.driver.items.Store(c.key, &item{ - val: []byte(strconv.FormatUint(v, 10)), - dur: c.expires, - expire: time.Now().Add(c.expires), - }) - return v, nil -} - -func (c *memoryCounter) Decr(n uint64) (uint64, error) { - c.locker.Lock() - defer c.locker.Unlock() - - v, err := c.init() - if err != nil { - return 0, err - } - if n > v { - v = 0 - } else { - v -= n - } - c.driver.items.Store(c.key, &item{ - val: []byte(strconv.FormatUint(v, 10)), - dur: c.expires, - expire: time.Now().Add(c.expires), - }) - return v, nil -} - -func (c *memoryCounter) init() (uint64, error) { - ret, loaded := c.driver.items.LoadOrStore(c.key, &item{ - val: c.val, - dur: c.expires, - expire: time.Now().Add(c.expires), - }) - - if !loaded { - return c.originVal, nil - } - return strconv.ParseUint(string(ret.(*item).val), 10, 64) -} - -func (c *memoryCounter) Value() (uint64, error) { - c.locker.RLock() - defer c.locker.RUnlock() - - if item, found := c.driver.findItem(c.key); found { - return strconv.ParseUint(string(item.val), 10, 64) - } - return c.originVal, cache.ErrCacheMiss() -} - -func (c *memoryCounter) Delete() error { return c.driver.Delete(c.key) } diff --git a/cache/caches/memory_test.go b/cache/caches/memory_test.go deleted file mode 100644 index 6dc7900c..00000000 --- a/cache/caches/memory_test.go +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT - -package caches - -import ( - "testing" - "time" - - "github.com/issue9/assert/v3" - - "github.com/issue9/web/cache" - "github.com/issue9/web/cache/cachetest" -) - -var _ cache.Cache = &memoryDriver{} - -func TestMemory(t *testing.T) { - a := assert.New(t, false) - - c, gc := NewMemory() - a.NotNil(c) - - ticker := time.NewTicker(500 * time.Millisecond) - go func() { - for now := range ticker.C { - a.NotError(gc(now)) - } - }() - - cachetest.Basic(a, c) - cachetest.Object(a, c) - cachetest.Counter(a, c) - - a.NotError(c.Close()) - ticker.Stop() -} diff --git a/cache/caches/redis.go b/cache/caches/redis.go deleted file mode 100644 index 4a1f8f21..00000000 --- a/cache/caches/redis.go +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: MIT - -package caches - -import ( - "context" - "errors" - "strconv" - "time" - - "github.com/redis/go-redis/v9" - - "github.com/issue9/web/cache" -) - -type redisDriver struct { - conn *redis.Client - decrByScript *redis.Script -} - -type redisCounter struct { - driver *redisDriver - key string - val string - originVal uint64 - ttl time.Duration -} - -// redis 处理 DECRBY 的事务脚本 -const redisDecrByScript = `local cnt = redis.call('DECRBY', KEYS[1], ARGV[1]) -if cnt < 0 then - redis.call('SET', KEYS[1], '0') -end -return (cnt < 0 and 0 or cnt)` - -// NewRedisFromURL 声明基于 [redis] 的缓存系统 -// -// url 为符合 [Redis URI scheme] 的字符串。 -// [cache.Driver.Driver] 的返回类型为 [redis.Client]。 -// -// [Redis URI scheme]: https://www.iana.org/assignments/uri-schemes/prov/redis -// [redis]: https://redis.io/ -func NewRedisFromURL(url string) (cache.Driver, error) { - opt, err := redis.ParseURL(url) - if err != nil { - return nil, err - } - return NewRedis(redis.NewClient(opt)), nil -} - -// NewRedis 声明基于 redis 的缓存系统 -func NewRedis(c *redis.Client) cache.Driver { - return &redisDriver{ - conn: c, - decrByScript: redis.NewScript(redisDecrByScript), - } -} - -func (d *redisDriver) Get(key string, val any) error { - bs, err := d.conn.Get(context.Background(), key).Bytes() - if errors.Is(err, redis.Nil) { - return cache.ErrCacheMiss() - } else if err != nil { - return err - } - - return Unmarshal(bs, val) -} - -func (d *redisDriver) Set(key string, val any, ttl time.Duration) error { - bs, err := Marshal(val) - if err != nil { - return err - } - return d.conn.Set(context.Background(), key, bs, ttl).Err() -} - -func (d *redisDriver) Delete(key string) error { - return d.conn.Del(context.Background(), key).Err() -} - -func (d *redisDriver) Exists(key string) bool { - rslt, err := d.conn.Exists(context.Background(), key).Result() - return err == nil && rslt > 0 -} - -func (d *redisDriver) Clean() error { - return d.conn.FlushDB(context.Background()).Err() -} - -func (d *redisDriver) Close() error { return d.conn.Close() } - -func (d *redisDriver) Driver() any { return d.conn } - -func (d *redisDriver) Counter(key string, val uint64, ttl time.Duration) cache.Counter { - return &redisCounter{ - driver: d, - key: key, - val: strconv.FormatUint(val, 10), - originVal: val, - ttl: ttl, - } -} - -func (c *redisCounter) Incr(n uint64) (uint64, error) { - if err := c.init(); err != nil { - return 0, err - } - - rslt, err := c.driver.conn.IncrBy(context.Background(), c.key, int64(n)).Result() - if err != nil { - return 0, err - } - return uint64(rslt), nil -} - -func (c *redisCounter) Decr(n uint64) (uint64, error) { - if err := c.init(); err != nil { - return 0, err - } - - in := int64(n) - v, err := c.driver.decrByScript.Run(context.Background(), c.driver.conn, []string{c.key}, in).Int64() - return uint64(v), err -} - -func (c *redisCounter) init() error { - return c.driver.conn.SetNX(context.Background(), c.key, c.val, c.ttl).Err() -} - -func (c *redisCounter) Value() (uint64, error) { - s, err := c.driver.conn.Get(context.Background(), c.key).Result() - if errors.Is(err, redis.Nil) { - return c.originVal, cache.ErrCacheMiss() - } else if err != nil { - return c.originVal, err - } - return strconv.ParseUint(s, 10, 64) -} - -func (c *redisCounter) Delete() error { - return c.driver.conn.Del(context.Background(), c.key).Err() -} diff --git a/cache/caches/redis_test.go b/cache/caches/redis_test.go deleted file mode 100644 index 7255b612..00000000 --- a/cache/caches/redis_test.go +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT - -package caches - -import ( - "testing" - - "github.com/issue9/assert/v3" - - "github.com/issue9/web/cache" - "github.com/issue9/web/cache/cachetest" -) - -var _ cache.Cache = &redisDriver{} - -const redisURL = "redis://localhost:6379?dial_timeout=1&db=1&read_timeout=1&write_timeout=1" - -func TestRedis(t *testing.T) { - a := assert.New(t, false) - - c, err := NewRedisFromURL(redisURL) - a.NotError(err).NotNil(c) - - cachetest.Basic(a, c) - cachetest.Object(a, c) - cachetest.Counter(a, c) - - a.NotError(c.Close()) -} - -func TestRedis_Close(t *testing.T) { - a := assert.New(t, false) - - c, err := NewRedisFromURL(redisURL) - a.NotError(err).NotNil(c) - a.NotError(c.Set("key", "val", cache.Forever)) - a.NotError(c.Close()) - - c, err = NewRedisFromURL(redisURL) - a.NotError(err).NotNil(c) - var val string - a.NotError(c.Get("key", &val)).Equal(val, "val") -} diff --git a/cache/cachetest/cachetest.go b/cache/cachetest/cachetest.go deleted file mode 100644 index 6c2f939d..00000000 --- a/cache/cachetest/cachetest.go +++ /dev/null @@ -1,151 +0,0 @@ -// SPDX-License-Identifier: MIT - -// Package cachetest 缓存的测试用例 -package cachetest - -import ( - "errors" - "strconv" - "strings" - "time" - - "github.com/issue9/assert/v3" - - "github.com/issue9/web/cache" -) - -// Counter 测试计数器 -func Counter(a *assert.Assertion, d cache.Driver) { - c := d.Counter("v1", 50, time.Second) - a.NotNil(c) - - v1, err := c.Value() - a.ErrorIs(err, cache.ErrCacheMiss()).Equal(v1, 50) - - v1, err = c.Incr(5) - a.NotError(err).Equal(v1, 55) - v1, err = c.Value() - a.Nil(err).Equal(v1, 55) - - v1, err = c.Decr(3) - a.NotError(err).Equal(v1, 52) - v1, err = c.Value() - a.Nil(err).Equal(v1, 52) - - a.True(d.Exists("v1")) - - a.NotError(c.Delete()) - a.False(d.Exists("v1")) - - // 没有值的情况 Decr - c = d.Counter("v2", 50, time.Second) - a.NotNil(c) - a.NotError(c.Delete()) - v2, err := c.Decr(3) - a.NotError(err).Equal(v2, 47) - v2, err = c.Value() - a.Nil(err).Equal(v2, 47) - - v2, err = c.Decr(4888) - a.NotError(err).Equal(v2, 0) - - v2, err = c.Value() - a.NotError(err).Equal(v2, 0) - - v2, err = c.Decr(47) - a.NotError(err).Equal(v2, 0) - - // 多个 Counter 指向同一个 key - - c1 := d.Counter("v3", 50, time.Second) - c2 := d.Counter("v3", 50, time.Second) - - v1, err = c1.Decr(5) - a.NotError(err).Equal(v1, 45) - v2, err = c2.Value() - a.NotError(err).Equal(v2, 45) -} - -// Basic 测试基本功能 -func Basic(a *assert.Assertion, c cache.Driver) { - // driver - a.NotNil(c.Driver()) - - var v string - err := c.Get("not_exists", &v) - a.ErrorIs(err, cache.ErrCacheMiss(), "找到了一个并不存在的值"). - Zero(v, "查找一个并不存在的值,且有返回。") - - a.NotError(c.Set("k1", 123, cache.Forever)) - var num int - err = c.Get("k1", &num) - a.NotError(err, "Forever 返回未知错误 %s", err). - Equal(num, 123, "无法正常获取 k1 的值 v1=%s,v2=%d", v, 123) - - // 重新设置 k1 - a.NotError(c.Set("k1", uint(789), time.Minute)) - var unum uint - err = c.Get("k1", &unum) - a.NotError(err, "1*time.Hour 的值 k1 返回错误信息 %s", err). - Equal(unum, 789, "无法正常获取重新设置之后 k1 的值 v1=%s, v2=%d", v, 789) - - // 被 delete 删除 - a.NotError(c.Delete("k1")) - err = c.Get("k1", &unum) - a.Equal(err, cache.ErrCacheMiss(), "k1 并未被回收"). - Zero(v, "被删除之后值并未为空:%+v", v) - - // 超时被回收 - a.NotError(c.Set("k1", 123, time.Second)) - a.NotError(c.Set("k2", 456, time.Second)) - a.NotError(c.Set("k3", 789, time.Second)) - time.Sleep(2 * time.Second) - a.False(c.Exists("k1"), "k1 超时且未被回收") - a.False(c.Exists("k2"), "k2 超时且未被回收") - a.False(c.Exists("k3"), "k3 超时且未被回收") - - // Clean - a.NotError(c.Set("k1", 123, time.Second)) - a.NotError(c.Set("k2", 456, time.Second)) - a.NotError(c.Set("k3", 789, time.Second)) - a.NotError(c.Clean()) - a.False(c.Exists("k1"), "clean 之后 k1 依然存在"). - False(c.Exists("k2"), "clean 之后 k2 依然存在"). - False(c.Exists("k3"), "clean 之后 k3 依然存在") -} - -type object struct { - Name string - age int -} - -func (o *object) MarshalCache() ([]byte, error) { - return []byte(o.Name + "," + strconv.Itoa(o.age)), nil -} - -func (o *object) UnmarshalCache(bs []byte) error { - fields := strings.Split(string(bs), ",") - if len(fields) != 2 { - return errors.New("error") - } - - o.Name = fields[0] - age, err := strconv.Atoi(fields[1]) - if err != nil { - return err - } - o.age = age - - return nil -} - -// Object 测试对象的缓存 -func Object(a *assert.Assertion, c cache.Driver) { - obj := &object{Name: "test", age: 5} - obj2 := &object{Name: "test", age: 5} - - a.NotError(c.Set("obj", obj, cache.Forever)) - var v object - err := c.Get("obj", &v) - a.NotError(err).Equal(&v, obj2) -} diff --git a/cache/prefix.go b/cache/prefix.go deleted file mode 100644 index 381f9e91..00000000 --- a/cache/prefix.go +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MIT - -package cache - -import "time" - -type prefix struct { - prefix string - cache Cache -} - -// Prefix 生成一个带有统一前缀名称的缓存访问对象 -// -// c := NewMemory(...) -// p := cache.Prefix(c, "prefix_") -// p.Get("k1") // 相当于 c.Get("prefix_k1") -func Prefix(a Cache, p string) Cache { return &prefix{prefix: p, cache: a} } - -func (p *prefix) Get(key string, v any) error { - return p.cache.Get(p.prefix+key, v) -} - -func (p *prefix) Set(key string, val any, seconds time.Duration) error { - return p.cache.Set(p.prefix+key, val, seconds) -} - -func (p *prefix) Delete(key string) error { - return p.cache.Delete(p.prefix + key) -} - -func (p *prefix) Exists(key string) bool { - return p.cache.Exists(p.prefix + key) -} - -func (p *prefix) Counter(key string, val uint64, ttl time.Duration) Counter { - return p.cache.Counter(p.prefix+key, val, ttl) -} diff --git a/cache/serializer.go b/cache/serializer.go deleted file mode 100644 index 9c17bad0..00000000 --- a/cache/serializer.go +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT - -package cache - -// Serializer 缓存系统存取数据时采用的序列化方法 -// -// 如果你存储的对象实现了该接口,那么在存取数据时,会采用此方法将对象进行编解码。 -// 否则会采用默认的方法进行编辑码。 -// -// 实现 Serializer 可以拥有更高效的转换效率,以及一些默认行为不可实现的功能, -// 比如需要对拥有不可导出的字段进行编解码。 -type Serializer interface { - // MarshalCache 将对象转换成 []byte - MarshalCache() ([]byte, error) - - // UnmarshalCache 从 []byte 中恢复数据 - UnmarshalCache([]byte) error -} diff --git a/cmd/web/go.mod b/cmd/web/go.mod index 1b71802f..8a20b262 100644 --- a/cmd/web/go.mod +++ b/cmd/web/go.mod @@ -3,12 +3,12 @@ module github.com/issue9/web/cmd/web go 1.21 require ( - github.com/caixw/gobuild v1.7.2 + github.com/caixw/gobuild v1.7.3 github.com/getkin/kin-openapi v0.120.0 github.com/issue9/assert/v3 v3.1.0 github.com/issue9/cmdopt v0.13.0 github.com/issue9/errwrap v0.3.1 - github.com/issue9/localeutil v0.24.0 + github.com/issue9/localeutil v0.24.1 github.com/issue9/query/v3 v3.1.2 github.com/issue9/source v0.7.0 github.com/issue9/term/v3 v3.2.4 @@ -26,6 +26,7 @@ require ( github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/swag v0.22.4 // indirect github.com/invopop/yaml v0.2.0 // indirect + github.com/issue9/cache v0.8.0 // indirect github.com/issue9/config v0.6.1 // indirect github.com/issue9/conv v1.3.4 // indirect github.com/issue9/logs/v7 v7.1.0 // indirect diff --git a/cmd/web/go.sum b/cmd/web/go.sum index 9d97e6a2..f733a5a8 100644 --- a/cmd/web/go.sum +++ b/cmd/web/go.sum @@ -1,15 +1,9 @@ -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= -github.com/caixw/gobuild v1.7.2 h1:iYFuslKqZjwLDduqOqbPhxQ0BRCADdgUkaYRWOOebbU= -github.com/caixw/gobuild v1.7.2/go.mod h1:9KwhLT9vEwoYwr4s3gky0V5BVDj2LvxMDnBzaRki72s= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/caixw/gobuild v1.7.3 h1:1l7tyFjOIuRKlxMYudYZ0VTlO31W6kecILe7Ad64RdQ= +github.com/caixw/gobuild v1.7.3/go.mod h1:Ca/Ao89tb/o23RlvntxF7COkZ21iwiBg+ew8A9XglPg= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg= @@ -25,6 +19,8 @@ github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/issue9/assert/v3 v3.1.0 h1:oxLFXS7QnBKI4lB31pRoYO96yErkWAJtR7iv+LNjAPg= github.com/issue9/assert/v3 v3.1.0/go.mod h1:yft/uaskRpwQTyBT3n1zRl91SR1wNlO4fLZHzOa4bdM= +github.com/issue9/cache v0.8.0 h1:4puBfouYYyprvEUrLGW3YsCEaXYNNiFbKhLfPp9/F4I= +github.com/issue9/cache v0.8.0/go.mod h1:avdNcf8037p4TmLMLaelqeXnnJbgyseLbMSJ2xh1w3U= github.com/issue9/cmdopt v0.13.0 h1:mLaa7R94a6MbCf+1GIt4YgFDW6fdt/nnL0AjgKgf8jQ= github.com/issue9/cmdopt v0.13.0/go.mod h1:l//IcugcBwX+vCc2KrgC4ylU6rEzhjQyxno7hWoLCDE= github.com/issue9/config v0.6.1 h1:9V6X09fDT6Wzd/89SxX1yNoydyxXev4JC2ClhCxQLac= @@ -33,8 +29,8 @@ github.com/issue9/conv v1.3.4 h1:v1j/p1lVNW4u1yrbUxxNCb61iTFnF86s+KAwS65MsBs= github.com/issue9/conv v1.3.4/go.mod h1:TXM2DyyJhzZMSwp9cxwFW/OhP5JRVZPMg5XE8OMzwUY= github.com/issue9/errwrap v0.3.1 h1:8g4lYJaGnoiXyZ1oZyH/7zPDGgw5RNiE9Q6ri9kE6Z8= github.com/issue9/errwrap v0.3.1/go.mod h1:HLR0e5iimd2aJXM9YrThOsRj3/6lMtk77lVp7zyvJ4E= -github.com/issue9/localeutil v0.24.0 h1:EnqsgkhWXqQXU+RM9ISAYnBaxxZYi3ec+6Z+AlxGga4= -github.com/issue9/localeutil v0.24.0/go.mod h1:JvTb8B/2oVEZU1VHrBJXPHlE/1gZJFRMcF/ziKD8JJY= +github.com/issue9/localeutil v0.24.1 h1:L0wttRvGpVdGFAEVjK0wUBE15FtTkUZ+3/0PmhAHmjE= +github.com/issue9/localeutil v0.24.1/go.mod h1:JvTb8B/2oVEZU1VHrBJXPHlE/1gZJFRMcF/ziKD8JJY= github.com/issue9/logs/v7 v7.1.0 h1:4SafDtfa4hMOTeEpQRX25YOvWw/don65QqxOA1iSa0M= github.com/issue9/logs/v7 v7.1.0/go.mod h1:WfimUOXUkMUq8inRXUfUAN7KkDUdMkdRC0xrP3nCpxA= github.com/issue9/mux/v7 v7.3.3 h1:cUKmaNspmjG1AmAwtPDWM/CjkicbhX0jbsUXrZdFeu0= @@ -73,8 +69,6 @@ github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0= -github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/cmd/web/locale/update/update.go b/cmd/web/locale/update/update.go index b73406a7..f23eff03 100644 --- a/cmd/web/locale/update/update.go +++ b/cmd/web/locale/update/update.go @@ -47,6 +47,7 @@ func Init(opt *cmdopt.CmdOpt, p *localeutil.Printer) { for _, d := range strings.Split(*dest, ",") { stat, err := os.Stat(d) + // TODO 当文件不存在时,创建一个新的? if err != nil { return err } diff --git a/go.mod b/go.mod index 67c8979c..ac4a134a 100644 --- a/go.mod +++ b/go.mod @@ -2,12 +2,12 @@ module github.com/issue9/web require ( github.com/andybalholm/brotli v1.0.6 - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 github.com/issue9/assert/v3 v3.1.0 + github.com/issue9/cache v0.8.0 github.com/issue9/config v0.6.1 github.com/issue9/conv v1.3.4 github.com/issue9/errwrap v0.3.1 - github.com/issue9/localeutil v0.24.0 + github.com/issue9/localeutil v0.24.1 github.com/issue9/logs/v7 v7.1.0 github.com/issue9/mux/v7 v7.3.3 github.com/issue9/query/v3 v3.1.2 @@ -16,7 +16,6 @@ require ( github.com/issue9/term/v3 v3.2.4 github.com/issue9/unique/v2 v2.0.0 github.com/klauspost/compress v1.17.3 - github.com/redis/go-redis/v9 v9.3.0 golang.org/x/crypto v0.15.0 golang.org/x/text v0.14.0 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 @@ -24,10 +23,12 @@ require ( ) require ( + github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/issue9/rands/v2 v2.0.0 // indirect github.com/issue9/source v0.7.0 // indirect + github.com/redis/go-redis/v9 v9.3.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.10.0 // indirect golang.org/x/sys v0.14.0 // indirect diff --git a/go.sum b/go.sum index 07dff222..671bc006 100644 --- a/go.sum +++ b/go.sum @@ -12,14 +12,16 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/issue9/assert/v3 v3.1.0 h1:oxLFXS7QnBKI4lB31pRoYO96yErkWAJtR7iv+LNjAPg= github.com/issue9/assert/v3 v3.1.0/go.mod h1:yft/uaskRpwQTyBT3n1zRl91SR1wNlO4fLZHzOa4bdM= +github.com/issue9/cache v0.8.0 h1:4puBfouYYyprvEUrLGW3YsCEaXYNNiFbKhLfPp9/F4I= +github.com/issue9/cache v0.8.0/go.mod h1:avdNcf8037p4TmLMLaelqeXnnJbgyseLbMSJ2xh1w3U= github.com/issue9/config v0.6.1 h1:9V6X09fDT6Wzd/89SxX1yNoydyxXev4JC2ClhCxQLac= github.com/issue9/config v0.6.1/go.mod h1:9z8F2n/9zp9VfRAv6Pku5qpAg89lC79P18K0eZTSvlc= github.com/issue9/conv v1.3.4 h1:v1j/p1lVNW4u1yrbUxxNCb61iTFnF86s+KAwS65MsBs= github.com/issue9/conv v1.3.4/go.mod h1:TXM2DyyJhzZMSwp9cxwFW/OhP5JRVZPMg5XE8OMzwUY= github.com/issue9/errwrap v0.3.1 h1:8g4lYJaGnoiXyZ1oZyH/7zPDGgw5RNiE9Q6ri9kE6Z8= github.com/issue9/errwrap v0.3.1/go.mod h1:HLR0e5iimd2aJXM9YrThOsRj3/6lMtk77lVp7zyvJ4E= -github.com/issue9/localeutil v0.24.0 h1:EnqsgkhWXqQXU+RM9ISAYnBaxxZYi3ec+6Z+AlxGga4= -github.com/issue9/localeutil v0.24.0/go.mod h1:JvTb8B/2oVEZU1VHrBJXPHlE/1gZJFRMcF/ziKD8JJY= +github.com/issue9/localeutil v0.24.1 h1:L0wttRvGpVdGFAEVjK0wUBE15FtTkUZ+3/0PmhAHmjE= +github.com/issue9/localeutil v0.24.1/go.mod h1:JvTb8B/2oVEZU1VHrBJXPHlE/1gZJFRMcF/ziKD8JJY= github.com/issue9/logs/v7 v7.1.0 h1:4SafDtfa4hMOTeEpQRX25YOvWw/don65QqxOA1iSa0M= github.com/issue9/logs/v7 v7.1.0/go.mod h1:WfimUOXUkMUq8inRXUfUAN7KkDUdMkdRC0xrP3nCpxA= github.com/issue9/mux/v7 v7.3.3 h1:cUKmaNspmjG1AmAwtPDWM/CjkicbhX0jbsUXrZdFeu0= diff --git a/locales/locales.go b/locales/locales.go index 778cc0a3..87719778 100644 --- a/locales/locales.go +++ b/locales/locales.go @@ -7,9 +7,10 @@ import ( "embed" "io/fs" - cl "github.com/issue9/config/locales" + cache "github.com/issue9/cache/locales" + config "github.com/issue9/config/locales" "github.com/issue9/localeutil" - ll "github.com/issue9/localeutil/locales" + localeutilL "github.com/issue9/localeutil/locales" ) //go:embed *.yaml @@ -23,8 +24,9 @@ var Locales embed.FS // s.Locale().LoadMessages("*.yaml", locales.All()...) var All = []fs.FS{ Locales, - ll.Locales, - cl.Locales, + localeutilL.Locales, + config.Locales, + cache.Locales, } // 一些多处用到的翻译项 diff --git a/locales/und.yaml b/locales/und.yaml index ad99c454..e878a253 100755 --- a/locales/und.yaml +++ b/locales/und.yaml @@ -9,9 +9,6 @@ messages: - key: SSE server message: msg: SSE server - - key: cache miss - message: - msg: cache miss - key: can not be empty message: msg: can not be empty @@ -27,9 +24,6 @@ messages: - key: duplicate value message: msg: duplicate value - - key: invalid cache key - message: - msg: invalid cache key - key: invalid sse format %s message: msg: invalid sse format %s diff --git a/locales/zh-CN.yaml b/locales/zh-CN.yaml index a62e97f0..372a16fb 100644 --- a/locales/zh-CN.yaml +++ b/locales/zh-CN.yaml @@ -9,9 +9,6 @@ messages: - key: SSE server message: msg: SSE 服务 - - key: cache miss - message: - msg: 未找到缓存项 - key: can not be empty message: msg: 不能为空 @@ -27,9 +24,6 @@ messages: - key: duplicate value message: msg: 重复的值 - - key: invalid cache key - message: - msg: 缓存的 key 不符合要求 - key: invalid sse format %s message: msg: '无效的 SSE 格式: %s' diff --git a/server.go b/server.go index 2cef73f7..cd21f3bc 100644 --- a/server.go +++ b/server.go @@ -9,12 +9,11 @@ import ( "sync" "time" + "github.com/issue9/cache" "github.com/issue9/config" "golang.org/x/text/language" "golang.org/x/text/message" "golang.org/x/text/message/catalog" - - "github.com/issue9/web/cache" ) // Server 服务接口 diff --git a/server/options.go b/server/options.go index a472c5bb..0fc59670 100644 --- a/server/options.go +++ b/server/options.go @@ -8,6 +8,10 @@ import ( "net/http" "time" + "github.com/issue9/cache" + "github.com/issue9/cache/caches/memcache" + "github.com/issue9/cache/caches/memory" + "github.com/issue9/cache/caches/redis" "github.com/issue9/config" "github.com/issue9/localeutil" "github.com/issue9/unique/v2" @@ -16,8 +20,6 @@ import ( "gopkg.in/yaml.v3" "github.com/issue9/web" - "github.com/issue9/web/cache" - "github.com/issue9/web/cache/caches" "github.com/issue9/web/internal/header" "github.com/issue9/web/internal/locale" "github.com/issue9/web/locales" @@ -46,8 +48,11 @@ type ( // 缓存系统 // - // 内置的几种驱动实现位于 [github.com/issue9/web/cache/caches] 之下。 - // 如果为空,采用 [caches.NewMemory] 作为默认值。 + // 内置了以下几种驱动: + // - NewMemory + // - NewMemcache + // - NewReidsFromURL + // 如果为空,采用 [NewMemory] 作为默认值。 Cache cache.Driver // 日志的相关设置 @@ -188,7 +193,7 @@ func sanitizeOptions(o *Options) (*Options, *config.FieldError) { } if o.Cache == nil { - c, job := caches.NewMemory() + c, job := NewMemory() o.Cache = c o.Init = append(o.Init, func(s web.Server) { // AddTicker 依赖 IDGenerator s.Services().AddTicker(locales.RecycleLocalCache, job, time.Minute, false, false) @@ -229,3 +234,22 @@ func sanitizeOptions(o *Options) (*Options, *config.FieldError) { return o, nil } + +// NewMemory 声明基于内在的缓存对象 +func NewMemory() (cache.Driver, web.JobFunc) { + d, job := memory.New() + return d, func(now time.Time) error { + job(now) + return nil + } +} + +// NewRedisFromURL 声明基于 redis 的缓存对象 +// +// 参数说明可参考 [redis.NewFromURL]。 +func NewRedisFromURL(url string) (cache.Driver, error) { return redis.NewFromURL(url) } + +// NewMemcache 声明基于 memcache 的缓存对象 +// +// 参数说明可参考 [memcache.New]。 +func NewMemcache(addr ...string) cache.Driver { return memcache.New(addr...) } diff --git a/server/server.go b/server/server.go index abb5bc49..99f8baa7 100644 --- a/server/server.go +++ b/server/server.go @@ -13,11 +13,11 @@ import ( "sync" "time" + "github.com/issue9/cache" "github.com/issue9/config" "github.com/issue9/mux/v7/group" "github.com/issue9/web" - "github.com/issue9/web/cache" "github.com/issue9/web/internal/locale" ) diff --git a/server/server_test.go b/server/server_test.go index 24b73067..cc02b624 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -11,10 +11,10 @@ import ( "time" "github.com/issue9/assert/v3" + "github.com/issue9/cache" "golang.org/x/text/language" "github.com/issue9/web" - "github.com/issue9/web/cache" "github.com/issue9/web/codec/mimetype/json" "github.com/issue9/web/codec/mimetype/xml" "github.com/issue9/web/logs" diff --git a/server_test.go b/server_test.go index 5f81b945..03cbfd5c 100644 --- a/server_test.go +++ b/server_test.go @@ -12,6 +12,8 @@ import ( "time" "github.com/issue9/assert/v3" + "github.com/issue9/cache" + "github.com/issue9/cache/caches/memory" "github.com/issue9/config" "github.com/issue9/mux/v7/group" "github.com/issue9/unique/v2" @@ -19,8 +21,6 @@ import ( "golang.org/x/text/message" "golang.org/x/text/message/catalog" - "github.com/issue9/web/cache" - "github.com/issue9/web/cache/caches" "github.com/issue9/web/internal/header" "github.com/issue9/web/internal/locale" "github.com/issue9/web/logs" @@ -63,7 +63,7 @@ func newTestServer(a *assert.Assertion) *testServer { u := unique.NewNumber(100) go u.Serve(context.Background()) - cc, _ := caches.NewMemory() + cc, _ := memory.New() srv := &testServer{ a: a, diff --git a/web.go b/web.go index 3dd096b8..1322291d 100644 --- a/web.go +++ b/web.go @@ -11,6 +11,7 @@ import ( "context" "io" + "github.com/issue9/cache" "github.com/issue9/config" "github.com/issue9/localeutil" "github.com/issue9/mux/v7" @@ -19,13 +20,12 @@ import ( "github.com/issue9/query/v3" "github.com/issue9/scheduled" - "github.com/issue9/web/cache" "github.com/issue9/web/internal/errs" "github.com/issue9/web/logs" ) // Version 当前框架的版本 -const Version = "0.86.2" +const Version = "0.87.0" // 服务的几种状态 const ( @@ -108,6 +108,9 @@ type ( UnmarshalFunc = func(io.Reader, any) error ) +// NewCache 声明带有统一前缀的缓存接口 +func NewCache(prefix string, c Cache) Cache { return cache.Prefix(c, prefix) } + // ErrUnsupportedSerialization 返回不支持序列化的错误信息 func ErrUnsupportedSerialization() error { return errUnsupportedSerialization }