antispam.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package antispam
  2. import (
  3. "fmt"
  4. "strings"
  5. "time"
  6. "go-common/app/interface/main/upload/model"
  7. "go-common/library/cache/redis"
  8. "go-common/library/ecode"
  9. bm "go-common/library/net/http/blademaster"
  10. "go-common/library/net/http/blademaster/binding"
  11. )
  12. const (
  13. _prefixRate = "r_%d_%s_%d"
  14. _prefixTotal = "t_%d_%s_%d"
  15. )
  16. // Antispam is a antispam instance.
  17. type Antispam struct {
  18. redis *redis.Pool
  19. limitFunc func(bucket, dir string) (model.DirRateConfig, bool)
  20. conf *Config
  21. }
  22. // Config antispam config.
  23. // On bool // switch on/off
  24. // Second int // every N second allow N requests.
  25. // N int // one unit allow N requests.
  26. // Hour int // every N hour allow M requests.
  27. // M int // one winodw allow M requests.
  28. type Config struct {
  29. On bool // switch on/off
  30. Second int // every N second allow N requests.
  31. N int // one unit allow N requests.
  32. Hour int // every N hour allow M requests.
  33. M int // one winodw allow M requests.
  34. Redis *redis.Config
  35. }
  36. // New new a antispam service.
  37. func New(c *Config, l func(bucket, dir string) (model.DirRateConfig, bool)) (s *Antispam) {
  38. if c == nil {
  39. panic("antispam config nil")
  40. }
  41. s = &Antispam{
  42. limitFunc: l,
  43. redis: redis.NewPool(c.Redis),
  44. }
  45. s.conf = c
  46. return s
  47. }
  48. // NativeRate limit user + path second level
  49. func (s *Antispam) NativeRate(c *bm.Context, path string, mid interface{}) (err error) {
  50. curSecond := int(time.Now().Unix())
  51. burst := curSecond - curSecond%s.conf.Second
  52. key := rateKey(mid.(int64), path, burst)
  53. return s.antispam(c, key, s.conf.Second, s.conf.N)
  54. }
  55. // Rate antispam by user + bucket + dir.
  56. func (s *Antispam) Rate(c *bm.Context) (err error) {
  57. mid, ok := c.Get("mid")
  58. if !ok {
  59. return
  60. }
  61. ap := new(struct {
  62. Bucket string `form:"bucket" json:"bucket"`
  63. Dir string `form:"dir" json:"dir"`
  64. })
  65. if err = c.BindWith(ap, binding.FormMultipart); err != nil {
  66. return s.NativeRate(c, c.Request.URL.Path, mid)
  67. }
  68. if ap.Bucket == "" || ap.Dir == "" { //not need dir limit
  69. return s.NativeRate(c, c.Request.URL.Path, mid)
  70. }
  71. limit, ok := s.limitFunc(ap.Bucket, ap.Dir)
  72. if !ok {
  73. return s.NativeRate(c, c.Request.URL.Path, mid)
  74. }
  75. if limit.SecondQPS == 0 || limit.CountQPS == 0 {
  76. return s.NativeRate(c, c.Request.URL.Path, mid)
  77. }
  78. path := strings.Join([]string{ap.Bucket, ap.Dir}, "_")
  79. curSecond := int(time.Now().Unix())
  80. burst := curSecond - curSecond%limit.SecondQPS
  81. key := rateKey(mid.(int64), path, burst)
  82. return s.antispam(c, key, limit.SecondQPS, limit.CountQPS)
  83. }
  84. func totalKey(mid int64, path string, burst int) string {
  85. return fmt.Sprintf(_prefixTotal, mid, path, burst)
  86. }
  87. // Total antispam by user + path hour level
  88. func (s *Antispam) Total(c *bm.Context, hour, count int) (err error) {
  89. second := hour * 3600
  90. mid, ok := c.Get("mid")
  91. if !ok {
  92. return
  93. }
  94. curHour := int(time.Now().Unix() / 3600)
  95. burst := curHour - curHour%hour
  96. key := totalKey(mid.(int64), c.Request.URL.Path, burst)
  97. return s.antispam(c, key, second, count)
  98. }
  99. func (s *Antispam) antispam(c *bm.Context, key string, interval, count int) (err error) {
  100. conn := s.redis.Get(c)
  101. defer conn.Close()
  102. cur, err := redis.Int(conn.Do("GET", key))
  103. if err != nil && err != redis.ErrNil {
  104. err = nil
  105. return
  106. }
  107. if cur >= count {
  108. err = ecode.LimitExceed
  109. return
  110. }
  111. err = nil
  112. conn.Send("INCR", key)
  113. conn.Send("EXPIRE", key, interval)
  114. if err1 := conn.Flush(); err1 != nil {
  115. return
  116. }
  117. for i := 0; i < 2; i++ {
  118. if _, err1 := conn.Receive(); err1 != nil {
  119. return
  120. }
  121. }
  122. return
  123. }
  124. func rateKey(mid int64, path string, burst int) string {
  125. return fmt.Sprintf(_prefixRate, mid, path, burst)
  126. }
  127. func (s *Antispam) ServeHTTP(ctx *bm.Context) {
  128. // user + bucket + dir.
  129. if err := s.Rate(ctx); err != nil {
  130. ctx.JSON(nil, ecode.ServiceUnavailable)
  131. ctx.Abort()
  132. return
  133. }
  134. // user + path
  135. if err := s.Total(ctx, s.conf.Hour, s.conf.M); err != nil {
  136. ctx.JSON(nil, ecode.ServiceUnavailable)
  137. ctx.Abort()
  138. return
  139. }
  140. }
  141. // Handler is antispam handle.
  142. func (s *Antispam) Handler() bm.HandlerFunc {
  143. return s.ServeHTTP
  144. }