我们通过一个系列文章跟大家详细展示一个 go-zero
微服务实例,整个系列分十三篇文章,目录结构如下:
- go-zero 实战 - 服务划分与项目创建
- go-zero 实战 - User API Gateway
- go-zero 实战 - User Login
- go-zero 实战 - User Register
- go-zero 实战 - User Userinfo
- go-zero 实战 - Food API Gateway
- go-zero 实战 - Food Search
- go-zero 实战 - Food AddFood
- go-zero 实战 - Food DeleteFood
- go-zero 实战 - Food Foodlist
- go-zero 实战进阶 - rpc 服务
- go-zero 实战进阶 - 用户管理 rpc 服务
- go-zero 实战进阶 - 食材管理 rpc 服务
期望通过本系列文章带你在本地利用 go-zero
快速开发一个《食谱指南》系统,让你快速上手微服务。
在上一篇文章中,我们介绍了如何搭建注册服务,本篇我们将介绍如何搭建获取用户信息服务。
做 iOS
或 Android
开发的小伙伴们应该都知道,userinfo
接口与 login
或 register
接口不同,此类接口一般都会要求传入 userId
和 token
来请求跟该用户相关的信息,我们一般称这个过程为 鉴权
,接下来的服务实现也同样设计如此。
API Gateway
增加 Jwt
鉴权
$vim usermanage/api/user.api
syntax = "v1"
info (
title: "UserApi"
desc: "用户服务相关 API"
author: "DESKTOP-4T5UKHP/Owner"
email: "renpanpan1990@163.com"
)
type (
LoginRequest {
Email string `json:"email"`
Password string `json:"password"`
}
LoginResponse {
UserReply
}
)
type (
RegisterRequest {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
}
RegisterResponse {
UserReply
}
)
type UserInfoResponse {
UserReply
}
type UserReply {
Id int64 `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
JwtToken
}
type JwtToken {
AccessToken string `json:"accessToken,omitempty"`
AccessExpire int64 `json:"accessExpire,omitempty"`
RefreshAfter int64 `json:"refreshAfter,omitempty"`
}
service user-api {
@handler Login
post /users/login (LoginRequest) returns (LoginResponse)
@handler Register
post /users/register (RegisterRequest) returns(RegisterResponse)
}
@server (
jwt: Auth
)
service user-api {
@handler UserInfo
post /users/userinfo returns(UserInfoResponse)
}
由于原来我们生成过 user-api
服务,userinfohandle.go
和 userinfologic.go
文件不符合我们现在的需求,需要删除掉这两个文件,重新生成 user-api
服务。
$ goctl api go -api user.api -dir .
我们再来查看 usermanage/api/internal/handler/routers.go
文件。 发现 users/userinfo
接口与 user/login
和 user/register
接口分开处理了,并且增加了 rest.WithJwt(serverCtx.Config.Auth.AccessSecret)
鉴权处理。
// Code generated by goctl. DO NOT EDIT.
package handler
import (
"net/http"
"FoodGuides/service/usermanage/api/internal/svc"
"github.com/zeromicro/go-zero/rest"
)
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodPost,
Path: "/users/login",
Handler: LoginHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/users/register",
Handler: RegisterHandler(serverCtx),
},
},
)
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodPost,
Path: "/users/userinfo",
Handler: UserInfoHandler(serverCtx),
},
},
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
)
}
添加用户信息逻辑
$ vim usermanage/api/internal/logic/userinfologic.go
package logic
import (
"FoodGuides/service/usermanage/api/internal/svc"
"FoodGuides/service/usermanage/api/internal/types"
"FoodGuides/service/usermanage/model"
"context"
"encoding/json"
"errors"
"github.com/zeromicro/go-zero/core/logx"
)
type UserInfoLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserInfoLogic {
return &UserInfoLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UserInfoLogic) UserInfo() (*types.UserInfoResponse, error) {
// 通过 l.ctx.Value("uid") 可获取 jwt 载体中 `uid` 信息
uid, _ := l.ctx.Value("uid").(json.Number).Int64()
// 查询用户是否存在
user, err := l.svcCtx.UserModel.FindOne(l.ctx, uid)
if err != nil {
if err == model.ErrNotFound {
return nil, errors.New("用户不存在")
}
return nil, err
}
response := types.UserReply{
Id: user.Id,
Username: user.Name,
Email: user.Email,
}
return &types.UserInfoResponse{UserReply: response}, nil
}
修改 Response
返回格式
$ vim usermanage/api/internal/handler/userinfohandler.go
package handler
import (
"FoodGuides/common/responsex"
"net/http"
"FoodGuides/service/usermanage/api/internal/logic"
"FoodGuides/service/usermanage/api/internal/svc"
"github.com/zeromicro/go-zero/rest/httpx"
)
func UserInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := logic.NewUserInfoLogic(r.Context(), svcCtx)
resp, err := l.UserInfo()
if err != nil {
httpx.OkJson(w, responsex.FailureResponse(nil, err.Error(), 1000))
} else {
httpx.OkJson(w, responsex.SuccessResponse(resp, "获取成功"))
}
}
}
启动服务
启动 user api
服务, 运行成功后,user api
则运行在本机的 8888
端口
➜ FoodGuides:
$ go run usermanage/api/user.go -f usermanage/api/etc/user-api.yaml
Starting server at 0.0.0.0:8888...
我们用 Postman
尝试请求用户信息接口:
- 我们先测试不携带
Authorization
的情况。其请求结果显示未授权(401 Unauthorized),如下图所示。
- 我们再测试携带
Authorization
后的情况。在Postman
的Authorization
选项中选择Bearer Token
,填写登录成功后Api
返回的accessToken
字段值。其请求结果如下图所示,说明用户信息服务运行正常。
注意:因为 jwt 载体中存在 uid
信息,所以只要 Authorization
不变,请求到的结果永远都是同一用户的信息。如果想请求其他用户信息,必须调用登录或注册接口,将返回的 accessToken
字段值设置到 Postman
的 Bearer Token
中,重新发起用户信息的请求。
至此,我们就完成了有关用户三个相关接口的搭建(登录,注册,用户信息)。从下一篇开始,我们将围绕 Food
开发相关接口,并与 user
关联起来。