GoFiber 从零系列(五):腾讯云SMS && goredis && 简单的短信登录功能

411 阅读8分钟

GitHub项目地址

GoFiber 从零系列(一):项目创建&配置文件&gorm-mysql

GoFiber 从零系列(二):热加载/热更新 && 日志系统 && 验证入参

GoFiber 从零系列(三):JWT注册,登录

GoFiber 从零系列(四):腾讯云cos实现文件上传

GoFiber 从零系列(五):腾讯云SMS && goredis && 简单的短信登录功能

# GoFiber 从零系列(六):项目部署 && docker && jenkins

自行百度安装redis

点击 (redis token 挤出登录) 文档里有相关说明

安装插件

go get -u github.com/gomodule/redigo/redis

使用goredis

修改配置app.ini文件

[tencent_sms]
SMS_APPID = 腾讯云sms相关
SIGN_NAME = 腾讯云sms相关
TEMP_ID = 腾讯云sms相关

[redis]
Host = 127.0.0.1:6379
Password = 
MaxIdle = 30
MaxActive = 30
IdleTimeout = 200

配置加载方式修改

  • 通过映射关系,将结构体映射添加配置

修改 /pkg/setting/setting.go

package setting

import (
	"time"

	"github.com/go-ini/ini"
	"github.com/jinpikaFE/go_fiber/pkg/logging"
)

var (
	Cfg *ini.File

	RunMode string

	HTTPPort     int
	ReadTimeout  time.Duration
	WriteTimeout time.Duration

	PageSize  int
	JwtSecret string

	SecretId  string
	SecretKey string
	CosUrl    string
)

// 配置声明 新添加
var RedisSetting = &Redis{}

var SmsStrSetting = &SmsStr{}

func init() {
	var err error
	Cfg, err = ini.Load("conf/app.ini")
	if err != nil {
		logging.Fatal("Fail to parse 'conf/app.ini': %v", err)
	}

	LoadBase()
	LoadServer()
	LoadApp()
	LoadTenCentCos()
        // 进行映射 新添加
	mapTo("redis", RedisSetting)
	mapTo("tencent_sms", SmsStrSetting)
}

// 结构体 新添加
type Redis struct {
	Host        string
	Password    string
	MaxIdle     int
	MaxActive   int
	IdleTimeout time.Duration
}

type SmsStr struct {
	SMS_APPID string
	SIGN_NAME string
	TEMP_ID string
}

func LoadBase() {
	RunMode = Cfg.Section("").Key("RUN_MODE").MustString("dev")
}

func LoadServer() {
	sec, err := Cfg.GetSection("server")
	if err != nil {
		logging.Fatal("Fail to get section 'server': %v", err)
	}

	HTTPPort = sec.Key("HTTP_PORT").MustInt(8000)
	ReadTimeout = time.Duration(sec.Key("READ_TIMEOUT").MustInt(60)) * time.Second
	WriteTimeout = time.Duration(sec.Key("WRITE_TIMEOUT").MustInt(60)) * time.Second
}

func LoadApp() {
	sec, err := Cfg.GetSection("app")
	if err != nil {
		logging.Fatal("Fail to get section 'app': %v", err)
	}

	JwtSecret = sec.Key("JWT_SECRET").MustString("!@)*#)!@U#@*!@!)")
	PageSize = sec.Key("PAGE_SIZE").MustInt(10)
}

func LoadTenCentCos() {
	sec, err := Cfg.GetSection("tencent_cos")
	if err != nil {
		logging.Fatal("Fail to get section 'tencent_cos': %v", err)
	}

	SecretId = sec.Key("SECRET_ID").MustString("")
	SecretKey = sec.Key("SECRET_KEY").MustString("")
	CosUrl = sec.Key("COS_URL").MustString("")
}

// mapTo map section 映射的方法 新添加
func mapTo(section string, v interface{}) {
	err := Cfg.Section(section).MapTo(v)
	if err != nil {
		logging.Error("Cfg.MapTo %s err: %v", section, err)
	}
}

映射后的配置使用方式

setting.RedisSetting.MaxIdle // 获取

添加文件/pkg/gredis/redis.go

package gredis

import (
	"encoding/json"
	"time"

	"github.com/gomodule/redigo/redis"

	"github.com/jinpikaFE/go_fiber/pkg/logging"
	"github.com/jinpikaFE/go_fiber/pkg/setting"
)

var RedisConn *redis.Pool

