本文最后更新于 2024-04-02,文章内容可能已经过时。

Hertz

1.简介:

QQ截图20230204145726

2.Hertz分层设计:

QQ截图20230204145757

3.特点:

Hertz | CloudWeGo

QQ截图20230204145934

微服务超 10 万、跨语言场景,字节服务网格依靠 CloudWeGo 扛住流量洪峰 (qq.com)QQ截图20230204150004

4.新特性:

QQ截图20230204150346

5.提问截图:

QQ截图20230204150458

QQ截图20230204150530QQ截图20230204150512

6.使用:

依据快速开始 | CloudWeGo此教程学习.

6.1 安装:

cmd,git,powershell的区别?

安装报错:

微信图片_20230204210542

解决方案:

QQ截图20230204215755

1
$env:GOPROXY = "https://proxy.golang.com.cn,direct"

QQ图片20230204215817

微信图片_20230205182837

6.2 goland配置:

全部爆红,识别不出来,同步也同步不了.

解决方案是设置代理.

(181条消息) Goland 下载 go 包_JikeStardy的博客-CSDN博客

解决方案从这个文章找到,略有修改.

1
GOPROXY="https://goproxy.cn";GO111MODULE=on

可能会出现版本不匹配的情况.建议切换到1.19版本.

1
curl http://127.0.0.1:8888/ping

6.3 hello world:

尝试编写一个类似于spring中,controller层的一个接口的一个简单的方法.

只需要一个main方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
   "context"
   "github.com/cloudwego/hertz/pkg/app"
   "github.com/cloudwego/hertz/pkg/app/server"
   "github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main() {
   h := server.Default(
      server.WithHostPorts("127.0.0.1:8080"),
      //使用这个配置指定ip和端口.
   )
   //建立一个get请求
   h.GET("/hello", func(c context.Context, ctx *app.RequestContext) {
   	  //在这里写访问这个接口的具体逻辑.
      ctx.String(consts.StatusOK, "hello hertz!")
   })
   //类似于接口的打开.
   h.Spin()
}

QQ截图20230205221923

6.4 获取参数:

1
name := ctx.Query("name")

6.5 hertz+gorm 实现简单的MVC逻辑:

也是只需要main函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package main

import (
   "context"
   "github.com/cloudwego/hertz/pkg/app"
   "github.com/cloudwego/hertz/pkg/app/server"
   "github.com/cloudwego/hertz/pkg/common/json"
   "github.com/cloudwego/hertz/pkg/protocol/consts"
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
)

//定义gorm model
type User struct {
   Name string `gorm:"column:name; type:varchar(32);"`
}

//定义表名
func (u User) TableName() string {
   return "User"
}

func main() {
   //建立数据库连接,数据库名:数据库密码@...
   dsn := "root:123456@tcp(127.0.0.1:3306)/dbgotest?charset=utf8mb4&parseTime=True&loc=Local"
   db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
   //处理错误
   if err != nil {
   	  //控制台打印错误日志
      panic("数据库连接失败!")
   }
   //新建数据库表,根据结构体创建
   m := db.Migrator()
   err = m.CreateTable(&User{})
   if err != nil {
      panic("新建表失败!")
   }
   //配置http
   h := server.Default(
      server.WithHostPorts("127.0.0.1:8080"),
   )
   h.GET("/hello", func(c context.Context, ctx *app.RequestContext) {
      username := ctx.Query("name")
      //创建一条数据,传入一个对象.
      db.Create(&User{Name: username})
      //查询一条数据
      var user User
      db.First(&user, "name = ?", username)
      //转换格式后输出
      res, _ := json.Marshal(user)
      ctx.String(consts.StatusOK, string(res))
   })
   h.Spin()
}

6.6 hertz+kitex+gorm实现douyin基础功能:

6.6.1user-server:

rpc的服务端.只被系统内调用.

package main

import (
	"context"
	"crypto/sha256"
	"douyin/kitex_gen/api"
	"encoding/hex"
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"math/big"
)

// UserImpl implements the last service interface defined in the IDL.
type UserImpl struct{}

type User struct {
	Id              int64  `gorm:"column:id;"`
	UserId          int64  `gorm:"column:user_id;"`
	Username        string `gorm:"column:username;"`
	Password        string `gorm:"column:password;"`
	Name            string `gorm:"column:name;"`
	Avatar          string `gorm:"column:avatar;"`
	BackgroundImage string `gorm:"column:background_image;"`
	Signature       string `gorm:"column:signature;"`
}

func (u *User) TableName() string {
	return "user"
}

// 全局盐
var salt = "byteDanceAgain"

