現(xiàn)在大多數(shù)app或wap都實(shí)現(xiàn)了通過手機(jī)號(hào)獲取驗(yàn)證碼進(jìn)行驗(yàn)證登錄,下面來看下用go來實(shí)現(xiàn)手機(jī)號(hào)發(fā)送短信驗(yàn)證碼登錄的過程,基于的框架是gin 。
首先是短信服務(wù)商的申請(qǐng),比如騰訊云、阿里云、網(wǎng)易易盾等,騰訊云自己申請(qǐng)個(gè)微信公眾號(hào)就行,然后申請(qǐng)相關(guān)的短信簽名、和短信模板,騰訊有100條試用喔。 具體的代碼實(shí)現(xiàn) 配置騰訊云短信服務(wù)的發(fā)送短信相關(guān)配置,具體可以參考騰訊云短信服務(wù)的api 文檔,進(jìn)行配置
go 這里采用的是viper進(jìn)行加載配置,相關(guān)的加載配置代碼如下 定義相關(guān)的配置結(jié)構(gòu)體,并加載到整個(gè)項(xiàng)目的總的options 配置結(jié)構(gòu)體中
// sms 發(fā)送短信的配置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: "",
}
}
// 這為項(xiàng)目總的一個(gè)options配置,項(xiàng)目啟動(dòng)的時(shí)候會(huì)將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
}
}
下面是發(fā)送驗(yàn)證碼的實(shí)現(xiàn)
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("發(fā)送短信失敗:%s,requestId:%s", err)
return false
}
logger.Info("發(fā)送短信驗(yàn)證碼成功")
return true
}
定義發(fā)送的client,這里采用function option 的編程模式來初始化發(fā)送的client.和發(fā)送的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)
}
}
創(chuàng)建發(fā)送驗(yàn)證碼的控制層,發(fā)送成功,并將此處的電話號(hào)碼和驗(yàn)證碼保存到redis緩存中,用來登錄時(shí)候的驗(yàn)證碼有效性的校驗(yàn)
func (u *userService) SendPhoneCode(ctx context.Context, phone string) bool {
// 獲取配置參數(shù)
smsSetting := global.TencenSmsSetting
phoneSet := []string{phone}
// 隨機(jī)生成6位的驗(yàn)證碼
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()
// 將驗(yàn)證碼和手機(jī)號(hào)保存到redis中
_ = u.cache.UserCaches().SetSendPhoneCodeCache(ctx, phone, randCode)
return true
}
后面是通過手機(jī)驗(yàn)證碼進(jìn)行登錄的流程
func (u *userService) LoginByPhoneCode(ctx context.Context, phone string, phoneCode string) (*model.User,error) { // 從緩存中獲取該手機(jī)號(hào)對(duì)應(yīng)的驗(yàn)證碼是否匹配 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
審核編輯:劉清
-
WAP
+關(guān)注
關(guān)注
0文章
25瀏覽量
34447 -
APP
+關(guān)注
關(guān)注
33文章
1577瀏覽量
72774
原文標(biāo)題:Go語言實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼并登錄
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論