这是我参与「第五届青训营」笔记创作活动的第4天
大项目基础架构图
上次对api部分进行了完善,并成功跑通。今天对user部分进行完善,明确user部分每个文件的技术使用,以下作说明记录:
1. db/init.go
package db
import (
"time"
"user/consts"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/plugin/opentelemetry/logging/logrus"
"gorm.io/plugin/opentelemetry/tracing"
)
var DB *gorm.DB
// Init init DB
func Init() {
var err error
gormlogrus := logger.New(
logrus.NewWriter(),
logger.Config{
SlowThreshold: time.Millisecond,
Colorful: false,
LogLevel: logger.Info,
},
)
DB, err = gorm.Open(mysql.Open(consts.MySQLDefaultDSN),
&gorm.Config{
PrepareStmt: true,
Logger: gormlogrus,
},
)
if err != nil {
panic(err)
}
if err := DB.Use(tracing.NewPlugin()); err != nil {
panic(err)
}
}
该文件实现了建立数据库连接,若连接建立失败会返回错误信息。
2. db/user.go
package db
import (
"context"
"user/consts"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Username string `json:"username"`
Password string `json:"password"`
}
func (u *User) TableName() string {
return consts.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, users []*User) error {
return DB.WithContext(ctx).Create(users).Error
}
// 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("username = ?", userName).Find(&res).Error; err != nil {
return nil, err
}
return res, nil
}
该文件实现了对user数据表的三种操作,分别是根据userID批量获取用户信息,创建新用户,根据username查询用户。
3. service文件夹
这三个文件分别提供了三种不同的服务供api部分调用。当api部分向etcd注册中心申请并获得user实例后,即可调用这三个服务对数据库进行操作,或获得数据库操作的返回值。
(1) check_user.go
package service
import (
"context"
"crypto/md5"
"fmt"
"io"
"user/dal/db"
"user/errno"
"user/kitex_gen/user"
)
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.CheckUserRequest) (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
}
该文件实现了检查用户名和密码服务,用于api完成的用户登录功能时调用。其中,对用户密码使用了MD5加密算法,并与数据库中存储的MD5码进行比对。
(2) create_user.go
package service
import (
"context"
"crypto/md5"
"fmt"
"io"
"user/dal/db"
"user/errno"
"user/kitex_gen/user"
)
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.CreateUserRequest) error {
users, err := db.QueryUser(s.ctx, req.Username)
if err != nil {
return err
}
if len(users) != 0 {
return errno.UserAlreadyExistErr
}
h := md5.New()
if _, err = io.WriteString(h, req.Password); err != nil {
return err
}
password := fmt.Sprintf("%x", h.Sum(nil))
return db.CreateUser(s.ctx, []*db.User{{
Username: req.Username,
Password: password,
}})
}
该文件实现了新用户数据的创建服务,供api部分实现新建用户功能时调用。其中,对用户密码使用了MD5加密算法,后续存储在数据库中也是MD5码的形式。
(3) mget_user.go
package service
import (
"context"
"user/dal/db"
"user/kitex_gen/user"
"user/pack"
)
type MGetUserService struct {
ctx context.Context
}
// NewMGetUserService new MGetUserService
func NewMGetUserService(ctx context.Context) *MGetUserService {
return &MGetUserService{ctx: ctx}
}
// MGetUser multiple get list of user info
func (s *MGetUserService) MGetUser(req *user.MGetUserRequest) ([]*user.User, error) {
modelUsers, err := db.MGetUsers(s.ctx, req.UserIds)
if err != nil {
return nil, err
}
return pack.Users(modelUsers), nil
}
该文件实现了根据userID查询用户信息的操作,供api部分实现用户信息查询功能时调用。若查询成功,则会返回查询到的用户切片;反之,切片为空。
4. handler.go
package main
import (
"context"
"user/errno"
user "user/kitex_gen/user"
"user/pack"
"user/service"
)
// 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.CreateUserRequest) (resp *user.CreateUserResponse, err error) {
// TODO: Your code here...
resp = new(user.CreateUserResponse)
if err = req.IsValid(); err != nil {
resp.BaseResp = pack.BuildBaseResp(errno.ParamErr)
return resp, nil
}
err = service.NewCreateUserService(ctx).CreateUser(req)
if err != nil {
resp.BaseResp = pack.BuildBaseResp(err)
return resp, nil
}
resp.BaseResp = pack.BuildBaseResp(errno.Success)
return resp, nil
}
// CheckUser implements the UserServiceImpl interface.
func (s *UserServiceImpl) CheckUser(ctx context.Context, req *user.CheckUserRequest) (resp *user.CheckUserResponse, err error) {
// TODO: Your code here...
resp = new(user.CheckUserResponse)
if err = req.IsValid(); err != nil {
resp.BaseResp = pack.BuildBaseResp(errno.ParamErr)
return resp, nil
}
uid, err := service.NewCheckUserService(ctx).CheckUser(req)
if err != nil {
resp.BaseResp = pack.BuildBaseResp(err)
return resp, nil
}
resp.UserId = uid
resp.BaseResp = pack.BuildBaseResp(errno.Success)
return resp, nil
}
// MGetUser implements the UserServiceImpl interface.
func (s *UserServiceImpl) MGetUser(ctx context.Context, req *user.MGetUserRequest) (resp *user.MGetUserResponse, err error) {
// TODO: Your code here...
resp = new(user.MGetUserResponse)
if err = req.IsValid(); err != nil {
resp.BaseResp = pack.BuildBaseResp(errno.ParamErr)
return resp, nil
}
users, err := service.NewMGetUserService(ctx).MGetUser(req)
if err != nil {
resp.BaseResp = pack.BuildBaseResp(err)
return resp, nil
}
resp.BaseResp = pack.BuildBaseResp(errno.Success)
resp.Users = users
return resp, nil
}
该文件实现了调用上述服务的三个接口,api部分通过这些接口调用这三个服务。
5. main.go
package main
import (
"log"
"net"
"user/consts"
"user/dal"
user "user/kitex_gen/user/userservice"
"github.com/cloudwego/kitex/pkg/rpcinfo"
"github.com/cloudwego/kitex/server"
etcd "github.com/kitex-contrib/registry-etcd"
)
func Init() {
dal.Init()
}
func main() {
r, err := etcd.NewEtcdRegistry([]string{consts.ETCDAddress})
if err != nil {
panic(err)
}
addr, err := net.ResolveTCPAddr(consts.TCP, consts.UserServiceAddr)
if err != nil {
panic(err)
}
Init()
svr := user.NewServer(new(UserServiceImpl),
server.WithServiceAddr(addr),
server.WithRegistry(r),
server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: consts.UserServiceName}),
)
err = svr.Run()
if err != nil {
log.Println(err.Error())
}
}
该文件是整个user部分的入口文件,实现了向etcd中心注册user服务的功能,并建立TCP连接和数据库连接。创建新的user服务实例供api部分调用。
小结
通过对user部分的深入剖析,基本明确了每个文件的用途和调用结构。对于数据库操作部分,还需要深入学习。另外,部分功能尚未完善,可以说完成了用户注册登录功能,后续需要完善用户信息查询功能。