微服务 library-user-service ,用户管理服务。提供用户管理的 Restful 接口,主要实现用户注册、根据用户ID或者 email 查询用户、查询用户所借书籍等功能。
完整代码:
https://github.com/Justin02180218/micro-kit
包结构说明
-
dao:数据访问层
-
dto:数据传输层
-
models:数据库表映射层
-
service:业务逻辑层
-
endpoint:go-kit 的概念,将服务提供的每一个方法封装成一个 endpoint。
-
transport:go-kit 的概念,协议传输层,支持 grpc,thrift,http 等协议,这里与gin web框架结合使用。
-
user.yaml:服务使用的配置文件
-
main.go:服务启动程序
代码实现
数据库表
首先在 library 数据库建立 user 表
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT '',
`password` varchar(255) DEFAULT '',
`email` varchar(255) DEFAULT '',
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
models层
对应在 models 层创建 user.go,定义与表 user 对应的 User struct:
type User struct {
ID uint64 `gorm:"primary_key" json:"id" form:"id"`
CreatedAt time.Time `form:"created_at" json:"created_at"`
UpdatedAt time.Time `form:"updated_at" json:"updated_at"`
Username string
Password string
Email string
}
dao层
在 dao 层创建与数据库交互的 user_dao.go
定义 UserDao 接口及实现:
type UserDao interface {
SelectByID(id uint64) (*models.User, error)
SelectByEmail(email string) (*models.User, error)
Save(user *models.User) error
}
type UserDaoImpl struct{}
func NewUserDaoImpl() UserDao {
return &UserDaoImpl{}
}
-
SelectByID:根据用户ID查询用户信息
-
SelectByEail:根据用户email查询用户信息
-
Save:保存用户信息
UserDao 接口的函数实现:
func (u *UserDaoImpl) SelectByID(id uint64) (*models.User, error) {
user := &models.User{}
err := databases.DB.Where("id = ?", id).First(user).Error
if err != nil {
return nil, err
}
return user, nil
}
func (u *UserDaoImpl) SelectByEmail(email string) (*models.User, error) {
user := &models.User{}
err := databases.DB.Where("email = ?", email).First(user).Error
if err != nil {
return nil, err
}
return user, nil
}
func (u *UserDaoImpl) Save(user *models.User) error {
return databases.DB.Create(user).Error
}
dto层
在 dto 层的 user_dto.go 中创建用于数据传输的struct UserInfo 和 RegisterUser:
type UserInfo struct {
ID uint64 `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
}
type RegisterUser struct {
Username string
Password string
Email string
}
service层
在 service 层创建 user_service.go
定义 UserService 接口及实现:
type UserService interface {
Register(ctx context.Context, vo *dto.RegisterUser) (*dto.UserInfo, error)
FindByID(ctx context.Context, id uint64) (*dto.UserInfo, error)
FindByEmail(ctx context.Context, email string) (*dto.UserInfo, error)
}
type UserServiceImpl struct {
userDao dao.UserDao
}
func NewUserServiceImpl(userDao dao.UserDao) UserService {
return &UserServiceImpl{
userDao: userDao,
}
}
UserService 接口的函数实现:
func (u *UserServiceImpl) Register(ctx context.Context, vo *dto.RegisterUser) (*dto.UserInfo, error) {
user, err := u.userDao.SelectByEmail(vo.Email)
if user != nil {
log.Println("User is already exist!")
return &dto.UserInfo{}, ErrUserExisted
}
if err == gorm.ErrRecordNotFound || err == nil {
newUser := &models.User{
Username: vo.Username,
Password: vo.Password,
Email: vo.Email,
}
err = u.userDao.Save(newUser)
if err != nil {
return nil, ErrRegistering
}
return &dto.UserInfo{
ID: newUser.ID,
Username: newUser.Username,
Email: newUser.Email,
}, nil
}
return nil, err
}
func (u *UserServiceImpl) FindByID(ctx context.Context, id uint64) (*dto.UserInfo, error) {
user, err := u.userDao.SelectByID(id)
if err != nil {
return nil, ErrNotFound
}
return &dto.UserInfo{
ID: user.ID,
Username: user.Username,
Email: user.Email,
}, nil
}
func (u *UserServiceImpl) FindByEmail(ctx context.Context, email string) (*dto.UserInfo, error) {
user, err := u.userDao.SelectByEmail(email)
if err != nil {
return nil, ErrNotFound
}
return &dto.UserInfo{
ID: user.ID,
Username: user.Username,
Email: user.Email,
}, nil
}
endpoint层
在 endpoint 层创建 user_endpoint.go,
定义 UserEndpoints struct,每一个请求对应一个endpoint。
type UserEndpoints struct {
RegisterEndpoint endpoint.Endpoint
FindByIDEndpoint endpoint.Endpoint
FindByEmailEndpoint endpoint.Endpoint
}
创建各endpoint
func MakeRegisterEndpoint(svc service.UserService) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(*dto.RegisterUser)
user, err := svc.Register(ctx, req)
if err != nil {
return nil, err
}
return user, nil
}
}
func MakeFindByIDEndpoint(svc service.UserService) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
id, _ := strconv.ParseUint(request.(string), 10, 64)
user, err := svc.FindByID(ctx, id)
if err != nil {
return nil, err
}
return user, nil
}
}
func MakeFindByEmailEndpoint(svc service.UserService) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
email := request.(string)
user, err := svc.FindByEmail(ctx, email)
if err != nil {
return nil, err
}
return user, nil
}
}
transport层
请求转发我们采用 gin web 框架与 go-kit transport 相结合的方式,因此在 http_util.go 中定 义 Router ,返回 gin.Engine
func NewRouter(mode string) *gin.Engine {
gin.SetMode(mode)
r := gin.New()
r.Use(gin.Recovery())
return r
}
在 user_transport.go 中定义 NewHttpHandler 返回 gin.Engine,gin 的请求处理 handler 由 go-kit 的http.NewServer 处理。
func NewHttpHandler(ctx context.Context, endpoints *endpoint.UserEndpoints) *gin.Engine {
r := utils.NewRouter(ctx.Value("ginMod").(string))
e := r.Group("/api/v1")
{
e.POST("register", func(c *gin.Context) {
kithttp.NewServer(
endpoints.RegisterEndpoint,
decodeRegisterRequest,
utils.EncodeJsonResponse,
).ServeHTTP(c.Writer, c.Request)
})
e.GET("findByID", func(c *gin.Context) {
kithttp.NewServer(
endpoints.FindByIDEndpoint,
decodeFindByIDRequest,
utils.EncodeJsonResponse,
).ServeHTTP(c.Writer, c.Request)
})
e.GET("findByEmail", func(c *gin.Context) {
kithttp.NewServer(
endpoints.FindByEmailEndpoint,
decodeFindByEmailRequest,
utils.EncodeJsonResponse,
).ServeHTTP(c.Writer, c.Request)
})
}
return r
}
启动服务
补全 main.go
var confFile = flag.String("f", "user.yaml", "user config file")
func main() {
flag.Parse()
err := configs.Init(*confFile)
if err != nil {
panic(err)
}
err = databases.InitMySql(configs.Conf.MySQLConfig)
if err != nil {
fmt.Println("load mysql failed")
}
ctx := context.Background()
userDao := dao.NewUserDaoImpl()
userService := service.NewUserServiceImpl(userDao)
userEndpoints := &endpoint.UserEndpoints{
RegisterEndpoint: endpoint.MakeRegisterEndpoint(userService),
FindByIDEndpoint: endpoint.MakeFindByIDEndpoint(userService),
FindByEmailEndpoint: endpoint.MakeFindByEmailEndpoint(userService),
}
ctx = context.WithValue(ctx, "ginMod", configs.Conf.ServerConfig.Mode)
r := transport.NewHttpHandler(ctx, userEndpoints)
errChan := make(chan error)
go func() {
errChan <- r.Run(fmt.Sprintf(":%s", strconv.Itoa(configs.Conf.ServerConfig.Port)))
}()
go func() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
errChan <- fmt.Errorf("%s", <-c)
}()
fmt.Println(<-errChan)
}
启动
进入 library-user-service 目录,执行 go run main.go 如图:
服务成功启动,监听10086端口
接口测试
使用postman进行接口测试,在这里进行了 register 的接口测试,结果如图:
测试成功,数据库成功插入一条用户记录
下一篇文章,我们开始编写书籍管理微服务:library-book-service
完整代码:
https://github.com/Justin02180218/micro-kit
更多【分布式专辑】【架构实战专辑】系列文章,请关注公众号