8

Go语言 实现发送短信验证码 并登录

 2 years ago
source link: https://studygolang.com/articles/35764
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Go语言 实现发送短信验证码 并登录

goCenter · 大约1分钟之前 · 84 次点击 · 预计阅读时间 8 分钟 · 大约8小时之前 开始浏览    

现在大多数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


}

参考链接:https://blog.csdn.net/leixiyu/article/details/123029204


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK