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

从零开始实现基于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短信服务

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

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

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

在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()
}

然后改成自己的:

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

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

在这里插入图片描述

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MXGgvVip-1682644996846)(/Users/liuxian/Pictures/typora图片/image-20230428084246948.png)]

validate校验参数

在utils包下新建validate.go,我这里参考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!有问题可以在评论区直接提出来,感谢大家的阅读!

参考

logx | go-zero
golang中使用validator进行数据校验及自定义翻译器_go语言validator自定义验证信息_秋叶原の黑猫的博客-CSDN博客

你可能感兴趣的:(golang,rpc,微服务,腾讯云)