github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/dbsync/dbsync.go (about) 1 package dbsync 2 3 import ( 4 "context" 5 "database/sql" 6 "log" 7 "time" 8 9 "github.com/bingoohuang/gg/pkg/mapp" 10 "github.com/bingoohuang/gg/pkg/sqx" 11 ) 12 13 type DbSync struct { 14 db *sql.DB 15 table string 16 config *Config 17 cache map[string]string 18 stop chan struct{} 19 } 20 21 func NewDbSync(db *sql.DB, table string, options ...Option) *DbSync { 22 return &DbSync{db: db, table: table, config: createConfig(options), stop: make(chan struct{})} 23 } 24 25 func WithPk(v string) Option { return func(c *Config) { c.pk = v } } 26 func WithV(v string) Option { return func(c *Config) { c.v = v } } 27 func WithDuration(v string) Option { return func(c *Config) { c.duration, _ = time.ParseDuration(v) } } 28 func WithNotify(f func(e Event, id, v string)) Option { return func(c *Config) { c.notify = f } } 29 func WithContext(v context.Context) Option { return func(c *Config) { c.Context = v } } 30 31 func (s *DbSync) Start() { 32 go s.loop() 33 } 34 35 func (s *DbSync) loop() { 36 t := time.NewTicker(s.config.duration) 37 defer t.Stop() 38 39 s.cache = make(map[string]string) 40 query := s.config.CreateQuery(s.table) 41 42 s.sync(query) 43 44 for { 45 select { 46 case <-t.C: 47 s.sync(query) 48 case <-s.config.Context.Done(): 49 return 50 case <-s.stop: 51 return 52 } 53 } 54 } 55 56 type row struct { 57 Pk string 58 V string 59 } 60 61 func (s *DbSync) sync(query string) { 62 var rows []row 63 err := sqx.NewSQL(query).Query(s.db, &rows) 64 if err != nil { 65 log.Printf("E! failed to execute query: %s, err: %v", query, err) 66 return 67 } 68 69 current := mapp.Clone(s.cache) 70 71 for _, r := range rows { 72 v, ok := s.cache[r.Pk] 73 if !ok || v != r.V { 74 s.cache[r.Pk] = r.V 75 76 if !ok { // 不存在 77 s.config.notify(EventCreate, r.Pk, r.V) 78 } else { // 存在,但是v变更了 79 s.config.notify(EventModify, r.Pk, r.V) 80 } 81 } 82 83 if ok { 84 delete(current, r.Pk) 85 } 86 } 87 88 for k, v := range current { 89 delete(s.cache, k) 90 s.config.notify(EventDelete, k, v) 91 } 92 } 93 94 func (s *DbSync) Stop() { 95 s.stop <- struct{}{} 96 } 97 98 type Option func(*Config) 99 100 type Event int 101 102 const ( 103 EventCreate Event = iota 104 EventDelete 105 EventModify 106 ) 107 108 type Config struct { 109 context.Context 110 v string 111 pk string 112 duration time.Duration 113 notify func(event Event, id, v string) 114 } 115 116 func (c Config) CreateQuery(t string) string { 117 return "select " + c.pk + " as pk," + c.v + " as v from " + t 118 } 119 120 func createConfig(options []Option) *Config { 121 c := &Config{} 122 123 for _, f := range options { 124 f(c) 125 } 126 127 if c.pk == "" { 128 c.pk = "id" 129 } 130 if c.v == "" { 131 c.v = "v" 132 } 133 if c.duration <= 0 { 134 c.duration = 3 * time.Second 135 } 136 if c.notify == nil { 137 c.notify = func(e Event, id, v string) { 138 log.Printf("event:%s id: %s v:%s,", e, id, v) 139 } 140 } 141 if c.Context == nil { 142 c.Context = context.Background() 143 } 144 145 return c 146 } 147 148 func (e Event) String() string { 149 switch e { 150 case EventCreate: 151 return "EventCreate" 152 case EventDelete: 153 return "EventDelete" 154 case EventModify: 155 return "EventModify" 156 } 157 158 return "Unknown" 159 }