项目地址:liuxianloveqiqi/XianShop: 使用go-zero搭建的电商项目 (github.com)
我们在service里面新建一个utils包,里面就放上面的一些加入
在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
}
请参考官方: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点击短信,按照指引创造好模版等待审核,完成后根据官方文档配置:
在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)]
在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的校验
在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博客