go 实现发送短信验证码登录

现在大多数app或wap都实现了通过手机号获取验证码进行验证登录,下面来看下用go来实现手机号发送短信验证码登录的过程,基于的框架是gin 。

  • 首先是短信服务商的申请,比如腾讯云、阿里云、网易易盾等,腾讯云自己申请个微信公众号就行,然后申请相关的短信签名、和短信模板,腾讯有100条试用喔。
  • 具体的代码实现
  • 配置腾讯云短信服务的发送短信相关配置,具体可以参考腾讯云短信服务的api 文档,进行配置
sms:
  secret-key:  #秘钥,可在控制台查询
  secret-id:  #秘钥id ,可在控制台查询
  sms-sdk-app-id:  #应用id
  Sign-name:  #申请的签名
  template-id:  #模板id

go 这里采用的是viper进行加载配置,相关的加载配置代码如下
定义相关的配置结构体,并加载到整个项目的总的options 配置结构体中

// sms 发送短信的配置options
type SmsOptions struct {
	SecretKey   string `json:"secret-key,omitempty" mapstructure:"secret-key"`
	SecretId    string `json:"secret-id,omitempty" mapstructure:"secret-id"`
	SmsSdkAppId string `json:"sms-sdk-app-id,omitempty" mapstructure:"sms-sdk-app-id"`
	SignName    string `json:"sign-name,omitempty" mapstructure:"sign-name"`
	TemplateId  string `json:"template-id,omitempty" mapstructure:"template-id"`
}

func NewSmsOptions() *SmsOptions {
	return &SmsOptions{
		SecretKey:   "",
		SecretId:    "",
		SmsSdkAppId: "",
		SignName:    "",
		TemplateId:  "",
	}
}
// 这为项目总的一个options配置,项目启动的时候会将yaml中的加载到option中
type Options struct {
	GenericServerRunOptions *genericoptions.ServerRunOptions `json:"server" mapstructure:"server"`
	MySQLOptions *genericoptions.MySQLOptions `json:"mysql" mapstructure:"mysql"`
	InsecuresServing *genericoptions.InsecureServerOptions `json:"insecure" mapstructure:"insecure"`
	Log *logger.Options `json:"log" mapstructure:"log"`
	RedisOptions *genericoptions.RedisOptions `json:"redis" mapstructure:"redis"`
	SmsOptions *genericoptions.SmsOptions `json:"sms" mapstructure:"sms"`
}

func NewOptions() *Options  {
	o:=Options{
		GenericServerRunOptions: genericoptions.NewServerRunOptions(),
		MySQLOptions: genericoptions.NewMySQLOptions(),
		InsecuresServing: genericoptions.NewInsecureServerOptions(),
		RedisOptions: genericoptions.NewRedisOptions(),
		Log: logger.NewOptions(),
		SmsOptions: genericoptions.NewSmsOptions(),

	}
	return &o
}

viper加载配置的代码如下

func AddConfigToOptions(options *options.Options) error {
	viper.SetConfigName("config")
	viper.AddConfigPath("config/")
	viper.SetConfigType("yaml")
	err := viper.ReadInConfig()
	if err != nil {
		return err
	}

	optDecode := viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(mapstructure.StringToTimeDurationHookFunc(), StringToByteSizeHookFunc()))

	err = viper.Unmarshal(options, optDecode)
	fmt.Println(options)
	if err != nil {
		return err
	}
	return nil
}

func StringToByteSizeHookFunc() mapstructure.DecodeHookFunc {
	return func(f reflect.Type,
		t reflect.Type, data interface{}) (interface{}, error) {
		if f.Kind() != reflect.String {
			return data, nil
		}
		if t != reflect.TypeOf(datasize.ByteSize(5)) {
			return data, nil
		}
		raw := data.(string)
		result := new(datasize.ByteSize)
		result.UnmarshalText([]byte(raw))
		return result.Bytes(), nil
	}
}

下面是发送验证码的实现

type SmsClient struct {
	Credential *common.Credential
	Region     string
	Cpf        *profile.ClientProfile
	Request    SmsRequest
}

type Option func(*SmsClient)

func NewSmsClient(options ...func(client *SmsClient)) *SmsClient {
	client := &SmsClient{
		Region: "ap-guangzhou",
		Cpf:    profile.NewClientProfile(),
	}
	for _, option := range options {
		option(client)
	}
	return client

}

