538 lines
14 KiB
Go
538 lines
14 KiB
Go
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
|
||
//}
|