现在大多数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 发送短信的配置optionstype SmsOptionsstruct{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"`}funcNewSmsOptions() *SmsOptions {return &SmsOptions{SecretKey: "",SecretId: "",SmsSdkAppId: "",SignName: "",TemplateId: "",}}// 这为项目总的一个options配置,项目启动的时候会将yaml中的加载到option中type Optionsstruct{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"`}funcNewOptions() *Options {o:=Options{GenericServerRunOptions: genericoptions.NewServerRunOptions(),MySQLOptions: genericoptions.NewMySQLOptions(),InsecuresServing: genericoptions.NewInsecureServerOptions(),RedisOptions: genericoptions.NewRedisOptions(),Log: logger.NewOptions(),SmsOptions: genericoptions.NewSmsOptions(),}return &o}
viper加载配置的代码如下
funcAddConfigToOptions(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}returnnil}funcStringToByteSizeHookFunc()mapstructure.DecodeHookFunc {returnfunc(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.CredentialRegion stringCpf *profile.ClientProfileRequest SmsRequest}type Option func(*SmsClient)funcNewSmsClient(options ...func(client *SmsClient)) *SmsClient {client := &SmsClient{Region: "ap-guangzhou",Cpf: profile.NewClientProfile(),}for _, option := range options {option(client)}return client}funcWithRequest(request SmsRequest)Option {returnfunc(smsClient *SmsClient) {smsClient.Request = request}}funcWithCredential(options options.SmsOptions)Option {returnfunc(smsClient *SmsClient) {smsClient.Credential = common.NewCredential(options.SecretId, options.SecretKey)}}funcWithCpfReqMethod(method string)Option {returnfunc(smsClient *SmsClient) {smsClient.Cpf.HttpProfile.ReqMethod = method}}funcWithCpfReqTimeout(timeout int)Option {returnfunc(smsClient *SmsClient) {smsClient.Cpf.HttpProfile.ReqTimeout = timeout}}funcWithCpfSignMethod(method string)Option {returnfunc(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)returnfalse}if err != nil {logger.Warnf("发送短信失败:%s,requestId:%s", err)returnfalse}logger.Info("发送短信验证码成功")returntrue}
定义发送的client,这里采用function option 的编程模式来初始化发送的client.和发送的request,request的代码如下
type SmsRequest struct {request *sms.SendSmsRequest}funcNewSmsRequest(options *options.SmsOptions, withOptions ...func(smsRequest *SmsRequest)) *SmsRequest {request := sms.NewSendSmsRequest()request.SmsSdkAppId = &options.SmsSdkAppIdrequest.SignName = &options.SignNamerequest.TemplateId = &options.TemplateIdsmsRequest := &SmsRequest{request: request}for _, option := range withOptions {option(smsRequest)}return smsRequest}type RequestOption func(*SmsRequest)funcWithPhoneNumberSet(phoneSet []string)RequestOption {returnfunc(smsRequest *SmsRequest) {smsRequest.request.PhoneNumberSet = common.StringPtrs(phoneSet)}}funcWithTemplateParamSet(templateSet []string)RequestOption {returnfunc(smsRequest *SmsRequest) {smsRequest.request.TemplateParamSet = common.StringPtrs(templateSet)}}
创建发送验证码的控制层,发送成功,并将此处的电话号码和验证码保存到redis缓存中,用来登录时候的验证码有效性的校验
func(u *userService)SendPhoneCode(ctx context.Context, phone string)bool {// 获取配置参数smsSetting := global.TencenSmsSettingphoneSet := []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)returntrue}
后面是通过手机验证码进行登录的流程
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}
链接:
(版权归原作者所有,侵删)
继续阅读
阅读原文