gin + kitex + gorm 抖音项目实现1: user微服务

目录导航:

  1. 环境配置
  2. demouser的实现
  3. 运行user RPC Server
  4. 参考资料
# 1.项目配置
技术路线:
- 接口描述语言:thrift [thrift官方文档](https://thrift.apache.org/docs/idl)
- RPC框架:kitex [kitex说明文档](https://www.cloudwego.io/zh/docs/kitex/getting-started/)
- go web框架:gin [gin官方文档](https://github.com/gin-gonic/gin#quick-start)

环境配置:
- go 1.18:[go下载地址](https://studygolang.com/dl)  注意与kitex的版本兼容性
- Goland: [Goland下载地址](https://www.jetbrains.com/zh-cn/go/) IDE方便编译运行代码
- docker: [安装教程](https://www.runoob.com/docker/windows-docker-install.html) 项目需要etcd服务
- git: [安装教程](https://www.liaoxuefeng.com/wiki/896043488029600/896067074338496) 方便代码管理
- wsl2: [安装教程](https://learn.microsoft.com/zh-cn/windows/wsl/install) window需要,kitex只能在linux内核下使用

网络环境:
- 参考了两篇文章[windows抖音项目的环境配置](https://juejin.cn/post/7197033247283445817)、
[抖音项目Windows下调试环境搭建](https://juejin.cn/post/7096857967747661831)。
- 如果是Linux系统,本地IP查看指令为:`ip addr show`。
# 2.user微服务的实现
## user.thirft
新建项目文件夹douyin,进入目录新建idl目录用来存放整个项目需要的idl文件,创建文本user.thirft:

namespace go user

struct BaseResp {
1:i64 status_code
2:string status_message
}

struct douyin_user_register_request {
1:required string username
2:required string password
}

struct douyin_user_register_response {
1:BaseResp base_resp
2:required i64 user_id
3:required string token
}

struct douyin_user_login_request {
1:required string username
2:required string password
}

struct douyin_user_login_response {
1:BaseResp base_resp
2:required i64 user_id
3:required string token
}

struct douyin_user_request {
1:required i64 user_id
2:required string token
}

struct douyin_user_response {
1:BaseResp base_resp
2:required User user
}

struct User {
1:required i64 id
2:required string name
}

service UserService {
douyin_user_register_response CreateUser(1:douyin_user_register_request req)
douyin_user_login_response CheckUser(1:douyin_user_login_request req)
douyin_user_response QueryCurUser(1:douyin_user_request req)
}

## dao层操作
代码存放在douyin/cmd/user/dal/db/。db文件夹存放数据库初始化文件init.go以及对user进行底层增删改查操作的user.go。user.go如下:

package db

import (
“context”

“douyin/pkg/constants”
“gorm.io/gorm”
)

type User struct {
ID int64 gorm:"column:id;primaryKey;not null"
Username string gorm:"column:u_name;unique;type:varchar(30);not null"
Password string gorm:"column:passwd;type:varchar(60);not null"
}

func (u *User) TableName() string {
return constants.UserTableName
}

// MGetUsers multiple get list of user info
func MGetUsers(ctx context.Context, userIDs []int64) ([]*User, error) {
res := make([]*User, 0)
if len(userIDs) == 0 {
return res, nil
}

if err := DB.WithContext(ctx).Where(“id in ?”, userIDs).Find(&res).Error; err != nil {
return nil, err
}
return res, nil
}

// CreateUser create user info
func CreateUser(ctx context.Context, uname, password string) ([]*User, error) {
users := []*User{{
Username: uname,
Password: password,
}}
res := make([]*User, 0)

err := DB.WithContext(ctx).Create(users).Error
if err != nil {
return nil, err
}

err = DB.WithContext(ctx).Where(“u_name = ?”, uname).Limit(1).Find(&res).Error
if err != nil {
return nil, err
}

return res, nil
}

// QueryUser query list of user info
func QueryUser(ctx context.Context, userName string) ([]*User, error) {
res := make([]*User, 0)
if err := DB.WithContext(ctx).Where(“u_name = ?”, userName).Find(&res).Error; err != nil {
return nil, err
}
return res, nil
}

## kitex生成部分代码
在douyin/cmd/user文件夹下进行kitex代码的生成,kitex只能够在Linux系统下使用,在window需要配合wsl进行生成代码。命令如下:

`kitex -module douyin -service userservise ../../idl/user.thrift`

生成的go.mod文件以及kitex_gen文件夹移至douyin文件夹,方便后续添加其他服务。在handler.go获取参数和返回数据对象,douyin/cmd/user/service层调用dao层的方法返回handler.go对应的接口方法需要的数据对象。

## service层
service层调用dao层的方法返回handler.go对应的接口方法需要的数据对象,各个文件如下:
check_user.go

package service

import (
“context”
“crypto/md5”
“fmt”
“io”

“douyin/pkg/errno”

“douyin/kitex_gen/user”

“douyin/cmd/user/dal/db”
)

type CheckUserService struct {
ctx context.Context
}

// NewCheckUserService new CheckUserService
func NewCheckUserService(ctx context.Context) *CheckUserService {
return &CheckUserService{
ctx: ctx,
}
}

// CheckUser check user info
func (s *CheckUserService) CheckUser(req *user.DouyinUserLoginRequest) (int64, error) {
h := md5.New()
if _, err := io.WriteString(h, req.Password); err != nil {
return 0, err
}
passWord := fmt.Sprintf(“%x”, h.Sum(nil))

userName := req.Username
users, err := db.QueryUser(s.ctx, userName)
if err != nil {
return 0, err
}
if len(users) == 0 {
return 0, errno.AuthorizationFailedErr
}
u := users[0]
if u.Password != passWord {
return 0, errno.AuthorizationFailedErr
}
return int64(u.ID), nil
}

create_user.go

package service

import (
“context”
“crypto/md5”
“fmt”
“io”

"douyin/cmd/user/dal/db"
"douyin/kitex_gen/user"
"douyin/pkg/errno"

)

type CreateUserService struct {
ctx context.Context
}

// NewCreateUserService new CreateUserService
func NewCreateUserService(ctx context.Context) *CreateUserService {
return &CreateUserService{ctx: ctx}
}

// CreateUser create user info.
func (s *CreateUserService) CreateUser(req *user.DouyinUserRegisterRequest) (int64, error) {
users, err := db.QueryUser(s.ctx, req.Username)
if err != nil {
return -1, err
}
if len(users) != 0 {
return -1, errno.UserAlreadyExistErr
}

h := md5.New()
if _, err = io.WriteString(h, req.Password); err != nil {
	return -1, err
}
passWord := fmt.Sprintf("%x", h.Sum(nil))
res, err := db.CreateUser(s.ctx, req.Username, passWord)
if err != nil {
	return -1, err
}
u := res[0]
return u.ID, nil

}

query_user.go

package service

import (
“context”

"douyin/cmd/user/dal/db"
"douyin/cmd/user/pack"
"douyin/kitex_gen/user"

)

type QueryCurUserService struct {
ctx context.Context
}

// NewQueryCurUserService new MGetUserService
func NewQueryCurUserService(ctx context.Context) *QueryCurUserService {
return &QueryCurUserService{ctx: ctx}
}

// MGetUser multiple get list of user info
func (s *QueryCurUserService) MGetUser(req *user.DouyinUserRequest) ([]*user.User, error) {
userIDs := make([]int64, 0)
userIDs[0] = req.UserId
modelUsers, err := db.MGetUsers(s.ctx, userIDs)
if err != nil {
return nil, err
}
return pack.Users(modelUsers), nil
}

## handler.go修改
修改douyin/cmd/user/handler.go就可以了,handler.go如下:

package main

import (
“context”
“douyin/cmd/user/pack”
“douyin/cmd/user/service”
“douyin/kitex_gen/user”
“douyin/pkg/constants”
“douyin/pkg/errno”
“douyin/pkg/middleware”
)

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

// CreateUser implements the UserServiceImpl interface.
func (s *UserServiceImpl) CreateUser(ctx context.Context, req *user.DouyinUserRegisterRequest) (resp *user.DouyinUserRegisterResponse, err error) {
// TODO: Your code here…
resp = new(user.DouyinUserRegisterResponse)

if len(req.Username) == 0 || len(req.Password) == 0 {
	resp.BaseResp = pack.BuildBaseResp(errno.ParamErr)
	resp.UserId = constants.EmptyUserId
	resp.Token = constants.EmptyToken
	return resp, nil
}

uid, err := service.NewCreateUserService(ctx).CreateUser(req)
if err != nil {
	resp.BaseResp = pack.BuildBaseResp(err)
	resp.UserId = constants.EmptyUserId
	resp.Token = constants.EmptyToken
	return resp, nil
}

// token, _ := global.Jwt.CreateToken(userID, global.JWTSetting.AppKey, global.JWTSetting.AppSecret)
token, err := middleware.CreateToken(uid)
if err != nil {
	resp.BaseResp = pack.BuildBaseResp(err)
	resp.UserId = constants.EmptyUserId
	resp.Token = constants.EmptyToken
	return resp, nil
}

resp.BaseResp = pack.BuildBaseResp(errno.Success)
resp.UserId = uid
resp.Token = token
return resp, nil

}

// CheckUser implements the UserServiceImpl interface.
func (s *UserServiceImpl) CheckUser(ctx context.Context, req *user.DouyinUserLoginRequest) (resp *user.DouyinUserLoginResponse, err error) {
// TODO: Your code here…
resp = new(user.DouyinUserLoginResponse)

if len(req.Username) == 0 || len(req.Password) == 0 {
	resp.BaseResp = pack.BuildBaseResp(errno.ParamErr)
	resp.UserId = constants.EmptyUserId
	resp.Token = constants.EmptyToken
	return resp, nil
}

uid, err := service.NewCheckUserService(ctx).CheckUser(req)
if err != nil {
	resp.BaseResp = pack.BuildBaseResp(err)
	resp.UserId = constants.EmptyUserId
	resp.Token = constants.EmptyToken
	return resp, nil
}

// token, _ := global.Jwt.CreateToken(userID, global.JWTSetting.AppKey, global.JWTSetting.AppSecret)
token, err := middleware.CreateToken(uid)
if err != nil {
	resp.BaseResp = pack.BuildBaseResp(err)
	resp.UserId = constants.EmptyUserId
	resp.Token = constants.EmptyToken
	return resp, nil
}

resp.BaseResp = pack.BuildBaseResp(errno.Success)
resp.UserId = uid
resp.Token = token
return resp, nil

}

// QueryCurUser implements the UserServiceImpl interface.
func (s *UserServiceImpl) QueryCurUser(ctx context.Context, req *user.DouyinUserRequest) (resp *user.DouyinUserResponse, err error) {
// TODO: Your code here…
resp = new(user.DouyinUserResponse)

if req.UserId == 0 {
	resp.BaseResp = pack.BuildBaseResp(errno.ParamErr)
	resp.User = nil
	return resp, nil
}

users, err := service.NewQueryCurUserService(ctx).MGetUser(req)
if err != nil {
	resp.BaseResp = pack.BuildBaseResp(err)
	resp.User = nil
	return resp, nil
}
resp.BaseResp = pack.BuildBaseResp(errno.Success)
resp.User = users[0]
return resp, nil

}

## 服务注册
将服务注册到etcd上,修改douyin/cmd/user/main.go:

package main

import (
“douyin/cmd/user/dal”
user “douyin/kitex_gen/user/userservice”
“douyin/pkg/bound”
“douyin/pkg/constants”
“douyin/pkg/middleware”
tracer2 “douyin/pkg/tracer”
“github.com/cloudwego/kitex/pkg/klog”
“github.com/cloudwego/kitex/pkg/limit”
“github.com/cloudwego/kitex/pkg/rpcinfo”
“github.com/cloudwego/kitex/server”
etcd “github.com/kitex-contrib/registry-etcd”
trace “github.com/kitex-contrib/tracer-opentracing”
“net”
)

func Init() {
tracer2.InitJaeger(constants.UserServiceName)
dal.Init()
}

func main() {
r, err := etcd.NewEtcdRegistry([]string{constants.EtcdAddress})
if err != nil {
panic(err)
}
addr, err := net.ResolveTCPAddr(“tcp”, constants.UserServiceAddr)
if err != nil {
panic(err)
}
Init()

svr := user.NewServer(new(UserServiceImpl),
	server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: constants.UserServiceName}), // server name
	server.WithMiddleware(middleware.CommonMiddleware),                                             // middleware
	server.WithMiddleware(middleware.ServerMiddleware),
	server.WithServiceAddr(addr),                                       // address
	server.WithLimit(&limit.Option{MaxConnections: 1000, MaxQPS: 100}), // limit
	server.WithMuxTransport(),                                          // Multiplex
	server.WithSuite(trace.NewDefaultServerSuite()),                    // tracer
	server.WithBoundHandler(bound.NewCpuLimitHandler()),                // BoundHandler
	server.WithRegistry(r),                                             // registry
)
err = svr.Run()
if err != nil {
	klog.Fatal(err)
}

}

# 3. 运行user RPC Server
终端根据docker-compose.yml文件拉取创建docker容器并启动:

sudo docker-compose up

另起一个终端运行user RPC Server:

cd ./cmd/user
sh build.sh
sh output/bootstrap.sh

终端提示server listen at addr=127.0.0.1:8889,已经开启了微服务,等待客户端链接。
之后再完成api层使用gin框架,下一章更新

项目地址:https://github.com/LaiYuShuang/douyin
# 4.资料
参考了以下资料:
-  [Web框架:Gin、FastHttp、Hertz ](https://zhuanlan.zhihu.com/p/588670733)
- [抖音方案说明](https://bytedance.feishu.cn/docs/doccnKrCsU5Iac6eftnFBdsXTof#)
-  [极简抖音APP的使用](https://bytedance.feishu.cn/docs/doccnM9KkBAdyDhg8qaeGlIz7S7)
- [极简版抖音](https://www.apifox.cn/apidoc/shared-09d88f32-0b6c-4157-9d07-a36d32d7a75c/api-50707521)
- [[kitex + gorm-gen + hertz] 快速写出一个基于go的微服务](https://blog.csdn.net/qq_41080854/article/details/128804495)
- [kitex说明文档](https://www.cloudwego.io/zh/docs/kitex/getting-started/)
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值