// 字符串hash,用于加密密码
func hashTo15Bits(input string) string {
	// 创建一个SHA-256哈希对象
	h := sha256.New()
	// 将字符串输入写入哈希对象
	_, err := h.Write([]byte(input))
	if err != nil {
		panic(err)
	}
	// 获取SHA-256哈希值的字节数组
	hashBytes := h.Sum(nil)
	// 取哈希值的前两个字节并转换为16进制字符串
	hashHex := hex.EncodeToString(hashBytes[:2])
	return hashHex
}

// 用户id生成哈希,根据账号和
func hashToI64(input string) int64 {
	// 创建一个SHA-256哈希对象
	h := sha256.New()
	// 将字符串输入写入哈希对象
	_, err := h.Write([]byte(input))
	if err != nil {
		panic(err)
	}
	// 获取SHA-256哈希值的字节数组
	hashBytes := h.Sum(nil)
	// 将哈希值的前8个字节转换为int64
	var hashInt big.Int
	hashInt.SetBytes(hashBytes[:8])
	result := hashInt.Int64()
	return result
}

// Register implements the UserImpl interface.
func (s *UserImpl) Register(ctx context.Context, req *api.RegisterRequest) (resp *api.RegisterResponse, err error) {

	///建立数据库连接,数据库名:数据库密码
	dsn := "root:123456@tcp(127.0.0.1:3306)/dbgotest"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		return nil, err
	}
	//判断用户名是否唯一
	var user User
	db.First(&user, "username = ?", req.Username)
	if user.Id != 0 {
		resp = &api.RegisterResponse{Code: "1", Msg: "用户名已经存在!"}
		return
	}
	//对密码加盐,加密存储进入数据库
	password := hashTo15Bits(salt + req.Password)
	fmt.Print(password)
	//生成用户id
	UserId := hashToI64(req.Username + req.Password)
	fmt.Print(UserId)
	//创建一条数据,传入一个对象
	db.Create(&User{Username: req.Username, Password: password, Name: req.Name, UserId: UserId})
	resp = &api.RegisterResponse{Code: "0", Msg: "用户注册成功!", Userid: UserId}
	return
}

// Login implements the UserImpl interface.
func (s *UserImpl) Login(ctx context.Context, req *api.LoginRequest) (resp *api.LoginResponse, err error) {
	///建立数据库连接,数据库名:数据库密码
	dsn := "root:123456@tcp(127.0.0.1:3306)/dbgotest"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		return nil, err
	}

	var user User
	db.First(&user, "username = ?", req.Username)
	if user.Id == 0 {
		resp = &api.LoginResponse{Code: "1", Msg: "用户不存在!"}
		return
	}
	//验证密码
	hashPassword := hashTo15Bits(salt + req.Password)
	if user.Password != hashPassword {
		resp = &api.LoginResponse{Code: "1", Msg: "用户密码错误!"}
		return
	}
	resp = &api.LoginResponse{Code: "0", Msg: "用户登录成功!", Userid: user.Id}
	return
}

// Get implements the UserImpl interface.
func (s *UserImpl) Get(ctx context.Context, req *api.GetInfoRequest) (resp *api.GetInfoResponse, err error) {
	///建立数据库连接,数据库名:数据库密码
	dsn := "root:123456@tcp(127.0.0.1:3306)/dbgotest"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		return nil, err
	}
	//获取用户信息
	var user User
	db.First(&user, "user_id = ?", req.Userid)
	resp = &api.GetInfoResponse{Userid: user.UserId, Name: user.Name, Avatar: user.Avatar, BackgroundImage: user.BackgroundImage, Signature: user.Signature}
	return
}

//批量获取用户信息

// GetAll implements the UserImpl interface.
func (s *UserImpl) GetAll(ctx context.Context, req *api.GetAllInfoResponse) (resp *api.GetAllInfoResponse, err error) {
	dsn := "root:123456@tcp(127.0.0.1:3306)/dbgotest"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		return nil, err
	}
	var User1 []User
	list := req.UserList
	for i := 0; i < len(list); i++ {
		db.First(&User1[i], "user_id = ?", list[i])
	}
	resp = &api.GetAllInfoResponse{UserList: User1}
	return
}

6.6.2 user-client:

http访问接口,调用user-server,与前端进行交互.

// Code generated by hertz generator.

package main

import (
	"context"
	"douyin/kitex_gen/api"
	"douyin/kitex_gen/api/user"
	"fmt"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
	"github.com/cloudwego/kitex/client"
	"github.com/cloudwego/kitex/client/callopt"
	"github.com/golang-jwt/jwt/v4"
	"log"
	"time"
)

