package session import ( "context" "time" "github.com/go-redis/redis/v8" "go.mongodb.org/mongo-driver/bson" "repositories.action2quare.com/ayo/gocommon" "repositories.action2quare.com/ayo/gocommon/logger" ) type sessionRedis struct { *Authorization expireAt time.Time } type consumer_redis struct { consumer_common[*sessionRedis] redisClient *redis.Client } func NewConsumerWithRedis(ctx context.Context, redisUrl string, ttl time.Duration) (Consumer, error) { redisClient, err := gocommon.NewRedisClient(redisUrl) if err != nil { return nil, err } consumer := &consumer_redis{ consumer_common: consumer_common[*sessionRedis]{ ttl: ttl, ctx: ctx, stages: [2]*cache_stage[*sessionRedis]{make_cache_stage[*sessionRedis](), make_cache_stage[*sessionRedis]()}, startTime: time.Now(), }, redisClient: redisClient, } updateChannel := communication_channel_name_prefix + "_u" deleteChannel := communication_channel_name_prefix + "_d" sub := redisClient.Subscribe(ctx, updateChannel, deleteChannel) go func() { stageswitch := time.Now().Add(ttl) tickTimer := time.After(ttl) for { select { case <-ctx.Done(): return case <-tickTimer: consumer.changeStage() stageswitch = stageswitch.Add(ttl) tempttl := time.Until(stageswitch) tickTimer = time.After(tempttl) case msg := <-sub.Channel(): if msg == nil { return } if len(msg.Payload) == 0 { continue } switch msg.Channel { case updateChannel: key := msg.Payload raw, err := redisClient.Get(ctx, key).Result() if err != nil { logger.Println(err) } else if len(raw) > 0 { var si Authorization if bson.Unmarshal([]byte(raw), &si) == nil { consumer.add(key, &sessionRedis{ Authorization: &si, expireAt: time.Now().Add(consumer.ttl), }) } } case deleteChannel: key := msg.Payload consumer.delete(key) } } } }() return consumer, nil } func (c *consumer_redis) query_internal(key string) (*sessionRedis, bool, error) { if _, deleted := c.stages[0].deleted[key]; deleted { return nil, false, nil } if _, deleted := c.stages[1].deleted[key]; deleted { return nil, false, nil } found, ok := c.stages[0].cache[key] if !ok { found, ok = c.stages[1].cache[key] } if ok { return found, false, nil } payload, err := c.redisClient.Get(c.ctx, key).Result() if err == redis.Nil { return nil, false, nil } else if err != nil { logger.Println("consumer Query :", err) return nil, false, err } if len(payload) == 0 { return nil, false, nil } var auth Authorization if err := bson.Unmarshal([]byte(payload), &auth); err != nil { return nil, false, err } ttl, err := c.redisClient.TTL(c.ctx, key).Result() if err != nil { logger.Println("consumer Query :", err) return nil, false, err } si := &sessionRedis{ Authorization: &auth, expireAt: time.Now().Add(ttl), } c.add_internal(key, si) return si, true, nil } func (c *consumer_redis) Query(key string) (*Authorization, error) { c.lock.Lock() defer c.lock.Unlock() si, _, err := c.query_internal(key) if err != nil { return nil, err } if si == nil { return nil, nil } if time.Now().After(si.expireAt) { return nil, nil } return si.Authorization, nil } func (c *consumer_redis) Touch(key string) (*Authorization, error) { c.lock.Lock() defer c.lock.Unlock() ok, err := c.redisClient.Expire(c.ctx, key, c.ttl).Result() if err == redis.Nil { return nil, nil } else if err != nil { logger.Println("consumer Touch :", err) return nil, err } if ok { // redis에 살아있다. si, added, err := c.query_internal(key) if err != nil { return nil, err } if si == nil { return nil, nil } if !added { si.expireAt = time.Now().Add(c.ttl) // stage 0으로 옮기기 위해 add_internal을 다시 부름 c.add_internal(key, si) } return si.Authorization, nil } return nil, nil }