目录
api的语法:
首先定义一个api文档
type:用于定义请求/响应体
type LoginRequest {
Username string `json:"username"`
Password string `json:"password"`
}
type UserInfoResponse {
UserId uint `json:"userId"`
Username string `json:"username"`
}
其中LoginRequest表示登录的请求体,要求包含string类型的username和password
语法类似于go的type struct
也支持写在一起
type(
LoginRequest {
Username string `json:"username"`
Password string `json:"password"`
}
UserInfoResponse {
UserId uint `json:"userId"`
Username string `json:"username"`
}
)
效果是一样的
service:定义HTTP服务
service users {
@handler login
post /login (LoginRequest) returns (string)
}
service users表示定义了一个名称为users的微服务,最后的入口文件名称也会是users.go
@handler 用于对生成的文件和方法命名
post /login (LoginRequest) returns (string)
- post:表示这是一个post请求
- /login:表示请求路由
- (Request) returns (Response) 表示根据Request请求体,返回响应体Response,样例中的就是根据LoginRequest登录请求体返回一个字符串,当然这个后面也是可以自定义的,也可以不需要请求体,写法就是这样post /login returns (string)
@server:控制生成HTTP服务时候的meta信息
@server (
prefix: /api/users
jwt: Auth
)
比如说prefix: /api/users 对该定义后面的services都加上路由前缀/api/users
jwt: Auth用于jwt鉴权(目前自己测试使用只成功了token的创建,token解析还没有成功)
目前支持的功能
- 路由分组
- 中间件声明
- 路由前缀
- 超时配置
- jwt 鉴权开关
如果需要对多个服务设置不同的meta信息
则可以这样写
type(
LoginRequest {
Username string `json:"username"`
Password string `json:"password"`
}
UserInfoResponse {
UserId uint `json:"userId"`
Username string `json:"username"`
}
)
@server (
prefix: /api/users
)
service users {
@handler login
post /login (LoginRequest) returns (string)
}
@server (
prefix: /api/users
jwt: Auth
)
service users {
@handler userInfo
get /info returns (UserInfoResponse)
}
//goctl api go -api user.api -dir .
第一个server管理login服务
第二个server管理userInfo服务
根据api文档生成最小HTTP服务
生成代码:
goctl api go -api user.api -dir .
goctl为关键字。api go,表示根据api文档生成go文档,-api用于指定api文件的位置这里的位置为(当前目录下的user.api文件,-dir用于指定go文档的生成位置,"."表示在当前目录下生成
目录结构
最终会在指定位置生成这样一个目录结构:
从上到下一个一个解释的话:
-
etc/user.yaml:用来保存基础的配置文件服务名称,主机地址,以及端口号
-
internal/config/config.go:主要用于加载保存user.yaml中的的数据以供使用
-
hander/loginhandler.go:用于处理login请求,过程大概就是下面这样的
func loginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//提取请求中保存的请求体,如果没有相应的请求体数据,则返回错误信息
var req types.LoginRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}//调用请求处理函数,返回响应结果 l := logic.NewLoginLogic(r.Context(), svcCtx) resp, err := l.Login(&req) //根据响应结果返回响应数据 if err != nil { httpx.ErrorCtx(r.Context(), w, err) } else { httpx.OkJsonCtx(r.Context(), w, resp) } }
}
-
handler/router.go:主要保存路由信息
-
logic/loginlogic.go和logic/userinfologic:数据处理函数,供handler中的方法调用
-
svc/servicontext.go:返回一个携带配置信息的上下文
-
types/types.go:用于存放实体类
-
user.go:程序入口
整体的一个运行流程差不多为:
从user.go开始读取users.yaml配置文件,并将内保存到config.go文件中
根据handler/routers.go中的路由加载路由配置,然后启动服务
接受到对应的请求后就会调用handler中对应的方法,handler会调用logic的具体处理业务,处理完成后返回处理结果,并响应数据
因此对数据的一些特殊处理也都可以在handler中实现
api响应封装
将所有的响应都封装为一个统一的格式
func Response(r *http.Request, w http.ResponseWriter, res any, err error) {
body := Body{}
if err != nil {
body = Body{
Code: 10086,
Data: nil,
Msg: "请求错误",
}
} else {
body = Body{
Code: 10086,
Data: res,
Msg: "请求成功",
}
}
//func WriteJson(w http.ResponseWriter, code int, v any)
//将数据v写入到响应体response中
httpx.WriteJson(w, http.StatusOK, body)
}
就可以自己写好一个Response方法,然后插入到handler方法中
func loginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//提取请求中保存的请求体,如果没有相应的请求体数据,则返回错误信息
var req types.LoginRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
//调用请求处理函数,返回响应结果
l := logic.NewLoginLogic(r.Context(), svcCtx)
resp, err := l.Login(&req)
response.Response(r, w, resp, err)
//根据响应结果返回响应数据
//if err != nil {
// httpx.ErrorCtx(r.Context(), w, err)
//} else {
// httpx.OkJsonCtx(r.Context(), w, resp)
//}
}
}
唯一麻烦的就是需要一个一个去加
官方本身也提供了一个这样的封装
type (
Response {
Code int `json:"code"`
Msg string `json:"msg"`
}
)
type (
UserInfo {
UserId uint `json:"userId"`
Username string `json:"username"`
}
)
type (
UserInfoResponse {
Response
Data UserInfo `json:"data"`
}
)
写的结构和最终生成的实体类的结构也是一样的。