从零开始实现基于go-zero框架的微服务电商项目(三)——gorm、redis、腾讯云SMS、validate、md5加密、日志输入到kafka的添加

1,694 阅读4分钟

从零开始实现基于go-zero框架的微服务电商项目(三)——gorm、redis、腾讯云SMS、validate、md5加密、日志输入到kafka的添加

项目地址liuxianloveqiqi/XianShop: 使用go-zero搭建的电商项目 (github.com)

开始

我们在service里面新建一个utils包,里面就放上面的一些加入

gorm和redis的初始化

在common包下新建文件init_system.go,添加:

 package common
 ​
 import (
     "context"
     "fmt"
     "github.com/redis/go-redis/v9"
     "github.com/zeromicro/go-zero/core/logx"
     "gorm.io/driver/mysql"
     "gorm.io/gorm"
     "gorm.io/gorm/schema"
 )
 ​
 // gorm初始化
 func InitGorm(MysqlDataSourece string) *gorm.DB {
     // 将日志写进kafka
     logx.SetWriter(*LogxKafka())
     db, err := gorm.Open(mysql.Open(MysqlDataSourece),
        &gorm.Config{
           NamingStrategy: schema.NamingStrategy{
              //TablePrefix:   "tech_", // 表名前缀,`User` 的表名应该是 `t_users`
              SingularTable: true, // 使用单数表名,启用该选项,此时,`User` 的表名应该是 `t_user`
           },
        })
     if err != nil {
        panic("连接mysql数据库失败, error=" + err.Error())
     } else {
        fmt.Println("连接mysql数据库成功")
     }
     return db
 }
 ​
 // redis初始化
 func InitRedis(add, password string, db int) *redis.Client {
 •    rdb := redis.NewClient(&redis.Options{
 •       Addr:     add,
 •       Password: password,
 •       DB:       db,
 •    })
 •    _, err := rdb.Ping(context.Background()).Result()
 •    if err != nil {
 •       panic("连接redis失败, error=" + err.Error())
 •    }
 •    fmt.Println("redis连接成功")
 •    return rdb
 }
 ​
 ​

日志输出到kafka

请参考官方logx | go-zero

在common包下建kafka_logx.go,我这里就是封装了一下官方的写法:

 package common
 ​
 import (
   "github.com/zeromicro/go-queue/kq"
   "github.com/zeromicro/go-zero/core/logx"
   "strings"
 )
 ​
 type KafkaWriter struct {
   Pusher *kq.Pusher
 }
 ​
 func NewKafkaWriter(pusher *kq.Pusher) *KafkaWriter {
   return &KafkaWriter{
     Pusher: pusher,
   }
 }
 ​
 func (w *KafkaWriter) Write(p []byte) (n int, err error) {
   // writing log with newlines, trim them.
   if err := w.Pusher.Push(strings.TrimSpace(string(p))); err != nil {
     return 0, err
   }
 ​
   return len(p), nil
 }
 func LogxKafka() *logx.Writer {
   pusher := kq.NewPusher([]string{"localhost:9092"}, "log")
   defer pusher.Close()
 ​
   writer := logx.NewWriter(NewKafkaWriter(pusher))
   return &writer
 }
 ​

*注意!*下面都是先在rpc里面写,然后api直接调自己的rpc,所以业务处理部分是在rpc里面写的,api直接调就行。

接着在user/rpc下的config里面添加redis和gorm

 package config
 ​
 import (
   "github.com/zeromicro/go-zero/core/stores/cache"
   "github.com/zeromicro/go-zero/zrpc"
 )
 ​
 type Config struct {
   zrpc.RpcServerConf
   Mysql struct {
     DataSource string
   }
   CacheRedis cache.CacheConf
   Redis      struct {
     Host string
     Pass string
     DB   int
   }
   Credential struct {
     SecretId  string
     SecretKey string
   }
 }
 ​

在svc下的servicecontext.go下添加gorm和redis并调用common里面的初始化

 package svc
 ​
 import (
   "XianShop/service/common"
   "XianShop/service/user/model"
   "XianShop/service/user/rpc/internal/config"
   "github.com/redis/go-redis/v9"
 ​
   "github.com/zeromicro/go-zero/core/stores/sqlx"
   "gorm.io/gorm"
 )
 ​
 type ServiceContext struct {
   Config    config.Config
   UserModel model.UserModel
   Rdb       *redis.Client
   DbEngine  *gorm.DB
 }
 ​
 func NewServiceContext(c config.Config) *ServiceContext {
   coon := sqlx.NewMysql(c.Mysql.DataSource)
   db := common.InitGorm(c.Mysql.DataSource)
   rdb := common.InitRedis(c.Redis.Host, c.Redis.Pass, c.Redis.DB)
   db.AutoMigrate(&model.User{})
   return &ServiceContext{
     Config:    c,
     UserModel: model.NewUserModel(coon, c.CacheRedis),
     Rdb:       rdb,
     DbEngine:  db,
   }
 }
 ​

