当前位置: 代码网 > it编程>数据库>Redis > 从零开始实现基于go-zero框架的微服务电商项目(三)——gorm、redis、腾讯云SMS、validate、md5加密、日志输入到kafka的添加

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

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

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

开始

我们在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短信服务

在这里插入图片描述

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

在这里插入图片描述

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

然后改成自己的:

在这里插入图片描述

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

在这里插入图片描述

这里的{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博客

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com