mirror of
https://github.com/unti-io/go-utils.git
synced 2025-09-28 13:12:16 +08:00
446 lines
9.4 KiB
Go
446 lines
9.4 KiB
Go
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()
|
||
}
|
||
}
|