这样之后我们就可以在logic里面调用gorm和redis

腾讯云SMS短信服务

image-20230428082731416.png

首先在腾讯云搜索sms点击短信,按照指引创造好模版等待审核,完成后根据官方文档配置:

image-20230428083135452.png

在utils包下建sms.go

 package utils
 ​
 import (
   "context"
   "encoding/json"
   "fmt"
   "github.com/redis/go-redis/v9"
   "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
   "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
   "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
   sms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms/v20210111"
   "math/rand"
   "strings"
   "time"
 )
 ​
 func SMS(phone, secretId, secretKey string, ctx context.Context, rdb *redis.Client) string {
   // 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey.
   credential := common.NewCredential(
     secretId,
     secretKey,
   )
 ​
   // 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey.
   //credential := common.NewCredential(
   //  "你的accessKeyId",
   //  "你的accessKeySecret",
   //)
   cpf := profile.NewClientProfile()
 ​
   cpf.HttpProfile.ReqMethod = "POST"
 ​
   cpf.HttpProfile.Endpoint = "sms.tencentcloudapi.com"
 ​
   client, _ := sms.NewClient(credential, "ap-beijing", cpf)
 ​
   /* 实例化一个请求对象,根据调用的接口和实际情况*/
   request := sms.NewSendSmsRequest()
 ​
   // 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看
   request.SmsSdkAppId = common.StringPtr("1400797992")
 ​
   // 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名
   request.SignName = common.StringPtr("我的学习记录网")
 ​
   /* 模板 ID: 必须填写已审核通过的模板 ID */
   request.TemplateId = common.StringPtr("1729324")
 ​
   /* 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空*/
   code1 := GenerateSmsCode(6)
   fmt.Println(code1, "ZHESHICODE")
   request.TemplateParamSet = common.StringPtrs([]string{code1, "3"})
   /* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号]
    * 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号*/
   phoneWithPrefix := "+86" + phone
   request.PhoneNumberSet = common.StringPtrs([]string{phoneWithPrefix})
   //使用redis缓存
   rdb.Set(ctx, phone, code1, 3*time.Minute)
   fmt.Println(phone, "  ", code1)
   // 通过client对象调用想要访问的接口,需要传入请求对象
   response, err := client.SendSms(request)
   // 处理异常
   if _, ok := err.(*errors.TencentCloudSDKError); ok {
     fmt.Printf("An API error has returned: %s", err)
     return ""
   }
   // 非SDK异常,直接失败。实际代码中可以加入其他的处理。
   if err != nil {
     panic(err)
   }
   b, _ := json.Marshal(response.Response)
   // 打印返回的json字符串
   fmt.Printf("%s", b)
   return code1
 }
 ​
 // GenerateSmsCode 生成验证码;length代表验证码的长度
 func GenerateSmsCode(length int) string {
   numberic := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
   rand.Seed(time.Now().Unix())
   var sb strings.Builder
   for i := 0; i < length; i++ {
     fmt.Fprintf(&sb, "%d", numberic[rand.Intn(len(numberic))])
   }
   return sb.String()
 }
 ​

然后改成自己的:

image-20230428083627793.png

红色的地方是根据自己的情况可以改动的,最后那个模版参数那里,是有几个参数填几个,一一对应的关系,比如我的模版是这样的

image-20230428083904858.png

这里的{1}就被code1代替了,{2}就被“3”代替

然后我们还需要secretId, secretKey,在腾讯云的密钥管理那里拿,然后copy到etcd到yaml文件下,就是上面一节中

image-20230428084236728.png

validate校验参数