func Setup() error {
	RedisConn = &redis.Pool{
		MaxIdle:     setting.RedisSetting.MaxIdle,
		MaxActive:   setting.RedisSetting.MaxActive,
		IdleTimeout: setting.RedisSetting.IdleTimeout,
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", setting.RedisSetting.Host)
			if err != nil {
				logging.Error(err)
				return nil, err
			}
			if setting.RedisSetting.Password != "" {
				if _, err := c.Do("AUTH", setting.RedisSetting.Password); err != nil {
					c.Close()
					logging.Error(err)
					return nil, err
				}
			}
			return c, err
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			if err != nil {
				logging.Error(err)
			}
			return err
		},
	}

	return nil
}

// 设置reids time失效时间 seconds
func Set(key string, data interface{}, time int, isString bool) error {
	conn := RedisConn.Get()
	defer conn.Close()
	value := data
	var err error
	if !isString {
		value, err = json.Marshal(data)
		if err != nil {
			return err
		}
	}

	_, err = conn.Do("SET", key, value)
	if err != nil {
		return err
	}

	_, err = conn.Do("EXPIRE", key, time)
	if err != nil {
		return err
	}

	return nil
}

// 判断是否存在
func Exists(key string) bool {
	conn := RedisConn.Get()
	defer conn.Close()

	exists, err := redis.Bool(conn.Do("EXISTS", key))
	if err != nil {
		return false
	}

	return exists
}

// 获取redis
func Get(key string) ([]byte, error) {
	conn := RedisConn.Get()
	defer conn.Close()

	reply, err := redis.Bytes(conn.Do("GET", key))
	if err != nil {
		return nil, err
	}

	return reply, nil
}

// 删除
func Delete(key string) (bool, error) {
	conn := RedisConn.Get()
	defer conn.Close()

	return redis.Bool(conn.Do("DEL", key))
}

func LikeDeletes(key string) error {
	conn := RedisConn.Get()
	defer conn.Close()

	keys, err := redis.Strings(conn.Do("KEYS", "*"+key+"*"))
	if err != nil {
		return err
	}

	for _, key := range keys {
		_, err = Delete(key)
		if err != nil {
			return err
		}
	}

	return nil
}

使用腾讯云sms

配置文件添加配置,前面已经添加

添加/pkg/tencent/sms.go

  • 缺少插件自行安装
  • 腾讯云相关配置 在注释有说明
package tencent

import (
	"encoding/json"
	"fmt"

	"github.com/jinpikaFE/go_fiber/pkg/logging"
	"github.com/jinpikaFE/go_fiber/pkg/setting"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
	sms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms/v20210111" // 引入sms
)

var (
	Request *sms.SendSmsRequest
	Client  *sms.Client
)

func SetupSms() {
	/* 必要步骤:
	 * 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。
	 * 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。
	 * 你也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人,
	 * 以免泄露密钥对危及你的财产安全。
	 * SecretId、SecretKey 查询: https://console.cloud.tencent.com/cam/capi */
	credential := common.NewCredential(
		fmt.Sprintf("%s", setting.SecretId),
		fmt.Sprintf("%s", setting.SecretKey),
	)

	/* 非必要步骤:
	 * 实例化一个客户端配置对象,可以指定超时时间等配置 */
	cpf := profile.NewClientProfile()

	/* SDK默认使用POST方法。
	 * 如果你一定要使用GET方法,可以在这里设置。GET方法无法处理一些较大的请求 */
	cpf.HttpProfile.ReqMethod = "POST"

	/* SDK有默认的超时时间,非必要请不要进行调整
	 * 如有需要请在代码中查阅以获取最新的默认值 */
	// cpf.HttpProfile.ReqTimeout = 5

	/* 指定接入地域域名,默认就近地域接入域名为 sms.tencentcloudapi.com ,也支持指定地域域名访问,例如广州地域的域名为 sms.ap-guangzhou.tencentcloudapi.com */
	cpf.HttpProfile.Endpoint = "sms.tencentcloudapi.com"

	/* SDK默认用TC3-HMAC-SHA256进行签名,非必要请不要修改这个字段 */
	cpf.SignMethod = "HmacSHA1"

	/* 实例化要请求产品(以sms为例)的client对象
	 * 第二个参数是地域信息,可以直接填写字符串ap-guangzhou,支持的地域列表参考 https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 */
	client, _ := sms.NewClient(credential, "ap-guangzhou", cpf)

	/* 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
	 * 你可以直接查询SDK源码确定接口有哪些属性可以设置
	 * 属性可能是基本类型,也可能引用了另一个数据结构
	 * 推荐使用IDE进行开发,可以方便的跳转查阅各个接口和数据结构的文档说明 */
	request := sms.NewSendSmsRequest()

	/* 基本类型的设置:
	 * SDK采用的是指针风格指定参数,即使对于基本类型你也需要用指针来对参数赋值。
	 * SDK提供对基本类型的指针引用封装函数
	 * 帮助链接:
	 * 短信控制台: https://console.cloud.tencent.com/smsv2
	 * 腾讯云短信小助手: https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81 */

	/* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666 */
	// 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看
	request.SmsSdkAppId = common.StringPtr(setting.SmsStrSetting.SMS_APPID)

	/* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名 */
	// 签名信息可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的签名管理查看
	request.SignName = common.StringPtr(setting.SmsStrSetting.SIGN_NAME)
	/* 模板 ID: 必须填写已审核通过的模板 ID */
	// 模板 ID 可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看
	request.TemplateId = common.StringPtr(setting.SmsStrSetting.TEMP_ID)

	Request = request
	Client = client
}

