Browse Source

Merge branch 'bzq/dev' of eta_mini/eta_mini_api into master

鲍自强 7 months ago
parent
commit
594185c953

+ 111 - 12
controllers/user.go

@@ -9,8 +9,12 @@ import (
 	"eta/eta_mini_api/services/go_redis"
 	"eta/eta_mini_api/utils"
 	"fmt"
+	"image/color"
+	"strconv"
 	"strings"
 	"time"
+
+	"github.com/mojocn/base64Captcha"
 )
 
 type UserController struct {
@@ -115,6 +119,65 @@ func (this *UserAuthController) Login() {
 	br.Ret = 200
 }
 
+// GenerateCaptcha
+// @Title 生成图形验证码
+// @Description 生成图形验证码
+// @Success 200 Ret=200 获取成功
+// @router /getCaptcha [get]
+func (this *UserController) GenerateCaptcha() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	// 自定义验证码样式
+	var driver base64Captcha.Driver
+	driverString := base64Captcha.DriverString{
+		Height:          60,    //高度
+		Width:           120,   //宽度
+		NoiseCount:      0,     //干扰数
+		ShowLineOptions: 2 | 4, //展示个数
+		Length:          4,     //长度
+		//Source:          "1234567890qwertyuioplkjhgfdsazxcvbnm", //验证码随机字符串来源
+		Source: "1234567890", //验证码随机字符串来源
+		BgColor: &color.RGBA{ // 背景颜色
+			R: 0,
+			G: 0,
+			B: 0,
+			A: 0,
+		},
+		Fonts: []string{"wqy-microhei.ttc"}, // 字体
+	}
+	driver = driverString.ConvertFonts()
+
+	// 生成验证码
+	store := services.CaptchaRedis{}
+	captcha := base64Captcha.NewCaptcha(driver, store)
+	id, b64s, _, err := captcha.Generate()
+	if err != nil {
+		br.Msg = "生成失败"
+		br.ErrMsg = "生成验证码失败, Err: " + err.Error()
+		return
+	}
+
+	type CaptchaResult struct {
+		Id         string
+		Base64Blob string
+	}
+	res := new(CaptchaResult)
+	res.Id = id
+	res.Base64Blob = b64s
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = res
+}
+
 // @Title 获取短信/邮箱验证码
 // @Description 用户登录
 // @Param	request	body models.LoginReq true "type json string"
@@ -138,6 +201,10 @@ func (this *UserController) GetVerifyCode() {
 		br.Msg = "验证方式有误"
 		br.ErrMsg = fmt.Sprintf("验证方式异常<%d>", req.VerifyType)
 	}
+	if req.CaptchaId == "" || req.CaptchaCode == "" {
+		br.Msg = "请输入图形验证码"
+		return
+	}
 
 	code := utils.GetRandDigit(6)
 	fmt.Println(code)
@@ -157,11 +224,24 @@ func (this *UserController) GetVerifyCode() {
 		}
 		phoneKey := utils.CACHE_ACCESS_PHONE_LOGIN_CODE + req.AreaCode + req.Phone
 		res, _ := go_redis.RedisInt(phoneKey)
