bd8a9ff96d
通过 getUpdates 将群写入 Redis,供创建/编辑渠道时选择 chat_id,避免前端重复传递 token。
118 lines
2.3 KiB
Go
118 lines
2.3 KiB
Go
package safew
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"sync"
|
|
"time"
|
|
|
|
"aiaa-notification-service/internal/adapter"
|
|
)
|
|
|
|
type Poller func(token string, offset int64, timeout int) ([]adapter.SafewChat, int64, error)
|
|
|
|
type Watcher struct {
|
|
store ChatStore
|
|
poll Poller
|
|
bgIdle time.Duration
|
|
|
|
mu sync.Mutex
|
|
running map[string]context.CancelFunc
|
|
stopped bool
|
|
}
|
|
|
|
func NewWatcher(store ChatStore, poll Poller) *Watcher {
|
|
return &Watcher{
|
|
store: store,
|
|
poll: poll,
|
|
bgIdle: time.Second,
|
|
running: map[string]context.CancelFunc{},
|
|
}
|
|
}
|
|
|
|
func (w *Watcher) Ensure(token string) {
|
|
if token == "" || w == nil {
|
|
return
|
|
}
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
if w.stopped {
|
|
return
|
|
}
|
|
if _, ok := w.running[token]; ok {
|
|
return
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
w.running[token] = cancel
|
|
go w.loop(ctx, token)
|
|
}
|
|
|
|
func (w *Watcher) Stop() {
|
|
w.mu.Lock()
|
|
w.stopped = true
|
|
for _, cancel := range w.running {
|
|
cancel()
|
|
}
|
|
w.running = map[string]context.CancelFunc{}
|
|
w.mu.Unlock()
|
|
}
|
|
|
|
func (w *Watcher) Refresh(ctx context.Context, token string) error {
|
|
return w.pollOnce(ctx, token, 0)
|
|
}
|
|
|
|
func (w *Watcher) List(ctx context.Context, token, q string) ([]adapter.SafewChat, error) {
|
|
chats, err := w.store.ListSafewChats(ctx, token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return adapter.FilterSafewChats(chats, q), nil
|
|
}
|
|
|
|
func (w *Watcher) loop(ctx context.Context, token string) {
|
|
for {
|
|
if err := w.pollOnce(ctx, token, 30); err != nil {
|
|
if ctx.Err() != nil {
|
|
return
|
|
}
|
|
slog.Warn("safew watcher poll", "error", err)
|
|
}
|
|
idle := w.bgIdle
|
|
if idle <= 0 {
|
|
idle = time.Second
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-time.After(idle):
|
|
}
|
|
}
|
|
}
|
|
|
|
func (w *Watcher) pollOnce(ctx context.Context, token string, timeout int) error {
|
|
ok, err := w.store.TrySafewPollLock(ctx, token, 35*time.Second)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ok {
|
|
return nil
|
|
}
|
|
defer func() { _ = w.store.UnlockSafewPoll(ctx, token) }()
|
|
|
|
offset, err := w.store.GetSafewOffset(ctx, token)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
chats, next, err := w.poll(token, offset, timeout)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := w.store.MergeSafewChats(ctx, token, chats); err != nil {
|
|
return err
|
|
}
|
|
if next != offset {
|
|
return w.store.SetSafewOffset(ctx, token, next)
|
|
}
|
|
return nil
|
|
}
|