Kratos 笔记
开始
- 安装wire,参见下面常用包章节
- 使用goland,import google包报红,参考这个解决,settings->protocol buffers->location中加入third_party路径
项目结构相关
- api目录下.proto文件可修改,经过protoc自动转为.pb.go文件
- cmd目录下,wire-gen.go用来做依赖注入,自动生成的
- biz目录下实现业务逻辑
- openapi.yaml可以可视化展示api,借助SwaggerEditor
API定义与生成
api目录下的.proto文件中定义api
1 | //编译生成api |
定义rpc
api定义中路径,:
对应为{}
,例如
1 | // GET /api/profiles/:username |
增删改查例子
1 | rpc GetArticle(GetArticleRequest) returns (SingleArticleReply){ |
message例子
1 | message CreateArticleRequest { |
空message
1 | import "google/protobuf/empty.proto"; |
生成 Service 代码
CLI工具,通过 proto文件,可以直接生成对应的 Service 实现代码模板
1 | kratos proto server api/realworld/v1/realworld.proto -t internal/service |
连接数据库
安装docker
写好docker的yaml文件
1
2
3
4
5
6
7
8version: '3'
services:
rwdb:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: dangerous
ports:
- "3306:3306"启动docker
docker-compose up -d
登录数据库,创建database
密码哈希加密解密
使用bcrypt包
1 | func hashPassword(pwd string) string { |
CORS和自定义http中间件
跨域资源共享, 用到的库是gorilla/handlers
自定义http中间件,实现对一些甚至全部的Http Request统一处理。使用http.Filter()
1 | //http.go |
自定义Error类型
目标:将业务抛出的error序列化后写入Response Body中,并设置HTTP Status Code
自定义Error类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38// 目录errors/errors.go
package errors
import (
"errors"
"fmt"
)
//创建一个新的Error类型,实现Error()方法,即可定制自己的Error
type HTTPError struct {
Errors map[string][]string `json:"errors"`
Code int `json:"-"`
}
func (e *HTTPError) Error() string {
return fmt.Sprintf("HTTPError: %d", e.Code)
}
//业务层使用时调用这个函数,创建Error
func NewHTTPError(code int, field string, detail string) *HTTPError {
return &HTTPError{
Code: code,
Errors: map[string][]string{
field: {detail},
},
}
}
// error实体转换为*HTTPError实体
func FromError(err error) *HTTPError {
if err == nil {
return nil
}
if se := new(HTTPError); errors.As(err, &se) {
return se
}
return &HTTPError{}
}Error序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27// 目录server/encoder.go
package server
import (
"github.com/go-kratos/kratos/v2/transport/http"
"kratos-realworld/internal/errors"
nethttp "net/http"
)
func errorEncoder(w nethttp.ResponseWriter, r *nethttp.Request, err error) {
se := errors.FromError(err)
codec, _ := http.CodecForRequest(r, "Accept")
body, err := codec.Marshal((se))
if err != nil {
w.WriteHeader(500)
return
}
w.Header().Set("Content-Type", "application/"+codec.Name())
if se.Code > 99 && se.Code < 600 {
w.WriteHeader(se.Code)
} else {
w.WriteHeader(500)
}
_, _ = w.Write(body)
}
1 | - http服务中注册 |
- 业务层使用
1
2
3
4
5
6
7func (s *RealWorldService) Login(ctx context.Context, req *pb.LoginRequest) (*pb.UserReply, error) {
if len(req.User.Email) == 0 {
return nil, errors.NewHTTPError(422, "email", "cannot be empty")
}
...
}
biz层
biz层中完成了以下任务:
定义了biz层的实体类型
Xxx
,比如type User struct
;定义了接口
XxxRepo
,里面定义了data层的函数,具体实现在data层写 ;定义了结构体
XxxUseCase
,结构体内是XxxRepo
,biz层的方法都绑定在该结构体上;写
NewXxxUsecase()
函数,依赖注入实现业务逻辑,通过调用data层的函数来操作数据库。
1 | // 目录:biz/user.go |
1 | // 目录:biz/biz.go |
data层
data层完成了以下任务:
连接启动数据库(参考连接数据库章节)
定义了结构体
xxxRepo
,结构体内是操作数据库的类型*Data
,data层的方法都绑定在该结构体上;定义了data层的实体类型
Xxx
,比如type User struct
,字段对应了数据库表中的列名;写
NewXxxRepo()
函数,依赖注入实现biz层中接口定义的函数,操作数据库CRUD
1 | // 目录:data/user.go |
1 | // 目录:data/data.go |
测试
testing
包
函数传入参数testing.T
用来测试
assert
包
1 | assert.NoError()// |
常用包和工具
json转protobuf
wire
依赖注入包
1 | go install github.com/google/wire/cmd/wire@latest` |
在wire.go所在目录运行 wire
即可
通过New()
函数来给结构体赋值,使用var ProviderSet = wire.NewSet(NewFuncName)
即可将该New()
函数加入自动依赖注入。
SwaggerEditor
方便与前端沟通接口
https://editor.swagger.io/
spew
spew.Dump(varName)
打印变量varName,支持打印各种类型,包括struct,map等,方便调试。
bcrypt
哈希加密包,可以用来给密码哈希加密和解密
json
Marshal:将数据编码成json字符串
func Marshal(v interface{}) ([]byte, error)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40type Stu struct {
Name string `json:"name"`
Age int
HIgh bool
sex string
Class *Class `json:"class"`
}
type Class struct {
Name string
Grade int
}
func main() {
//实例化一个数据结构,用于生成json字符串
stu := Stu{
Name: "张三",
Age: 18,
HIgh: true,
sex: "男",
}
//指针变量
cla := new(Class)
cla.Name = "1班"
cla.Grade = 3
stu.Class=cla
//Marshal失败时err!=nil
jsonStu, err := json.Marshal(stu)
if err != nil {
fmt.Println("生成json字符串错误")
}
//jsonStu是[]byte类型,转化成string类型便于查看
fmt.Println(string(jsonStu))
}
//结果
//Unmarshal:将json字符串解码到相应的数据结构
func Unmarshal(data []byte, v interface{}) error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36type StuRead struct {
Name interface{} `json:"name"`
Age interface{}
HIgh interface{}
sex interface{}
Class interface{} `json:"class"`
Test interface{}
}
type Class struct {
Name string
Grade int
}
func main() {
//json字符中的"引号,需用\进行转义,否则编译出错
//json字符串沿用上面的结果,但对key进行了大小的修改,并添加了sex数据
data:="{\"name\":\"张三\",\"Age\":18,\"high\":true,\"sex\":\"男\",\"CLASS\":{\"naME\":\"1班\",\"GradE\":3}}"
str:=[]byte(data)
//1.Unmarshal的第一个参数是json字符串,第二个参数是接受json解析的数据结构。
//第二个参数必须是指针,否则无法接收解析的数据,如stu仍为空对象StuRead{}
//2.可以直接stu:=new(StuRead),此时的stu自身就是指针
stu:=StuRead{}
err:=json.Unmarshal(str,&stu)
//解析失败会报错,如json字符串格式不对,缺"号,缺}等。
if err!=nil{
fmt.Println(err)
}
fmt.Println(stu)
}
//结果
//{张三 18 true <nil> map[naME:1班 GradE:3] <nil>}