redis.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. package cache
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "time"
  7. "git.bvbej.com/bvbej/base-golang/pkg/time_parse"
  8. "git.bvbej.com/bvbej/base-golang/pkg/trace"
  9. "github.com/redis/go-redis/v9"
  10. )
  11. type Option func(*option)
  12. type Trace = trace.T
  13. type option struct {
  14. Trace *trace.Trace
  15. Redis *trace.Redis
  16. }
  17. type RedisConfig struct {
  18. Addr string `yaml:"addr"`
  19. Pass string `yaml:"pass"`
  20. DB int `yaml:"db"`
  21. MaxRetries int `yaml:"maxRetries"` // 最大重试次数
  22. PoolSize int `yaml:"poolSize"` // Redis连接池大小
  23. MinIdleConn int `yaml:"minIdleConn"` // 最小空闲连接数
  24. }
  25. func newOption() *option {
  26. return &option{}
  27. }
  28. var _ Repo = (*cacheRepo)(nil)
  29. type Repo interface {
  30. i()
  31. Client() *redis.Client
  32. Set(key, value string, ttl time.Duration, options ...Option) error
  33. Get(key string, options ...Option) (string, error)
  34. TTL(key string) (time.Duration, error)
  35. Expire(key string, ttl time.Duration) bool
  36. ExpireAt(key string, ttl time.Time) bool
  37. Del(key string, options ...Option) bool
  38. Exists(keys ...string) bool
  39. Incr(key string, options ...Option) (int64, error)
  40. Decr(key string, options ...Option) (int64, error)
  41. HGet(key, field string, options ...Option) (string, error)
  42. HSet(key, field, value string, options ...Option) error
  43. HDel(key, field string, options ...Option) error
  44. HGetAll(key string, options ...Option) (map[string]string, error)
  45. LPush(key, value string, options ...Option) error
  46. LLen(key string, options ...Option) (int64, error)
  47. BRPop(key string, timeout time.Duration, options ...Option) (string, error)
  48. Close() error
  49. }
  50. type cacheRepo struct {
  51. client *redis.Client
  52. ctx context.Context
  53. }
  54. func New(cfg RedisConfig) (Repo, error) {
  55. client := redis.NewClient(&redis.Options{
  56. Addr: cfg.Addr,
  57. Password: cfg.Pass,
  58. DB: cfg.DB,
  59. MaxRetries: cfg.MaxRetries,
  60. PoolSize: cfg.PoolSize,
  61. MinIdleConns: cfg.MinIdleConn,
  62. })
  63. ctx := context.TODO()
  64. if err := client.Ping(ctx).Err(); err != nil {
  65. return nil, errors.Join(err, errors.New("ping redis err"))
  66. }
  67. return &cacheRepo{
  68. client: client,
  69. ctx: ctx,
  70. }, nil
  71. }
  72. func WithTrace(t Trace) Option {
  73. return func(opt *option) {
  74. if t != nil {
  75. opt.Trace = t.(*trace.Trace)
  76. opt.Redis = new(trace.Redis)
  77. }
  78. }
  79. }
  80. func (c *cacheRepo) i() {}
  81. func (c *cacheRepo) Client() *redis.Client {
  82. return c.client
  83. }
  84. func (c *cacheRepo) Set(key, value string, ttl time.Duration, options ...Option) error {
  85. ts := time.Now()
  86. opt := newOption()
  87. defer func() {
  88. if opt.Trace != nil {
  89. opt.Redis.Timestamp = time_parse.CSTLayoutString()
  90. opt.Redis.Handle = "set"
  91. opt.Redis.Key = key
  92. opt.Redis.Value = value
  93. opt.Redis.TTL = ttl.Minutes()
  94. opt.Redis.CostSeconds = time.Since(ts).Seconds()
  95. opt.Trace.AppendRedis(opt.Redis)
  96. }
  97. }()
  98. for _, f := range options {
  99. f(opt)
  100. }
  101. if err := c.client.Set(c.ctx, key, value, ttl).Err(); err != nil {
  102. return errors.Join(err, fmt.Errorf("redis set key: %s err", key))
  103. }
  104. return nil
  105. }
  106. func (c *cacheRepo) Get(key string, options ...Option) (string, error) {
  107. ts := time.Now()
  108. opt := newOption()
  109. defer func() {
  110. if opt.Trace != nil {
  111. opt.Redis.Timestamp = time_parse.CSTLayoutString()
  112. opt.Redis.Handle = "get"
  113. opt.Redis.Key = key
  114. opt.Redis.CostSeconds = time.Since(ts).Seconds()
  115. opt.Trace.AppendRedis(opt.Redis)
  116. }
  117. }()
  118. for _, f := range options {
  119. f(opt)
  120. }
  121. value, err := c.client.Get(c.ctx, key).Result()
  122. if err != nil {
  123. return "", errors.Join(err, fmt.Errorf("redis get key: %s err", key))
  124. }
  125. return value, nil
  126. }
  127. func (c *cacheRepo) TTL(key string) (time.Duration, error) {
  128. ttl, err := c.client.TTL(c.ctx, key).Result()
  129. if err != nil {
  130. return -1, errors.Join(err, fmt.Errorf("redis get key: %s err", key))
  131. }
  132. return ttl, nil
  133. }
  134. func (c *cacheRepo) Expire(key string, ttl time.Duration) bool {
  135. ok, _ := c.client.Expire(c.ctx, key, ttl).Result()
  136. return ok
  137. }
  138. func (c *cacheRepo) ExpireAt(key string, ttl time.Time) bool {
  139. ok, _ := c.client.ExpireAt(c.ctx, key, ttl).Result()
  140. return ok
  141. }
  142. func (c *cacheRepo) Exists(keys ...string) bool {
  143. if len(keys) == 0 {
  144. return true
  145. }
  146. value, _ := c.client.Exists(c.ctx, keys...).Result()
  147. return value > 0
  148. }
  149. func (c *cacheRepo) Del(key string, options ...Option) bool {
  150. ts := time.Now()
  151. opt := newOption()
  152. defer func() {
  153. if opt.Trace != nil {
  154. opt.Redis.Timestamp = time_parse.CSTLayoutString()
  155. opt.Redis.Handle = "del"
  156. opt.Redis.Key = key
  157. opt.Redis.CostSeconds = time.Since(ts).Seconds()
  158. opt.Trace.AppendRedis(opt.Redis)
  159. }
  160. }()
  161. for _, f := range options {
  162. f(opt)
  163. }
  164. if key == "" {
  165. return true
  166. }
  167. value, _ := c.client.Del(c.ctx, key).Result()
  168. return value > 0
  169. }
  170. func (c *cacheRepo) Incr(key string, options ...Option) (int64, error) {
  171. ts := time.Now()
  172. opt := newOption()
  173. defer func() {
  174. if opt.Trace != nil {
  175. opt.Redis.Timestamp = time_parse.CSTLayoutString()
  176. opt.Redis.Handle = "incr"
  177. opt.Redis.Key = key
  178. opt.Redis.CostSeconds = time.Since(ts).Seconds()
  179. opt.Trace.AppendRedis(opt.Redis)
  180. }
  181. }()
  182. for _, f := range options {
  183. f(opt)
  184. }
  185. value, err := c.client.Incr(c.ctx, key).Result()
  186. if err != nil {
  187. return 0, errors.Join(err, fmt.Errorf("redis incr key: %s err", key))
  188. }
  189. return value, nil
  190. }
  191. func (c *cacheRepo) Decr(key string, options ...Option) (int64, error) {
  192. ts := time.Now()
  193. opt := newOption()
  194. defer func() {
  195. if opt.Trace != nil {
  196. opt.Redis.Timestamp = time_parse.CSTLayoutString()
  197. opt.Redis.Handle = "decr"
  198. opt.Redis.Key = key
  199. opt.Redis.CostSeconds = time.Since(ts).Seconds()
  200. opt.Trace.AppendRedis(opt.Redis)
  201. }
  202. }()
  203. for _, f := range options {
  204. f(opt)
  205. }
  206. value, err := c.client.Decr(c.ctx, key).Result()
  207. if err != nil {
  208. return 0, errors.Join(err, fmt.Errorf("redis decr key: %s err", key))
  209. }
  210. return value, nil
  211. }
  212. func (c *cacheRepo) HGet(key, field string, options ...Option) (string, error) {
  213. ts := time.Now()
  214. opt := newOption()
  215. defer func() {
  216. if opt.Trace != nil {
  217. opt.Redis.Timestamp = time_parse.CSTLayoutString()
  218. opt.Redis.Handle = "hash get"
  219. opt.Redis.Key = key
  220. opt.Redis.Value = field
  221. opt.Redis.CostSeconds = time.Since(ts).Seconds()
  222. opt.Trace.AppendRedis(opt.Redis)
  223. }
  224. }()
  225. for _, f := range options {
  226. f(opt)
  227. }
  228. value, err := c.client.HGet(c.ctx, key, field).Result()
  229. if err != nil {
  230. return "", errors.Join(err, fmt.Errorf("redis hget key: %s field: %s err", key, field))
  231. }
  232. return value, nil
  233. }
  234. func (c *cacheRepo) HSet(key, field, value string, options ...Option) error {
  235. ts := time.Now()
  236. opt := newOption()
  237. defer func() {
  238. if opt.Trace != nil {
  239. opt.Redis.Timestamp = time_parse.CSTLayoutString()
  240. opt.Redis.Handle = "hash set"
  241. opt.Redis.Key = key
  242. opt.Redis.Value = field + "/" + value
  243. opt.Redis.CostSeconds = time.Since(ts).Seconds()
  244. opt.Trace.AppendRedis(opt.Redis)
  245. }
  246. }()
  247. for _, f := range options {
  248. f(opt)
  249. }
  250. if err := c.client.HSet(c.ctx, key, field, value).Err(); err != nil {
  251. return errors.Join(err, fmt.Errorf("redis hset key: %s field: %s err", key, field))
  252. }
  253. return nil
  254. }
  255. func (c *cacheRepo) HDel(key, field string, options ...Option) error {
  256. ts := time.Now()
  257. opt := newOption()
  258. defer func() {
  259. if opt.Trace != nil {
  260. opt.Redis.Timestamp = time_parse.CSTLayoutString()
  261. opt.Redis.Handle = "hash del"
  262. opt.Redis.Key = key
  263. opt.Redis.Value = field
  264. opt.Redis.CostSeconds = time.Since(ts).Seconds()
  265. opt.Trace.AppendRedis(opt.Redis)
  266. }
  267. }()
  268. for _, f := range options {
  269. f(opt)
  270. }
  271. if err := c.client.HDel(c.ctx, key, field).Err(); err != nil {
  272. return errors.Join(err, fmt.Errorf("redis hdel key: %s field: %s err", key, field))
  273. }
  274. return nil
  275. }
  276. func (c *cacheRepo) HGetAll(key string, options ...Option) (map[string]string, error) {
  277. ts := time.Now()
  278. opt := newOption()
  279. defer func() {
  280. if opt.Trace != nil {
  281. opt.Redis.Timestamp = time_parse.CSTLayoutString()
  282. opt.Redis.Handle = "hash get all"
  283. opt.Redis.Key = key
  284. opt.Redis.CostSeconds = time.Since(ts).Seconds()
  285. opt.Trace.AppendRedis(opt.Redis)
  286. }
  287. }()
  288. for _, f := range options {
  289. f(opt)
  290. }
  291. value, err := c.client.HGetAll(c.ctx, key).Result()
  292. if err != nil {
  293. return nil, errors.Join(err, fmt.Errorf("redis hget all key: %s err", key))
  294. }
  295. return value, nil
  296. }
  297. func (c *cacheRepo) LPush(key, value string, options ...Option) error {
  298. ts := time.Now()
  299. opt := newOption()
  300. defer func() {
  301. if opt.Trace != nil {
  302. opt.Redis.Timestamp = time_parse.CSTLayoutString()
  303. opt.Redis.Handle = "list push"
  304. opt.Redis.Key = key
  305. opt.Redis.Value = value
  306. opt.Redis.CostSeconds = time.Since(ts).Seconds()
  307. opt.Trace.AppendRedis(opt.Redis)
  308. }
  309. }()
  310. for _, f := range options {
  311. f(opt)
  312. }
  313. _, err := c.client.LPush(c.ctx, key, value).Result()
  314. if err != nil {
  315. return errors.Join(err, fmt.Errorf("redis list push key: %s value: %s err", key, value))
  316. }
  317. return nil
  318. }
  319. func (c *cacheRepo) LLen(key string, options ...Option) (int64, error) {
  320. ts := time.Now()
  321. opt := newOption()
  322. defer func() {
  323. if opt.Trace != nil {
  324. opt.Redis.Timestamp = time_parse.CSTLayoutString()
  325. opt.Redis.Handle = "list len"
  326. opt.Redis.Key = key
  327. opt.Redis.CostSeconds = time.Since(ts).Seconds()
  328. opt.Trace.AppendRedis(opt.Redis)
  329. }
  330. }()
  331. for _, f := range options {
  332. f(opt)
  333. }
  334. value, err := c.client.LLen(c.ctx, key).Result()
  335. if err != nil {
  336. return 0, errors.Join(err, fmt.Errorf("redis list len key: %s err", key))
  337. }
  338. return value, nil
  339. }
  340. func (c *cacheRepo) BRPop(key string, timeout time.Duration, options ...Option) (string, error) {
  341. ts := time.Now()
  342. opt := newOption()
  343. defer func() {
  344. if opt.Trace != nil {
  345. opt.Redis.Timestamp = time_parse.CSTLayoutString()
  346. opt.Redis.Handle = "list brpop"
  347. opt.Redis.Key = key
  348. opt.Redis.TTL = timeout.Seconds()
  349. opt.Redis.CostSeconds = time.Since(ts).Seconds()
  350. opt.Trace.AppendRedis(opt.Redis)
  351. }
  352. }()
  353. for _, f := range options {
  354. f(opt)
  355. }
  356. value, err := c.client.BRPop(c.ctx, timeout, key).Result()
  357. if err != nil {
  358. return "", errors.Join(err, fmt.Errorf("redis list len key: %s err", key))
  359. }
  360. return value[1], nil
  361. }
  362. func (c *cacheRepo) Close() error {
  363. return c.client.Close()
  364. }