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

goCenter · · 2769 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

现在大多数app或wap都实现了通过手机号获取验证码进行验证登录,下面来看下用go来实现手机号发送短信验证码登录的过程,基于的框架是gin 。 首先是短信服务商的申请,比如腾讯云、阿里云、网易易盾等,腾讯云自己申请个微信公众号就行,然后申请相关的短信签名、和短信模板,腾讯有100条试用喔。 具体的代码实现。 配置腾讯云短信服务的发送短信相关配置,具体可以参考腾讯云短信服务的api 文档,进行配置。 ```go sms: secret-key: #秘钥,可在控制台查询 secret-id: #秘钥id ,可在控制台查询 sms-sdk-app-id: #应用id Sign-name: #申请的签名 template-id: #模板id ``` go 这里采用的是viper进行加载配置,相关的加载配置代码如下 定义相关的配置结构体,并加载到整个项目的总的`options` 配置结构体中 ```go // 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加载配置的代码如下 ```go 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 } } ``` 下面是发送验证码的实现 ```go 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的代码如下 ```go 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缓存中,用来登录时候的验证码有效性的校验。 ```go 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 } ``` 后面是通过手机验证码进行登录的流程 ```go 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

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2769 次点击  ∙  2 赞  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传