You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

slideimage.go 6.9 kB

3 years ago
3 years ago
3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. package slideimage
  2. import (
  3. "bytes"
  4. "image"
  5. "image/png"
  6. "math"
  7. "math/rand"
  8. "path"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "code.gitea.io/gitea/modules/labelmsg"
  13. "github.com/gomodule/redigo/redis"
  14. "code.gitea.io/gitea/modules/public"
  15. "code.gitea.io/gitea/modules/log"
  16. "code.gitea.io/gitea/modules/setting"
  17. "code.gitea.io/gitea/modules/redis/redis_client"
  18. "gitea.com/macaron/macaron"
  19. "github.com/disintegration/imaging"
  20. "github.com/google/uuid"
  21. )
  22. type SlideImage struct {
  23. SubURL string
  24. URLPrefix string
  25. SampleImages int
  26. StdWidth int
  27. StdHeight int
  28. MaskSize int
  29. ImageY int
  30. ImageXStart int
  31. MinImageX int
  32. Expiration int
  33. Tolerance int
  34. CachePrefix string
  35. CacheManualPrefix string
  36. }
  37. type Options struct {
  38. // Suburl path. Default is empty.
  39. SubURL string
  40. // URL prefix of getting captcha pictures. Default is "/slideimage/".
  41. URLPrefix string
  42. //Default is 4
  43. SampleImages int
  44. //Image width default 391
  45. StdWidth int
  46. //Image Height default 196
  47. StdHeight int
  48. // default 51
  49. MaskSize int
  50. // default 125
  51. ImageY int
  52. //default 0
  53. ImageXStart int
  54. //容忍的误差 default 2px
  55. Tolerance int
  56. // default 150
  57. MinImageX int
  58. // default 600 seconds
  59. Expiration int
  60. //default slide:
  61. CachePrefix string
  62. //default mslide: 验证通过,在缓存中记录已进行过人工操作,然后在发送验证码之前再进行校验是否进行了人工操作。
  63. CacheManualPrefix string
  64. }
  65. func NewSlideImage(opt Options) *SlideImage {
  66. return &SlideImage{
  67. SubURL: opt.SubURL,
  68. URLPrefix: opt.URLPrefix,
  69. SampleImages: opt.SampleImages,
  70. StdWidth: opt.StdWidth,
  71. StdHeight: opt.StdHeight,
  72. MaskSize: opt.MaskSize,
  73. ImageY: opt.ImageY,
  74. ImageXStart: opt.ImageXStart,
  75. Tolerance: opt.Tolerance,
  76. MinImageX: opt.MinImageX,
  77. Expiration: opt.Expiration,
  78. CachePrefix: opt.CachePrefix,
  79. }
  80. }
  81. func prepareOptions(options []Options) Options {
  82. var opt Options
  83. if len(options) > 0 {
  84. opt = options[0]
  85. }
  86. opt.SubURL = strings.TrimSuffix(opt.SubURL, "/")
  87. // Defaults.
  88. if len(opt.URLPrefix) == 0 {
  89. opt.URLPrefix = "/slideimage/"
  90. } else if opt.URLPrefix[len(opt.URLPrefix)-1] != '/' {
  91. opt.URLPrefix += "/"
  92. }
  93. if opt.SampleImages == 0 {
  94. opt.SampleImages = 4
  95. }
  96. if opt.StdWidth == 0 {
  97. opt.StdWidth = 391
  98. }
  99. if opt.StdHeight == 0 {
  100. opt.StdHeight = 196
  101. }
  102. if opt.MaskSize == 0 {
  103. opt.MaskSize = 51
  104. }
  105. if opt.ImageY == 0 {
  106. opt.ImageY = 75
  107. }
  108. if opt.ImageXStart == 0 {
  109. opt.ImageXStart = 2
  110. }
  111. if opt.Tolerance == 0 {
  112. opt.Tolerance = 2
  113. }
  114. if opt.MinImageX == 0 {
  115. opt.MinImageX = 150
  116. }
  117. if opt.Expiration == 0 {
  118. opt.Expiration = 600
  119. }
  120. if len(opt.CachePrefix) == 0 {
  121. opt.CachePrefix = "slide:"
  122. }
  123. if len(opt.CacheManualPrefix) == 0 {
  124. opt.CacheManualPrefix = "mslide:"
  125. }
  126. return opt
  127. }
  128. func (s *SlideImage) key(id string) string {
  129. return s.CachePrefix + id
  130. }
  131. func (s *SlideImage) mkey(id string) string {
  132. return s.CacheManualPrefix + id
  133. }
  134. func (s *SlideImage) VerifyManual(id string) (bool, error) {
  135. redisConn := labelmsg.Get()
  136. defer redisConn.Close()
  137. return redis_client.EXISTS(redisConn, s.mkey(id))
  138. }
  139. func (s *SlideImage) Verify(id string, x int) bool {
  140. redisConn := labelmsg.Get()
  141. defer redisConn.Close()
  142. v, err := redis_client.GET(redisConn, s.key(id))
  143. v1, err := redis.String(v, err)
  144. if err != nil {
  145. log.Warn("redis err", err)
  146. return false
  147. }
  148. if v1 == "" {
  149. return false
  150. }
  151. values := strings.Split(v1, "-")
  152. imageRandX, _ := strconv.Atoi(values[1])
  153. if int(math.Abs(float64(imageRandX-x))) <= s.Tolerance {
  154. redis_client.SETNX(redisConn, s.mkey(id), "1", s.Expiration)
  155. return true
  156. }
  157. return false
  158. }
  159. func (s *SlideImage) CreateCode() (string, int, int) {
  160. nums := rand.Intn(s.SampleImages)
  161. imageId := uuid.New().String()
  162. //获取随机x坐标
  163. imageRandX := rand.Intn(s.StdWidth - s.MaskSize - 3)
  164. if imageRandX < s.MinImageX {
  165. imageRandX += s.MinImageX
  166. }
  167. redis_client.Setex(s.key(imageId), strconv.Itoa(nums)+"-"+strconv.Itoa(imageRandX), time.Second*time.Duration(s.Expiration))
  168. return imageId, nums, imageRandX
  169. }
  170. func SlideImager(options ...Options) macaron.Handler {
  171. return func(ctx *macaron.Context) {
  172. slideImage := NewSlideImage(prepareOptions(options))
  173. if strings.HasPrefix(ctx.Req.URL.Path, slideImage.URLPrefix) {
  174. id := path.Base(ctx.Req.URL.Path)
  175. if i := strings.Index(id, "."); i > -1 {
  176. id = id[:i]
  177. }
  178. isScreenshot := strings.HasSuffix(id, "screenshot")
  179. if isScreenshot {
  180. id = strings.TrimSuffix(id, "screenshot")
  181. }
  182. key := slideImage.key(id)
  183. v, err := redis_client.Get(key)
  184. if err != nil || v == "" {
  185. ctx.Status(404)
  186. ctx.Write([]byte("not found"))
  187. //png.Encode(ctx.Resp, *setting.SlideImagesBg[0])
  188. return
  189. }
  190. values := strings.Split(v, "-")
  191. imageIndex, _ := strconv.Atoi(values[0])
  192. imageRandX, _ := strconv.Atoi(values[1])
  193. imageBg := setting.SlideImagesBg[imageIndex]
  194. maxPotion := image.Point{
  195. X: imageRandX + slideImage.MaskSize,
  196. Y: slideImage.ImageY + slideImage.MaskSize,
  197. }
  198. minPotion := image.Point{
  199. X: imageRandX,
  200. Y: slideImage.ImageY,
  201. }
  202. subimg := image.Rectangle{
  203. Max: maxPotion,
  204. Min: minPotion,
  205. }
  206. if isScreenshot {
  207. data := imaging.Crop(*imageBg, subimg)
  208. png.Encode(ctx.Resp, data)
  209. } else {
  210. data := imaging.Overlay(*imageBg, *setting.SlideMaskImage, minPotion, 1.0)
  211. png.Encode(ctx.Resp, data)
  212. }
  213. ctx.Status(200)
  214. return
  215. }
  216. ctx.Data["SlideImageInfo"] = slideImage
  217. ctx.Data["EnablePhone"] = setting.PhoneService.Enabled
  218. ctx.Map(slideImage)
  219. }
  220. }
  221. func InitSlideImage() {
  222. if setting.PhoneService.Enabled {
  223. filenames, err := public.Dir(path.Join("img", "slide", "bg"))
  224. if err != nil {
  225. panic("Slide Image Service init failed")
  226. }
  227. maskFileName, err := public.Dir(path.Join("img", "slide", "mask"))
  228. if err != nil {
  229. panic("Slide Image Service init failed")
  230. }
  231. for _, filename := range filenames {
  232. if strings.HasSuffix(filename, ".png") {
  233. content, err := public.Asset(path.Join("img", "slide", "bg", filename))
  234. if err != nil {
  235. log.Warn("can not open "+filename, err)
  236. continue
  237. }
  238. m, err := png.Decode(bytes.NewReader(content))
  239. if err != nil {
  240. log.Warn("can not decode "+filename, err)
  241. continue
  242. }
  243. setting.SlideImagesBg = append(setting.SlideImagesBg, &m)
  244. }
  245. }
  246. setting.SlideImagesCount = len(setting.SlideImagesBg)
  247. if setting.SlideImagesCount == 0 {
  248. panic("Slide Image Service init failed")
  249. }
  250. maskContent, err := public.Asset(path.Join("img", "slide", "mask", maskFileName[0]))
  251. if err != nil {
  252. panic("Slide Image Service init failed")
  253. }
  254. MaskImage, err := png.Decode(bytes.NewReader(maskContent))
  255. if err != nil {
  256. panic("Slide Image Service init failed")
  257. }
  258. setting.SlideMaskImage = &MaskImage
  259. log.Info("Slide Image Service Enabled")
  260. }
  261. }