type SendStatusSetStu struct {
	Code        string
	Fee         int
	Message     string
	IsoCode     string
	PhoneNumber string
	SerialNo    string
}

type SmsResStu struct {
	SendStatusSet []SendStatusSetStu
	RequestId     string
}

// 发送短信
func SendSms(tempParam string, phoneNum string) (*SmsResStu, error) {
	/* 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空*/
	Request.TemplateParamSet = common.StringPtrs([]string{tempParam, "5"})

	/* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号]
	 * 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号*/
	Request.PhoneNumberSet = common.StringPtrs([]string{phoneNum})

	// 通过client对象调用想要访问的接口,需要传入请求对象
	response, err := Client.SendSms(Request)
	// 处理异常
	if _, ok := err.(*errors.TencentCloudSDKError); ok {
		logging.Error("An API error has returned: %s", err)
		return nil, err
	}
	// 非SDK异常,直接失败。实际代码中可以加入其他的处理。
	if err != nil {
		logging.Error(err)
		return nil, err
	}
	b, _ := json.Marshal(response.Response)
	result := &SmsResStu{}
	if errJson := json.Unmarshal(b, &result); errJson != nil {
		return nil, errJson
	}
	return result, nil
}

添加获取验证码路由 /controllers/login.go 中添加函数

func GetCaptcha(c *fiber.Ctx) error {
	appF := app.Fiber{C: c}
	// 短信验证码发送
	loginMobile := &models.LoginMobile{}

	captcha := fmt.Sprintf("%v", rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(1000000))

	if err := c.BodyParser(loginMobile); err != nil {
		return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "参数解析错误", nil)
	}

	if !untils.VerifyMobileFormat(loginMobile.Mobile) {
		return appF.Response(fiber.StatusBadRequest, fiber.StatusBadRequest, "请输入正确的手机号", nil)
	}

	result, smsErr := tencent.SendSms(captcha, loginMobile.Mobile)

	if smsErr != nil {
		return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "短信发送错误", smsErr)
	}

	if result.SendStatusSet[0].Code == "Ok" {
        // 暂存redis 失效时间5分钟,即300s
		redisErr := gredis.Set(loginMobile.Mobile, captcha, 300, true)

		if redisErr != nil {
			logging.Error(redisErr)
			return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "redis错误", redisErr)
		}
		return appF.Response(fiber.StatusOK, fiber.StatusOK, "短信发送成功", result)
	}

	return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "短信发送失败", result)
}

添加路由

// 放在apiv1.Use(jwt.Jwt)上方,不需要jwt拦截
apiv1.Post("/captcha", controller.GetCaptcha)

登录功能修改

修改 /models/login.go

package models

import (
	"time"

	"github.com/golang-jwt/jwt/v4"
	"github.com/jinpikaFE/go_fiber/pkg/logging"
	"github.com/jinpikaFE/go_fiber/pkg/untils"
)

type LoginAccount struct {
	Username string `validate:"required" query:"username" json:"username" xml:"username" form:"username"`
	Password string `validate:"required" query:"password" json:"password" xml:"password" form:"password"`
}

type LoginMobile struct {
	Mobile  string `validate:"required" query:"mobile" json:"mobile" xml:"mobile" form:"mobile"`
	Captcha string `validate:"required" query:"captcha" json:"captcha" xml:"captcha" form:"captcha"`
}

type LoginWx struct {
	Appid     string `validate:"required" query:"appid" json:"appid" xml:"appid" form:"appid"`
	Appsecret string `validate:"required" query:"appsecret" json:"appsecret" xml:"appsecret" form:"appsecret"`
	Code      string `validate:"required" query:"code" json:"code" xml:"code" form:"code"`
}

