从零开始实现基于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博客
发表评论