func WithRequest(request SmsRequest) Option {
	return func(smsClient *SmsClient) {
		smsClient.Request = request
	}
}

func WithCredential(options options.SmsOptions) Option {
	return func(smsClient *SmsClient) {
		smsClient.Credential = common.NewCredential(options.SecretId, options.SecretKey)
	}
}
func WithCpfReqMethod(method string) Option {
	return func(smsClient *SmsClient) {
		smsClient.Cpf.HttpProfile.ReqMethod = method
	}
}
func WithCpfReqTimeout(timeout int) Option {
	return func(smsClient *SmsClient) {
		smsClient.Cpf.HttpProfile.ReqTimeout = timeout
	}
}
func WithCpfSignMethod(method string) Option {
	return func(smsClient *SmsClient) {
		smsClient.Cpf.SignMethod = method
	}
}

func (s *SmsClient) Send() bool {
	sendClient, _ := sms.NewClient(s.Credential, s.Region, s.Cpf)
	_, err := sendClient.SendSms(s.Request.request)
	if _, ok := err.(*errors.TencentCloudSDKError); ok {
		logger.Warnf("An API error has returned: %s", err)
		return false
	}

	if err != nil {
		logger.Warnf("发送短信失败:%s,requestId:%s", err)
		return false

	}
	logger.Info("发送短信验证码成功")
	return true
}

定义发送的client,这里采用function option 的编程模式来初始化发送的client.和发送的request,request的代码如下

type SmsRequest struct {
	request *sms.SendSmsRequest
}

func NewSmsRequest(options *options.SmsOptions, withOptions ...func(smsRequest *SmsRequest)) *SmsRequest {
	request := sms.NewSendSmsRequest()

	request.SmsSdkAppId = &options.SmsSdkAppId
	request.SignName = &options.SignName
	request.TemplateId = &options.TemplateId
	smsRequest := &SmsRequest{request: request}
	for _, option := range withOptions {
		option(smsRequest)
	}
	return smsRequest

}

type RequestOption func(*SmsRequest)

func WithPhoneNumberSet(phoneSet []string) RequestOption {
	return func(smsRequest *SmsRequest) {
		smsRequest.request.PhoneNumberSet = common.StringPtrs(phoneSet)
	}
}

func WithTemplateParamSet(templateSet []string) RequestOption {
	return func(smsRequest *SmsRequest) {
		smsRequest.request.TemplateParamSet = common.StringPtrs(templateSet)
	}
}

创建发送验证码的控制层,发送成功,并将此处的电话号码和验证码保存到redis缓存中,用来登录时候的验证码有效性的校验。

func (u *userService) SendPhoneCode(ctx context.Context, phone string) bool {

	// 获取配置参数
	smsSetting := global.TencenSmsSetting
	phoneSet := []string{phone}
	// 随机生成6位的验证码
	var randCode string = fmt.Sprintf("%06v", rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(1000000))
	templateSet := []string{randCode, "60"}
	smsRequest := tencenSms.NewSmsRequest(smsSetting, tencenSms.WithPhoneNumberSet(phoneSet), tencenSms.WithTemplateParamSet(templateSet))
	smsClient := tencenSms.NewSmsClient(tencenSms.WithRequest(*smsRequest), tencenSms.WithCredential(*smsSetting))
	go smsClient.Send()
	// 将验证码和手机号保存到redis中
	_ = u.cache.UserCaches().SetSendPhoneCodeCache(ctx, phone, randCode)
	return true

}

后面是通过手机验证码进行登录的流程

func (u *userService) LoginByPhoneCode(ctx context.Context, phone string, phoneCode string) (*model.User,error) {
	// 从缓存中获取该手机号对应的验证码是否匹配
	cacheCode, err :=u.cache.UserCaches().GetSendPhoneCodeFromCache(ctx,phone)
	if err != nil {
		return nil, errors.WithCode(code.ErrUserPhoneCodeExpire,err.Error())
	}
	if cacheCode!=phoneCode {
		return nil,errors.WithCode(code.ErrUserPhoneCodeMiss,"")
	}
	return &model.User{
		Nickname: "lala",
	}, nil


}
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值