-		if res > 5 {
+		if res >= 5 {
 			br.Msg = "验证码发送太频繁,请稍后重试"
 			return
 		}
+
+		phoneCountKey := utils.CACHE_ACCESS_PHONE_COUNT_LOGIN_CODE + req.AreaCode + req.Phone
+		resCount, _ := go_redis.RedisInt(phoneCountKey)
+		if resCount >= utils.VerifyCodeSendLimit {
+			br.Msg = fmt.Sprintf("一天最多获取%s次,已超限", strconv.Itoa(utils.VerifyCodeSendLimit))
+			return
+		}
+		store := services.CaptchaRedis{}
 		var ok bool
+		ok = store.Verify(req.CaptchaId, req.CaptchaCode, true)
+		if !ok {
+			br.Msg = "图形验证码错误"
+			return
+		}
 		if req.AreaCode == "86" {
 			ok = services.SendSmsCode(req.Phone, code)
 		}
@@ -183,11 +263,14 @@ func (this *UserController) GetVerifyCode() {
 				return
 			}
 			br.Msg = "发送成功"
-			isExist := go_redis.IsExist(phoneKey)
-			if isExist {
-				go_redis.Incr(phoneKey)
-			} else {
-				go_redis.SetNX(phoneKey, 1, time.Minute*15)
+			phoneVerifyCahcheSvc := &services.VerifyCacheIncrService{}
+			err = phoneVerifyCahcheSvc.VerifyCacheIncr(phoneKey, 15*int(time.Minute.Seconds()))
+			if err != nil {
+				utils.FileLog.Info("验证码手机号临时缓存失败", err.Error())
+			}
+			err = phoneVerifyCahcheSvc.VerifyCacheIncr(phoneCountKey, int(utils.SetKeyExpireToday().Seconds()))
+			if err != nil {
+				utils.FileLog.Info("验证码手机号当日缓存失败", err.Error())
 			}
 		}
 	case 2:
@@ -201,11 +284,24 @@ func (this *UserController) GetVerifyCode() {
 
 		emailKey := utils.CACHE_ACCESS_EMAIL_LOGIN_CODE + req.Email
 		res, _ := go_redis.RedisInt(emailKey)
-		if res > 5 {
+		if res >= 5 {
 			br.Msg = "验证码发送太频繁,请稍后重试"
 			return
 		}
+		emailCountKey := utils.CACHE_ACCESS_EMAIL_COUNT_LOGIN_CODE + req.Email
+		resCount, _ := go_redis.RedisInt(emailCountKey)
+		if resCount >= utils.VerifyCodeSendLimit {
+			br.Msg = fmt.Sprintf("一天最多获取%s次,已超限", strconv.Itoa(utils.VerifyCodeSendLimit))
+			return
+		}
 
+		store := services.CaptchaRedis{}
+		var ok bool
+		ok = store.Verify(req.CaptchaId, req.CaptchaCode, true)
+		if !ok {
+			br.Msg = "图形验证码错误"
+			return
+		}
 		date := time.Now()
 		content := "尊敬的用户:</br>本次请求的验证码为:" + code + "(为了保障您账号的安全性,请在15分钟内完成验证。)</br>东吴期货研究团队 </br>" + fmt.Sprintf("%d年%02d月%02d日", date.Year(), date.Month(), date.Day())
 		title := "东吴期货登录验证"
@@ -229,11 +325,14 @@ func (this *UserController) GetVerifyCode() {
 				return
 			}
 			br.Msg = "发送成功"
-			isExist := go_redis.IsExist(emailKey)
-			if isExist {
-				go_redis.Incr(emailKey)
-			} else {
-				go_redis.SetNX(emailKey, 1, time.Minute*15)
+			emailVerifyCahcheSvc := &services.VerifyCacheIncrService{}
+			err = emailVerifyCahcheSvc.VerifyCacheIncr(emailKey, 15*int(time.Minute.Seconds()))
+			if err != nil {
+				utils.FileLog.Info("验证码邮箱临时缓存失败, err:", err.Error())
+			}
+			err = emailVerifyCahcheSvc.VerifyCacheIncr(emailCountKey, int(utils.SetKeyExpireToday().Seconds()))
+			if err != nil {
+				utils.FileLog.Info("验证码邮箱当日缓存失败, err:", err.Error())
 			}
 		} else {
 			br.Msg = "发送失败"

+ 4 - 1
go.mod

@@ -9,6 +9,7 @@ require (
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/go-redis/redis/v8 v8.11.5
 	github.com/go-sql-driver/mysql v1.7.0
+	github.com/mojocn/base64Captcha v1.3.6
 	github.com/rdlucklib/rdluck_tools v1.0.3
 	github.com/silenceper/wechat/v2 v2.1.6
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
@@ -20,6 +21,7 @@ require (
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 	github.com/fatih/structs v1.1.0 // indirect
+	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
 	github.com/kr/text v0.2.0 // indirect
@@ -37,9 +39,10 @@ require (
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.0 // indirect
 	golang.org/x/crypto v0.12.0 // indirect
+	golang.org/x/image v0.13.0 // indirect
 	golang.org/x/net v0.14.0 // indirect
 	golang.org/x/sys v0.11.0 // indirect
-	golang.org/x/text v0.12.0 // indirect
+	golang.org/x/text v0.13.0 // indirect
 	google.golang.org/protobuf v1.30.0 // indirect
 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect

+ 6 - 4
models/request/user.go

@@ -17,10 +17,12 @@ type LoginReq struct {
 }
 
 type VerifyCodeReq struct {
-	VerifyType int    `description:"验证方式: 1-手机号; 2-邮箱"`
-	Phone      string `description:"手机号"`
-	AreaCode   string `description:"手机区号"`
-	Email      string `description:"邮箱"`
+	VerifyType  int    `description:"验证方式: 1-手机号; 2-邮箱"`
+	CaptchaId   string `description:"验证码ID"`
+	CaptchaCode string `description:"图形验证码"`
+	Phone       string `description:"手机号"`
+	AreaCode    string `description:"手机区号"`
+	Email       string `description:"邮箱"`
 }
 
 type ReportRecordReq struct {

+ 9 - 0
routers/commentsRouter.go

@@ -358,6 +358,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mini_api/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_api/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "GenerateCaptcha",
+            Router: `/getCaptcha`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mini_api/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_api/controllers:UserController"],
         beego.ControllerComments{
             Method: "GetVerifyCode",

+ 49 - 0
services/captcha_redis.go

@@ -0,0 +1,49 @@
+package services
+
+import (
+	"eta/eta_mini_api/services/go_redis"
+	"eta/eta_mini_api/utils"
+	"fmt"
+	"time"
+)
+
+// CaptchaRedis Redis验证器
+type CaptchaRedis struct{}
+
+// Set 实现验证器set方法
+func (r CaptchaRedis) Set(id string, value string) (err error) {
+	if utils.Redis == nil {
+		err = fmt.Errorf("redis config err")
+		return
+	}
+	key := utils.CaptchaCachePrefix + id
+	b := go_redis.SetNX(key, value, time.Minute*5)
+	if !b {
+		err = fmt.Errorf("redis setnx err")
+	}
+	return
+}
+
+// Get 实现原验证器get方法
+func (r CaptchaRedis) Get(id string, clear bool) (code string) {
+	if utils.Redis == nil {
+		return
+	}
+	key := utils.CaptchaCachePrefix + id
+	val, err := go_redis.RedisString(key)
+	if err != nil {
+		return
+	}
+	code = val
+	// clear为true时验证通过删除验证码
+	if clear {
+		_ = go_redis.Delete(key)
+	}
+	return
+}
+
+// Verify 实现原验证器verify方法
+func (r CaptchaRedis) Verify(id, answer string, clear bool) bool {
+	v := r.Get(id, clear)
+	return v == answer
+}

+ 12 - 0
services/lua/verify_code.lua

@@ -0,0 +1,12 @@
+-- 定义Lua脚本
+local key = KEYS[1]      -- 从KEYS数组获取要检查的Redis key
+local expireTime = tonumber(ARGV[1]) -- 从ARGV数组获取过期时间
+
+-- 检查key是否存在并执行相应操作
+if redis.call('EXISTS', key) == 1 then
+    -- 存在:将key的值加一
+    return redis.call('INCR', key)
+else
+    -- 不存在:设置key的值设置为1
+    return redis.call('SET', key, 1, 'EX', expireTime) -- 'EX' 设置过期时间(秒)
+end

+ 17 - 0
services/verify_code.go

@@ -0,0 +1,17 @@
+package services
+
+import (
+	"context"
+	_ "embed"
+	"eta/eta_mini_api/utils"
+)
+
+//go:embed lua\verify_code.lua
+var luaVerifyCacheIncr string
+
+type VerifyCacheIncrService struct {
+}
+
+func (v *VerifyCacheIncrService) VerifyCacheIncr(key string, expireInSeconds int) error {
+	return utils.Redis.Eval(context.Background(), luaVerifyCacheIncr, []string{key}, expireInSeconds).Err()
+}

+ 0 - 5
services/wechat/template_msg.go

@@ -84,7 +84,6 @@ func (c *TemplateMsgSendClient) SendMsg() (sendRes *SendTemplateResponse, err er
 	// 模板消息发送超过当日10万次限制错误处理
 	if sendRes.Errcode == 45009 {
 		// 发送提示邮件
-		// go alarm_msg.SendAlarmMsg("模板消息发送超过当日10万次限制, SendTemplateResponse: "+string(body), 3)
 		// 清理限制
 		clearRes, e := c.ClearQuota()
 		if e != nil {
@@ -97,18 +96,14 @@ func (c *TemplateMsgSendClient) SendMsg() (sendRes *SendTemplateResponse, err er
 				return nil, er
 			}
 			fmt.Println("自动清理模板消息限制接口, 调用失败, ClearQuotaResponse: " + string(clearJson))
-			// go alarm_msg.SendAlarmMsg("自动清理模板消息限制接口, 调用失败, ClearQuotaResponse: "+string(clearJson), 3)
 			return
 		}
 		// 发送成功邮件
-		// go alarm_msg.SendAlarmMsg("自动清理模板消息限制接口, 调用成功", 1)
 		// 重新推送
 		go func() {
 			_, e := c.SendMsg()
 			if e != nil {
 				return
-				// reSendJson, _ := json.Marshal(reSend)
-				// alarm_msg.SendAlarmMsg("重新推送模板消息失败, SendTemplateResponse: "+string(reSendJson), 3)
 			}
 		}()
 	}

+ 6 - 0
utils/common.go

@@ -190,3 +190,9 @@ func GetOrmReplaceHolder(num int) string {
 	}
 	return stringBuffer.String()
 }
+
+func SetKeyExpireToday() time.Duration {
+	now := time.Now()
+	endOfDay := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location())
+	return time.Until(endOfDay)
+}

+ 11 - 8
utils/constants.go

@@ -2,8 +2,10 @@ package utils
 
 // 验证码
 const (
-	CodeCachePrefix        = "eta:mini:login:" // 验证码缓存Key
-	VerifyCodeExpireMinute = 15                // 短信/邮箱验证码过期时间-分钟
+	CodeCachePrefix        = "eta:mini:login:"            // 验证码缓存Key
+	VerifyCodeExpireMinute = 15                           // 短信/邮箱验证码过期时间-分钟
+	VerifyCodeSendLimit    = 20                           // 短信/邮箱验证码发送上限
+	CaptchaCachePrefix     = "captcha:lock:eta_mini_api_" // 验证码缓存Key
 )
 
 // 报告权限状态定义
@@ -67,7 +69,6 @@ const (
 
 const (
 	JhGnAppKey = "4c8504c49dd335e99cfd7b6a3a9e2415" //聚合国内AppKey
-	JhGjAppKey = "3326ad2c1047a4cd92ace153e6044ca3"
 )
 
 const (
@@ -75,11 +76,13 @@ const (
 )
 
 const (
-	CACHE_ACCESS_TOKEN_LOGIN          = "pc_eta_min_crm:login:"          //管理后台登录
-	CACHE_ACCESS_TOKEN_LOGIN_NO_TRUST = "pc_eta_min_crm:login:no_trust:" //管理后台登录(不可信登录态)
-	CACHE_ACCESS_WX_BIND              = "eta_mini_api:phone:email:"      //管理后台登录(不可信登录态)
-	CACHE_ACCESS_EMAIL_LOGIN_CODE     = "eta_mini_api:login:email:"      //邮箱验证码防攻击key
-	CACHE_ACCESS_PHONE_LOGIN_CODE     = "eta_mini_api:login:phone:"      //手机验证码防攻击key
+	CACHE_ACCESS_TOKEN_LOGIN            = "pc_eta_min_crm:login:"           //管理后台登录
+	CACHE_ACCESS_TOKEN_LOGIN_NO_TRUST   = "pc_eta_min_crm:login:no_trust:"  //管理后台登录(不可信登录态)
+	CACHE_ACCESS_WX_BIND                = "eta_mini_api:phone:email:"       //管理后台登录(不可信登录态)
+	CACHE_ACCESS_EMAIL_LOGIN_CODE       = "eta_mini_api:login:email:"       //邮箱验证码防攻击key
+	CACHE_ACCESS_PHONE_LOGIN_CODE       = "eta_mini_api:login:phone:"       //手机验证码防攻击key
+	CACHE_ACCESS_EMAIL_COUNT_LOGIN_CODE = "eta_mini_api:login:email_count:" //邮箱验证码登录次数key
+	CACHE_ACCESS_PHONE_COUNT_LOGIN_CODE = "eta_mini_api:login:phone_count:" //手机验证码登录次数key
 )
 
 // 缓存key