package mid // //import ( // "context" // "log" // "net/http" // "sync" // "time" // // "management/internal/erpserver/model/dto" // v1 "management/internal/erpserver/service/v1" // "management/internal/pkg/know" // "management/internal/pkg/session" //) // //var publicRoutes = map[string]bool{ // "/home.html": true, // "/dashboard": true, // "/system/menus": true, // "/upload/img": true, // "/upload/file": true, // "/upload/multi_files": true, // "/pear.json": true, // "/logout": true, //} // //// MenuCacheItem 菜单缓存项 //type MenuCacheItem struct { // Data map[string]*dto.OwnerMenuDto // ExpireAt time.Time // LoadTime time.Time // Version int64 //} // //// IsExpired 检查是否过期 //func (item *MenuCacheItem) IsExpired() bool { // return time.Now().After(item.ExpireAt) //} // //// MenuCache 内存缓存管理器 //type MenuCache struct { // mu sync.RWMutex // cache map[int32]*MenuCacheItem // roleID -> MenuCacheItem // maxSize int // 最大缓存条目数 // ttl time.Duration // 缓存TTL // refreshTTL time.Duration // 刷新TTL (提前刷新时间) // stats CacheStats // 缓存统计 // cleanupTick *time.Ticker // 清理定时器 // stopCh chan struct{} // 停止信号 //} // //// CacheStats 缓存统计信息 //type CacheStats struct { // mu sync.RWMutex // Hits int64 // Misses int64 // Evictions int64 // RefreshCount int64 //} // //// NewMenuCache 创建新的菜单缓存 //func NewMenuCache(maxSize int, ttl time.Duration) *MenuCache { // cache := &MenuCache{ // cache: make(map[int32]*MenuCacheItem), // maxSize: maxSize, // ttl: ttl, // refreshTTL: ttl - time.Duration(float64(ttl)*0.1), // 提前10%刷新 // stopCh: make(chan struct{}), // cleanupTick: time.NewTicker(time.Minute * 5), // 每5分钟清理一次 // } // // // 启动后台清理协程 // go cache.cleanup() // // return cache //} // //// Get 获取缓存数据 //func (mc *MenuCache) Get(roleID int32) (map[string]*dto.OwnerMenuDto, bool) { // mc.mu.RLock() // item, exists := mc.cache[roleID] // mc.mu.RUnlock() // // if !exists { // mc.recordMiss() // return nil, false // } // // // 检查是否过期 // if item.IsExpired() { // mc.recordMiss() // // 异步删除过期项 // go func() { // mc.mu.Lock() // delete(mc.cache, roleID) // mc.mu.Unlock() // }() // return nil, false // } // // mc.recordHit() // return item.Data, true //} // //// Set 设置缓存数据 //func (mc *MenuCache) Set(roleID int32, data map[string]*dto.OwnerMenuDto, version int64) { // mc.mu.Lock() // defer mc.mu.Unlock() // // // 如果缓存已满,执行LRU淘汰 // if len(mc.cache) >= mc.maxSize { // mc.evictLRU() // } // // item := &MenuCacheItem{ // Data: data, // ExpireAt: time.Now().Add(mc.ttl), // LoadTime: time.Now(), // Version: version, // } // // mc.cache[roleID] = item //} // //// NeedRefresh 检查是否需要提前刷新 //func (mc *MenuCache) NeedRefresh(roleID int32) bool { // mc.mu.RLock() // item, exists := mc.cache[roleID] // mc.mu.RUnlock() // // if !exists { // return true // } // // // 提前刷新策略:在过期前10%时间开始刷新 // refreshTime := item.LoadTime.Add(mc.refreshTTL) // return time.Now().After(refreshTime) //} // //// evictLRU 淘汰最久未使用的缓存项 //func (mc *MenuCache) evictLRU() { // var oldestRoleID int32 // var oldestTime time.Time = time.Now() // // for roleID, item := range mc.cache { // if item.LoadTime.Before(oldestTime) { // oldestTime = item.LoadTime // oldestRoleID = roleID // } // } // // if oldestRoleID != 0 { // delete(mc.cache, oldestRoleID) // mc.stats.mu.Lock() // mc.stats.Evictions++ // mc.stats.mu.Unlock() // } //} // //// cleanup 后台清理过期缓存 //func (mc *MenuCache) cleanup() { // for { // select { // case <-mc.cleanupTick.C: // mc.cleanupExpired() // case <-mc.stopCh: // mc.cleanupTick.Stop() // return // } // } //} // //// cleanupExpired 清理过期缓存 //func (mc *MenuCache) cleanupExpired() { // mc.mu.Lock() // defer mc.mu.Unlock() // // now := time.Now() // for roleID, item := range mc.cache { // if now.After(item.ExpireAt) { // delete(mc.cache, roleID) // } // } //} // //// GetStats 获取缓存统计信息 //func (mc *MenuCache) GetStats() CacheStats { // mc.stats.mu.RLock() // defer mc.stats.mu.RUnlock() // return mc.stats //} // //// recordHit 记录缓存命中 //func (mc *MenuCache) recordHit() { // mc.stats.mu.Lock() // mc.stats.Hits++ // mc.stats.mu.Unlock() //} // //// recordMiss 记录缓存未命中 //func (mc *MenuCache) recordMiss() { // mc.stats.mu.Lock() // mc.stats.Misses++ // mc.stats.mu.Unlock() //} // //// Close 关闭缓存 //func (mc *MenuCache) Close() { // close(mc.stopCh) //} // //// CachedMenuService 带缓存的菜单服务包装器 //type CachedMenuService struct { // menuService v1.MenuService // cache *MenuCache // mu sync.RWMutex // refreshing map[int32]bool // 正在刷新的roleID //} // //// NewCachedMenuService 创建带缓存的菜单服务 //func NewCachedMenuService(menuService v1.MenuService, cache *MenuCache) *CachedMenuService { // return &CachedMenuService{ // menuService: menuService, // cache: cache, // refreshing: make(map[int32]bool), // } //} // //// ListByRoleIDToMap 获取菜单数据(带缓存) //func (cms *CachedMenuService) ListByRoleIDToMap(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) { // // 先尝试从缓存获取 // if data, hit := cms.cache.Get(roleID); hit { // // 检查是否需要异步刷新 // if cms.cache.NeedRefresh(roleID) { // go cms.asyncRefresh(ctx, roleID) // } // return data, nil // } // // // 缓存未命中,同步获取数据 // return cms.loadAndCache(ctx, roleID) //} // //// loadAndCache 加载数据并缓存 //func (cms *CachedMenuService) loadAndCache(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) { // // 防止并发重复加载 // cms.mu.Lock() // if cms.refreshing[roleID] { // cms.mu.Unlock() // // 如果正在加载,等待一小段时间后重试缓存 // time.Sleep(time.Millisecond * 10) // if data, hit := cms.cache.Get(roleID); hit { // return data, nil // } // // 重试失败,继续执行加载逻辑 // cms.mu.Lock() // } // cms.refreshing[roleID] = true // cms.mu.Unlock() // // defer func() { // cms.mu.Lock() // delete(cms.refreshing, roleID) // cms.mu.Unlock() // }() // // // 从原始服务获取数据 // data, err := cms.menuService.ListByRoleIDToMap(ctx, roleID) // if err != nil { // return nil, err // } // // // 缓存数据 // version := time.Now().UnixNano() // cms.cache.Set(roleID, data, version) // // return data, nil //} // //// asyncRefresh 异步刷新缓存 //func (cms *CachedMenuService) asyncRefresh(ctx context.Context, roleID int32) { // // 使用背景上下文,避免原请求取消影响刷新 // bgCtx := context.Background() // // cms.mu.RLock() // if cms.refreshing[roleID] { // cms.mu.RUnlock() // return // } // cms.mu.RUnlock() // // _, err := cms.loadAndCache(bgCtx, roleID) // if err != nil { // log.Printf("async refresh menu cache failed for roleID %d: %v", roleID, err) // } // // cms.cache.stats.mu.Lock() // cms.cache.stats.RefreshCount++ // cms.cache.stats.mu.Unlock() //} // //// 全局缓存实例 //var ( // menuCache *MenuCache // cachedMenuSvc *CachedMenuService // cacheInitOnce sync.Once //) // //// InitMenuCache 初始化菜单缓存(在应用启动时调用) //func InitMenuCache(menuService v1.MenuService) { // cacheInitOnce.Do(func() { // // 配置参数:最大1000个角色的缓存,TTL 10分钟 // menuCache = NewMenuCache(1000, time.Minute*10) // cachedMenuSvc = NewCachedMenuService(menuService, menuCache) // }) //} // //// GetCachedMenuService 获取缓存菜单服务实例 //func GetCachedMenuService() *CachedMenuService { // return cachedMenuSvc //} // //// Authorize 修改后的授权中间件 //func Authorize( // sess session.Manager, // menuService v1.MenuService, //) func(http.Handler) http.Handler { // // 初始化缓存 // InitMenuCache(menuService) // // return func(next http.Handler) http.Handler { // return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // ctx := r.Context() // path := r.URL.Path // // // 登陆检查 // n := time.Now() // user, err := sess.GetUser(ctx, know.StoreName) // if err != nil || user.ID == 0 { // http.Redirect(w, r, "/", http.StatusFound) // return // } // // log.Printf("scs get user: %s", time.Since(n).String()) // // // 公共路由放行 // if publicRoutes[path] { // ctx = setUser(ctx, user) // next.ServeHTTP(w, r.WithContext(ctx)) // return // } // // n1 := time.Now() // // 权限检查 - 使用缓存服务 // menus, err := GetCachedMenuService().ListByRoleIDToMap(ctx, user.RoleID) // if err != nil || !hasPermission(menus, path) { // http.Error(w, "Forbidden", http.StatusForbidden) // return // } // // log.Printf("listByRoleIDToMap (cached): %s", time.Since(n1).String()) // // n2 := time.Now() // cur := getCurrentMenus(menus, path) // log.Printf("getCurrentMenus: %s", time.Since(n2).String()) // // ctx = setUser(ctx, user) // ctx = setCurMenus(ctx, cur) // // next.ServeHTTP(w, r.WithContext(ctx)) // }) // } //} // //func hasPermission(menus map[string]*dto.OwnerMenuDto, path string) bool { // _, ok := menus[path] // return ok //} // //func getCurrentMenus(data map[string]*dto.OwnerMenuDto, path string) []dto.OwnerMenuDto { // var res []dto.OwnerMenuDto // // menu, ok := data[path] // if !ok { // return res // } // // for _, item := range data { // if menu.IsList { // if item.ParentID == menu.ID || item.ID == menu.ID { // res = append(res, *item) // } // } else { // if item.ParentID == menu.ParentID { // res = append(res, *item) // } // } // } // // return res //}