package query import ( "fmt" "go-admin/app/observe/models" "math/rand" "strconv" "sync" "time" log "github.com/go-admin-team/go-admin-core/logger" "github.com/go-redis/redis/v7" "github.com/pkg/errors" ) type UrlMapping struct { Query } func NewUrlMapping() UrlMapping { q := Query{} q.Init() return UrlMapping{q} } // 通过一个url匹配表中的kind,即匹配表中的url,表中的url为正则 func (q UrlMapping) UrlToKind(appId int64, url string) (kind string, err error) { err = q.db.Model(&models.UrlMapping{}).Where("app_id", appId).Where("? REGEXP CONCAT('^', url, '$')", url).Pluck("url", &kind).Error return } // 这里的url参数,必须不包括?后的参数,仅path部分,否则会有很多 func (q UrlMapping) UrlToKindCache(appId int64, url string) (kind string, err error) { key := fmt.Sprintf("observe__url_mapping_url_kind_%d_%s", appId, url) if kind, err = q.rdb.Get(key).Result(); err == nil { return } else if err == redis.Nil { // 说明不存在该key kind, err = q.UrlToKind(appId, url) if err != nil { return } q.rdb.Set(key, kind, time.Hour) // 缓存一小时,这里即使kind为空也缓存,防止一直访问数据库 } return } func (q UrlMapping) UrlToType(appId int64, url string) (t int, err error) { err = q.db.Model(&models.UrlMapping{}).Where("app_id", appId).Where("? REGEXP CONCAT('^', url, '$')", url).Pluck("type", &t).Error return } func (q UrlMapping) UrlToTypeCache(appId int64, url string) (t int, err error) { key := fmt.Sprintf("observe__url_mapping_url_type_%d_%s", appId, url) var ts string if ts, err = q.rdb.Get(key).Result(); err == nil { t, err = strconv.Atoi(ts) return } else if err == redis.Nil { // 说明不存在该key t, err = q.UrlToType(appId, url) if err != nil { return } q.rdb.Set(key, t, time.Hour) // 缓存一小时,这里即使t为0也缓存,防止一直访问数据库 } return } type urlMap struct { ExpireAt time.Time SafeMap *sync.Map // Map map[string]int } // var app2UrlMap map[int64]urlMap = map[int64]urlMap{} var app2UrlMap sync.Map = sync.Map{} func (q UrlMapping) UrlToTypeFast(appId int64, url string) (t int, err error) { if umi, ok := app2UrlMap.Load(appId); ok { um := umi.(*urlMap) if um.ExpireAt.After(time.Now()) { if t, ok := um.SafeMap.Load(url); ok { return t.(int), nil } else { return 0, errors.New(fmt.Sprintf("缓存中未获取到url type, appid: %d, url: %s", appId, url)) } } } // 分布式锁 lock := fmt.Sprintf("urlmap_lock_%d", appId) b, err := q.rdb.SetNX(lock, 1, time.Second*30).Result() if err != nil { return 0, errors.Wrap(err, fmt.Sprintf("分布式锁设置失败: %d", appId)) } if !b { return 0, errors.New(fmt.Sprintf("分布式锁生效中: %d", appId)) } defer q.rdb.Del(lock) urlList := []struct { Url string Type int }{} err = q.db.Model(&models.UrlMapping{}).Select("url, type").Where("app_id", appId).Where("is_perfect_match", 1).Find(&urlList).Error if err != nil { return 0, errors.Wrap(err, fmt.Sprintf("获取应用%d下urlList失败", appId)) } seconds := time.Second * time.Duration(rand.Intn(600)+300) // 缓存 10~15 分钟, 目的就防止同时失效导致多个应用并发读库 um := &urlMap{ ExpireAt: time.Now().Add(seconds), SafeMap: &sync.Map{}, } err = errors.New(fmt.Sprintf("url: %s 不存在", url)) for _, urlItem := range urlList { um.SafeMap.Store(urlItem.Url, urlItem.Type) if urlItem.Url == url { t, err = urlItem.Type, nil } } app2UrlMap.Store(appId, um) return t, err } func (q UrlMapping) Exists(appAlias, serviceName, method, route string) (bool, error) { var count int64 if err := q.db.Model(&models.UrlMapping{}). Where("app_alias=? and service_name=? and method=? and url=?", appAlias, serviceName, method, route). Count(&count).Error; err != nil { return false, errors.Wrap(err, "查询失败") } return count > 0, nil } func (q UrlMapping) ExistsCache(appAlias, serviceName, method, route string) (bool, error) { key := fmt.Sprintf("observe__url_mapping_exists_%s_%s_%s_%s", appAlias, serviceName, method, route) if val, err := q.rdb.Get(key).Result(); err == nil { if val == "1" { return true, nil } return false, nil } exists, err := q.Exists(appAlias, serviceName, method, route) if err != nil { return false, err } val := 0 if exists { val = 1 } q.rdb.Set(key, val, 10*time.Minute) return exists, nil } func (q UrlMapping) UrlMappingID(appAlias, serviceName, method, route string) int { key := fmt.Sprintf("observe__biz_url_mapping_id_app_%s", appAlias) // 为了防止value过大,每个appid存一个 field := fmt.Sprintf("%s-%s-%s", serviceName, method, route) id, err := q.rdb.HGet(key, field).Result() if err == nil { // 如果获取不到数据, err 为 redis.Nil, err == nil时,肯定获取到了数据 res, _ := strconv.Atoi(id) return res } else if err == redis.Nil { log.Debugf("key: %s, field: %s不存在", key, field) } urlmappings := []struct { ServiceName string Method string Url string Id int }{} if err := q.db.Model(&models.UrlMapping{}).Select("service_name, method, url, id"). Where("app_alias=?", appAlias).Find(&urlmappings).Error; err != nil { log.Errorf("获取url mapping信息失败: app_alias %s", appAlias) return 0 } vals := map[string]interface{}{} for _, item := range urlmappings { field := fmt.Sprintf("%s-%s-%s", item.ServiceName, item.Method, item.Url) vals[field] = item.Id } q.rdb.HSet(key, vals) q.rdb.Expire(key, time.Minute*5) if val, ok := vals[field]; ok { return val.(int) } log.Debugf("url mapping表中不存在该数据(app_alias:%s, serviceName:%s, method:%s, url:%s)", appAlias, serviceName, method, route) return 0 } func (q UrlMapping) UrlToName(appAlias, method, url string) (string, error) { var name string db := q.db.Model(&models.UrlMapping{}). Where("app_alias", appAlias).Where("url", url).Where("is_perfect_match", 1) if method != "" { db.Where("method", method) } db.Pluck("name", &name) if err := db.Pluck("name", &name).Error; err != nil { return "", err } return name, nil } func (q UrlMapping) UrlToNameCache(appAlias, method, url string) (string, error) { key := fmt.Sprintf("observe__biz_url_to_name_%s_%s_%s", appAlias, method, url) name, err := q.rdb.Get(key).Result() if err == nil { return name, nil } name, err = q.UrlToName(appAlias, method, url) if err != nil { return "", err } q.rdb.Set(key, name, time.Minute*10) return name, nil }