package slideimage import ( "bytes" "image" "image/png" "math" "math/rand" "path" "strconv" "strings" "time" "code.gitea.io/gitea/modules/labelmsg" "github.com/gomodule/redigo/redis" "code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/redis/redis_client" "gitea.com/macaron/macaron" "github.com/disintegration/imaging" "github.com/google/uuid" ) type SlideImage struct { SubURL string URLPrefix string SampleImages int StdWidth int StdHeight int MaskSize int ImageY int ImageXStart int MinImageX int Expiration int Tolerance int CachePrefix string CacheManualPrefix string } type Options struct { // Suburl path. Default is empty. SubURL string // URL prefix of getting captcha pictures. Default is "/slideimage/". URLPrefix string //Default is 4 SampleImages int //Image width default 391 StdWidth int //Image Height default 196 StdHeight int // default 51 MaskSize int // default 125 ImageY int //default 0 ImageXStart int //容忍的误差 default 2px Tolerance int // default 150 MinImageX int // default 600 seconds Expiration int //default slide: CachePrefix string //default mslide: 验证通过,在缓存中记录已进行过人工操作,然后在发送验证码之前再进行校验是否进行了人工操作。 CacheManualPrefix string } func NewSlideImage(opt Options) *SlideImage { return &SlideImage{ SubURL: opt.SubURL, URLPrefix: opt.URLPrefix, SampleImages: opt.SampleImages, StdWidth: opt.StdWidth, StdHeight: opt.StdHeight, MaskSize: opt.MaskSize, ImageY: opt.ImageY, ImageXStart: opt.ImageXStart, Tolerance: opt.Tolerance, MinImageX: opt.MinImageX, Expiration: opt.Expiration, CachePrefix: opt.CachePrefix, } } func prepareOptions(options []Options) Options { var opt Options if len(options) > 0 { opt = options[0] } opt.SubURL = strings.TrimSuffix(opt.SubURL, "/") // Defaults. if len(opt.URLPrefix) == 0 { opt.URLPrefix = "/slideimage/" } else if opt.URLPrefix[len(opt.URLPrefix)-1] != '/' { opt.URLPrefix += "/" } if opt.SampleImages == 0 { opt.SampleImages = 4 } if opt.StdWidth == 0 { opt.StdWidth = 391 } if opt.StdHeight == 0 { opt.StdHeight = 196 } if opt.MaskSize == 0 { opt.MaskSize = 51 } if opt.ImageY == 0 { opt.ImageY = 75 } if opt.ImageXStart == 0 { opt.ImageXStart = 2 } if opt.Tolerance == 0 { opt.Tolerance = 2 } if opt.MinImageX == 0 { opt.MinImageX = 150 } if opt.Expiration == 0 { opt.Expiration = 600 } if len(opt.CachePrefix) == 0 { opt.CachePrefix = "slide:" } if len(opt.CacheManualPrefix) == 0 { opt.CacheManualPrefix = "mslide:" } return opt } func (s *SlideImage) key(id string) string { return s.CachePrefix + id } func (s *SlideImage) mkey(id string) string { return s.CacheManualPrefix + id } func (s *SlideImage) VerifyManual(id string) (bool, error) { redisConn := labelmsg.Get() defer redisConn.Close() return redis_client.EXISTS(redisConn, s.mkey(id)) } func (s *SlideImage) Verify(id string, x int) bool { redisConn := labelmsg.Get() defer redisConn.Close() v, err := redis_client.GET(redisConn, s.key(id)) v1, err := redis.String(v, err) if err != nil { log.Warn("redis err", err) return false } if v1 == "" { return false } values := strings.Split(v1, "-") imageRandX, _ := strconv.Atoi(values[1]) if int(math.Abs(float64(imageRandX-x))) <= s.Tolerance { redis_client.SETNX(redisConn, s.mkey(id), "1", s.Expiration) return true } return false } func (s *SlideImage) CreateCode() (string, int, int) { nums := rand.Intn(s.SampleImages) imageId := uuid.New().String() //获取随机x坐标 imageRandX := rand.Intn(s.StdWidth - s.MaskSize - 3) if imageRandX < s.MinImageX { imageRandX += s.MinImageX } redis_client.Setex(s.key(imageId), strconv.Itoa(nums)+"-"+strconv.Itoa(imageRandX), time.Second*time.Duration(s.Expiration)) return imageId, nums, imageRandX } func SlideImager(options ...Options) macaron.Handler { return func(ctx *macaron.Context) { slideImage := NewSlideImage(prepareOptions(options)) if strings.HasPrefix(ctx.Req.URL.Path, slideImage.URLPrefix) { id := path.Base(ctx.Req.URL.Path) if i := strings.Index(id, "."); i > -1 { id = id[:i] } isScreenshot := strings.HasSuffix(id, "screenshot") if isScreenshot { id = strings.TrimSuffix(id, "screenshot") } key := slideImage.key(id) v, err := redis_client.Get(key) if err != nil || v == "" { ctx.Status(404) ctx.Write([]byte("not found")) //png.Encode(ctx.Resp, *setting.SlideImagesBg[0]) return } values := strings.Split(v, "-") imageIndex, _ := strconv.Atoi(values[0]) imageRandX, _ := strconv.Atoi(values[1]) imageBg := setting.SlideImagesBg[imageIndex] maxPotion := image.Point{ X: imageRandX + slideImage.MaskSize, Y: slideImage.ImageY + slideImage.MaskSize, } minPotion := image.Point{ X: imageRandX, Y: slideImage.ImageY, } subimg := image.Rectangle{ Max: maxPotion, Min: minPotion, } if isScreenshot { data := imaging.Crop(*imageBg, subimg) png.Encode(ctx.Resp, data) } else { data := imaging.Overlay(*imageBg, *setting.SlideMaskImage, minPotion, 1.0) png.Encode(ctx.Resp, data) } ctx.Status(200) return } ctx.Data["SlideImageInfo"] = slideImage ctx.Data["EnablePhone"] = setting.PhoneService.Enabled ctx.Map(slideImage) } } func InitSlideImage() { if setting.PhoneService.Enabled { filenames, err := public.Dir(path.Join("img", "slide", "bg")) if err != nil { panic("Slide Image Service init failed") } maskFileName, err := public.Dir(path.Join("img", "slide", "mask")) if err != nil { panic("Slide Image Service init failed") } for _, filename := range filenames { if strings.HasSuffix(filename, ".png") { content, err := public.Asset(path.Join("img", "slide", "bg", filename)) if err != nil { log.Warn("can not open "+filename, err) continue } m, err := png.Decode(bytes.NewReader(content)) if err != nil { log.Warn("can not decode "+filename, err) continue } setting.SlideImagesBg = append(setting.SlideImagesBg, &m) } } setting.SlideImagesCount = len(setting.SlideImagesBg) if setting.SlideImagesCount == 0 { panic("Slide Image Service init failed") } maskContent, err := public.Asset(path.Join("img", "slide", "mask", maskFileName[0])) if err != nil { panic("Slide Image Service init failed") } MaskImage, err := png.Decode(bytes.NewReader(maskContent)) if err != nil { panic("Slide Image Service init failed") } setting.SlideMaskImage = &MaskImage log.Info("Slide Image Service Enabled") } }