type Type struct {
	LoginType string `validate:"required,oneof=1 2 3" query:"loginType" json:"loginType" xml:"loginType" form:"loginType"`
}

type Login struct {
	LoginAccount

	LoginWx

	Type
}

func GetToken(login *Login, user *User) string {
	claims := jwt.MapClaims{
		"username": login.Username,
		"admin":    true,
		"exp":      time.Now().Add(time.Hour * 72).Unix(),
	}
	// logging.Error(user.Openid, user.Openid == nil)
	if user.Openid == nil {
		if user.Mobile == nil {
			if login.Username != *user.Username || untils.GetSha256(login.Password) != user.Password {
				return ""
			}
		} else {
			claims = jwt.MapClaims{
				"openid": user.Mobile,
				"admin":  true,
				"exp":    time.Now().Add(time.Hour * 72).Unix(),
			}
		}
	} else {
		claims = jwt.MapClaims{
			"openid": user.Openid,
			"admin":  true,
			"exp":    time.Now().Add(time.Hour * 72).Unix(),
		}
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	t, err := token.SignedString([]byte("secret"))
	if err != nil {
		logging.Error(err)
		return ""
	}

	return t
}

/controllers/login.go 文件修改

  • 三种登录方式 账号密码、微信、手机号 自行取舍
package controller

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"net/url"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/jinpikaFE/go_fiber/models"
	"github.com/jinpikaFE/go_fiber/pkg/app"
	"github.com/jinpikaFE/go_fiber/pkg/gredis"
	"github.com/jinpikaFE/go_fiber/pkg/logging"
	"github.com/jinpikaFE/go_fiber/pkg/tencent"
	"github.com/jinpikaFE/go_fiber/pkg/untils"
	"github.com/jinpikaFE/go_fiber/pkg/valodates"
	"github.com/vicanso/go-axios"
)

// ResponseHTTP represents response body of this API
type ResponseHTTP struct {
	Code    int         `json:"code"`
	Data    interface{} `json:"data"`
	Message string      `json:"message"`
}

