Go语言 实现发送短信验证码并登录
现在大多数app或wap都实现了通过手机号获取验证码进行验证登录,下面来看下用go来实现手机号发送短信验证码登录的过程,基于的框架是gin 。
首先是短信服务商的申请,比如腾讯云、阿里云、网易易盾等,腾讯云自己申请个微信公众号就行,然后申请相关的短信签名、和短信模板,腾讯有100条试用喔。
具体的代码实现
配置腾讯云短信服务的发送短信相关配置,具体可以参考腾讯云短信服务的api 文档,进行配置
1sms:
2 secret-key: #秘钥,可在控制台查询
3 secret-id: #秘钥id ,可在控制台查询
4 sms-sdk-app-id: #应用id
5 Sign-name: #申请的签名
6 template-id: #模板id
go 这里采用的是viper进行加载配置,相关的加载配置代码如下
定义相关的配置结构体,并加载到整个项目的总的options 配置结构体中
1// sms 发送短信的配置options
2type SmsOptions struct {
3 SecretKey string `json:"secret-key,omitempty" mapstructure:"secret-key"`
4 SecretId string `json:"secret-id,omitempty" mapstructure:"secret-id"`
5 SmsSdkAppId string `json:"sms-sdk-app-id,omitempty" mapstructure:"sms-sdk-app-id"`
6 SignName string `json:"sign-name,omitempty" mapstructure:"sign-name"`
7 TemplateId string `json:"template-id,omitempty" mapstructure:"template-id"`
8}
9
10func NewSmsOptions() *SmsOptions {
11 return &SmsOptions{
12 SecretKey: "",
13 SecretId: "",
14 SmsSdkAppId: "",
15 SignName: "",
16 TemplateId: "",
17 }
18}
19
20// 这为项目总的一个options配置,项目启动的时候会将yaml中的加载到option中
21type Options struct {
22 GenericServerRunOptions *genericoptions.ServerRunOptions `json:"server" mapstructure:"server"`
23 MySQLOptions *genericoptions.MySQLOptions `json:"mysql" mapstructure:"mysql"`
24 InsecuresServing *genericoptions.InsecureServerOptions `json:"insecure" mapstructure:"insecure"`
25 Log *logger.Options `json:"log" mapstructure:"log"`
26 RedisOptions *genericoptions.RedisOptions `json:"redis" mapstructure:"redis"`
27 SmsOptions *genericoptions.SmsOptions `json:"sms" mapstructure:"sms"`
28}
29
30func NewOptions() *Options {
31 o:=Options{
32 GenericServerRunOptions: genericoptions.NewServerRunOptions(),
33 MySQLOptions: genericoptions.NewMySQLOptions(),
34 InsecuresServing: genericoptions.NewInsecureServerOptions(),
35 RedisOptions: genericoptions.NewRedisOptions(),
36 Log: logger.NewOptions(),
37 SmsOptions: genericoptions.NewSmsOptions(),
38 }
39 return &o
40}
viper加载配置的代码如下
1func AddConfigToOptions(options *options.Options) error {
2 viper.SetConfigName("config")
3 viper.AddConfigPath("config/")
4 viper.SetConfigType("yaml")
5 err := viper.ReadInConfig()
6 if err != nil {
7 return err
8 }
9
10
11 optDecode := viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(mapstructure.StringToTimeDurationHookFunc(), StringToByteSizeHookFunc()))
12
13
14 err = viper.Unmarshal(options, optDecode)
15 fmt.Println(options)
16 if err != nil {
17 return err
18 }
19 return nil
20}
21
22
23func StringToByteSizeHookFunc() mapstructure.DecodeHookFunc {
24 return func(f reflect.Type,
25 t reflect.Type, data interface{}) (interface{}, error) {
26 if f.Kind() != reflect.String {
27 return data, nil
28 }
29 if t != reflect.TypeOf(datasize.ByteSize(5)) {
30 return data, nil
31 }
32 raw := data.(string)
33 result := new(datasize.ByteSize)
34 result.UnmarshalText([]byte(raw))
35 return result.Bytes(), nil
36 }
37}
下面是发送验证码的实现
1type SmsClient struct {
2 Credential *common.Credential
3 Region string
4 Cpf *profile.ClientProfile
5 Request SmsRequest
6}
7
8type Option func(*SmsClient)
9
10func NewSmsClient(options ...func(client *SmsClient)) *SmsClient {
11 client := &SmsClient{
12 Region: "ap-guangzhou",
13 Cpf: profile.NewClientProfile(),
14 }
15 for _, option := range options {
16 option(client)
17 }
18 return client
19
20}
21
22func WithRequest(request SmsRequest) Option {
23 return func(smsClient *SmsClient) {
24 smsClient.Request = request
25 }
26}
27
28func WithCredential(options options.SmsOptions) Option {
29 return func(smsClient *SmsClient) {
30 smsClient.Credential = common.NewCredential(options.SecretId, options.SecretKey)
31 }
32}
33func WithCpfReqMethod(method string) Option {
34 return func(smsClient *SmsClient) {
35 smsClient.Cpf.HttpProfile.ReqMethod = method
36 }
37}
38func WithCpfReqTimeout(timeout int) Option {
39 return func(smsClient *SmsClient) {
40 smsClient.Cpf.HttpProfile.ReqTimeout = timeout
41 }
42}
43func WithCpfSignMethod(method string) Option {
44 return func(smsClient *SmsClient) {
45 smsClient.Cpf.SignMethod = method
46 }
47}
48
49func (s *SmsClient) Send() bool {
50 sendClient, _ := sms.NewClient(s.Credential, s.Region, s.Cpf)
51 _, err := sendClient.SendSms(s.Request.request)
52 if _, ok := err.(*errors.TencentCloudSDKError); ok {
53 logger.Warnf("An API error has returned: %s", err)
54 return false
55 }
56
57 if err != nil {
58 logger.Warnf("发送短信失败:%s,requestId:%s", err)
59 return false
60 }
61 logger.Info("发送短信验证码成功")
62 return true
63}
定义发送的client,这里采用function option 的编程模式来初始化发送的client.和发送的request,request的代码如下
1type SmsRequest struct {
2 request *sms.SendSmsRequest
3}
4
5func NewSmsRequest(options *options.SmsOptions, withOptions ...func(smsRequest *SmsRequest)) *SmsRequest {
6 request := sms.NewSendSmsRequest()
7
8 request.SmsSdkAppId = &options.SmsSdkAppId
9 request.SignName = &options.SignName
10 request.TemplateId = &options.TemplateId
11 smsRequest := &SmsRequest{request: request}
12 for _, option := range withOptions {
13 option(smsRequest)
14 }
15 return smsRequest
16
17}
18
19type RequestOption func(*SmsRequest)
20
21func WithPhoneNumberSet(phoneSet []string) RequestOption {
22 return func(smsRequest *SmsRequest) {
23 smsRequest.request.PhoneNumberSet = common.StringPtrs(phoneSet)
24 }
25}
26
27func WithTemplateParamSet(templateSet []string) RequestOption {
28 return func(smsRequest *SmsRequest) {
29 smsRequest.request.TemplateParamSet = common.StringPtrs(templateSet)
30 }
31}
创建发送验证码的控制层,发送成功,并将此处的电话号码和验证码保存到redis缓存中,用来登录时候的验证码有效性的校验
1func (u *userService) SendPhoneCode(ctx context.Context, phone string) bool {
2 // 获取配置参数
3 smsSetting := global.TencenSmsSetting
4 phoneSet := []string{phone}
5 // 随机生成6位的验证码
6 var randCode string = fmt.Sprintf("%06v", rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(1000000))
7 templateSet := []string{randCode, "60"}
8 smsRequest := tencenSms.NewSmsRequest(smsSetting, tencenSms.WithPhoneNumberSet(phoneSet), tencenSms.WithTemplateParamSet(templateSet))
9 smsClient := tencenSms.NewSmsClient(tencenSms.WithRequest(*smsRequest), tencenSms.WithCredential(*smsSetting))
10 go smsClient.Send()
11 // 将验证码和手机号保存到redis中
12 _ = u.cache.UserCaches().SetSendPhoneCodeCache(ctx, phone, randCode)
13 return true
14
15}
后面是通过手机验证码进行登录的流程
1func (u *userService) LoginByPhoneCode(ctx context.Context, phone string, phoneCode string) (*model.User,error) {
2 // 从缓存中获取该手机号对应的验证码是否匹配
3 cacheCode, err :=u.cache.UserCaches().GetSendPhoneCodeFromCache(ctx,phone)
4 if err != nil {
5 return nil, errors.WithCode(code.ErrUserPhoneCodeExpire,err.Error())
6 }
7 if cacheCode!=phoneCode {
8 return nil,errors.WithCode(code.ErrUserPhoneCodeMiss,"")
9 }
10 return &model.User{
11 Nickname: "lala",
12 }, nil
13}