Files
go-utils/utils/cache.go
陈兔子 eaaac7b20a v1.7.0
2025-09-12 02:33:18 +08:00

446 lines
9.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package utils
import (
"fmt"
"os"
"path"
"path/filepath"
"reflect"
"regexp"
"strings"
"sync"
"time"
"github.com/spf13/cast"
)
type FileCacheClientItem struct {
// 过期时间
expire int64
// 文件名
name string
// 开始时间
start time.Time
// 过期时间
end time.Time
}
type FileCacheClient struct {
dir string // 缓存目录
mutex sync.Mutex // 互斥锁,用于保证并发安全
prefix string // 缓存文件名前缀
expire int64 // 默认缓存过期时间
items map[string]*FileCacheClientItem
}
// NewFileCache - 新建文件缓存
/**
* @param dir 缓存目录
* @param prefix 缓存名前缀
* @return *FileCacheClient, error
* @example
* 1. cache, err := facade.NewFileCacheClient("runtime/cache")
* 2. cache, err := facade.NewFileCacheClient("runtime/cache", "cache_")
*/
func NewFileCache(dir any, expire any, prefix ...any) (*FileCacheClient, error) {
err := os.MkdirAll(cast.ToString(dir), 0755)
if err != nil {
return nil, fmt.Errorf("create cache dir error: %v", err)
}
if len(prefix) == 0 {
prefix = append(prefix, "cache_")
}
client := &FileCacheClient{
dir: cast.ToString(dir),
prefix: cast.ToString(prefix[0]),
items: make(map[string]*FileCacheClientItem),
expire: cast.ToInt64(expire),
}
// 定时器 - 每隔一段时间清理过期的缓存文件
go client.timer()
return client, nil
}
// Get 从缓存中获取key对应的数据
func (this *FileCacheClient) Get(key any) (result []byte) {
this.mutex.Lock()
defer this.mutex.Unlock()
item, ok := this.items[cast.ToString(key)]
if !ok {
return nil
}
if time.Now().After(item.end) {
// 文件已过期删除文件并返回false
err := os.Remove(item.name)
if err != nil {
return nil
}
delete(this.items, cast.ToString(key))
return nil
}
data, err := os.ReadFile(item.name)
if err != nil {
return nil
}
return data
}
// Has 检查缓存中是否存在key对应的数据
func (this *FileCacheClient) Has(key any) (exist bool) {
this.mutex.Lock()
defer this.mutex.Unlock()
item, ok := this.items[cast.ToString(key)]
if !ok {
return false
}
if time.Now().After(item.end) {
// 文件已过期删除文件并返回false
err := os.Remove(item.name)
if err != nil {
return false
}
delete(this.items, cast.ToString(key))
return false
}
return true
}
// Set 将key-value数据加入到缓存中
func (this *FileCacheClient) Set(key any, value []byte, expire ...any) (ok bool) {
exp := this.expire
if len(expire) > 0 {
if !Is.Empty(expire[0]) {
// 判断 expire[0] 是否为Duration类型
if reflect.TypeOf(expire[0]).String() == "time.Duration" {
// 转换为int64
exp = cast.ToInt64(cast.ToDuration(expire[0]).Seconds())
} else {
exp = cast.ToInt64(expire[0])
}
}
}
err := this.SetE(key, value, exp)
return Ternary(err != nil, false, true)
}
// Del 从缓存中删除key对应的数据
func (this *FileCacheClient) Del(key any) (ok bool) {
err := this.DelE(key)
return Ternary(err != nil, false, true)
}
// DelPrefix 从缓存中删除指定前缀的数据
func (this *FileCacheClient) DelPrefix(prefix ...any) (ok bool) {
err := this.DelPrefixE(prefix...)
return Ternary(err != nil, false, true)
}
// DelTags 从缓存中删除指定标签的数据
func (this *FileCacheClient) DelTags(tags ...any) (ok bool) {
err := this.DelTagsE(tags...)
return Ternary(err != nil, false, true)
}
// Clear 清空缓存
func (this *FileCacheClient) Clear() (ok bool) {
err := this.ClearE()
return Ternary(err != nil, false, true)
}
// SetE 将key-value数据加入到缓存中
func (this *FileCacheClient) SetE(key any, value []byte, expire int64) (err error) {
this.mutex.Lock()
defer this.mutex.Unlock()
// 检查缓存目录是否存在
if exist := File().Exist(this.dir); !exist {
err = os.MkdirAll(this.dir, 0755)
if err != nil {
return fmt.Errorf("create cache dir error: %v", err)
}
}
name := this.name(cast.ToString(key))
file, err := os.Create(name)
if err != nil {
return fmt.Errorf("create cache file %s error: %v", name, err)
}
_, err = file.Write(value)
if err != nil {
return fmt.Errorf("write to cache file %s error: %v", name, err)
}
err = file.Close()
if err != nil {
return err
}
// end 过期时间expire = 0 表示永不过期
var end time.Time
if expire == 0 {
end = time.Now().AddDate(100, 0, 0)
} else {
end = time.Now().Add(time.Duration(expire) * time.Second)
}
this.items[cast.ToString(key)] = &FileCacheClientItem{
expire: expire,
name: name,
start: time.Now(),
end: end,
}
return nil
}
// DelE 从缓存中删除key对应的数据
func (this *FileCacheClient) DelE(key any) (err error) {
this.mutex.Lock()
defer this.mutex.Unlock()
cacheItem, ok := this.items[cast.ToString(key)]
if !ok {
return nil
}
err = os.Remove(cacheItem.name)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("delete cache file %s error: %v", cacheItem.name, err)
}
delete(this.items, cast.ToString(key))
return nil
}
// GetKeys 获取所有缓存的key
func (this *FileCacheClient) GetKeys() (slice []string) {
this.mutex.Lock()
defer this.mutex.Unlock()
keys := make([]string, 0, len(this.items))
for key := range this.items {
keys = append(keys, key)
}
return keys
}
// ClearE 清空缓存
func (this *FileCacheClient) ClearE() (err error) {
this.mutex.Lock()
defer this.mutex.Unlock()
for _, cacheItem := range this.items {
err := os.Remove(cacheItem.name)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("delete cache file %s error: %v", cacheItem.name, err)
}
}
this.items = make(map[string]*FileCacheClientItem)
// 清空缓存目录
err = os.RemoveAll(this.dir)
if err != nil {
return fmt.Errorf("remove cache dir %s error: %v", this.dir, err)
}
return nil
}
// DelPrefixE 删除指定前缀的缓存
func (this *FileCacheClient) DelPrefixE(prefix ...any) (err error) {
var keys []string
var prefixes []string
if len(prefix) == 0 {
return nil
}
for _, value := range prefix {
// 判断是否为切片
if reflect.ValueOf(value).Kind() == reflect.Slice {
for _, val := range cast.ToSlice(value) {
prefixes = append(prefixes, cast.ToString(val))
}
} else {
prefixes = append(prefixes, cast.ToString(value))
}
}
this.mutex.Lock()
defer this.mutex.Unlock()
for key, cacheItem := range this.items {
for _, value := range prefixes {
if strings.HasPrefix(key, value) {
err := os.Remove(cacheItem.name)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("delete cache file %s error: %v", cacheItem.name, err)
}
keys = append(keys, key)
}
}
}
for _, key := range keys {
delete(this.items, key)
}
return nil
}
// DelTagsE 删除指定标签的缓存
func (this *FileCacheClient) DelTagsE(tag ...any) (err error) {
var keys []string
var tags []string
if len(tag) == 0 {
return nil
}
for _, value := range tag {
var item string
// 判断是否为切片
if reflect.ValueOf(value).Kind() == reflect.Slice {
var tmp []string
for _, val := range cast.ToSlice(value) {
tmp = append(tmp, cast.ToString(val))
}
item = strings.Join(tmp, "*")
} else {
item = cast.ToString(value)
}
tags = append(tags, fmt.Sprintf("*%s*", item))
}
// 获取所有缓存名称
for key := range this.items {
keys = append(keys, key)
}
// 模糊匹配
keys = this.fuzzyMatch(keys, tags)
this.mutex.Lock()
defer this.mutex.Unlock()
for _, key := range keys {
item, ok := this.items[key]
if !ok {
continue
}
err := os.Remove(item.name)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("delete cache file %s error: %v", item.name, err)
}
delete(this.items, key)
}
return nil
}
// GetInfo 获取缓存信息
func (this *FileCacheClient) GetInfo(key any) (info map[string]any) {
this.mutex.Lock()
defer this.mutex.Unlock()
item, ok := this.items[cast.ToString(key)]
if !ok {
return nil
}
// 剩余剩余多少秒过期
var expire int64
if item.end.IsZero() {
expire = 0
} else {
expire = int64(item.end.Sub(time.Now()).Seconds())
}
var value []byte
value, _ = os.ReadFile(item.name)
return map[string]any{
"name": item.name,
"expire": expire,
"value": string(value),
}
}
// 模糊匹配
// keys := []string{"cache_test1","cache_test2","cache_inis_test1","cache_inis_test2","cache_admin_name","cache_unti_name"}
// tags := []string{"*inis*test*", "*unti*", "*admin*"}
// return []string{"cache_inis_test1","cache_inis_test2","cache_admin_name","cache_unti_name"}
func (this *FileCacheClient) fuzzyMatch(keys []string, tags []string) (result []string) {
for _, item := range keys {
for _, tag := range tags {
if matched, _ := filepath.Match(tag, item); matched {
result = append(result, item)
break
}
}
}
return result
}
// name 返回缓存文件名
func (this *FileCacheClient) name(key any) (result string) {
// 过滤掉 windows、linux和mac下的非法字符
key = regexp.MustCompile(`[\\/:*?"<>|]`).ReplaceAllString(cast.ToString(fmt.Sprintf("%s%s", this.prefix, key)), "")
return path.Join(this.dir, cast.ToString(key))
}
// timer 定时器 - 每隔一段时间清理过期的缓存文件
func (this *FileCacheClient) timer() {
for {
time.Sleep(1 * time.Second)
this.mutex.Lock()
for key, item := range this.items {
if time.Now().After(item.end) {
// 文件已过期,删除文件并从缓存中删除
err := os.Remove(item.name)
if err != nil {
continue
}
delete(this.items, key)
}
}
this.mutex.Unlock()
}
}