func Login(c *fiber.Ctx) error {
	appF := app.Fiber{C: c}
	types := &models.Type{}

	if err := c.BodyParser(types); err != nil {
		return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "参数解析错误", nil)
	}

	// 账号登录
	if types.LoginType == "1" {
		loginAccount := &models.LoginAccount{}

		if err := c.BodyParser(loginAccount); err != nil {
			return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "参数解析错误", nil)
		}
		// 入参验证
		errors := valodates.ValidateStruct(*loginAccount)

		if errors != nil {
			return appF.Response(fiber.StatusBadRequest, fiber.StatusBadRequest, "检验参数错误", errors)
		}

		userSt := &models.User{}
		userSt.Username = &loginAccount.Username

		res, errs := models.GetUser(userSt)

		if errs != nil {
			return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "查询失败", errs)
		}

		if !(res.ID > 0) {
			return appF.Response(fiber.StatusBadRequest, fiber.StatusBadRequest, "用户不存在", nil)
		}

		login := &models.Login{}
		login.Username = loginAccount.Username
		login.Password = loginAccount.Password

		token := models.GetToken(login, res)

		redisErr := gredis.Set("token", token, 300, true)

		if redisErr != nil {
			logging.Error(redisErr)
			return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "redis错误", redisErr)
		}

		if token == "" {
			return appF.Response(fiber.StatusUnauthorized, fiber.StatusUnauthorized, "账户或者密码错误", nil)
		}

		loginres := map[string]interface{}{"token": token, "username": loginAccount.Username}

		return appF.Response(fiber.StatusOK, fiber.StatusOK, "SUCCESS", loginres)
	}

	// 微信登录
	if types.LoginType == "2" {
		loginWx := &models.LoginWx{}

		if err := c.BodyParser(loginWx); err != nil {
			return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "参数解析错误", nil)
		}

		// 入参验证
		errors := valodates.ValidateStruct(*loginWx)

		if errors != nil {
			return appF.Response(fiber.StatusBadRequest, fiber.StatusBadRequest, "检验参数错误", errors)
		}
		// 使用axios进行请求
		queryParams := url.Values{}
		queryParams.Add("appid", loginWx.Appid)
		queryParams.Add("secret", loginWx.Appsecret)
		queryParams.Add("js_code", loginWx.Code)
		queryParams.Add("grant_type", "authorization_code")
		axiosConfig := &axios.InstanceConfig{}
		axiosConfig.BaseURL = "https://api.weixin.qq.com"
		resp, err := untils.Request(axiosConfig).Get("/sns/jscode2session", queryParams)
		if err != nil {
			return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "获取openid失败", err)
		}
		result := make(map[string]interface{})
		if errJson := json.Unmarshal(resp.Data, &result); errJson != nil {
			return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "解析code2Session数据失败", errJson)
		}
		logging.Error(result, result["openid"])
		// body, err := ioutil.ReadAll(response.Body)
		// if err == nil {
		// 	logging.Error(string(body))
		// }

		userSt := &models.User{}
		openid := result["openid"].(string)
		userSt.Openid = &openid
		resUser, errs := models.GetUser(userSt)
		if errs != nil {
			return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "查询失败", errs)
		}

		userWx := &models.User{}
		userWx.Openid = &openid

		if !(resUser.ID > 0) {
			// 不存在就创建

			nickName := "微信用户"
			userWx.NickName = &nickName
			if err := models.AddUser(*userWx); err != nil {
				return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "添加失败", err)
			}
		}
		// 登录

		token := models.GetToken(&models.Login{}, userWx)

		redisErr := gredis.Set("token", token, 300, true)

		if redisErr != nil {
			logging.Error(redisErr)
			return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "redis错误", redisErr)
		}

		loginres := map[string]interface{}{"token": token, "openid": openid}

		return appF.Response(fiber.StatusOK, fiber.StatusOK, "SUCCESS", loginres)
	}

	// 手机号登录
	if types.LoginType == "3" {
		loginMobile := &models.LoginMobile{}

		if err := c.BodyParser(loginMobile); err != nil {
			return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "参数解析错误", nil)
		}

		// 入参验证
		errors := valodates.ValidateStruct(*loginMobile)

		if errors != nil {
			return appF.Response(fiber.StatusBadRequest, fiber.StatusBadRequest, "检验参数错误", errors)
		}

		userSt := &models.User{}
		userSt.Mobile = &loginMobile.Mobile
		resUser, errs := models.GetUser(userSt)
		if errs != nil {
			return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "查询失败", errs)
		}

		userMobile := &models.User{}
		userMobile.Mobile = &loginMobile.Mobile

		if !gredis.Exists(loginMobile.Mobile) {
			return appF.Response(fiber.StatusBadRequest, fiber.StatusBadRequest, "验证码过期请重新获取", nil)
		}
                // 取出reids中的缓存值进行比较
		reply, replyErr := gredis.Get(loginMobile.Mobile)
		if replyErr != nil {
			logging.Error(replyErr)
			return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "redis错误", replyErr)
		}

		if loginMobile.Captcha == string(reply) {
			if !(resUser.ID > 0) {
				// 不存在就创建
				userMobile.NickName = &loginMobile.Mobile
				if err := models.AddUser(*userMobile); err != nil {
					return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "添加失败", err)
				}
			}
			// 登录

			token := models.GetToken(&models.Login{}, userMobile)
			redisErr := gredis.Set("token", token, 300, true)

			if redisErr != nil {
				logging.Error(redisErr)
				return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "redis错误", redisErr)
			}

			loginres := map[string]interface{}{"token": token, "mobile": &loginMobile.Mobile}

			return appF.Response(fiber.StatusOK, fiber.StatusOK, "SUCCESS", loginres)
		}

		return appF.Response(fiber.StatusBadRequest, fiber.StatusBadRequest, "验证码错误", nil)
	}

	return appF.Response(fiber.StatusBadRequest, fiber.StatusBadRequest, "未知登录类型", nil)
}

func GetCaptcha(c *fiber.Ctx) error {
	appF := app.Fiber{C: c}
	// 短信验证码发送
	loginMobile := &models.LoginMobile{}

	captcha := fmt.Sprintf("%v", rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(1000000))

	if err := c.BodyParser(loginMobile); err != nil {
		return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "参数解析错误", nil)
	}

	if !untils.VerifyMobileFormat(loginMobile.Mobile) {
		return appF.Response(fiber.StatusBadRequest, fiber.StatusBadRequest, "请输入正确的手机号", nil)
	}

	result, smsErr := tencent.SendSms(captcha, loginMobile.Mobile)

	if smsErr != nil {
		return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "短信发送错误", smsErr)
	}

	if result.SendStatusSet[0].Code == "Ok" {
		redisErr := gredis.Set(loginMobile.Mobile, captcha, 300, true)

		if redisErr != nil {
			logging.Error(redisErr)
			return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "redis错误", redisErr)
		}
		return appF.Response(fiber.StatusOK, fiber.StatusOK, "短信发送成功", result)
	}

	return appF.Response(fiber.StatusInternalServerError, fiber.StatusInternalServerError, "短信发送失败", result)
}