当前位置: 首页 > news >正文

Hertz+Kitex快速上手开发

本篇文章以用户注册接口为例,快速上手Hertz+Kitex

以用户注册接口来演示hertz结合kitex实现网关+微服务架构的最简易版本

项目结构

api- gateway:网关实现,这里采用hertz框架

idl:接口定义用来生成kitex代码

kitex_gen:thrift协议自动生成的代码

rpc_service:微服务的服务逻辑

hertz框架

hertz框架其实和gin作用差不多,为字节跳动开源的go语言web网络框架

框架细节部分 大家感兴趣可以参考官方文档路由 | CloudWeGo

快速引入

服务逻辑

用户请求注册接口->网关层服务拿到进行处理路由,分发到具体的微服务

核心处理路由函数handler.go
package userimport ("context""log""time""video_douyin/kitex_gen/user"             // Kitex生成的用户服务数据结构"video_douyin/kitex_gen/user/userservice" // Kitex生成的用户服务客户端"github.com/cloudwego/hertz/pkg/app"        // Hertz HTTP上下文处理"github.com/cloudwego/kitex/client"         // Kitex客户端核心"github.com/cloudwego/kitex/client/callopt" // Kitex调用选项
)// 全局用户服务客户端实例
var userClient userservice.Client// 初始化Kitex客户端连接
func init() {c, err := userservice.NewClient("douyin.video.user",                  // 服务名client.WithHostPorts("0.0.0.0:8888"), // 服务地址)if err != nil {panic(err) // 连接失败终止程序}userClient = c
}// Handler 处理用户注册HTTP请求
func Handler(ctx context.Context, c *app.RequestContext) {req := user.NewRegisterRequest()   // 创建注册请求结构体log.Printf("Phone: %s", req.Phone) // 记录请求参数// 调用用户服务RPC接口,超时3秒resp, err := userClient.Register(context.Background(),req,callopt.WithRPCTimeout(3*time.Second),)if err != nil {log.Fatal(err) // RPC调用失败记录日志}c.String(200, resp.String()) // 返回RPC响应内容
}

这里服务依赖的kitex目前先不看,只用知道这里做了如下动作

通过hertz服务拿到了请求参数

往微服务进行传递参数

main函数
package mainimport ("log""api_gateway/user""github.com/cloudwego/hertz/pkg/app/server"
)func main() {// 创建路由分组(符合 RESTful 风格)// 第一级分组 /douyin 作为 API douyin := hz.Group("/douyin")// 第二级分组 /user 用于用户相关操作userGroup := douyin.Group("/user")// 注册用户注册接口,POST 方法对应创建操作// 处理函数指向 user 包的 Handler 方法userGroup.POST("/register/", user.Handler)// 启动 HTTP 服务,若失败则记录错误日志// Run() 会阻塞直到服务终止:ml-citation{ref="6" data="citationList"}if err := hz.Run(); err != nil {log.Fatal(err)}
}

可以看到这里和gin的操作差不多 gin的笔记:gin框架学习笔记_gin学习-CSDN博客

这里的主要逻辑:路由组注册

导入依赖

go mod tidy

kitex框架

kitex框架可以类比于grpc框架,grpc框架笔记:gRPC学习笔记记录以及整合gin开发-CSDN博客

也可以类比成dubbo和Java的SpringCloud

快速引入

本篇文章采用的协议为thrift

kitex工具下载

这个工具主要用来根据idl定义来自动生成代码,提高开发效率

go install github.com/cloudwego/kitex/tool/cmd/kitex@latest

idl定义

namespace go user// 用户注册请求
struct RegisterRequest {1: required string phone;       // 手机号2: required string verifyCode;  // 验证码
}// 用户注册响应
struct RegisterResponse {1: required bool success;       // 是否成功2: optional string message;     // 错误信息3: optional i64 userId;         // 用户ID4: optional string username;    // 自动生成的用户名
}// 用户登录请求
struct LoginRequest {1: required string phone;       // 手机号2: required string verifyCode;  // 验证码
}// 用户登录响应
struct LoginResponse {1: required bool success;       // 是否成功2: optional string message;     // 错误信息3: optional i64 userId;         // 用户ID4: optional string token;       // 登录令牌5: optional UserInfo userInfo;  // 用户信息
}// 用户基本信息
struct UserInfo {1: required i64 userId;         // 用户ID2: required string username;    // 用户名3: optional string avatar;      // 头像URL4: optional string signature;   // 个性签名5: optional i32 followCount;    // 关注数6: optional i32 followerCount;  // 粉丝数7: optional bool isFollow;      // 当前用户是否关注了该用户
}// 获取用户信息请求
struct GetUserInfoRequest {1: required i64 userId;         // 要查询的用户ID2: optional i64 currentUserId;  // 当前登录的用户ID
}// 获取用户信息响应
struct GetUserInfoResponse {1: required bool success;       // 是否成功2: optional string message;     // 错误信息3: optional UserInfo userInfo;  // 用户信息
}// 发送验证码请求
struct SendVerifyCodeRequest {1: required string phone;       // 手机号2: required i32 codeType;       // 验证码类型: 1-注册,2-登录,3-重置密码
}// 发送验证码响应
struct SendVerifyCodeResponse {1: required bool success;       // 是否成功2: optional string message;     // 错误信息
}// 用户服务接口定义
service UserService {// 用户注册RegisterResponse Register(1: RegisterRequest req);// 用户登录LoginResponse Login(1: LoginRequest req);// 获取用户信息GetUserInfoResponse GetUserInfo(1: GetUserInfoRequest req);// 发送验证码SendVerifyCodeResponse SendVerifyCode(1: SendVerifyCodeRequest req);
} 

这里定义了很多接口,包括用户请求参数结构定义,请求返回参数定义

自动生成代码

执行kitex指令

kitex -module video_douyin idl/user.thrift

返回

代码成功生成

user.go: 根据 IDL 生成的编解码文件,由 IDL 编译器生成

k-consts.go、k-user.go:kitex 专用的一些拓展内容

userservice:kitex 封装代码主要在这里

main.go入口函数定义

package mainimport ("log"user "video_douyin/kitex_gen/user/userservice""video_douyin/pkg/db"
)func main() {// 初始化MySQLif err := db.InitMySQL("root:901project@tcp(127.0.0.1:3306)/kanyuServer?charset=utf8mb4&parseTime=True&loc=Local"); err != nil {log.Fatalf("MySQL初始化失败: %v", err)}// 初始化Redisif err := db.InitRedis("127.0.0.1:6379", 0); err != nil {log.Fatalf("Redis初始化失败: %v", err)}// 创建UserServiceImpl实例并设置db和redisimpl := &UserServiceImpl{db:    db.DB,redis: db.Redis,}// 创建serversvr := user.NewServer(impl)// 运行服务if err := svr.Run(); err != nil {log.Fatalf("服务运行失败: %v", err)}
}

这里处理的作用:

1,初始化mysql

2,初始化redis

3,新建rpc服务实例注入db和redis

4,新建server

5,运行服务

依赖配置初始化db和redis

package dbimport ("context""fmt""sync""video_douyin/dal/model""github.com/go-redis/redis/v8""gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/schema"
)var (DB        *gorm.DBRedis     *redis.ClientredisOnce sync.Once
)func InitMySQL(dsn string) error {var err errorDB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true,// 禁用表名复数化NamingStrategy: schema.NamingStrategy{SingularTable: true,},})if err != nil {return err}// 自动迁移if err := DB.AutoMigrate(&model.User{}); err != nil {return err}return nil
}func InitRedis(addr string, db int) error {var initErr errorredisOnce.Do(func() {Redis = redis.NewClient(&redis.Options{Addr: addr,DB:   db,})if _, err := Redis.Ping(context.Background()).Result(); err != nil {initErr = fmt.Errorf("redis连接失败: %w", err)}})return initErr
}

model定义

这里实现了注册接口,所以定义user_db表

package modelimport ("gorm.io/gorm"
)type User struct {gorm.ModelPhone    string `gorm:"unique"`Password stringUsername stringAvatar   string
}// TableName 实现接口返回自定义表名
func (User) TableName() string {return "user_db" // 指定实际表名
}

接口核心服务逻辑

package mainimport ("context""crypto/rand""errors""fmt""log""math/big""time""video_douyin/dal/model"user "video_douyin/kitex_gen/user""github.com/go-redis/redis/v8""gorm.io/gorm"
)// UserServiceImpl implements the last service interface defined in the IDL.
type UserServiceImpl struct {db    *gorm.DBredis *redis.Client
}const (verifyCodePrefix = "verify_code:"tokenPrefix      = "user_token:"codeExpiration   = 5 * time.MinutetokenExpiration  = 24 * time.Hour
)// Register implements the UserServiceImpl interface
func (s *UserServiceImpl) Register(ctx context.Context, req *user.RegisterRequest) (resp *user.RegisterResponse, err error) {resp = user.NewRegisterResponse()if resp == nil {return nil, errors.New("failed to initialize response")}// 1. Validate the request parameterslog.Printf("进入rpc: %v\n", req.GetPhone())if req.GetPhone() == "" || req.GetVerifyCode() == "" {msg := "参数为空错误" // 改为包级变量或堆分配resp.SetMessage(&msg)resp.SetSuccess(false)if resp.Message != nil {log.Printf("错误详情: %s\n", *resp.Message)}return}// 2. 验证码校验storedCode, err := s.redis.Get(ctx, verifyCodePrefix+req.GetPhone()).Result()if err != nil || storedCode != req.GetVerifyCode() {msg := "验证码错误或已过期"resp.SetMessage(&msg)resp.SetSuccess(false)if resp.Message != nil {log.Printf("错误详情: %s\n", *resp.Message)}return resp, nil}// 3. 检查用户是否存在 存在则进入注册网页var existingUser model.Userif err := s.db.Where("phone = ?", req.GetPhone()).First(&existingUser).Error; err == nil {errorMsg := "用户已存在"resp.SetMessage(&errorMsg)resp.SetSuccess(false)if resp.Message != nil {log.Printf("错误详情: %s\n", *resp.Message)}return resp, nil}// 4. 创建用户newUser := model.User{Phone:    req.GetPhone(),Username: fmt.Sprintf("用户%s", req.GetPhone()[:4]),}if err := s.db.Create(&newUser).Error; err != nil {errorMsg := "注册失败"resp.SetMessage(&errorMsg)resp.SetSuccess(false)if resp.Message != nil {log.Printf("错误详情: %s\n", *resp.Message)}return resp, nil}// 5. 生成tokentoken, err := generateToken()if err != nil {errorMsg := "系统错误"resp.SetMessage(&errorMsg)resp.SetSuccess(false)if resp.Message != nil {log.Printf("错误详情: %s\n", *resp.Message)}return resp, nil}// 6. 存储tokenif err := s.redis.Set(ctx, tokenPrefix+token, newUser.ID, tokenExpiration).Err(); err != nil {errorMsg := "系统错误"resp.SetMessage(&errorMsg)resp.SetSuccess(false)if resp.Message != nil {log.Printf("错误详情: %s\n", *resp.Message)}return resp, nil}successMsg := "注册成功"resp.SetMessage(&successMsg)resp.SetSuccess(true)// resp.SetToken(token)// 需改下idlreturn resp, nil
}// Login implements the UserServiceImpl interface.
func (s *UserServiceImpl) Login(ctx context.Context, req *user.LoginRequest) (resp *user.LoginResponse, err error) {resp = user.NewLoginResponse()// 1. 校验参数异常if req.GetPhone() == "" || req.GetVerifyCode() == "" {errorMsg := "Invalid request parameters" // 声明字符串变量resp.SetMessage(&errorMsg)               // 传递指针resp.SetSuccess(true)}//2,验证码校验// 2. 验证码校验storedCode, err := s.redis.Get(ctx, verifyCodePrefix+req.GetPhone()).Result()if err != nil || storedCode != req.GetVerifyCode() {errorMsg := "验证码错误或已过期"resp.SetMessage(&errorMsg)resp.SetSuccess(false)return resp, nil}// 3. 查询用户是否存在var existingUser model.Userif err := s.db.Where("phone = ?", req.GetPhone()).First(&existingUser).Error; err != nil {errorMsg := "用户不存在"resp.SetMessage(&errorMsg)resp.SetSuccess(false)return resp, nil}// 4. 生成tokentoken, err := generateToken()if err != nil {errorMsg := "系统错误"resp.SetMessage(&errorMsg)resp.SetSuccess(false)return resp, nil}// 5. 存储tokenif err := s.redis.Set(ctx, tokenPrefix+token, existingUser.ID, tokenExpiration).Err(); err != nil {errorMsg := "系统错误"resp.SetMessage(&errorMsg)resp.SetSuccess(false)return resp, nil}successMsg := "登录成功"resp.SetMessage(&successMsg)resp.SetSuccess(true)// resp.SetToken(token) 需加上返回给前端return resp, nil
}// GetUserInfo implements the UserServiceImpl interface.
func (s *UserServiceImpl) GetUserInfo(ctx context.Context, req *user.GetUserInfoRequest) (resp *user.GetUserInfoResponse, err error) {resp = user.NewGetUserInfoResponse()// 1. 参数校验if req.GetUserId() == 0 {errorMsg := "Invalid user id"resp.SetMessage(&errorMsg)resp.SetSuccess(false)return resp, nil}// 2. 查询用户信息var userModel model.Userif err := s.db.Where("id = ?", req.GetUserId()).First(&userModel).Error; err != nil {if errors.Is(err, gorm.ErrRecordNotFound) {errorMsg := "User not found"resp.SetMessage(&errorMsg)resp.SetSuccess(false)return resp, nil}errorMsg := "Database error"resp.SetMessage(&errorMsg)resp.SetSuccess(false)return resp, nil}// 3. 构造UserInfo结构体userInfo := &user.UserInfo{UserId:   userModel.ID,Username: userModel.Username,Avatar:   &userModel.Avatar,}// 4. 设置响应resp.SetSuccess(true)resp.SetUserInfo(userInfo)return resp, nil
}// SendVerifyCode implements the UserServiceImpl interface.
func (s *UserServiceImpl) SendVerifyCode(ctx context.Context, req *user.SendVerifyCodeRequest) (resp *user.SendVerifyCodeResponse, err error) {resp = user.NewSendVerifyCodeResponse()phone := req.GetPhone()// 1. 校验参数异常if phone == "" {errorMsg := "Invalid request parameters" // 声明字符串变量resp.SetMessage(&errorMsg)               // 传递指针resp.SetSuccess(false)}//2,查询db看用户手机号是否存在CheckPhoneExists(s.db, phone)//3, 生成验证码code, _ := generateCaptcha()log.Printf("code: %s", code) // 记录请求参数//4. 保存验证码到redisif err := s.saveCodeToRedis(phone, code); err != nil {errorMsg := "Failed to save verification code"resp.SetMessage(&errorMsg)resp.SetSuccess(false)return resp, nil}//5. 发送验证码successMsg := "success"      // 声明字符串变量resp.SetMessage(&successMsg) // 传递指针resp.SetSuccess(true)return resp, nil
}func generateCaptcha() (string, error) {// 生成一个0到999999之间的随机数(6位数)n, err := rand.Int(rand.Reader, big.NewInt(1000000)) // 1000000是10^6,即最大值+1if err != nil {return "", err}// 将随机数转换为字符串,并确保长度为6位(不足时前面补0)captcha := fmt.Sprintf("%06d", n) // %06d确保至少6位数字,不足时前面补0return captcha, nil
}// 生成token
func generateToken() (string, error) {b := make([]byte, 32)if _, err := rand.Read(b); err != nil {return "", err}return fmt.Sprintf("%x", b), nil
}// CheckPhoneExists 检查手机号是否已存在
func CheckPhoneExists(db *gorm.DB, phone string) (bool, error) {var user = &model.User{}result := db.Where("phone = ?", phone).First(user)if result.Error != nil {if result.Error == gorm.ErrRecordNotFound {return false, nil // 手机号不存在}return false, result.Error // 查询出错}return true, nil // 手机号已存在
}// 存入redis
func (s *UserServiceImpl) saveCodeToRedis(phone, code string) error {key := verifyCodePrefix + phonereturn s.redis.Set(context.Background(), key, code, codeExpiration).Err()
}

导入依赖

go mod tidy

服务启动与测试

rpc服务启动

http服务启动

请求

返回

看db存储

源码

GitHub - enjoykanyu/video_feed: 仿抖音后端项目 springcloud+springboot+mysql+redis+rabiitmq

觉得不错的话可以帮点个star呗,感谢

参考

Hertz | CloudWeGo

Kitex | CloudWeGo

相关文章:

  • 揭开C语言指针的神秘面纱:地址、变量与“指向”的力量
  • 【Python 元祖】 Tuple 核心知识点
  • QT单例模式简单讲解与实现
  • Redis哨兵模式,CLUSTERDOWN Hash slot not server 解决
  • 整数反转(7)
  • 《1.1_3_2 电路交换、报文交换、分组交换的性能分析|精讲篇》
  • 性能优化关键:link、script和meta的正确打开方式
  • 网络基础学习
  • 【Linux网络】UDP套接字【实现英汉转化】
  • 探索容器技术:Docker与Kubernetes的实践指南
  • ​​IIS文件上传漏洞绕过:深入解析与高效防御​
  • 关于PHP的详细介绍,结合其核心特点、应用场景及2025年的技术发展趋势,以清晰的结构呈现:
  • TCP 的三次握手
  • 构造题(Constructive Problem)
  • 历年福州大学保研上机真题
  • 【论文阅读】KIMI-VL TECHNICAL REPORT
  • C语言中的寄存器:理解与应用
  • 2025年渗透测试面试题总结-匿名[实习]安全工程师(大厂) (2)(题目+回答)
  • OpenGL Chan视频学习-6 How Shaders Work in OpenGL
  • JVM——JNI 的运行机制
  • 网站被抄袭怎么办/下载百度 安装
  • 如何在自己的网站上做友情链接/seo搜索引擎优化推广
  • 做商城网站在哪里注册营业执照/如何优化搜索引擎
  • 毕业设计做旅游网站/黑帽seo什么意思
  • 网站建设后台管理/百度关键词搜索趋势
  • 城市建设最好的网站/如何做网站优化seo