type User struct {
	Id              int64  `gorm:"column:id;"`
	UserId          int64  `gorm:"column:user_id;"`
	Username        string `gorm:"column:username;"`
	Password        string `gorm:"column:password;"`
	Name            string `gorm:"column:name;"`
	Avatar          string `gorm:"column:avatar;"`
	BackgroundImage string `gorm:"column:background_image;"`
	Signature       string `gorm:"column:signature;"`
}

// 定义一个密钥,用于签名和验证JWT
var jwtKey = []byte("byteDanceAgain")

// 使用hertz集成的jwt出现问题,故使用另一个jwt
// 创建一个JWT令牌
func createJWTToken(username string, password string, userId int64) (string, error) {
	// 创建一个JWT声明
	claims := jwt.MapClaims{
		"Username": username,
		"Password": password,
		"UserId":   userId,
		"exp":      time.Now().Add(time.Hour * 24).Unix(), // 设置过期时间为24小时
	}

	// 创建一个JWT令牌对象
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// 使用密钥签名JWT令牌
	tokenString, err := token.SignedString(jwtKey)
	if err != nil {
		return "", err
	}

	return tokenString, nil
}

// 解析并验证JWT令牌
func parseJWTToken(tokenString string) (*jwt.MapClaims, error, int64) {
	// 解析JWT令牌
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		// 验证签名方法
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return jwtKey, nil
	})

	if err != nil {
		return nil, err, 0
	}

	// 验证JWT令牌的有效性
	if !token.Valid {
		return nil, fmt.Errorf("invalid token"), 0
	}

	// 获取声明中的信息
	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return nil, fmt.Errorf("couldn't parse claims"), 0
	}

	userID, ok := claims["UserID"].(int64)
	if !ok {
		return nil, fmt.Errorf("couldn't parse UserID"), 0
	}
	return &claims, nil, userID
}

func main() {

	h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
	newClient, err := user.NewClient("user-server", client.WithHostPorts("127.0.0.1:8888"))
	if err != nil {
		log.Fatal(err)
	}
	//用户注册
	h.POST("/user/register", func(c context.Context, ctx *app.RequestContext) {
		username1 := ctx.Query("username")
		password1 := ctx.Query("password")
		name := ctx.Query("name")
		req := &api.RegisterRequest{
			Username: username1,
			Password: password1,
			Name:     name,
		}
		resp, err := newClient.Register(context.Background(), req, callopt.WithRPCTimeout(3*time.Second))
		if err != nil {
			ctx.String(consts.StatusInternalServerError, "rpc链路失效!")
		}
		ctx.String(consts.StatusOK, resp.Code, resp.Msg, resp.Userid)
	})
	//用户登录
	h.POST("/user/login", func(c context.Context, ctx *app.RequestContext) {
		username1 := ctx.Query("username")
		password1 := ctx.Query("password")
		req := &api.LoginRequest{
			Username: username1,
			Password: password1,
		}
		resp, err := newClient.Login(context.Background(), req, callopt.WithRPCTimeout(3*time.Second))
		if err != nil {
			ctx.String(consts.StatusInternalServerError, "rpc链路失效!")
		}
		//判断是否登录成功
		if resp.Code != "0" {
			ctx.String(consts.StatusInternalServerError, "登录失败!")
		}
		//生成token
		token, nil := createJWTToken(username1, password1, resp.Userid)
		if err != nil {
			ctx.String(consts.StatusInternalServerError, "token生成失败!")
		}
		ctx.String(consts.StatusOK, resp.Code, resp.Msg, resp.Userid, token)
	})
	//获取用户信息
	h.POST("/user/getUser", func(c context.Context, ctx *app.RequestContext) {
		userId := ctx.GetInt64("userId")
		token1 := ctx.Query("token")
		//进行token解析
		_, err, userId1 := parseJWTToken(token1)
		if err != nil {
			ctx.String(consts.StatusInternalServerError, "token错误!")
		}
		//传入的userid和token所代表的的用户可能不是同一个,所以不需要进行验证,只需要token本身正确即可.
		//if userId1 != userId {
		//	ctx.String(consts.StatusInternalServerError, "token与用户id不匹配!")
		//}
		req := &api.GetInfoRequest{
			Userid: userId,
		}
		resp, err := newClient.Get(context.Background(), req, callopt.WithRPCTimeout(3*time.Second))
		if err != nil {
			ctx.String(consts.StatusInternalServerError, "rpc链路失效!")
		}
		ctx.String(consts.StatusOK, "获取用户信息成功!", resp.Userid, resp.Name, resp.Avatar, resp.BackgroundImage)
	})
	h.Spin()
}