This repository has been archived on 2023-12-05. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
protect_trans_info/application/repository/logs.go
2023-08-24 23:40:46 +03:00

143 lines
2.5 KiB
Go

package repository
import (
"context"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type Cache interface {
Get(id uint64) []string
Append(id uint64, ip string)
}
type ConnLog struct {
bun.BaseModel `bun:"table:conn_log"`
UserID uint64 `bun:"user_id"`
IP string `bun:"ip_addr"`
TS time.Time `bun:"ts"`
}
func NewConnLogs(ctx context.Context, db *bun.DB, cache Cache, logger *zap.Logger, updateInterval time.Duration) (*ConnLogs, error) {
connLogs := &ConnLogs{db: db, cache: cache}
logger.Info("filling initial cache")
err := connLogs.fillCache(ctx)
if err != nil {
return nil, err
}
logger.Info("initial cache filled")
go func() {
ticker := time.NewTicker(updateInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
var err error
err = connLogs.fillCache(ctx)
if err != nil {
logger.Error("update cache", zap.Error(err))
}
}
}
}()
return connLogs, nil
}
type ConnLogs struct {
db *bun.DB
mu sync.RWMutex
cache Cache
lastTS time.Time
}
func (l *ConnLogs) fillCache(ctx context.Context) error {
var entity []ConnLog
l.mu.Lock()
defer l.mu.Unlock()
query := l.db.NewSelect().Model(&entity).
Order("ts").
Group("user_id", "ip_addr").
Column("user_id", "ip_addr").
ColumnExpr(`max("ts") as ts`)
if !l.lastTS.IsZero() {
query.Where(`"ts" > ? `, l.lastTS)
}
if err := query.Scan(ctx); err != nil {
return fmt.Errorf("select: %w", err)
}
loop:
for i := range entity {
item := &entity[i]
if ips := l.cache.Get(item.UserID); len(ips) == 0 {
l.cache.Append(item.UserID, item.IP)
continue
}
for _, s := range l.cache.Get(item.UserID) {
if s == item.IP {
continue loop
}
}
l.cache.Append(item.UserID, item.IP)
}
if len(entity) > 0 {
l.lastTS = entity[len(entity)-1].TS
}
return nil
}
func (l *ConnLogs) Get(_ context.Context, first, second uint64) (bool, error) {
ips1 := l.cache.Get(first)
ips2 := l.cache.Get(second)
if len(ips1) == 0 || len(ips2) == 0 {
return false, nil
}
for i := range ips1 {
for j := range ips2 {
if ips1[i] == ips2[j] {
return true, nil
}
}
}
return false, nil
}
func (l *ConnLogs) List(ctx context.Context) (string, error) {
var entity []ConnLog
if err := l.db.NewSelect().Model(&entity).Scan(ctx); err != nil {
return "", fmt.Errorf("select: %w", err)
}
marshal, err := json.Marshal(entity)
if err != nil {
return "", fmt.Errorf("marshal: %w", err)
}
return string(marshal), nil
}