在utils包下新建validate.go,我这里参考(86条消息) golang中使用validator进行数据校验及自定义翻译器go语言validator自定义验证信息秋叶原の黑猫的博客-CSDN博客做了一些封装,因为博主使用的gin

 package utils
 ​
 import (
   "context"
   "errors"
   "fmt"
   "github.com/go-playground/locales/en"
   "github.com/go-playground/locales/zh"
   ut "github.com/go-playground/universal-translator"
   "github.com/go-playground/validator/v10"
   enTranslations "github.com/go-playground/validator/v10/translations/en"
   zhTranslations "github.com/go-playground/validator/v10/translations/zh"
   "reflect"
   "regexp"
   "strings"
 )
 ​
 const (
   ValidatorKey  = "ValidatorKey"
   TranslatorKey = "TranslatorKey"
   locale        = "chinese"
 )
 ​
 func TransInit(ctx context.Context) context.Context {
   //设置支持语言
   chinese := zh.New()
   english := en.New()
   //设置国际化翻译器
   uni := ut.New(chinese, chinese, english)
   //设置验证器
   val := validator.New()
   //根据参数取翻译器实例
   trans, _ := uni.GetTranslator(locale)
   //翻译器注册到validator
   switch locale {
   case "chinese":
     zhTranslations.RegisterDefaultTranslations(val, trans)
     //使用fld.Tag.Get("comment")注册一个获取tag的自定义方法
     val.RegisterTagNameFunc(func(fld reflect.StructField) string {
       return fld.Tag.Get("comment")
     })
     val.RegisterValidation("phone", func(fl validator.FieldLevel) bool {
       phone := fl.Field().String()
       //使用正则表达式验证手机号码
       pattern := `^1[3456789]\d{9}$`
       matched, _ := regexp.MatchString(pattern, phone)
       return matched
     })
     // 在TransInit函数中添加电话号码翻译信息
     zhTranslations.RegisterDefaultTranslations(val, trans)
     val.RegisterTranslation("phone", trans, func(ut ut.Translator) error {
       return ut.Add("phone", "{0}格式不正确,必须为手机号码", true)
     }, func(ut ut.Translator, fe validator.FieldError) string {
       t, _ := ut.T("phone", fe.Field())
       return t
     })
 ​
   case "english":
     enTranslations.RegisterDefaultTranslations(val, trans)
     val.RegisterTagNameFunc(func(fld reflect.StructField) string {
       return fld.Tag.Get("en_comment")
     })
   }
   ctx = context.WithValue(ctx, ValidatorKey, val)
   ctx = context.WithValue(ctx, TranslatorKey, trans)
   return ctx
 }
 ​
 func DefaultGetValidParams(ctx context.Context, params interface{}) error {
   ctx = TransInit(ctx)
   err := validate(ctx, params)
   if err != nil {
     return err
   }
   return nil
 }
 ​
 func validate(ctx context.Context, params interface{}) error {
   //获取验证器
   val, ok := ctx.Value(ValidatorKey).(*validator.Validate)
   if !ok {
     return errors.New("Validator not found in context")
   }
 ​
   //获取翻译器
   tran, ok := ctx.Value(TranslatorKey).(ut.Translator)
   fmt.Println(val, tran)
   if !ok {
     return errors.New("Translator not found in context")
   }
   err := val.Struct(params)
   //如果数据效验不通过,则将所有err以切片形式输出
   if err != nil {
     errs := err.(validator.ValidationErrors)
     sliceErrs := []string{}
     for _, e := range errs {
       //使用validator.ValidationErrors类型里的Translate方法进行翻译
       sliceErrs = append(sliceErrs, e.Translate(tran))
     }
     return errors.New(strings.Join(sliceErrs, ","))
   }
   return nil
 }
 ​

如果后续想加入自定义的校验,可以参考phone的校验

md5加密

在utils包下建md5.go

 package utils
 ​
 import (
   "crypto/md5"
   "crypto/rand"
   "encoding/hex"
   "math/big"
 )
 ​
 func GeneratePassword(length int) string {
   // 定义密码包含的字符集
   charset := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*()_+`-={}|[]\:";'<>,.?/"
 ​
   // 定义密码长度
   // 这里可以根据实际需求进行调整
   passwordLength := length
 ​
   // 初始化密码切片
   password := make([]byte, passwordLength)
 ​
   // 生成随机密码
   for i := 0; i < passwordLength; i++ {
     charIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
     if err != nil {
       panic(err)
     }
     password[i] = charset[charIndex.Int64()]
   }
 ​
   return string(password)
 }
 ​
 // md5加密
 func Md5(pasaword string) string {
   hash := md5.New()
   hash.Write([]byte(pasaword))
   passwordHash := hash.Sum(nil)
   // 将密码转换为16进制储存
   passwordHash16 := hex.EncodeToString(passwordHash)
   return passwordHash16
 }
 ​
 // 加盐值加密
 func Md5Password(password, salt string) string {
   return Md5(password + salt)
 }
 ​
 // 解密
 func ValidMd5Password(password, salt, dataPwd string) bool {
   return Md5Password(password, salt) == dataPwd
 }
 ​

我这里做了个加盐处理,减少被破译的可能性,在业务部分直接调Md5Password()输入密码和盐值就可以了

,然后还做了个随机密码,因为用户在手机验证码登陆的时候,要给密码一个随机值

总结

这一章我们引入了gorm、redis、腾讯云SMS短信服务、validate校验参数、md5加密和解密、日志输入到kafka,下一章我们将完成api的编写。

感谢

如果你觉得我的文章对你有帮忙,欢迎点赞,关注,star!有问题可以在评论区直接提出来,感谢大家的阅读!🥰🥰🥰🥰🥰🥰

参考

(86条消息) golang中使用validator进行数据校验及自定义翻译器go语言validator自定义验证信息秋叶原の黑猫的博客-CSDN博客

logx | go-zero