抖音项目实现1: user微服务 | 青训营笔记

167 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 10 天

目录导航:
1. 环境配置
2. demouser的实现
3. 运行user RPC Server
4. 参考资料

1.项目配置

技术路线:

环境配置:

网络环境:

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框架,下一章更新

项目地址:github.com/LaiYuShuang…

4.资料

参考了以下资料: