164 lines
3.5 KiB
Go
164 lines
3.5 KiB
Go
package system
|
||
|
||
import (
|
||
"sync"
|
||
"time"
|
||
|
||
"kra/internal/conf"
|
||
|
||
"github.com/go-kratos/kratos/v2/transport/http"
|
||
"github.com/mojocn/base64Captcha"
|
||
)
|
||
|
||
// CaptchaService 验证码服务
|
||
type CaptchaService struct {
|
||
keyLong int
|
||
imgWidth int
|
||
imgHeight int
|
||
openCaptcha int // 防爆次数,0表示关闭
|
||
openCaptchaTimeOut int // 缓存超时时间(秒)
|
||
store base64Captcha.Store
|
||
blackCache *BlackCache
|
||
}
|
||
|
||
// CaptchaConfig 验证码配置(保留用于兼容)
|
||
type CaptchaConfig struct {
|
||
KeyLong int
|
||
ImgWidth int
|
||
ImgHeight int
|
||
OpenCaptcha int
|
||
OpenCaptchaTimeOut int
|
||
}
|
||
|
||
// BlackCache 黑名单缓存
|
||
type BlackCache struct {
|
||
mu sync.RWMutex
|
||
items map[string]*cacheItem
|
||
}
|
||
|
||
type cacheItem struct {
|
||
value int
|
||
expiration time.Time
|
||
}
|
||
|
||
// NewBlackCache 创建黑名单缓存
|
||
func NewBlackCache() *BlackCache {
|
||
bc := &BlackCache{
|
||
items: make(map[string]*cacheItem),
|
||
}
|
||
go bc.cleanup()
|
||
return bc
|
||
}
|
||
|
||
func (c *BlackCache) Set(key string, value int, duration time.Duration) {
|
||
c.mu.Lock()
|
||
defer c.mu.Unlock()
|
||
c.items[key] = &cacheItem{
|
||
value: value,
|
||
expiration: time.Now().Add(duration),
|
||
}
|
||
}
|
||
|
||
func (c *BlackCache) Get(key string) (int, bool) {
|
||
c.mu.RLock()
|
||
defer c.mu.RUnlock()
|
||
item, ok := c.items[key]
|
||
if !ok || time.Now().After(item.expiration) {
|
||
return 0, false
|
||
}
|
||
return item.value, true
|
||
}
|
||
|
||
func (c *BlackCache) cleanup() {
|
||
ticker := time.NewTicker(time.Minute)
|
||
for range ticker.C {
|
||
c.mu.Lock()
|
||
now := time.Now()
|
||
for k, v := range c.items {
|
||
if now.After(v.expiration) {
|
||
delete(c.items, k)
|
||
}
|
||
}
|
||
c.mu.Unlock()
|
||
}
|
||
}
|
||
|
||
// CaptchaResponse 验证码响应
|
||
type CaptchaResponse struct {
|
||
CaptchaId string `json:"captchaId"`
|
||
PicPath string `json:"picPath"`
|
||
CaptchaLength int `json:"captchaLength"`
|
||
OpenCaptcha bool `json:"openCaptcha"`
|
||
}
|
||
|
||
// NewCaptchaService 创建验证码服务
|
||
func NewCaptchaService(c *conf.Captcha) *CaptchaService {
|
||
return &CaptchaService{
|
||
keyLong: int(c.KeyLong),
|
||
imgWidth: int(c.ImgWidth),
|
||
imgHeight: int(c.ImgHeight),
|
||
openCaptcha: int(c.OpenCaptcha),
|
||
openCaptchaTimeOut: int(c.OpenCaptchaTimeout),
|
||
store: base64Captcha.DefaultMemStore,
|
||
blackCache: NewBlackCache(),
|
||
}
|
||
}
|
||
|
||
// RegisterRoutes 注册路由
|
||
func (s *CaptchaService) RegisterRoutes(rg *RouterGroup) {
|
||
// 验证码(公开路由,无需认证)
|
||
rg.Public.POST("/base/captcha", s.handleCaptcha)
|
||
}
|
||
|
||
func (s *CaptchaService) handleCaptcha(ctx http.Context) error {
|
||
// 获取客户端IP
|
||
clientIP := ctx.Request().RemoteAddr
|
||
|
||
// 判断验证码是否开启
|
||
openCaptcha := s.openCaptcha
|
||
openCaptchaTimeOut := s.openCaptchaTimeOut
|
||
|
||
v, ok := s.blackCache.Get(clientIP)
|
||
if !ok {
|
||
s.blackCache.Set(clientIP, 1, time.Second*time.Duration(openCaptchaTimeOut))
|
||
}
|
||
|
||
var oc bool
|
||
if openCaptcha == 0 || openCaptcha < v {
|
||
oc = true
|
||
}
|
||
|
||
// 生成验证码
|
||
driver := base64Captcha.NewDriverDigit(
|
||
s.imgHeight,
|
||
s.imgWidth,
|
||
s.keyLong,
|
||
0.7,
|
||
80,
|
||
)
|
||
cp := base64Captcha.NewCaptcha(driver, s.store)
|
||
id, b64s, _, err := cp.Generate()
|
||
if err != nil {
|
||
return ctx.Result(200, map[string]any{
|
||
"code": 7,
|
||
"msg": "验证码获取失败",
|
||
})
|
||
}
|
||
|
||
return ctx.Result(200, map[string]any{
|
||
"code": 0,
|
||
"msg": "验证码获取成功",
|
||
"data": CaptchaResponse{
|
||
CaptchaId: id,
|
||
PicPath: b64s,
|
||
CaptchaLength: s.keyLong,
|
||
OpenCaptcha: oc,
|
||
},
|
||
})
|
||
}
|
||
|
||
// Verify 验证验证码
|
||
func (s *CaptchaService) Verify(id, answer string) bool {
|
||
return s.store.Verify(id, answer, true)
|
||
}
|