package mid //import ( // "context" // "encoding/json" // "errors" // "fmt" // "log" // "net/http" // "strconv" // "sync" // "time" // // "management/internal/erpserver/model/dto" // v1 "management/internal/erpserver/service/v1" // "management/internal/pkg/know" // "management/internal/pkg/session" // // "github.com/allegro/bigcache/v3" //) // //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, //} // //// MenuCacheEntry 菜单缓存条目 //type MenuCacheEntry struct { // Data map[string]*dto.OwnerMenuDto `json:"data"` // Timestamp int64 `json:"timestamp"` // Version int64 `json:"version"` //} // //// CacheStats 缓存统计信息 //type CacheStats struct { // mu sync.RWMutex // Hits int64 // Misses int64 // Errors int64 // RefreshCount int64 // AsyncRefreshHits int64 // LastRefreshTime time.Time //} // //// GetHitRate 获取命中率 //func (cs *CacheStats) GetHitRate() float64 { // cs.mu.RLock() // defer cs.mu.RUnlock() // // total := cs.Hits + cs.Misses // if total == 0 { // return 0 // } // return float64(cs.Hits) / float64(total) * 100 //} // //// MenuCacheManager BigCache菜单缓存管理器 //type MenuCacheManager struct { // cache *bigcache.BigCache // refreshTTL time.Duration // stats *CacheStats // mu sync.RWMutex // refreshing map[int64]bool // stopCh chan struct{} // monitorTicker *time.Ticker //} // //// NewMenuCacheManager 创建菜单缓存管理器 //func NewMenuCacheManager(ttl time.Duration) (*MenuCacheManager, error) { // config := bigcache.DefaultConfig(ttl) // // // 生产环境优化配置 // config.Shards = 256 // 分片数,减少锁竞争 // config.MaxEntriesInWindow = 10000 // 窗口内最大条目数 // config.MaxEntrySize = 1024 * 50 // 最大条目50KB // config.HardMaxCacheSize = 512 // 最大缓存512MB // config.StatsEnabled = true // 启用统计 // config.Verbose = false // 关闭详细日志 // config.CleanWindow = 5 * time.Minute // 清理窗口 // // ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // defer cancel() // // cache, err := bigcache.New(ctx, config) // if err != nil { // return nil, fmt.Errorf("failed to create BigCache: %w", err) // } // // manager := &MenuCacheManager{ // cache: cache, // refreshTTL: time.Duration(float64(ttl) * 0.8), // 80%时间后开始刷新 // stats: &CacheStats{}, // refreshing: make(map[int64]bool), // stopCh: make(chan struct{}), // monitorTicker: time.NewTicker(time.Minute), // 每分钟监控一次 // } // // // 启动监控协程 // go manager.monitor() // // return manager, nil //} // //// Get 获取缓存数据 //func (mcm *MenuCacheManager) Get(roleID int32) (map[string]*dto.OwnerMenuDto, bool, bool) { // key := mcm.makeKey(roleID) // // data, err := mcm.cache.Get(key) // if err != nil { // if !errors.Is(err, bigcache.ErrEntryNotFound) { // mcm.recordError() // log.Printf("BigCache get error for roleID %d: %v", roleID, err) // } // mcm.recordMiss() // return nil, false, false // } // // // 反序列化 // var entry MenuCacheEntry // if err := json.Unmarshal(data, &entry); err != nil { // mcm.recordError() // log.Printf("Failed to unmarshal cache entry for roleID %d: %v", roleID, err) // mcm.recordMiss() // return nil, false, false // } // // mcm.recordHit() // // // 检查是否需要刷新 // needRefresh := time.Since(time.Unix(entry.Timestamp, 0)) > mcm.refreshTTL // // return entry.Data, true, needRefresh //} // //// Set 设置缓存数据 //func (mcm *MenuCacheManager) Set(roleID int32, data map[string]*dto.OwnerMenuDto) error { // key := mcm.makeKey(roleID) // // entry := MenuCacheEntry{ // Data: data, // Timestamp: time.Now().Unix(), // Version: time.Now().UnixNano(), // } // // // 序列化 // entryData, err := json.Marshal(entry) // if err != nil { // mcm.recordError() // return fmt.Errorf("failed to marshal cache entry: %w", err) // } // // // 存储到BigCache // err = mcm.cache.Set(key, entryData) // if err != nil { // mcm.recordError() // return fmt.Errorf("failed to set cache: %w", err) // } // // return nil //} // //// Delete 删除缓存数据 //func (mcm *MenuCacheManager) Delete(roleID int32) error { // key := mcm.makeKey(roleID) // err := mcm.cache.Delete(key) // if err != nil && !errors.Is(err, bigcache.ErrEntryNotFound) { // mcm.recordError() // return fmt.Errorf("failed to delete cache: %w", err) // } // return nil //} // //// makeKey 生成缓存key //func (mcm *MenuCacheManager) makeKey(roleID int32) string { // return "menu:role:" + strconv.Itoa(int(roleID)) //} // //// GetStats 获取统计信息 //func (mcm *MenuCacheManager) GetStats() *CacheStats { // mcm.stats.mu.RLock() // defer mcm.stats.mu.RUnlock() // // // 复制统计数据 // stats := *mcm.stats // return &stats //} // //// GetBigCacheStats 获取BigCache原生统计 //func (mcm *MenuCacheManager) GetBigCacheStats() bigcache.Stats { // return mcm.cache.Stats() //} // //// recordHit 记录命中 //func (mcm *MenuCacheManager) recordHit() { // mcm.stats.mu.Lock() // mcm.stats.Hits++ // mcm.stats.mu.Unlock() //} // //// recordMiss 记录未命中 //func (mcm *MenuCacheManager) recordMiss() { // mcm.stats.mu.Lock() // mcm.stats.Misses++ // mcm.stats.mu.Unlock() //} // //// recordError 记录错误 //func (mcm *MenuCacheManager) recordError() { // mcm.stats.mu.Lock() // mcm.stats.Errors++ // mcm.stats.mu.Unlock() //} // //// recordRefresh 记录刷新 //func (mcm *MenuCacheManager) recordRefresh() { // mcm.stats.mu.Lock() // mcm.stats.RefreshCount++ // mcm.stats.LastRefreshTime = time.Now() // mcm.stats.mu.Unlock() //} // //// recordAsyncRefreshHit 记录异步刷新命中 //func (mcm *MenuCacheManager) recordAsyncRefreshHit() { // mcm.stats.mu.Lock() // mcm.stats.AsyncRefreshHits++ // mcm.stats.mu.Unlock() //} // //// monitor 监控协程 //func (mcm *MenuCacheManager) monitor() { // for { // select { // case <-mcm.monitorTicker.C: // mcm.logStats() // case <-mcm.stopCh: // mcm.monitorTicker.Stop() // return // } // } //} // //// logStats 记录统计信息 //func (mcm *MenuCacheManager) logStats() { // stats := mcm.GetStats() // bigCacheStats := mcm.GetBigCacheStats() // // log.Printf("MenuCache Stats - Hits: %d, Misses: %d, Errors: %d, HitRate: %.2f%%, "+ // "RefreshCount: %d, AsyncRefreshHits: %d, BigCache Hits: %d, BigCache Misses: %d", // stats.Hits, stats.Misses, stats.Errors, stats.GetHitRate(), // stats.RefreshCount, stats.AsyncRefreshHits, // bigCacheStats.Hits, bigCacheStats.Misses) //} // //// Close 关闭缓存管理器 //func (mcm *MenuCacheManager) Close() error { // close(mcm.stopCh) // return mcm.cache.Close() //} // //// CachedMenuService 带BigCache的菜单服务 //type CachedMenuService struct { // menuService v1.MenuService // cache *MenuCacheManager // mu sync.RWMutex // refreshing map[int32]bool //} // //// NewCachedMenuService 创建带缓存的菜单服务 //func NewCachedMenuService(menuService v1.MenuService, cache *MenuCacheManager) *CachedMenuService { // return &CachedMenuService{ // menuService: menuService, // cache: cache, // refreshing: make(map[int32]bool), // } //} // //// ListByRoleIDToMap 获取菜单数据(带BigCache缓存) //func (cms *CachedMenuService) ListByRoleIDToMap(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) { // // 尝试从缓存获取 // data, hit, needRefresh := cms.cache.Get(roleID) // if hit { // // 如果需要刷新且当前没有在刷新中,启动异步刷新 // if needRefresh && !cms.isRefreshing(roleID) { // go cms.asyncRefresh(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 * 5) // 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 // } // // // 缓存数据 // if cacheErr := cms.cache.Set(roleID, data); cacheErr != nil { // log.Printf("Failed to cache menu data for roleID %d: %v", roleID, cacheErr) // // 缓存失败不影响业务逻辑,继续返回数据 // } // // return data, nil //} // //// asyncRefresh 异步刷新缓存 //func (cms *CachedMenuService) asyncRefresh(roleID int32) { // // 检查是否已在刷新中 // if cms.isRefreshing(roleID) { // return // } // // // 使用背景上下文 // ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) // defer cancel() // // _, err := cms.loadAndCache(ctx, roleID) // if err != nil { // log.Printf("Async refresh menu cache failed for roleID %d: %v", roleID, err) // } else { // cms.cache.recordRefresh() // cms.cache.recordAsyncRefreshHit() // } //} // //// isRefreshing 检查是否正在刷新 //func (cms *CachedMenuService) isRefreshing(roleID int32) bool { // cms.mu.RLock() // refreshing := cms.refreshing[roleID] // cms.mu.RUnlock() // return refreshing //} // //// InvalidateRole 使指定角色缓存失效 //func (cms *CachedMenuService) InvalidateRole(roleID int32) error { // return cms.cache.Delete(roleID) //} // //// GetCacheStats 获取缓存统计 //func (cms *CachedMenuService) GetCacheStats() *CacheStats { // return cms.cache.GetStats() //} // //// 全局实例 //var ( // menuCacheManager *MenuCacheManager // cachedMenuSvc *CachedMenuService // cacheInitOnce sync.Once // cacheInitErr error //) // //// InitMenuCache 初始化菜单缓存 //func InitMenuCache(menuService v1.MenuService) error { // cacheInitOnce.Do(func() { // // 缓存TTL设置为15分钟 // manager, err := NewMenuCacheManager(15 * time.Minute) // if err != nil { // cacheInitErr = fmt.Errorf("failed to initialize menu cache: %w", err) // return // } // // menuCacheManager = manager // cachedMenuSvc = NewCachedMenuService(menuService, manager) // // log.Println("MenuCache initialized successfully with BigCache") // }) // // return cacheInitErr //} // //// GetCachedMenuService 获取缓存菜单服务 //func GetCachedMenuService() *CachedMenuService { // return cachedMenuSvc //} // //// GetMenuCacheManager 获取缓存管理器 //func GetMenuCacheManager() *MenuCacheManager { // return menuCacheManager //} // //// CloseMenuCache 关闭菜单缓存 //func CloseMenuCache() error { // if menuCacheManager != nil { // return menuCacheManager.Close() // } // return nil //} // //// Authorize 修改后的授权中间件 //func Authorize( // sess session.Manager, // menuService v1.MenuService, //) func(http.Handler) http.Handler { // // 初始化缓存 // if err := InitMenuCache(menuService); err != nil { // log.Printf("Failed to initialize menu cache: %v", err) // // 缓存初始化失败,降级到原始服务 // 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 := menuService.ListByRoleIDToMap(ctx, user.RoleID) // if err != nil || !hasPermission(menus, path) { // http.Error(w, "Forbidden", http.StatusForbidden) // return // } // log.Printf("listByRoleIDToMap (fallback): %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)) // }) // } // } // // 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() // // 权限检查 - 使用BigCache缓存服务 // menus, err := GetCachedMenuService().ListByRoleIDToMap(ctx, user.RoleID) // if err != nil || !hasPermission(menus, path) { // http.Error(w, "Forbidden", http.StatusForbidden) // return // } // log.Printf("listByRoleIDToMap (BigCache): %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 //}