Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d12e9fa2d7 | ||
|
|
ce5961a7d0 | ||
|
|
e1d942a799 | ||
|
|
754e631dc4 | ||
|
|
72aeac3fa9 | ||
|
|
1c3c8f4bbc | ||
|
|
17e6cfb7a9 | ||
|
|
0d151c17f8 | ||
|
|
52990550fb | ||
|
|
3a9b9ceace | ||
|
|
3128d63134 | ||
|
|
4408767981 | ||
|
|
ff7c14c6b6 | ||
|
|
520f4d7c1b | ||
|
|
0e674933f3 | ||
|
|
1d12f20ff6 | ||
|
|
2b815162f6 | ||
|
|
1602f6ce81 | ||
|
|
c5cd0d32d1 | ||
|
|
1cb17311dd | ||
|
|
e987eb60d3 | ||
|
|
99a863e8be | ||
|
|
5333fb93e5 | ||
|
|
cb13556461 | ||
|
|
561370d5c9 | ||
|
|
7c779d0433 | ||
|
|
6814c86fcd | ||
|
|
a1d2ea9d85 | ||
|
|
4dfbd66323 | ||
|
|
dbf556e7d2 | ||
|
|
c0d0e00803 | ||
|
|
b4aa89fc25 | ||
|
|
11dd3d75ec | ||
|
|
167422ac4f | ||
|
|
a74d73fb2e | ||
|
|
81a9ada2d9 |
@@ -16,7 +16,10 @@ const (
|
||||
minWorkers = 1
|
||||
)
|
||||
|
||||
var ErrCancelWithNil = errors.New("mapreduce cancelled with nil")
|
||||
var (
|
||||
ErrCancelWithNil = errors.New("mapreduce cancelled with nil")
|
||||
ErrReduceNoOutput = errors.New("reduce not writing value")
|
||||
)
|
||||
|
||||
type (
|
||||
GenerateFunc func(source chan<- interface{})
|
||||
@@ -93,7 +96,14 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
|
||||
collector := make(chan interface{}, options.workers)
|
||||
done := syncx.NewDoneChan()
|
||||
writer := newGuardedWriter(output, done.Done())
|
||||
var closeOnce sync.Once
|
||||
var retErr errorx.AtomicError
|
||||
finish := func() {
|
||||
closeOnce.Do(func() {
|
||||
done.Close()
|
||||
close(output)
|
||||
})
|
||||
}
|
||||
cancel := once(func(err error) {
|
||||
if err != nil {
|
||||
retErr.Set(err)
|
||||
@@ -102,14 +112,15 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
|
||||
}
|
||||
|
||||
drain(source)
|
||||
done.Close()
|
||||
close(output)
|
||||
finish()
|
||||
})
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
cancel(fmt.Errorf("%v", r))
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}()
|
||||
reducer(collector, writer, cancel)
|
||||
@@ -122,7 +133,7 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
|
||||
} else if ok {
|
||||
return value, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
return nil, ErrReduceNoOutput
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -378,6 +378,22 @@ func TestMapReduceVoidCancelWithRemains(t *testing.T) {
|
||||
assert.True(t, done.True())
|
||||
}
|
||||
|
||||
func TestMapReduceWithoutReducerWrite(t *testing.T) {
|
||||
uids := []int{1, 2, 3}
|
||||
res, err := MapReduce(func(source chan<- interface{}) {
|
||||
for _, uid := range uids {
|
||||
source <- uid
|
||||
}
|
||||
}, func(item interface{}, writer Writer, cancel func(error)) {
|
||||
writer.Write(item)
|
||||
}, func(pipe <-chan interface{}, writer Writer, cancel func(error)) {
|
||||
drain(pipe)
|
||||
// not calling writer.Write(...), should not panic
|
||||
})
|
||||
assert.Equal(t, ErrReduceNoOutput, err)
|
||||
assert.Nil(t, res)
|
||||
}
|
||||
|
||||
func BenchmarkMapReduce(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
|
||||
617
doc/bookstore-en.md
Normal file
@@ -0,0 +1,617 @@
|
||||
English | [简体中文](bookstore.md)
|
||||
|
||||
# Rapid development of microservices - multiple RPCs
|
||||
|
||||
## 0. Why building microservices are so difficult?
|
||||
|
||||
To build a well working microservice, we need lots of knowledges from different aspects.
|
||||
|
||||
* basic functionalities
|
||||
1. concurrency control and rate limit, to avoid being brought down by unexpected inbound
|
||||
2. service discovery, make sure new or terminated nodes are detected asap
|
||||
3. load balancing, balance the traffic base on the throughput of nodes
|
||||
4. timeout control, avoid the nodes continue to process the timed out requests
|
||||
5. circuit breaker, load shedding, fail fast, protects the failure nodes to recover asap
|
||||
|
||||
* advanced functionalities
|
||||
1. authorization, make sure users can only access their own data
|
||||
2. tracing, to understand the whole system and locate the specific problem quickly
|
||||
3. logging, collects data and helps to backtrace problems
|
||||
4. observability, no metrics, no optimization
|
||||
|
||||
For any point listed above, we need a long article to describe the theory and the implementation. But for us, the developers, it’s very difficult to understand all the concepts and make it happen in our systems. Although, we can use the frameworks that have been well served busy sites. [go-zero](https://github.com/tal-tech/go-zero) is born for this purpose, especially for cloud-native microservice systems.
|
||||
|
||||
As well, we always adhere to the idea that **prefer tools over conventions and documents**. We hope to reduce the boilerplate code as much as possible, and let developers focus on developing the business related code. For this purpose, we developed the tool `goctl`.
|
||||
|
||||
Let’s take the shorturl microservice as a quick example to demonstrate how to quickly create microservices by using [go-zero](https://github.com/tal-tech/go-zero). After finishing this tutorial, you’ll find that it’s so easy to write microservices!
|
||||
|
||||
## 1. What is a bookstore service?
|
||||
|
||||
For simplicity, the bookstore service only contains two functionalities, adding books and quering prices.
|
||||
|
||||
Writting this bookstore service is to demonstrate the complete flow of creating a microservice by using go-zero. But algorithms and detail implementations are quite simplified, and this bookstore service is not suitable for production use.
|
||||
|
||||
## 2. Architecture of shorturl microservice
|
||||
|
||||
<img src="images/bookstore-arch.png" alt="architecture" width="800" />
|
||||
|
||||
## 3. goctl generated code overview
|
||||
|
||||
All modules with green background are generated, and will be enabled when necessary. The modules with red background are handwritten code, which is typically business logic code.
|
||||
|
||||
* API Gateway
|
||||
|
||||
<img src="images/api-gen.png" alt="api" width="800" />
|
||||
|
||||
* RPC
|
||||
|
||||
<img src="images/rpc-gen.png" alt="rpc" width="800" />
|
||||
|
||||
* model
|
||||
|
||||
<img src="images/model-gen.png" alt="model" width="800" />
|
||||
|
||||
And now, let’s walk through the complete flow of quickly create a microservice with go-zero.
|
||||
|
||||
## 4. Get started
|
||||
|
||||
* install etcd, mysql, redis
|
||||
|
||||
* install goctl
|
||||
|
||||
```shell
|
||||
GO111MODULE=on go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
|
||||
* create the working dir bookstore
|
||||
|
||||
* in `bookstore` dir, execute `go mod init bookstore` to initialize `go.mod``
|
||||
|
||||
## 5. Write code for API Gateway
|
||||
|
||||
* use goctl to generate `api/bookstore.api`
|
||||
|
||||
```
|
||||
goctl api -o bookstore.api
|
||||
```
|
||||
|
||||
for simplicity, the leading `info` block is removed, and the code looks like:
|
||||
|
||||
```go
|
||||
type (
|
||||
addReq struct {
|
||||
book string `form:"book"`
|
||||
price int64 `form:"price"`
|
||||
}
|
||||
|
||||
addResp struct {
|
||||
ok bool `json:"ok"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
checkReq struct {
|
||||
book string `form:"book"`
|
||||
}
|
||||
|
||||
checkResp struct {
|
||||
found bool `json:"found"`
|
||||
price int64 `json:"price"`
|
||||
}
|
||||
)
|
||||
|
||||
service bookstore-api {
|
||||
@server(
|
||||
handler: AddHandler
|
||||
)
|
||||
get /add(addReq) returns(addResp)
|
||||
|
||||
@server(
|
||||
handler: CheckHandler
|
||||
)
|
||||
get /check(checkReq) returns(checkResp)
|
||||
}
|
||||
```
|
||||
|
||||
the usage of `type` keyword is the same as that in go, service is used to define get/post/head/delete api requests, described below:
|
||||
|
||||
* `service bookstore-api { defines the service name
|
||||
* `@server` defines the properties that used in server side
|
||||
* `handler` defines the handler name
|
||||
* `get /add(addReq) returns(addResp)` defines this is a GET request, the request parameters, and the response parameters
|
||||
|
||||
* generate the code for API Gateway by using goctl
|
||||
|
||||
```shell
|
||||
goctl api go -api bookstore.api -dir .
|
||||
```
|
||||
|
||||
the generated file structure looks like:
|
||||
|
||||
```
|
||||
api
|
||||
├── bookstore.api // api definition
|
||||
├── bookstore.go // main entrance
|
||||
├── etc
|
||||
│ └── bookstore-api.yaml // configuration file
|
||||
└── internal
|
||||
├── config
|
||||
│ └── config.go // configuration definition
|
||||
├── handler
|
||||
│ ├── addhandler.go // implements addHandler
|
||||
│ ├── checkhandler.go // implements checkHandler
|
||||
│ └── routes.go // routes definition
|
||||
├── logic
|
||||
│ ├── addlogic.go // implements AddLogic
|
||||
│ └── checklogic.go // implements CheckLogic
|
||||
├── svc
|
||||
│ └── servicecontext.go // defines ServiceContext
|
||||
└── types
|
||||
└── types.go // defines request/response
|
||||
```
|
||||
|
||||
* start API Gateway service, listens on port 8888 by default
|
||||
|
||||
```shell
|
||||
go run bookstore.go -f etc/bookstore-api.yaml
|
||||
```
|
||||
|
||||
* test API Gateway service
|
||||
|
||||
```shell
|
||||
curl -i "http://localhost:8888/check?book=go-zero"
|
||||
```
|
||||
|
||||
response like:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Thu, 03 Sep 2020 06:46:18 GMT
|
||||
Content-Length: 25
|
||||
|
||||
{"found":false,"price":0}
|
||||
```
|
||||
|
||||
You can see that the API Gateway service did nothing except returned a zero value. And let’s implement the business logic in rpc service.
|
||||
|
||||
* you can modify `internal/svc/servicecontext.go` to pass dependencies if needed
|
||||
|
||||
* implement logic in package `internal/logic`
|
||||
|
||||
* you can use goctl to generate code for clients base on the .api file
|
||||
|
||||
* till now, the client engineer can work with the api, don’t need to wait for the implementation of server side
|
||||
|
||||
## 6. Write code for add rpc service
|
||||
|
||||
* under directory `rpc/add` create `add.proto` file
|
||||
|
||||
```shell
|
||||
goctl rpc template -o add.proto
|
||||
```
|
||||
|
||||
edit the file and make the code looks like:
|
||||
|
||||
```protobuf
|
||||
syntax = "proto3";
|
||||
|
||||
package add;
|
||||
|
||||
message addReq {
|
||||
string book = 1;
|
||||
int64 price = 2;
|
||||
}
|
||||
|
||||
message addResp {
|
||||
bool ok = 1;
|
||||
}
|
||||
|
||||
service adder {
|
||||
rpc add(addReq) returns(addResp);
|
||||
}
|
||||
```
|
||||
|
||||
* use goctl to generate the rpc code, execute the following command in `rpc/add`
|
||||
|
||||
```shell
|
||||
goctl rpc proto -src add.proto
|
||||
```
|
||||
|
||||
the generated file structure looks like:
|
||||
|
||||
```
|
||||
rpc/add
|
||||
├── add.go // rpc main entrance
|
||||
├── add.proto // rpc definition
|
||||
├── adder
|
||||
│ ├── adder.go // defines how rpc clients call this service
|
||||
│ ├── adder_mock.go // mock file, for test purpose
|
||||
│ └── types.go // request/response definition
|
||||
├── etc
|
||||
│ └── add.yaml // configuration file
|
||||
├── internal
|
||||
│ ├── config
|
||||
│ │ └── config.go // configuration definition
|
||||
│ ├── logic
|
||||
│ │ └── addlogic.go // add logic here
|
||||
│ ├── server
|
||||
│ │ └── adderserver.go // rpc handler
|
||||
│ └── svc
|
||||
│ └── servicecontext.go // defines service context, like dependencies
|
||||
└── pb
|
||||
└── add.pb.go
|
||||
```
|
||||
|
||||
|
||||
just run it, looks like:
|
||||
|
||||
```shell
|
||||
$ go run add.go -f etc/add.yaml
|
||||
Starting rpc server at 127.0.0.1:8080...
|
||||
```
|
||||
|
||||
you can change the listening port in file `etc/add.yaml`.
|
||||
|
||||
## 7. Write code for check rpc service
|
||||
|
||||
* under directory `rpc/check` create `check.proto` file
|
||||
|
||||
```shell
|
||||
goctl rpc template -o check.proto
|
||||
```
|
||||
|
||||
edit the file and make the code looks like:
|
||||
|
||||
```protobuf
|
||||
syntax = "proto3";
|
||||
|
||||
package check;
|
||||
|
||||
message checkReq {
|
||||
string book = 1;
|
||||
}
|
||||
|
||||
message checkResp {
|
||||
bool found = 1;
|
||||
int64 price = 2;
|
||||
}
|
||||
|
||||
service checker {
|
||||
rpc check(checkReq) returns(checkResp);
|
||||
}
|
||||
```
|
||||
|
||||
* use goctl to generate the rpc code, execute the following command in `rpc/check`
|
||||
|
||||
```shell
|
||||
goctl rpc proto -src check.proto
|
||||
```
|
||||
|
||||
the generated file structure looks like:
|
||||
|
||||
```
|
||||
rpc/check
|
||||
├── check.go // rpc main entrance
|
||||
├── check.proto // rpc definition
|
||||
├── checker
|
||||
│ ├── checker.go // defines how rpc clients call this service
|
||||
│ ├── checker_mock.go // mock file, for test purpose
|
||||
│ └── types.go // request/response definition
|
||||
├── etc
|
||||
│ └── check.yaml // configuration file
|
||||
├── internal
|
||||
│ ├── config
|
||||
│ │ └── config.go // configuration definition
|
||||
│ ├── logic
|
||||
│ │ └── checklogic.go // check logic here
|
||||
│ ├── server
|
||||
│ │ └── checkerserver.go // rpc handler
|
||||
│ └── svc
|
||||
│ └── servicecontext.go // defines service context, like dependencies
|
||||
└── pb
|
||||
└── check.pb.go
|
||||
```
|
||||
|
||||
you can change the listening port in `etc/check.yaml`.
|
||||
|
||||
we need to change the port in `etc/check.yaml` to `8081`, because `8080 ` is used by `add` service.
|
||||
|
||||
just run it, looks like:
|
||||
|
||||
```shell
|
||||
$ go run check.go -f etc/check.yaml
|
||||
Starting rpc server at 127.0.0.1:8081...
|
||||
```
|
||||
|
||||
## 8. Modify API Gateway to call add/check rpc service
|
||||
|
||||
* modify the configuration file `bookstore-api.yaml`, add the following:
|
||||
|
||||
```yaml
|
||||
Add:
|
||||
Etcd:
|
||||
Hosts:
|
||||
- localhost:2379
|
||||
Key: add.rpc
|
||||
Check:
|
||||
Etcd:
|
||||
Hosts:
|
||||
- localhost:2379
|
||||
Key: check.rpc
|
||||
```
|
||||
|
||||
automatically discover the add/check service by using etcd.
|
||||
|
||||
* modify the file `internal/config/config.go`, add dependency on add/check service:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
rest.RestConf
|
||||
Add rpcx.RpcClientConf // manual code
|
||||
Check rpcx.RpcClientConf // manual code
|
||||
}
|
||||
```
|
||||
|
||||
* modify the file `internal/svc/servicecontext.go`, like below:
|
||||
|
||||
```go
|
||||
type ServiceContext struct {
|
||||
Config config.Config
|
||||
Adder adder.Adder // manual code
|
||||
Checker checker.Checker // manual code
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{
|
||||
Config: c,
|
||||
Adder: adder.NewAdder(rpcx.MustNewClient(c.Add)), // manual code
|
||||
Checker: checker.NewChecker(rpcx.MustNewClient(c.Check)), // manual code
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
passing the dependencies among services within ServiceContext.
|
||||
|
||||
* modify the method `Add` in the file `internal/logic/addlogic.go`, looks like:
|
||||
|
||||
```go
|
||||
func (l *AddLogic) Add(req types.AddReq) (*types.AddResp, error) {
|
||||
// manual code start
|
||||
resp, err := l.svcCtx.Adder.Add(l.ctx, &adder.AddReq{
|
||||
Book: req.Book,
|
||||
Price: req.Price,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.AddResp{
|
||||
Ok: resp.Ok,
|
||||
}, nil
|
||||
// manual code stop
|
||||
}
|
||||
```
|
||||
|
||||
by calling the method `Add` of `adder` to add books into bookstore.
|
||||
|
||||
* modify the file `internal/logic/checklogic.go`, looks like:
|
||||
|
||||
```go
|
||||
func (l *CheckLogic) Check(req types.CheckReq) (*types.CheckResp, error) {
|
||||
// manual code start
|
||||
resp, err := l.svcCtx.Checker.Check(l.ctx, &checker.CheckReq{
|
||||
Book: req.Book,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.CheckResp{
|
||||
Found: resp.Found,
|
||||
Price: resp.Price,
|
||||
}, nil
|
||||
// manual code stop
|
||||
}
|
||||
```
|
||||
|
||||
by calling the method `Check` of `checker` to check the prices from the bookstore.
|
||||
|
||||
Till now, we’ve done the modification of API Gateway. All the manually added code are marked.
|
||||
|
||||
## 9. Define the database schema, generate the code for CRUD+cache
|
||||
|
||||
* under bookstore, create the directory `rpc/model`: `mkdir -p rpc/model`
|
||||
|
||||
* under the directory rpc/model create the file called `book.sql`, contents as below:
|
||||
|
||||
```sql
|
||||
CREATE TABLE `book`
|
||||
(
|
||||
`book` varchar(255) NOT NULL COMMENT 'book name',
|
||||
`price` int NOT NULL COMMENT 'book price',
|
||||
PRIMARY KEY(`book`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
```
|
||||
|
||||
* create DB and table
|
||||
|
||||
```sql
|
||||
create database gozero;
|
||||
```
|
||||
|
||||
```sql
|
||||
source book.sql;
|
||||
```
|
||||
|
||||
* under the directory `rpc/model execute the following command to genrate CRUD+cache code, `-c` means using `redis cache`
|
||||
|
||||
```shell
|
||||
goctl model mysql ddl -c -src book.sql -dir .
|
||||
```
|
||||
|
||||
you can also generate the code from the database url by using `datasource` subcommand instead of `ddl`
|
||||
|
||||
the generated file structure looks like:
|
||||
|
||||
```
|
||||
rpc/model
|
||||
├── bookstore.sql
|
||||
├── bookstoremodel.go // CRUD+cache code
|
||||
└── vars.go // const and var definition
|
||||
```
|
||||
|
||||
## 10. Modify add/check rpc to call crud+cache
|
||||
|
||||
* modify `rpc/add/etc/add.yaml`, add the following:
|
||||
|
||||
```yaml
|
||||
DataSource: root:@tcp(localhost:3306)/gozero
|
||||
Table: book
|
||||
Cache:
|
||||
- Host: localhost:6379
|
||||
```
|
||||
|
||||
you can use multiple redis as cache. redis node and cluster are both supported.
|
||||
|
||||
* modify `rpc/add/internal/config.go`, like below:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
rpcx.RpcServerConf
|
||||
DataSource string // manual code
|
||||
Table string // manual code
|
||||
Cache cache.CacheConf // manual code
|
||||
}
|
||||
```
|
||||
|
||||
added the configuration for mysql and redis cache.
|
||||
|
||||
* modify `rpc/add/internal/svc/servicecontext.go` and `rpc/check/internal/svc/servicecontext.go`, like below:
|
||||
|
||||
```go
|
||||
type ServiceContext struct {
|
||||
c config.Config
|
||||
Model *model.BookModel // manual code
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{
|
||||
c: c,
|
||||
Model: model.NewBookModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // manual code
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* modify `rpc/add/internal/logic/addlogic.go`, like below:
|
||||
|
||||
```go
|
||||
func (l *AddLogic) Add(in *add.AddReq) (*add.AddResp, error) {
|
||||
// manual code start
|
||||
_, err := l.svcCtx.Model.Insert(model.Book{
|
||||
Book: in.Book,
|
||||
Price: in.Price,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &add.AddResp{
|
||||
Ok: true,
|
||||
}, nil
|
||||
// manual code stop
|
||||
}
|
||||
```
|
||||
|
||||
* modify `rpc/check/internal/logic/checklogic.go`, like below:
|
||||
|
||||
```go
|
||||
func (l *CheckLogic) Check(in *check.CheckReq) (*check.CheckResp, error) {
|
||||
// manual code start
|
||||
resp, err := l.svcCtx.Model.FindOne(in.Book)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &check.CheckResp{
|
||||
Found: true,
|
||||
Price: resp.Price,
|
||||
}, nil
|
||||
// manual code stop
|
||||
}
|
||||
```
|
||||
|
||||
till now, we finished modifing the code, all the modified code is marked.
|
||||
|
||||
## 11. Call shorten and expand services
|
||||
|
||||
* call add api
|
||||
|
||||
```shell
|
||||
curl -i "http://localhost:8888/add?book=go-zero&price=10"
|
||||
```
|
||||
|
||||
response like:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Thu, 03 Sep 2020 09:42:13 GMT
|
||||
Content-Length: 11
|
||||
|
||||
{"ok":true}
|
||||
```
|
||||
|
||||
* call check api
|
||||
|
||||
```shell
|
||||
curl -i "http://localhost:8888/check?book=go-zero"
|
||||
```
|
||||
|
||||
response like:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Thu, 03 Sep 2020 09:47:34 GMT
|
||||
Content-Length: 25
|
||||
|
||||
{"found":true,"price":10}
|
||||
```
|
||||
|
||||
## 12. Benchmark
|
||||
|
||||
Because benchmarking the write requests depends on the write throughput of mysql, we only benchmarked the check api. We read the data from mysql and cache it in redis. For simplicity, I only check one book, because of cache, the effect is the same for multiple books.
|
||||
|
||||
Before benchmark, we need to change the max open files:
|
||||
|
||||
```shel
|
||||
ulimit -n 20000
|
||||
```
|
||||
|
||||
And change the log level to error, to avoid too many logs affect the benchmark. Add the following in every yaml file:
|
||||
|
||||
```yaml
|
||||
Log:
|
||||
Level: error
|
||||
```
|
||||
|
||||

|
||||
|
||||
as shown above, in my MacBook Pro, the QPS is like 30K+.
|
||||
|
||||
## 13. Full code
|
||||
|
||||
[https://github.com/tal-tech/go-zero/tree/master/example/bookstore](https://github.com/tal-tech/go-zero/tree/master/example/bookstore)
|
||||
|
||||
## 14. Conclusion
|
||||
|
||||
We always adhere to **prefer tools over conventions and documents**.
|
||||
|
||||
go-zero is not only a framework, but also a tool to simplify and standardize the building of micoservice systems.
|
||||
|
||||
We not only keep the framework simple, but also encapsulate the complexity into the framework. And the developers are free from building the difficult and boilerplate code. Then we get the rapid development and less failure.
|
||||
|
||||
For the generated code by goctl, lots of microservice components are included, like concurrency control, adaptive circuit breaker, adaptive load shedding, auto cache control etc. And it’s easy to deal with the busy sites.
|
||||
|
||||
If you have any ideas that can help us to improve the productivity, tell me any time! 👏
|
||||
618
doc/bookstore.md
Normal file
@@ -0,0 +1,618 @@
|
||||
[English](bookstore-en.md) | 简体中文
|
||||
|
||||
# 快速构建微服务-多RPC版
|
||||
|
||||
## 0. 为什么说做好微服务很难?
|
||||
|
||||
要想做好微服务,我们需要理解和掌握的知识点非常多,从几个维度上来说:
|
||||
|
||||
* 基本功能层面
|
||||
1. 并发控制&限流,避免服务被突发流量击垮
|
||||
2. 服务注册与服务发现,确保能够动态侦测增减的节点
|
||||
3. 负载均衡,需要根据节点承受能力分发流量
|
||||
4. 超时控制,避免对已超时请求做无用功
|
||||
5. 熔断设计,快速失败,保障故障节点的恢复能力
|
||||
|
||||
* 高阶功能层面
|
||||
1. 请求认证,确保每个用户只能访问自己的数据
|
||||
2. 链路追踪,用于理解整个系统和快速定位特定请求的问题
|
||||
3. 日志,用于数据收集和问题定位
|
||||
4. 可观测性,没有度量就没有优化
|
||||
|
||||
对于其中每一点,我们都需要用很长的篇幅来讲述其原理和实现,那么对我们后端开发者来说,要想把这些知识点都掌握并落实到业务系统里,难度是非常大的,不过我们可以依赖已经被大流量验证过的框架体系。[go-zero微服务框架](https://github.com/tal-tech/go-zero)就是为此而生。
|
||||
|
||||
另外,我们始终秉承**工具大于约定和文档**的理念。我们希望尽可能减少开发人员的心智负担,把精力都投入到产生业务价值的代码上,减少重复代码的编写,所以我们开发了`goctl`工具。
|
||||
|
||||
下面我通过书店服务来演示通过[go-zero](https://github.com/tal-tech/go-zero)快速的创建微服务的流程,走完一遍,你就会发现:原来编写微服务如此简单!
|
||||
|
||||
## 1. 书店服务示例简介
|
||||
|
||||
为了教程简单,我们用书店服务做示例,并且只实现其中的增加书目和检查价格功能。
|
||||
|
||||
写此书店服务是为了从整体上演示go-zero构建完整微服务的过程,实现细节尽可能简化了。
|
||||
|
||||
## 2. 书店微服务架构图
|
||||
|
||||
<img src="images/bookstore-arch.png" alt="架构图" width="800" />
|
||||
|
||||
## 3. goctl各层代码生成一览
|
||||
|
||||
所有绿色背景的功能模块是自动生成的,按需激活,红色模块是需要自己写的,也就是增加下依赖,编写业务特有逻辑,各层示意图分别如下:
|
||||
|
||||
* API Gateway
|
||||
|
||||
<img src="images/bookstore-api.png" alt="api" width="800" />
|
||||
|
||||
* RPC
|
||||
|
||||
<img src="images/bookstore-rpc.png" alt="架构图" width="800" />
|
||||
|
||||
* model
|
||||
|
||||
<img src="images/bookstore-model.png" alt="model" width="800" />
|
||||
|
||||
下面我们来一起完整走一遍快速构建微服务的流程,Let’s `Go`!🏃♂️
|
||||
|
||||
## 4. 准备工作
|
||||
|
||||
* 安装etcd, mysql, redis
|
||||
|
||||
* 安装goctl工具
|
||||
|
||||
```shell
|
||||
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
|
||||
* 创建工作目录`bookstore`
|
||||
|
||||
* 在`bookstore`目录下执行`go mod init bookstore`初始化`go.mod`
|
||||
|
||||
## 5. 编写API Gateway代码
|
||||
|
||||
* 在`bookstore/api`目录下通过goctl生成`api/bookstore.api`:
|
||||
|
||||
```
|
||||
goctl api -o bookstore.api
|
||||
```
|
||||
|
||||
编辑`bookstore.api`,为了简洁,去除了文件开头的`info`,代码如下:
|
||||
|
||||
```go
|
||||
type (
|
||||
addReq struct {
|
||||
book string `form:"book"`
|
||||
price int64 `form:"price"`
|
||||
}
|
||||
|
||||
addResp struct {
|
||||
ok bool `json:"ok"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
checkReq struct {
|
||||
book string `form:"book"`
|
||||
}
|
||||
|
||||
checkResp struct {
|
||||
found bool `json:"found"`
|
||||
price int64 `json:"price"`
|
||||
}
|
||||
)
|
||||
|
||||
service bookstore-api {
|
||||
@server(
|
||||
handler: AddHandler
|
||||
)
|
||||
get /add(addReq) returns(addResp)
|
||||
|
||||
@server(
|
||||
handler: CheckHandler
|
||||
)
|
||||
get /check(checkReq) returns(checkResp)
|
||||
}
|
||||
```
|
||||
|
||||
type用法和go一致,service用来定义get/post/head/delete等api请求,解释如下:
|
||||
|
||||
* `service bookstore-api {`这一行定义了service名字
|
||||
* `@server`部分用来定义server端用到的属性
|
||||
* `handler`定义了服务端handler名字
|
||||
* `get /add(addReq) returns(addResp)`定义了get方法的路由、请求参数、返回参数等
|
||||
|
||||
* 使用goctl生成API Gateway代码
|
||||
|
||||
```shell
|
||||
goctl api go -api bookstore.api -dir .
|
||||
```
|
||||
|
||||
生成的文件结构如下:
|
||||
|
||||
```
|
||||
api
|
||||
├── bookstore.api // api定义
|
||||
├── bookstore.go // main入口定义
|
||||
├── etc
|
||||
│ └── bookstore-api.yaml // 配置文件
|
||||
└── internal
|
||||
├── config
|
||||
│ └── config.go // 定义配置
|
||||
├── handler
|
||||
│ ├── addhandler.go // 实现addHandler
|
||||
│ ├── checkhandler.go // 实现checkHandler
|
||||
│ └── routes.go // 定义路由处理
|
||||
├── logic
|
||||
│ ├── addlogic.go // 实现AddLogic
|
||||
│ └── checklogic.go // 实现CheckLogic
|
||||
├── svc
|
||||
│ └── servicecontext.go // 定义ServiceContext
|
||||
└── types
|
||||
└── types.go // 定义请求、返回结构体
|
||||
```
|
||||
|
||||
* 启动API Gateway服务,默认侦听在8888端口
|
||||
|
||||
```shell
|
||||
go run bookstore.go -f etc/bookstore-api.yaml
|
||||
```
|
||||
|
||||
* 测试API Gateway服务
|
||||
|
||||
```shell
|
||||
curl -i "http://localhost:8888/check?book=go-zero"
|
||||
```
|
||||
|
||||
返回如下:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Thu, 03 Sep 2020 06:46:18 GMT
|
||||
Content-Length: 25
|
||||
|
||||
{"found":false,"price":0}
|
||||
```
|
||||
|
||||
可以看到我们API Gateway其实啥也没干,就返回了个空值,接下来我们会在rpc服务里实现业务逻辑
|
||||
|
||||
* 可以修改`internal/svc/servicecontext.go`来传递服务依赖(如果需要)
|
||||
|
||||
* 实现逻辑可以修改`internal/logic`下的对应文件
|
||||
|
||||
* 可以通过`goctl`生成各种客户端语言的api调用代码
|
||||
|
||||
* 到这里,你已经可以通过goctl生成客户端代码给客户端同学并行开发了,支持多种语言,详见文档
|
||||
|
||||
## 6. 编写add rpc服务
|
||||
|
||||
* 在`rpc/add`目录下编写`add.proto`文件
|
||||
|
||||
可以通过命令生成proto文件模板
|
||||
|
||||
```shell
|
||||
goctl rpc template -o add.proto
|
||||
```
|
||||
|
||||
修改后文件内容如下:
|
||||
|
||||
```protobuf
|
||||
syntax = "proto3";
|
||||
|
||||
package add;
|
||||
|
||||
message addReq {
|
||||
string book = 1;
|
||||
int64 price = 2;
|
||||
}
|
||||
|
||||
message addResp {
|
||||
bool ok = 1;
|
||||
}
|
||||
|
||||
service adder {
|
||||
rpc add(addReq) returns(addResp);
|
||||
}
|
||||
```
|
||||
|
||||
* 用`goctl`生成rpc代码,在`rpc/add`目录下执行命令
|
||||
|
||||
```shell
|
||||
goctl rpc proto -src add.proto
|
||||
```
|
||||
|
||||
文件结构如下:
|
||||
|
||||
```
|
||||
rpc/add
|
||||
├── add.go // rpc服务main函数
|
||||
├── add.proto // rpc接口定义
|
||||
├── adder
|
||||
│ ├── adder.go // 提供了外部调用方法,无需修改
|
||||
│ ├── adder_mock.go // mock方法,测试用
|
||||
│ └── types.go // request/response结构体定义
|
||||
├── etc
|
||||
│ └── add.yaml // 配置文件
|
||||
├── internal
|
||||
│ ├── config
|
||||
│ │ └── config.go // 配置定义
|
||||
│ ├── logic
|
||||
│ │ └── addlogic.go // add业务逻辑在这里实现
|
||||
│ ├── server
|
||||
│ │ └── adderserver.go // 调用入口, 不需要修改
|
||||
│ └── svc
|
||||
│ └── servicecontext.go // 定义ServiceContext,传递依赖
|
||||
└── pb
|
||||
└── add.pb.go
|
||||
```
|
||||
|
||||
|
||||
直接可以运行,如下:
|
||||
|
||||
```shell
|
||||
$ go run add.go -f etc/add.yaml
|
||||
Starting rpc server at 127.0.0.1:8080...
|
||||
```
|
||||
|
||||
`etc/add.yaml`文件里可以修改侦听端口等配置
|
||||
|
||||
## 7. 编写check rpc服务
|
||||
|
||||
* 在`rpc/check`目录下编写`check.proto`文件
|
||||
|
||||
可以通过命令生成proto文件模板
|
||||
|
||||
```shell
|
||||
goctl rpc template -o check.proto
|
||||
```
|
||||
|
||||
修改后文件内容如下:
|
||||
|
||||
```protobuf
|
||||
syntax = "proto3";
|
||||
|
||||
package check;
|
||||
|
||||
message checkReq {
|
||||
string book = 1;
|
||||
}
|
||||
|
||||
message checkResp {
|
||||
bool found = 1;
|
||||
int64 price = 2;
|
||||
}
|
||||
|
||||
service checker {
|
||||
rpc check(checkReq) returns(checkResp);
|
||||
}
|
||||
```
|
||||
|
||||
* 用`goctl`生成rpc代码,在`rpc/check`目录下执行命令
|
||||
|
||||
```shell
|
||||
goctl rpc proto -src check.proto
|
||||
```
|
||||
|
||||
文件结构如下:
|
||||
|
||||
```
|
||||
rpc/check
|
||||
├── check.go // rpc服务main函数
|
||||
├── check.proto // rpc接口定义
|
||||
├── checker
|
||||
│ ├── checker.go // 提供了外部调用方法,无需修改
|
||||
│ ├── checker_mock.go // mock方法,测试用
|
||||
│ └── types.go // request/response结构体定义
|
||||
├── etc
|
||||
│ └── check.yaml // 配置文件
|
||||
├── internal
|
||||
│ ├── config
|
||||
│ │ └── config.go // 配置定义
|
||||
│ ├── logic
|
||||
│ │ └── checklogic.go // check业务逻辑在这里实现
|
||||
│ ├── server
|
||||
│ │ └── checkerserver.go // 调用入口, 不需要修改
|
||||
│ └── svc
|
||||
│ └── servicecontext.go // 定义ServiceContext,传递依赖
|
||||
└── pb
|
||||
└── check.pb.go
|
||||
```
|
||||
|
||||
`etc/check.yaml`文件里可以修改侦听端口等配置
|
||||
|
||||
需要修改`etc/check.yaml`的端口为`8081`,因为`8080`已经被`add`服务使用了,直接可以运行,如下:
|
||||
|
||||
```shell
|
||||
$ go run check.go -f etc/check.yaml
|
||||
Starting rpc server at 127.0.0.1:8081...
|
||||
```
|
||||
|
||||
## 8. 修改API Gateway代码调用add/check rpc服务
|
||||
|
||||
* 修改配置文件`bookstore-api.yaml`,增加如下内容
|
||||
|
||||
```yaml
|
||||
Add:
|
||||
Etcd:
|
||||
Hosts:
|
||||
- localhost:2379
|
||||
Key: add.rpc
|
||||
Check:
|
||||
Etcd:
|
||||
Hosts:
|
||||
- localhost:2379
|
||||
Key: check.rpc
|
||||
```
|
||||
|
||||
通过etcd自动去发现可用的add/check服务
|
||||
|
||||
* 修改`internal/config/config.go`如下,增加add/check服务依赖
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
rest.RestConf
|
||||
Add rpcx.RpcClientConf // 手动代码
|
||||
Check rpcx.RpcClientConf // 手动代码
|
||||
}
|
||||
```
|
||||
|
||||
* 修改`internal/svc/servicecontext.go`,如下:
|
||||
|
||||
```go
|
||||
type ServiceContext struct {
|
||||
Config config.Config
|
||||
Adder adder.Adder // 手动代码
|
||||
Checker checker.Checker // 手动代码
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{
|
||||
Config: c,
|
||||
Adder: adder.NewAdder(rpcx.MustNewClient(c.Add)), // 手动代码
|
||||
Checker: checker.NewChecker(rpcx.MustNewClient(c.Check)), // 手动代码
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过ServiceContext在不同业务逻辑之间传递依赖
|
||||
|
||||
* 修改`internal/logic/addlogic.go`里的`Add`方法,如下:
|
||||
|
||||
```go
|
||||
func (l *AddLogic) Add(req types.AddReq) (*types.AddResp, error) {
|
||||
// 手动代码开始
|
||||
resp, err := l.svcCtx.Adder.Add(l.ctx, &adder.AddReq{
|
||||
Book: req.Book,
|
||||
Price: req.Price,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.AddResp{
|
||||
Ok: resp.Ok,
|
||||
}, nil
|
||||
// 手动代码结束
|
||||
}
|
||||
```
|
||||
|
||||
通过调用`adder`的`Add`方法实现添加图书到bookstore系统
|
||||
|
||||
* 修改`internal/logic/checklogic.go`里的`Check`方法,如下:
|
||||
|
||||
```go
|
||||
func (l *CheckLogic) Check(req types.CheckReq) (*types.CheckResp, error) {
|
||||
// 手动代码开始
|
||||
resp, err := l.svcCtx.Checker.Check(l.ctx, &checker.CheckReq{
|
||||
Book: req.Book,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.CheckResp{
|
||||
Found: resp.Found,
|
||||
Price: resp.Price,
|
||||
}, nil
|
||||
// 手动代码结束
|
||||
}
|
||||
```
|
||||
|
||||
通过调用`checker`的`Check`方法实现从bookstore系统中查询图书的价格
|
||||
|
||||
## 9. 定义数据库表结构,并生成CRUD+cache代码
|
||||
|
||||
* bookstore下创建`rpc/model`目录:`mkdir -p rpc/model`
|
||||
|
||||
* 在rpc/model目录下编写创建book表的sql文件`book.sql`,如下:
|
||||
|
||||
```sql
|
||||
CREATE TABLE `book`
|
||||
(
|
||||
`book` varchar(255) NOT NULL COMMENT 'book name',
|
||||
`price` int NOT NULL COMMENT 'book price',
|
||||
PRIMARY KEY(`book`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
```
|
||||
|
||||
* 创建DB和table
|
||||
|
||||
```sql
|
||||
create database gozero;
|
||||
```
|
||||
|
||||
```sql
|
||||
source book.sql;
|
||||
```
|
||||
|
||||
* 在`rpc/model`目录下执行如下命令生成CRUD+cache代码,`-c`表示使用`redis cache`
|
||||
|
||||
```shell
|
||||
goctl model mysql ddl -c -src book.sql -dir .
|
||||
```
|
||||
|
||||
也可以用`datasource`命令代替`ddl`来指定数据库链接直接从schema生成
|
||||
|
||||
生成后的文件结构如下:
|
||||
|
||||
```
|
||||
rpc/model
|
||||
├── bookstore.sql
|
||||
├── bookstoremodel.go // CRUD+cache代码
|
||||
└── vars.go // 定义常量和变量
|
||||
```
|
||||
|
||||
## 10. 修改add/check rpc代码调用crud+cache代码
|
||||
|
||||
* 修改`rpc/add/etc/add.yaml`和`rpc/check/etc/check.yaml`,增加如下内容:
|
||||
|
||||
```yaml
|
||||
DataSource: root:@tcp(localhost:3306)/gozero
|
||||
Table: book
|
||||
Cache:
|
||||
- Host: localhost:6379
|
||||
```
|
||||
|
||||
可以使用多个redis作为cache,支持redis单点或者redis集群
|
||||
|
||||
* 修改`rpc/add/internal/config.go`和`rpc/check/internal/config.go`,如下:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
rpcx.RpcServerConf
|
||||
DataSource string // 手动代码
|
||||
Table string // 手动代码
|
||||
Cache cache.CacheConf // 手动代码
|
||||
}
|
||||
```
|
||||
|
||||
增加了mysql和redis cache配置
|
||||
|
||||
* 修改`rpc/add/internal/svc/servicecontext.go`和`rpc/check/internal/svc/servicecontext.go`,如下:
|
||||
|
||||
```go
|
||||
type ServiceContext struct {
|
||||
c config.Config
|
||||
Model *model.BookModel // 手动代码
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{
|
||||
c: c,
|
||||
Model: model.NewBookModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // 手动代码
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* 修改`rpc/add/internal/logic/addlogic.go`,如下:
|
||||
|
||||
```go
|
||||
func (l *AddLogic) Add(in *add.AddReq) (*add.AddResp, error) {
|
||||
// 手动代码开始
|
||||
_, err := l.svcCtx.Model.Insert(model.Book{
|
||||
Book: in.Book,
|
||||
Price: in.Price,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &add.AddResp{
|
||||
Ok: true,
|
||||
}, nil
|
||||
// 手动代码结束
|
||||
}
|
||||
```
|
||||
|
||||
* 修改`rpc/check/internal/logic/checklogic.go`,如下:
|
||||
|
||||
```go
|
||||
func (l *CheckLogic) Check(in *check.CheckReq) (*check.CheckResp, error) {
|
||||
// 手动代码开始
|
||||
resp, err := l.svcCtx.Model.FindOne(in.Book)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &check.CheckResp{
|
||||
Found: true,
|
||||
Price: resp.Price,
|
||||
}, nil
|
||||
// 手动代码结束
|
||||
}
|
||||
```
|
||||
|
||||
至此代码修改完成,凡事手动修改的代码我加了标注
|
||||
|
||||
## 11. 完整调用演示
|
||||
|
||||
* add api调用
|
||||
|
||||
```shell
|
||||
curl -i "http://localhost:8888/add?book=go-zero&price=10"
|
||||
```
|
||||
|
||||
返回如下:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Thu, 03 Sep 2020 09:42:13 GMT
|
||||
Content-Length: 11
|
||||
|
||||
{"ok":true}
|
||||
```
|
||||
|
||||
* check api调用
|
||||
|
||||
```shell
|
||||
curl -i "http://localhost:8888/check?book=go-zero"
|
||||
```
|
||||
|
||||
返回如下:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Thu, 03 Sep 2020 09:47:34 GMT
|
||||
Content-Length: 25
|
||||
|
||||
{"found":true,"price":10}
|
||||
```
|
||||
|
||||
## 12. Benchmark
|
||||
|
||||
因为写入依赖于mysql的写入速度,就相当于压mysql了,所以压测只测试了check接口,相当于从mysql里读取并利用缓存,为了方便,直接压这一本书,因为有缓存,多本书也是一样的,对压测结果没有影响。
|
||||
|
||||
压测之前,让我们先把打开文件句柄数调大:
|
||||
|
||||
```shel
|
||||
ulimit -n 20000
|
||||
```
|
||||
|
||||
并日志的等级改为`error`,防止过多的info影响压测结果,在每个yaml配置文件里加上如下:
|
||||
|
||||
```yaml
|
||||
Log:
|
||||
Level: error
|
||||
```
|
||||
|
||||

|
||||
|
||||
可以看出在我的MacBook Pro上能达到3万+的qps。
|
||||
|
||||
## 13. 完整代码
|
||||
|
||||
[https://github.com/tal-tech/go-zero/tree/master/example/bookstore](https://github.com/tal-tech/go-zero/tree/master/example/bookstore)
|
||||
|
||||
## 14. 总结
|
||||
|
||||
我们一直强调**工具大于约定和文档**。
|
||||
|
||||
go-zero不只是一个框架,更是一个建立在框架+工具基础上的,简化和规范了整个微服务构建的技术体系。
|
||||
|
||||
我们在保持简单的同时也尽可能把微服务治理的复杂度封装到了框架内部,极大的降低了开发人员的心智负担,使得业务开发得以快速推进。
|
||||
|
||||
通过go-zero+goctl生成的代码,包含了微服务治理的各种组件,包括:并发控制、自适应熔断、自适应降载、自动缓存控制等,可以轻松部署以承载巨大访问量。
|
||||
|
||||
有任何好的提升工程效率的想法,随时欢迎交流!👏
|
||||
|
||||
107
doc/collection.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# 通过 collection.Cache 进行缓存
|
||||
|
||||
go-zero微服务框架中提供了许多开箱即用的工具,好的工具不仅能提升服务的性能而且还能提升代码的鲁棒性避免出错,实现代码风格的统一方便他人阅读等等,本系列文章将分别介绍go-zero框架中工具的使用及其实现原理
|
||||
|
||||
## 进程内缓存工具[collection.Cache](https://github.com/tal-tech/go-zero/tree/master/core/collection/cache.go)
|
||||
|
||||
在做服务器开发的时候,相信都会遇到使用缓存的情况,go-zero 提供的简单的缓存封装 **collection.Cache**,简单使用方式如下
|
||||
|
||||
```go
|
||||
// 初始化 cache,其中 WithLimit 可以指定最大缓存的数量
|
||||
c, err := collection.NewCache(time.Minute, collection.WithLimit(10000))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 设置缓存
|
||||
c.Set("key", user)
|
||||
|
||||
// 获取缓存,ok:是否存在
|
||||
v, ok := c.Get("key")
|
||||
|
||||
// 删除缓存
|
||||
c.Del("key")
|
||||
|
||||
// 获取缓存,如果 key 不存在的,则会调用 func 去生成缓存
|
||||
v, err := c.Take("key", func() (interface{}, error) {
|
||||
return user, nil
|
||||
})
|
||||
```
|
||||
|
||||
cache 实现的建的功能包括
|
||||
* 缓存自动失效,可以指定过期时间
|
||||
* 缓存大小限制,可以指定缓存个数
|
||||
* 缓存增删改
|
||||
* 缓存命中率统计
|
||||
* 并发安全
|
||||
* 缓存击穿
|
||||
|
||||
实现原理:
|
||||
Cache 自动失效,是采用 TimingWheel(https://github.com/tal-tech/go-zero/blob/master/core/collection/timingwheel.go) 进行管理的
|
||||
``` go
|
||||
timingWheel, err := NewTimingWheel(time.Second, slots, func(k, v interface{}) {
|
||||
key, ok := k.(string)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
cache.Del(key)
|
||||
})
|
||||
```
|
||||
|
||||
Cache 大小限制,是采用 LRU 淘汰策略,在新增缓存的时候会去检查是否已经超出过限制,具体代码在 keyLru 中实现
|
||||
``` go
|
||||
func (klru *keyLru) add(key string) {
|
||||
if elem, ok := klru.elements[key]; ok {
|
||||
klru.evicts.MoveToFront(elem)
|
||||
return
|
||||
}
|
||||
|
||||
// Add new item
|
||||
elem := klru.evicts.PushFront(key)
|
||||
klru.elements[key] = elem
|
||||
|
||||
// Verify size not exceeded
|
||||
if klru.evicts.Len() > klru.limit {
|
||||
klru.removeOldest()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Cache 的命中率统计,是在代码中实现 cacheStat,在缓存命中丢失的时候自动统计,并且会定时打印使用的命中率, qps 等状态.
|
||||
|
||||
打印的具体效果如下
|
||||
```go
|
||||
cache(proc) - qpm: 2, hit_ratio: 50.0%, elements: 0, hit: 1, miss: 1
|
||||
```
|
||||
|
||||
缓存击穿包含是使用 syncx.SharedCalls(https://github.com/tal-tech/go-zero/blob/master/core/syncx/sharedcalls.go) 进行实现的,就是将同时请求同一个 key 的请求, 关于 sharedcalls 后续会继续补充。 相关具体实现是在:
|
||||
|
||||
```go
|
||||
func (c *Cache) Take(key string, fetch func() (interface{}, error)) (interface{}, error) {
|
||||
val, fresh, err := c.barrier.DoEx(key, func() (interface{}, error) {
|
||||
v, e := fetch()
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
c.Set(key, v)
|
||||
return v, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fresh {
|
||||
c.stats.IncrementMiss()
|
||||
return val, nil
|
||||
} else {
|
||||
// got the result from previous ongoing query
|
||||
c.stats.IncrementHit()
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
```
|
||||
|
||||
本文主要介绍了go-zero框架中的 Cache 工具,在实际的项目中非常实用。用好工具对于提升服务性能和开发效率都有很大的帮助,希望本篇文章能给大家带来一些收获。
|
||||
274
doc/goctl-model-sql.md
Normal file
@@ -0,0 +1,274 @@
|
||||
# Goctl Model
|
||||
|
||||
goctl model 为go-zero下的工具模块中的组件之一,目前支持识别mysql ddl进行model层代码生成,通过命令行或者idea插件(即将支持)可以有选择地生成带redis cache或者不带redis cache的代码逻辑。
|
||||
|
||||
# 快速开始
|
||||
|
||||
* 通过ddl生成
|
||||
|
||||
```shell script
|
||||
$ goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c=true
|
||||
```
|
||||
|
||||
执行上述命令后即可快速生成CURD代码。
|
||||
|
||||
```
|
||||
model
|
||||
│ ├── error.go
|
||||
│ └── usermodel.go
|
||||
```
|
||||
* 通过datasource生成
|
||||
|
||||
```shell script
|
||||
$ goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model"
|
||||
```
|
||||
|
||||
|
||||
* 生成代码示例
|
||||
|
||||
``` go
|
||||
package model
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlc"
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
"github.com/tal-tech/go-zero/core/stringx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx"
|
||||
)
|
||||
|
||||
var (
|
||||
userFieldNames = builderx.FieldNames(&User{})
|
||||
userRows = strings.Join(userFieldNames, ",")
|
||||
userRowsExpectAutoSet = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), ",")
|
||||
userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), "=?,") + "=?"
|
||||
|
||||
cacheUserMobilePrefix = "cache#User#mobile#"
|
||||
cacheUserIdPrefix = "cache#User#id#"
|
||||
cacheUserNamePrefix = "cache#User#name#"
|
||||
)
|
||||
|
||||
type (
|
||||
UserModel struct {
|
||||
sqlc.CachedConn
|
||||
table string
|
||||
}
|
||||
|
||||
User struct {
|
||||
Id int64 `db:"id"`
|
||||
Name string `db:"name"` // 用户名称
|
||||
Password string `db:"password"` // 用户密码
|
||||
Mobile string `db:"mobile"` // 手机号
|
||||
Gender string `db:"gender"` // 男|女|未公开
|
||||
Nickname string `db:"nickname"` // 用户昵称
|
||||
CreateTime time.Time `db:"create_time"`
|
||||
UpdateTime time.Time `db:"update_time"`
|
||||
}
|
||||
)
|
||||
|
||||
func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *UserModel {
|
||||
return &UserModel{
|
||||
CachedConn: sqlc.NewConn(conn, c),
|
||||
table: table,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *UserModel) Insert(data User) (sql.Result, error) {
|
||||
query := `insert into ` + m.table + `(` + userRowsExpectAutoSet + `) value (?, ?, ?, ?, ?)`
|
||||
return m.ExecNoCache(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname)
|
||||
}
|
||||
|
||||
func (m *UserModel) FindOne(id int64) (*User, error) {
|
||||
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
|
||||
var resp User
|
||||
err := m.QueryRow(&resp, userIdKey, func(conn sqlx.SqlConn, v interface{}) error {
|
||||
query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1`
|
||||
return conn.QueryRow(v, query, id)
|
||||
})
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *UserModel) FindOneByName(name string) (*User, error) {
|
||||
userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, name)
|
||||
var resp User
|
||||
err := m.QueryRowIndex(&resp, userNameKey, func(primary interface{}) string {
|
||||
return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary)
|
||||
}, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
|
||||
query := `select ` + userRows + ` from ` + m.table + ` where name = ? limit 1`
|
||||
if err := conn.QueryRow(&resp, query, name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Id, nil
|
||||
}, func(conn sqlx.SqlConn, v, primary interface{}) error {
|
||||
query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1`
|
||||
return conn.QueryRow(v, query, primary)
|
||||
})
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *UserModel) FindOneByMobile(mobile string) (*User, error) {
|
||||
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, mobile)
|
||||
var resp User
|
||||
err := m.QueryRowIndex(&resp, userMobileKey, func(primary interface{}) string {
|
||||
return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary)
|
||||
}, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
|
||||
query := `select ` + userRows + ` from ` + m.table + ` where mobile = ? limit 1`
|
||||
if err := conn.QueryRow(&resp, query, mobile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Id, nil
|
||||
}, func(conn sqlx.SqlConn, v, primary interface{}) error {
|
||||
query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1`
|
||||
return conn.QueryRow(v, query, primary)
|
||||
})
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *UserModel) Update(data User) error {
|
||||
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)
|
||||
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := `update ` + m.table + ` set ` + userRowsWithPlaceHolder + ` where id = ?`
|
||||
return conn.Exec(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname, data.Id)
|
||||
}, userIdKey)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *UserModel) Delete(id int64) error {
|
||||
data, err := m.FindOne(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
|
||||
userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, data.Name)
|
||||
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, data.Mobile)
|
||||
_, err = m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := `delete from ` + m.table + ` where id = ?`
|
||||
return conn.Exec(query, id)
|
||||
}, userIdKey, userNameKey, userMobileKey)
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
# 用法
|
||||
|
||||
```
|
||||
$ goctl model mysql -h
|
||||
```
|
||||
|
||||
```
|
||||
NAME:
|
||||
goctl model mysql - generate mysql model"
|
||||
|
||||
USAGE:
|
||||
goctl model mysql command [command options] [arguments...]
|
||||
|
||||
COMMANDS:
|
||||
ddl generate mysql model from ddl"
|
||||
datasource generate model from datasource"
|
||||
|
||||
OPTIONS:
|
||||
--help, -h show help
|
||||
```
|
||||
|
||||
# 生成规则
|
||||
|
||||
* 默认规则
|
||||
|
||||
我们默认用户在建表时会创建createTime、updateTime字段(忽略大小写、下划线命名风格)且默认值均为`CURRENT_TIMESTAMP`,而updateTime支持`ON UPDATE CURRENT_TIMESTAMP`,对于这两个字段生成`insert`、`update`时会被移除,不在赋值范畴内,当然,如果你不需要这两个字段那也无大碍。
|
||||
* 带缓存模式
|
||||
* ddl
|
||||
|
||||
```shell script
|
||||
$ goctl model mysql -src={filename} -dir={dir} -cache=true
|
||||
```
|
||||
* datasource
|
||||
|
||||
```shell script
|
||||
$ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=true
|
||||
```
|
||||
|
||||
目前仅支持redis缓存,如果选择带缓存模式,即生成的`FindOne(ByXxx)`&`Delete`代码会生成带缓存逻辑的代码,目前仅支持单索引字段(除全文索引外),对于联合索引我们默认认为不需要带缓存,且不属于通用型代码,因此没有放在代码生成行列,如example中user表中的`id`、`name`、`mobile`字段均属于单字段索引。
|
||||
|
||||
* 不带缓存模式
|
||||
|
||||
* ddl
|
||||
|
||||
```shell script
|
||||
$ goctl model -src={filename} -dir={dir}
|
||||
```
|
||||
* datasource
|
||||
|
||||
```shell script
|
||||
$ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir}
|
||||
```
|
||||
or
|
||||
* ddl
|
||||
|
||||
```shell script
|
||||
$ goctl model -src={filename} -dir={dir} -cache=false
|
||||
```
|
||||
* datasource
|
||||
|
||||
```shell script
|
||||
$ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=false
|
||||
```
|
||||
|
||||
生成代码仅基本的CURD结构。
|
||||
|
||||
# 缓存
|
||||
|
||||
对于缓存这一块我选择用一问一答的形式进行罗列。我想这样能够更清晰的描述model中缓存的功能。
|
||||
|
||||
* 缓存会缓存哪些信息?
|
||||
|
||||
对于主键字段缓存,会缓存整个结构体信息,而对于单索引字段(除全文索引)则缓存主键字段值。
|
||||
|
||||
* 数据有更新(`update`)操作会清空缓存吗?
|
||||
|
||||
会,但仅清空主键缓存的信息,why?这里就不做详细赘述了。
|
||||
|
||||
* 为什么不按照单索引字段生成`updateByXxx`和`deleteByXxx`的代码?
|
||||
|
||||
理论上是没任何问题,但是我们认为,对于model层的数据操作均是以整个结构体为单位,包括查询,我不建议只查询某部分字段(不反对),否则我们的缓存就没有意义了。
|
||||
|
||||
* 为什么不支持`findPageLimit`、`findAll`这么模式代码生层?
|
||||
|
||||
目前,我认为除了基本的CURD外,其他的代码均属于<i>业务型</i>代码,这个我觉得开发人员根据业务需要进行编写更好。
|
||||
|
||||
# QA
|
||||
|
||||
* goctl model除了命令行模式,支持插件模式吗?
|
||||
|
||||
很快支持idea插件。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
223
doc/goctl-rpc.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# Rpc Generation
|
||||
Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块,支持proto模板生成和rpc服务代码生成,通过此工具生成代码你只需要关注业务逻辑编写而不用去编写一些重复性的代码。这使得我们把精力重心放在业务上,从而加快了开发效率且降低了代码出错率。
|
||||
|
||||
# 特性
|
||||
* 简单易用
|
||||
* 快速提升开发效率
|
||||
* 出错率低
|
||||
|
||||
# 快速开始
|
||||
|
||||
### 方式一:快速生成greet服务
|
||||
|
||||
通过命令 `goctl rpc new ${servieName}`生成
|
||||
|
||||
如生成greet rpc服务:
|
||||
|
||||
```shell script
|
||||
$ goctl rpc new greet
|
||||
```
|
||||
|
||||
执行后代码结构如下:
|
||||
|
||||
```golang
|
||||
└── greet
|
||||
├── etc
|
||||
│ └── greet.yaml
|
||||
├── go.mod
|
||||
├── go.sum
|
||||
├── greet
|
||||
│ ├── greet.go
|
||||
│ ├── greet_mock.go
|
||||
│ └── types.go
|
||||
├── greet.go
|
||||
├── greet.proto
|
||||
├── internal
|
||||
│ ├── config
|
||||
│ │ └── config.go
|
||||
│ ├── logic
|
||||
│ │ └── pinglogic.go
|
||||
│ ├── server
|
||||
│ │ └── greetserver.go
|
||||
│ └── svc
|
||||
│ └── servicecontext.go
|
||||
└── pb
|
||||
└── greet.pb.go
|
||||
```
|
||||
|
||||
rpc一键生成常见问题解决见 <a href="#常见问题解决">常见问题解决</a>
|
||||
### 方式二:通过指定proto生成rpc服务
|
||||
|
||||
* 生成proto模板
|
||||
|
||||
```shell script
|
||||
$ goctl rpc template -o=user.proto
|
||||
```
|
||||
|
||||
```golang
|
||||
syntax = "proto3";
|
||||
|
||||
package remote;
|
||||
|
||||
message Request {
|
||||
// 用户名
|
||||
string username = 1;
|
||||
// 用户密码
|
||||
string password = 2;
|
||||
}
|
||||
|
||||
message Response {
|
||||
// 用户名称
|
||||
string name = 1;
|
||||
// 用户性别
|
||||
string gender = 2;
|
||||
}
|
||||
|
||||
service User {
|
||||
// 登录
|
||||
rpc Login(Request)returns(Response);
|
||||
}
|
||||
```
|
||||
* 生成rpc服务代码
|
||||
|
||||
```
|
||||
$ goctl rpc proto -src=user.proto
|
||||
```
|
||||
|
||||
代码tree
|
||||
|
||||
```
|
||||
user
|
||||
├── etc
|
||||
│ └── user.json
|
||||
├── internal
|
||||
│ ├── config
|
||||
│ │ └── config.go
|
||||
│ ├── handler
|
||||
│ │ ├── loginhandler.go
|
||||
│ ├── logic
|
||||
│ │ └── loginlogic.go
|
||||
│ └── svc
|
||||
│ └── servicecontext.go
|
||||
├── pb
|
||||
│ └── user.pb.go
|
||||
├── shared
|
||||
│ ├── mockusermodel.go
|
||||
│ ├── types.go
|
||||
│ └── usermodel.go
|
||||
├── user.go
|
||||
└── user.proto
|
||||
|
||||
```
|
||||
# 准备工作
|
||||
|
||||
* 安装了go环境
|
||||
* 安装了protoc&protoc-gen-go,并且已经设置环境变量
|
||||
* mockgen(可选,将移除)
|
||||
* 更多问题请见 <a href="#注意事项">注意事项</a>
|
||||
|
||||
# 用法
|
||||
|
||||
### rpc服务生成用法
|
||||
|
||||
```shell script
|
||||
$ goctl rpc proto -h
|
||||
```
|
||||
|
||||
```shell script
|
||||
NAME:
|
||||
goctl rpc proto - generate rpc from proto
|
||||
|
||||
USAGE:
|
||||
goctl rpc proto [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--src value, -s value the file path of the proto source file
|
||||
--dir value, -d value the target path of the code,default path is "${pwd}". [option]
|
||||
--service value, --srv value the name of rpc service. [option]
|
||||
--shared[已废弃] value the dir of the shared file,default path is "${pwd}/shared. [option]"
|
||||
--idea whether the command execution environment is from idea plugin. [option]
|
||||
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
* --src 必填,proto数据源,目前暂时支持单个proto文件生成,这里不支持(不建议)外部依赖
|
||||
* --dir 非必填,默认为proto文件所在目录,生成代码的目标目录
|
||||
* --service 服务名称,非必填,默认为proto文件所在目录名称,但是,如果proto所在目录为一下结构:
|
||||
```shell script
|
||||
user
|
||||
├── cmd
|
||||
│ └── rpc
|
||||
│ └── user.proto
|
||||
```
|
||||
则服务名称亦为user,而非proto所在文件夹名称了,这里推荐使用这种结构,可以方便在同一个服务名下建立不同类型的服务(api、rpc、mq等),便于代码管理与维护。
|
||||
* --shared[⚠️已废弃] 非必填,默认为$dir(xxx.proto)/shared,rpc client逻辑代码存放目录。
|
||||
|
||||
> 注意:这里的shared文件夹名称将会是代码中的package名称。
|
||||
|
||||
* --idea 非必填,是否为idea插件中执行,保留字段,终端执行可以忽略
|
||||
|
||||
# 开发人员需要做什么
|
||||
|
||||
关注业务代码编写,将重复性、与业务无关的工作交给goctl,生成好rpc服务代码后,开饭人员仅需要修改
|
||||
* 服务中的配置文件编写(etc/xx.json、internal/config/config.go)
|
||||
* 服务中业务逻辑编写(internal/logic/xxlogic.go)
|
||||
* 服务中资源上下文的编写(internal/svc/servicecontext.go)
|
||||
|
||||
# 扩展
|
||||
对于需要进行rpc mock的开发人员,在安装了`mockgen`工具的前提下可以在rpc的shared文件中生成好对应的mock文件。
|
||||
|
||||
# 注意事项
|
||||
* `google.golang.org/grpc`需要降级到v1.26.0,且protoc-gen-go版本不能高于v1.3.2(see [https://github.com/grpc/grpc-go/issues/3347](https://github.com/grpc/grpc-go/issues/3347))即
|
||||
```
|
||||
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0
|
||||
```
|
||||
* proto不支持暂多文件同时生成
|
||||
* proto不支持外部依赖包引入,message不支持inline
|
||||
* 目前main文件、shared文件、handler文件会被强制覆盖,而和开发人员手动需要编写的则不会覆盖生成,这一类在代码头部均有
|
||||
```shell script
|
||||
// Code generated by goctl. DO NOT EDIT!
|
||||
// Source: xxx.proto
|
||||
```
|
||||
的标识,请注意不要将也写业务性代码写在里面。
|
||||
|
||||
# 常见问题解决(go mod工程)
|
||||
|
||||
* 错误一:
|
||||
|
||||
```golang
|
||||
pb/xx.pb.go:220:7: undefined: grpc.ClientConnInterface
|
||||
pb/xx.pb.go:224:11: undefined: grpc.SupportPackageIsVersion6
|
||||
pb/xx.pb.go:234:5: undefined: grpc.ClientConnInterface
|
||||
pb/xx.pb.go:237:24: undefined: grpc.ClientConnInterface
|
||||
```
|
||||
解决方法:请将`protoc-gen-go`版本降至v1.3.2及一下
|
||||
|
||||
* 错误二:
|
||||
|
||||
```golang
|
||||
|
||||
# go.etcd.io/etcd/clientv3/balancer/picker
|
||||
../../../go/pkg/mod/go.etcd.io/etcd@v0.0.0-20200402134248-51bdeb39e698/clientv3/balancer/picker/err.go:25:9: cannot use &errPicker literal (type *errPicker) as type Picker in return argument:*errPicker does not implement Picker (wrong type for Pick method)
|
||||
have Pick(context.Context, balancer.PickInfo) (balancer.SubConn, func(balancer.DoneInfo), error)
|
||||
want Pick(balancer.PickInfo) (balancer.PickResult, error)
|
||||
../../../go/pkg/mod/go.etcd.io/etcd@v0.0.0-20200402134248-51bdeb39e698/clientv3/balancer/picker/roundrobin_balanced.go:33:9: cannot use &rrBalanced literal (type *rrBalanced) as type Picker in return argument:
|
||||
*rrBalanced does not implement Picker (wrong type for Pick method)
|
||||
have Pick(context.Context, balancer.PickInfo) (balancer.SubConn, func(balancer.DoneInfo), error)
|
||||
want Pick(balancer.PickInfo) (balancer.PickResult, error)
|
||||
#github.com/tal-tech/go-zero/rpcx/internal/balancer/p2c
|
||||
../../../go/pkg/mod/github.com/tal-tech/go-zero@v1.0.12/rpcx/internal/balancer/p2c/p2c.go:41:32: not enough arguments in call to base.NewBalancerBuilder
|
||||
have (string, *p2cPickerBuilder)
|
||||
want (string, base.PickerBuilder, base.Config)
|
||||
../../../go/pkg/mod/github.com/tal-tech/go-zero@v1.0.12/rpcx/internal/balancer/p2c/p2c.go:58:9: cannot use &p2cPicker literal (type *p2cPicker) as type balancer.Picker in return argument:
|
||||
*p2cPicker does not implement balancer.Picker (wrong type for Pick method)
|
||||
have Pick(context.Context, balancer.PickInfo) (balancer.SubConn, func(balancer.DoneInfo), error)
|
||||
want Pick(balancer.PickInfo) (balancer.PickResult, error)
|
||||
```
|
||||
|
||||
解决方法:
|
||||
|
||||
```golang
|
||||
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0
|
||||
```
|
||||
18
doc/goctl.md
@@ -7,6 +7,11 @@
|
||||
* 生成MongoDB CURD+Cache
|
||||
|
||||
## goctl使用说明
|
||||
|
||||
#### 快速生成服务
|
||||
* api: goctl api new xxxx
|
||||
* rpc: goctl rpc new xxxx
|
||||
|
||||
#### goctl参数说明
|
||||
|
||||
`goctl api [go/java/ts] [-api user/user.api] [-dir ./src]`
|
||||
@@ -17,17 +22,10 @@
|
||||
|
||||
> -dir 自定义生成目录
|
||||
|
||||
#### 保持goctl总是最新版
|
||||
|
||||
第一次运行会在~/.goctl里增加下面两行:
|
||||
|
||||
```
|
||||
url = http://47.97.184.41:7777/
|
||||
```
|
||||
|
||||
#### API 语法说明
|
||||
|
||||
```
|
||||
``` golang
|
||||
|
||||
info(
|
||||
title: doc title
|
||||
desc: >
|
||||
@@ -123,7 +121,9 @@ service user-api {
|
||||
)
|
||||
head /api/ping()
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
1. info部分:描述了api基本信息,比如Auth,api是哪个用途。
|
||||
2. type部分:type类型声明和golang语法兼容。
|
||||
3. service部分:service代表一组服务,一个服务可以由多组名称相同的service组成,可以针对每一组service配置jwt和auth认证,另外通过folder属性可以指定service生成所在子目录。
|
||||
|
||||
BIN
doc/images/api-gen.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
doc/images/architecture-en.png
Normal file
|
After Width: | Height: | Size: 286 KiB |
BIN
doc/images/bookstore-api.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
doc/images/bookstore-arch.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
doc/images/bookstore-benchmark.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
doc/images/bookstore-model.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
doc/images/bookstore-rpc.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
doc/images/model-gen.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
doc/images/mr.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
doc/images/mr_time.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
doc/images/resilience-en.png
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
doc/images/rpc-gen.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
doc/images/wechat.jpg
Normal file
|
After Width: | Height: | Size: 125 KiB |
197
doc/mapreduce.md
@@ -1,29 +1,184 @@
|
||||
# mapreduce用法
|
||||
# 通过MapReduce降低服务响应时间
|
||||
|
||||
## Map
|
||||
在微服务中开发中,api网关扮演对外提供restful api的角色,而api的数据往往会依赖其他服务,复杂的api更是会依赖多个甚至数十个服务。虽然单个被依赖服务的耗时一般都比较低,但如果多个服务串行依赖的话那么整个api的耗时将会大大增加。
|
||||
|
||||
> channel是Map的返回值
|
||||
那么通过什么手段来优化呢?我们首先想到的是通过并发来的方式来处理依赖,这样就能降低整个依赖的耗时,Go基础库中为我们提供了 [WaitGroup](https://golang.org/pkg/sync/#WaitGroup) 工具用来进行并发控制,但实际业务场景中多个依赖如果有一个出错我们期望能立即返回而不是等所有依赖都执行完再返回结果,而且WaitGroup中对变量的赋值往往需要加锁,每个依赖函数都需要添加Add和Done对于新手来说比较容易出错
|
||||
|
||||
由于Map是个并发操作,如果不用range或drain的方式,那么在使用返回值的时候,可能Map里面的代码还在读写这个返回值,可能导致数据不全或者`concurrent read write错误`
|
||||
基于以上的背景,go-zero框架中为我们提供了并发处理工具[MapReduce](https://github.com/tal-tech/go-zero/blob/master/core/mr/mapreduce.go),该工具开箱即用,不需要做什么初始化,我们通过下图看下使用MapReduce和没使用的耗时对比:
|
||||
|
||||
* 如果需要收集Map生成的结果,那么使用如下方式
|
||||

|
||||
|
||||
```
|
||||
for v := range channel {
|
||||
// v is with type interface{}
|
||||
相同的依赖,串行处理的话需要200ms,使用MapReduce后的耗时等于所有依赖中最大的耗时为100ms,可见MapReduce可以大大降低服务耗时,而且随着依赖的增加效果就会越明显,减少处理耗时的同时并不会增加服务器压力
|
||||
|
||||
## 并发处理工具[MapReduce](https://github.com/tal-tech/go-zero/tree/master/core/mr)
|
||||
|
||||
[MapReduce](https://zh.wikipedia.org/wiki/MapReduce)是Google提出的一个软件架构,用于大规模数据集的并行运算,go-zero中的MapReduce工具正是借鉴了这种架构思想
|
||||
|
||||
go-zero框架中的MapReduce工具主要用来对批量数据进行并发的处理,以此来提升服务的性能
|
||||
|
||||

|
||||
|
||||
我们通过几个示例来演示MapReduce的用法
|
||||
|
||||
MapReduce主要有三个参数,第一个参数为generate用以生产数据,第二个参数为mapper用以对数据进行处理,第三个参数为reducer用以对mapper后的数据做聚合返回,还可以通过opts选项设置并发处理的线程数量
|
||||
|
||||
场景一: 某些功能的结果往往需要依赖多个服务,比如商品详情的结果往往会依赖用户服务、库存服务、订单服务等等,一般被依赖的服务都是以rpc的形式对外提供,为了降低依赖的耗时我们往往需要对依赖做并行处理
|
||||
|
||||
```
|
||||
func productDetail(uid, pid int64) (*ProductDetail, error) {
|
||||
var pd ProductDetail
|
||||
err := mr.Finish(func() (err error) {
|
||||
pd.User, err = userRpc.User(uid)
|
||||
return
|
||||
}, func() (err error) {
|
||||
pd.Store, err = storeRpc.Store(pid)
|
||||
return
|
||||
}, func() (err error) {
|
||||
pd.Order, err = orderRpc.Order(pid)
|
||||
return
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Printf("product detail error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
```
|
||||
|
||||
* 如果不需要收集结果,那么就需要显式的调用mapreduce.Drain,如
|
||||
|
||||
```
|
||||
mapreduce.Drain(channel)
|
||||
```
|
||||
|
||||
## MapReduce
|
||||
return &pd, nil
|
||||
}
|
||||
```
|
||||
该示例中返回商品详情依赖了多个服务获取数据,因此做并发的依赖处理,对接口的性能有很大的提升
|
||||
|
||||
场景二: 很多时候我们需要对一批数据进行处理,比如对一批用户id,效验每个用户的合法性并且效验过程中有一个出错就认为效验失败,返回的结果为效验合法的用户id
|
||||
|
||||
```
|
||||
func checkLegal(uids []int64) ([]int64, error) {
|
||||
r, err := mr.MapReduce(func(source chan<- interface{}) {
|
||||
for _, uid := range uids {
|
||||
source <- uid
|
||||
}
|
||||
}, func(item interface{}, writer mr.Writer, cancel func(error)) {
|
||||
uid := item.(int64)
|
||||
ok, err := check(uid)
|
||||
if err != nil {
|
||||
cancel(err)
|
||||
}
|
||||
if ok {
|
||||
writer.Write(uid)
|
||||
}
|
||||
}, func(pipe <-chan interface{}, writer mr.Writer, cancel func(error)) {
|
||||
var uids []int64
|
||||
for p := range pipe {
|
||||
uids = append(uids, p.(int64))
|
||||
}
|
||||
writer.Write(uids)
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("check error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.([]int64), nil
|
||||
}
|
||||
|
||||
func check(uid int64) (bool, error) {
|
||||
// do something check user legal
|
||||
return true, nil
|
||||
}
|
||||
```
|
||||
该示例中,如果check过程出现错误则通过cancel方法结束效验过程,并返回error整个效验过程结束,如果某个uid效验结果为false则最终结果不返回该uid
|
||||
|
||||
**MapReduce使用注意事项**
|
||||
* mapper和reducer中都可以调用cancel,参数为error,调用后立即返回,返回结果为nil, error
|
||||
* mapper中如果不调用writer.Write则item最终不会被reducer聚合
|
||||
* reducer中如果不调用writer.Wirte则返回结果为nil, ErrReduceNoOutput
|
||||
* reducer为单线程,所有mapper出来的结果在这里串行聚合
|
||||
|
||||
***实现原理分析:***
|
||||
|
||||
MapReduce中首先通过buildSource方法通过执行generate(参数为无缓冲channel)产生数据,并返回无缓冲的channel,mapper会从该channel中读取数据
|
||||
```
|
||||
func buildSource(generate GenerateFunc) chan interface{} {
|
||||
source := make(chan interface{})
|
||||
go func() {
|
||||
defer close(source)
|
||||
generate(source)
|
||||
}()
|
||||
|
||||
return source
|
||||
}
|
||||
```
|
||||
|
||||
在MapReduceWithSource方法中定义了cancel方法,mapper和reducer中都可以调用该方法,调用后主线程收到close信号会立马返回
|
||||
```
|
||||
cancel := once(func(err error) {
|
||||
if err != nil {
|
||||
retErr.Set(err)
|
||||
} else {
|
||||
// 默认的error
|
||||
retErr.Set(ErrCancelWithNil)
|
||||
}
|
||||
|
||||
drain(source)
|
||||
// 调用close(ouput)主线程收到Done信号,立马返回
|
||||
finish()
|
||||
})
|
||||
```
|
||||
|
||||
在mapperDispatcher方法中调用了executeMappers,executeMappers消费buildSource产生的数据,每一个item都会起一个goroutine单独处理,默认最大并发数为16,可以通过WithWorkers进行设置
|
||||
```
|
||||
var wg sync.WaitGroup
|
||||
defer func() {
|
||||
wg.Wait() // 保证所有的item都处理完成
|
||||
close(collector)
|
||||
}()
|
||||
|
||||
pool := make(chan lang.PlaceholderType, workers)
|
||||
writer := newGuardedWriter(collector, done) // 将mapper处理完的数据写入collector
|
||||
for {
|
||||
select {
|
||||
case <-done: // 当调用了cancel会触发立即返回
|
||||
return
|
||||
case pool <- lang.Placeholder: // 控制最大并发数
|
||||
item, ok := <-input
|
||||
if !ok {
|
||||
<-pool
|
||||
return
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer func() {
|
||||
wg.Done()
|
||||
<-pool
|
||||
}()
|
||||
|
||||
mapper(item, writer) // 对item进行处理,处理完调用writer.Write把结果写入collector对应的channel中
|
||||
}()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
reducer单goroutine对数mapper写入collector的数据进行处理,如果reducer中没有手动调用writer.Write则最终会执行finish方法对output进行close避免死锁
|
||||
```
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
cancel(fmt.Errorf("%v", r))
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}()
|
||||
reducer(collector, writer, cancel)
|
||||
}()
|
||||
```
|
||||
|
||||
在该工具包中还提供了许多针对不同业务场景的方法,实现原理与MapReduce大同小异,感兴趣的同学可以查看源码学习
|
||||
* MapReduceVoid 功能和MapReduce类似但没有结果返回只返回error
|
||||
* Finish 处理固定数量的依赖,返回error,有一个error立即返回
|
||||
* FinishVoid 和Finish方法功能类似,没有返回值
|
||||
* Map 只做generate和mapper处理,返回channel
|
||||
* MapVoid 和Map功能类似,无返回
|
||||
|
||||
|
||||
本文主要介绍了go-zero框架中的MapReduce工具,在实际的项目中非常实用。用好工具对于提升服务性能和开发效率都有很大的帮助,希望本篇文章能给大家带来一些收获。
|
||||
|
||||
* mapper和reducer方法里可以调用cancel,调用了cancel之后返回值会是`nil, false`
|
||||
* mapper里面如果有item不写入writer,那么这个item就不会被reduce收集
|
||||
* mapper里面如果有处理item时panic,那么这个item也不会被reduce收集
|
||||
* reduce是单线程,所有mapper出来的结果在这里串行处理
|
||||
* reduce里面不写writer,或者panic,会导致返回`nil, false`
|
||||
536
doc/shorturl-en.md
Normal file
@@ -0,0 +1,536 @@
|
||||
English | [简体中文](shorturl.md)
|
||||
|
||||
# Rapid development of microservices
|
||||
|
||||
## 0. Why building microservices are so difficult?
|
||||
|
||||
To build a well working microservice, we need lots of knowledges from different aspects.
|
||||
|
||||
* basic functionalities
|
||||
1. concurrency control and rate limit, to avoid being brought down by unexpected inbound
|
||||
2. service discovery, make sure new or terminated nodes are detected asap
|
||||
3. load balancing, balance the traffic base on the throughput of nodes
|
||||
4. timeout control, avoid the nodes continue to process the timed out requests
|
||||
5. circuit breaker, load shedding, fail fast, protects the failure nodes to recover asap
|
||||
|
||||
* advanced functionalities
|
||||
1. authorization, make sure users can only access their own data
|
||||
2. tracing, to understand the whole system and locate the specific problem quickly
|
||||
3. logging, collects data and helps to backtrace problems
|
||||
4. observability, no metrics, no optimization
|
||||
|
||||
For any point listed above, we need a long article to describe the theory and the implementation. But for us, the developers, it’s very difficult to understand all the concepts and make it happen in our systems. Although, we can use the frameworks that have been well served busy sites. [go-zero](https://github.com/tal-tech/go-zero) is born for this purpose, especially for cloud-native microservice systems.
|
||||
|
||||
As well, we always adhere to the idea that **prefer tools over conventions and documents**. We hope to reduce the boilerplate code as much as possible, and let developers focus on developing the business related code. For this purpose, we developed the tool `goctl`.
|
||||
|
||||
Let’s take the shorturl microservice as a quick example to demonstrate how to quickly create microservices by using [go-zero](https://github.com/tal-tech/go-zero). After finishing this tutorial, you’ll find that it’s so easy to write microservices!
|
||||
|
||||
## 1. What is a shorturl service?
|
||||
|
||||
A shorturl service is that it converts a long url into a short one, by well designed algorithms.
|
||||
|
||||
Writting this shorturl service is to demonstrate the complete flow of creating a microservice by using go-zero. But algorithms and detail implementations are quite simplified, and this shorturl service is not suitable for production use.
|
||||
|
||||
## 2. Architecture of shorturl microservice
|
||||
|
||||
<img src="images/shorturl-arch.png" alt="Architecture" width="800" />
|
||||
|
||||
* In this tutorial, I only use one rpc service, transform, to demonstrate. It’s not telling that one API Gateway only can call one RPC service, it’s only for simplicity here.
|
||||
* In production, we should try best to isolate the data belongs to services, that means each service should only use its own database.
|
||||
|
||||
## 3. goctl generated code overview
|
||||
|
||||
All modules with green background are generated, and will be enabled when necessary. The modules with red background are handwritten code, which is typically business logic code.
|
||||
|
||||
* API Gateway
|
||||
|
||||
<img src="images/api-gen.png" alt="api" width="800" />
|
||||
|
||||
* RPC
|
||||
|
||||
<img src="images/rpc-gen.png" alt="rpc" width="800" />
|
||||
|
||||
* model
|
||||
|
||||
<img src="images/model-gen.png" alt="model" width="800" />
|
||||
|
||||
And now, let’s walk through the complete flow of quickly create a microservice with go-zero.
|
||||
|
||||
## 4. Get started
|
||||
|
||||
* install etcd, mysql, redis
|
||||
|
||||
* install goctl
|
||||
|
||||
```shell
|
||||
GO111MODULE=on go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
|
||||
* create the working dir `shorturl`
|
||||
|
||||
* in `shorturl` dir, execute `go mod init shorturl` to initialize `go.mod`
|
||||
|
||||
## 5. Write code for API Gateway
|
||||
|
||||
* use goctl to generate `api/shorturl.api`
|
||||
|
||||
```shell
|
||||
goctl api -o bookstore.api
|
||||
```
|
||||
|
||||
for simplicity, the leading `info` block is removed, and the code looks like:
|
||||
|
||||
```go
|
||||
type (
|
||||
expandReq struct {
|
||||
shorten string `form:"shorten"`
|
||||
}
|
||||
|
||||
expandResp struct {
|
||||
url string `json:"url"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
shortenReq struct {
|
||||
url string `form:"url"`
|
||||
}
|
||||
|
||||
shortenResp struct {
|
||||
shorten string `json:"shorten"`
|
||||
}
|
||||
)
|
||||
|
||||
service shorturl-api {
|
||||
@server(
|
||||
handler: ShortenHandler
|
||||
)
|
||||
get /shorten(shortenReq) returns(shortenResp)
|
||||
|
||||
@server(
|
||||
handler: ExpandHandler
|
||||
)
|
||||
get /expand(expandReq) returns(expandResp)
|
||||
}
|
||||
```
|
||||
|
||||
the usage of `type` keyword is the same as that in go, service is used to define get/post/head/delete api requests, described below:
|
||||
|
||||
* `service shorturl-api {` defines the service name
|
||||
* `@server` defines the properties that used in server side
|
||||
* `handler` defines the handler name
|
||||
* `get /shorten(shortenReq) returns(shortenResp)` defines this is a GET request, the request parameters, and the response parameters
|
||||
|
||||
* generate the code for API Gateway by using goctl
|
||||
|
||||
```shell
|
||||
goctl api go -api shorturl.api -dir .
|
||||
```
|
||||
|
||||
the generated file structure looks like:
|
||||
|
||||
```
|
||||
.
|
||||
├── api
|
||||
│ ├── etc
|
||||
│ │ └── shorturl-api.yaml // configuration file
|
||||
│ ├── internal
|
||||
│ │ ├── config
|
||||
│ │ │ └── config.go // configuration definition
|
||||
│ │ ├── handler
|
||||
│ │ │ ├── expandhandler.go // implements expandHandler
|
||||
│ │ │ ├── routes.go // routes definition
|
||||
│ │ │ └── shortenhandler.go // implements shortenHandler
|
||||
│ │ ├── logic
|
||||
│ │ │ ├── expandlogic.go // implements ExpandLogic
|
||||
│ │ │ └── shortenlogic.go // implements ShortenLogic
|
||||
│ │ ├── svc
|
||||
│ │ │ └── servicecontext.go // defines ServiceContext
|
||||
│ │ └── types
|
||||
│ │ └── types.go // defines request/response
|
||||
│ ├── shorturl.api
|
||||
│ └── shorturl.go // main entrance
|
||||
├── go.mod
|
||||
└── go.sum
|
||||
```
|
||||
|
||||
* start API Gateway service, listens on port 8888 by default
|
||||
|
||||
```shell
|
||||
go run shorturl.go -f etc/shorturl-api.yaml
|
||||
```
|
||||
|
||||
* test API Gateway service
|
||||
|
||||
```shell
|
||||
curl -i "http://localhost:8888/shorten?url=http://www.xiaoheiban.cn"
|
||||
```
|
||||
|
||||
response like:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Thu, 27 Aug 2020 14:31:39 GMT
|
||||
Content-Length: 15
|
||||
|
||||
{"shortUrl":""}
|
||||
```
|
||||
|
||||
You can see that the API Gateway service did nothing except returned a zero value. And let’s implement the business logic in rpc service.
|
||||
|
||||
* you can modify `internal/svc/servicecontext.go` to pass dependencies if needed
|
||||
|
||||
* implement logic in package `internal/logic`
|
||||
|
||||
* you can use goctl to generate code for clients base on the .api file
|
||||
|
||||
* till now, the client engineer can work with the api, don’t need to wait for the implementation of server side
|
||||
|
||||
## 6. Write code for transform rpc service
|
||||
|
||||
* under directory `rpc/transform` create `transform.proto` file
|
||||
|
||||
```shell
|
||||
goctl rpc template -o transform.proto
|
||||
```
|
||||
|
||||
edit the file and make the code looks like:
|
||||
|
||||
```protobuf
|
||||
syntax = "proto3";
|
||||
|
||||
package transform;
|
||||
|
||||
message expandReq {
|
||||
string shorten = 1;
|
||||
}
|
||||
|
||||
message expandResp {
|
||||
string url = 1;
|
||||
}
|
||||
|
||||
message shortenReq {
|
||||
string url = 1;
|
||||
}
|
||||
|
||||
message shortenResp {
|
||||
string shorten = 1;
|
||||
}
|
||||
|
||||
service transformer {
|
||||
rpc expand(expandReq) returns(expandResp);
|
||||
rpc shorten(shortenReq) returns(shortenResp);
|
||||
}
|
||||
```
|
||||
|
||||
* use goctl to generate the rpc code, execute the following command in `rpc/transofrm`
|
||||
|
||||
```shell
|
||||
goctl rpc proto -src transform.proto
|
||||
```
|
||||
|
||||
the generated file structure looks like:
|
||||
|
||||
```
|
||||
rpc/transform
|
||||
├── etc
|
||||
│ └── transform.yaml // configuration file
|
||||
├── internal
|
||||
│ ├── config
|
||||
│ │ └── config.go // configuration definition
|
||||
│ ├── logic
|
||||
│ │ ├── expandlogic.go // implements expand logic
|
||||
│ │ └── shortenlogic.go // implements shorten logic
|
||||
│ ├── server
|
||||
│ │ └── transformerserver.go // rpc handler
|
||||
│ └── svc
|
||||
│ └── servicecontext.go // defines service context, like dependencies
|
||||
├── pb
|
||||
│ └── transform.pb.go
|
||||
├── transform.go // rpc main entrance
|
||||
├── transform.proto
|
||||
└── transformer
|
||||
├── transformer.go // defines how rpc clients call this service
|
||||
├── transformer_mock.go // mock file, for test purpose
|
||||
└── types.go // request/response definition
|
||||
```
|
||||
|
||||
just run it, looks like:
|
||||
|
||||
```shell
|
||||
$ go run transform.go -f etc/transform.yaml
|
||||
Starting rpc server at 127.0.0.1:8080...
|
||||
```
|
||||
|
||||
you can change the listening port in file `etc/transform.yaml`.
|
||||
|
||||
## 7. Modify API Gateway to call transform rpc service
|
||||
|
||||
* modify the configuration file `shorturl-api.yaml`, add the following:
|
||||
|
||||
```yaml
|
||||
Transform:
|
||||
Etcd:
|
||||
Hosts:
|
||||
- localhost:2379
|
||||
Key: transform.rpc
|
||||
```
|
||||
|
||||
automatically discover the transform service by using etcd.
|
||||
|
||||
* modify the file `internal/config/config.go`, add dependency on transform service:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
rest.RestConf
|
||||
Transform rpcx.RpcClientConf // manual code
|
||||
}
|
||||
```
|
||||
|
||||
* modify the file `internal/svc/servicecontext.go`, like below:
|
||||
|
||||
```go
|
||||
type ServiceContext struct {
|
||||
Config config.Config
|
||||
Transformer transformer.Transformer // manual code
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{
|
||||
Config: c,
|
||||
Transformer: transformer.NewTransformer(rpcx.MustNewClient(c.Transform)), // manual code
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
passing the dependencies among services within ServiceContext.
|
||||
|
||||
* modify the method `Expand` in the file `internal/logic/expandlogic.go`, looks like:
|
||||
|
||||
```go
|
||||
func (l *ExpandLogic) Expand(req types.ExpandReq) (*types.ExpandResp, error) {
|
||||
// manual code start
|
||||
resp, err := l.svcCtx.Transformer.Expand(l.ctx, &transformer.ExpandReq{
|
||||
Shorten: req.Shorten,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.ExpandResp{
|
||||
Url: resp.Url,
|
||||
}, nil
|
||||
// manual code stop
|
||||
}
|
||||
```
|
||||
|
||||
by calling the method `Expand` of `transformer` to restore the shortened url.
|
||||
|
||||
* modify the file `internal/logic/shortenlogic.go`, looks like:
|
||||
|
||||
```go
|
||||
func (l *ShortenLogic) Shorten(req types.ShortenReq) (*types.ShortenResp, error) {
|
||||
// manual code start
|
||||
resp, err := l.svcCtx.Transformer.Shorten(l.ctx, &transformer.ShortenReq{
|
||||
Url: req.Url,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.ShortenResp{
|
||||
Shorten: resp.Shorten,
|
||||
}, nil
|
||||
// manual code stop
|
||||
}
|
||||
```
|
||||
|
||||
by calling the method `Shorten` of `transformer` to shorten the url.
|
||||
|
||||
Till now, we’ve done the modification of API Gateway. All the manually added code are marked.
|
||||
|
||||
## 8. Define the database schema, generate the code for CRUD+cache
|
||||
|
||||
* under shorturl, create the directory `rpc/transform/model`: `mkdir -p rpc/transform/model`
|
||||
|
||||
* under the directory rpc/transform/model create the file called shorturl.sql`, contents as below:
|
||||
|
||||
```sql
|
||||
CREATE TABLE `shorturl`
|
||||
(
|
||||
`shorten` varchar(255) NOT NULL COMMENT 'shorten key',
|
||||
`url` varchar(255) NOT NULL COMMENT 'original url',
|
||||
PRIMARY KEY(`shorten`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
```
|
||||
|
||||
* create DB and table
|
||||
|
||||
```sql
|
||||
create database gozero;
|
||||
```
|
||||
|
||||
```sql
|
||||
source shorturl.sql;
|
||||
```
|
||||
|
||||
* under the directory `rpc/transform/model execute the following command to genrate CRUD+cache code, `-c` means using `redis cache`
|
||||
|
||||
```shell
|
||||
goctl model mysql ddl -c -src shorturl.sql -dir .
|
||||
```
|
||||
|
||||
you can also generate the code from the database url by using `datasource` subcommand instead of `ddl`
|
||||
|
||||
the generated file structure looks like:
|
||||
|
||||
```
|
||||
rpc/transform/model
|
||||
├── shorturl.sql
|
||||
├── shorturlmodel.go // CRUD+cache code
|
||||
└── vars.go // const and var definition
|
||||
```
|
||||
|
||||
## 9. Modify shorten/expand rpc to call crud+cache
|
||||
|
||||
* modify `rpc/transform/etc/transform.yaml`, add the following:
|
||||
|
||||
```yaml
|
||||
DataSource: root:@tcp(localhost:3306)/gozero
|
||||
Table: shorturl
|
||||
Cache:
|
||||
- Host: localhost:6379
|
||||
```
|
||||
|
||||
you can use multiple redis as cache. redis node and cluster are both supported.
|
||||
|
||||
* modify `rpc/transform/internal/config.go`, like below:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
rpcx.RpcServerConf
|
||||
DataSource string // manual code
|
||||
Table string // manual code
|
||||
Cache cache.CacheConf // manual code
|
||||
}
|
||||
```
|
||||
|
||||
added the configuration for mysql and redis cache.
|
||||
|
||||
* modify `rpc/transform/internal/svc/servicecontext.go`, like below:
|
||||
|
||||
```go
|
||||
type ServiceContext struct {
|
||||
c config.Config
|
||||
Model *model.ShorturlModel // manual code
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{
|
||||
c: c,
|
||||
Model: model.NewShorturlModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // manual code
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* modify `rpc/transform/internal/logic/expandlogic.go`, like below:
|
||||
|
||||
```go
|
||||
func (l *ExpandLogic) Expand(in *transform.ExpandReq) (*transform.ExpandResp, error) {
|
||||
// manual code start
|
||||
res, err := l.svcCtx.Model.FindOne(in.Shorten)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &transform.ExpandResp{
|
||||
Url: res.Url,
|
||||
}, nil
|
||||
// manual code stop
|
||||
}
|
||||
```
|
||||
|
||||
* modify `rpc/shorten/internal/logic/shortenlogic.go`, looks like:
|
||||
|
||||
```go
|
||||
func (l *ShortenLogic) Shorten(in *transform.ShortenReq) (*transform.ShortenResp, error) {
|
||||
// manual code start, generates shorturl
|
||||
key := hash.Md5Hex([]byte(in.Url))[:6]
|
||||
_, err := l.svcCtx.Model.Insert(model.Shorturl{
|
||||
Shorten: key,
|
||||
Url: in.Url,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &transform.ShortenResp{
|
||||
Shorten: key,
|
||||
}, nil
|
||||
// manual code stop
|
||||
}
|
||||
```
|
||||
|
||||
till now, we finished modifing the code, all the modified code is marked.
|
||||
|
||||
## 10. Call shorten and expand services
|
||||
|
||||
* call shorten api
|
||||
|
||||
```shell
|
||||
curl -i "http://localhost:8888/shorten?url=http://www.xiaoheiban.cn"
|
||||
```
|
||||
|
||||
response like:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Sat, 29 Aug 2020 10:49:49 GMT
|
||||
Content-Length: 21
|
||||
|
||||
{"shorten":"f35b2a"}
|
||||
```
|
||||
|
||||
* call expand api
|
||||
|
||||
```shell
|
||||
curl -i "http://localhost:8888/expand?shorten=f35b2a"
|
||||
```
|
||||
|
||||
response like:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Sat, 29 Aug 2020 10:51:53 GMT
|
||||
Content-Length: 34
|
||||
|
||||
{"url":"http://www.xiaoheiban.cn"}
|
||||
```
|
||||
|
||||
## 11. Benchmark
|
||||
|
||||
Because benchmarking the write requests depends on the write throughput of mysql, we only benchmarked the expand api. We read the data from mysql and cache it in redis. I chose 100 hot keys hardcoded in shorten.lua to generate the benchmark.
|
||||
|
||||

|
||||
|
||||
as shown above, in my MacBook Pro, the QPS is like 30K+.
|
||||
|
||||
## 12. Full code
|
||||
|
||||
[https://github.com/tal-tech/go-zero/tree/master/example/shorturl](https://github.com/tal-tech/go-zero/tree/master/example/shorturl)
|
||||
|
||||
## 13. Conclusion
|
||||
|
||||
We always adhere to **prefer tools over conventions and documents**.
|
||||
|
||||
go-zero is not only a framework, but also a tool to simplify and standardize the building of micoservice systems.
|
||||
|
||||
We not only keep the framework simple, but also encapsulate the complexity into the framework. And the developers are free from building the difficult and boilerplate code. Then we get the rapid development and less failure.
|
||||
|
||||
For the generated code by goctl, lots of microservice components are included, like concurrency control, adaptive circuit breaker, adaptive load shedding, auto cache control etc. And it’s easy to deal with the busy sites.
|
||||
|
||||
If you have any ideas that can help us to improve the productivity, tell me any time! 👏
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
[English](shorturl-en.md) | 简体中文
|
||||
|
||||
# 快速构建高并发微服务
|
||||
|
||||
## 0. 为什么说做好微服务很难?
|
||||
@@ -61,7 +63,7 @@
|
||||
* 安装goctl工具
|
||||
|
||||
```shell
|
||||
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get github.com/tal-tech/go-zero/tools/goctl
|
||||
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
|
||||
* 创建工作目录`shorturl`
|
||||
@@ -287,13 +289,13 @@
|
||||
```go
|
||||
type ServiceContext struct {
|
||||
Config config.Config
|
||||
Transformer rpcx.Client // 手动代码
|
||||
Transformer transformer.Transformer // 手动代码
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{
|
||||
Config: c,
|
||||
Transformer: rpcx.MustNewClient(c.Transform), // 手动代码
|
||||
Transformer: transformer.NewTransformer(rpcx.MustNewClient(c.Transform)), // 手动代码
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -305,8 +307,7 @@
|
||||
```go
|
||||
func (l *ExpandLogic) Expand(req types.ExpandReq) (*types.ExpandResp, error) {
|
||||
// 手动代码开始
|
||||
trans := transformer.NewTransformer(l.svcCtx.Transformer)
|
||||
resp, err := trans.Expand(l.ctx, &transformer.ExpandReq{
|
||||
resp, err := l.svcCtx.Transformer.Expand(l.ctx, &transformer.ExpandReq{
|
||||
Shorten: req.Shorten,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -319,16 +320,16 @@
|
||||
// 手动代码结束
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
通过调用`transformer`的`Expand`方法实现短链恢复到url
|
||||
通过调用`transformer`的`Expand`方法实现短链恢复到url
|
||||
|
||||
* 修改`internal/logic/shortenlogic.go`,如下:
|
||||
|
||||
```go
|
||||
func (l *ShortenLogic) Shorten(req types.ShortenReq) (*types.ShortenResp, error) {
|
||||
// 手动代码开始
|
||||
trans := transformer.NewTransformer(l.svcCtx.Transformer)
|
||||
resp, err := trans.Shorten(l.ctx, &transformer.ShortenReq{
|
||||
resp, err := l.svcCtx.Transformer.Shorten(l.ctx, &transformer.ShortenReq{
|
||||
Url: req.Url,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -341,10 +342,11 @@
|
||||
// 手动代码结束
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
通过调用`transformer`的`Shorten`方法实现url到短链的变换
|
||||
通过调用`transformer`的`Shorten`方法实现url到短链的变换
|
||||
|
||||
至此,API Gateway修改完成,虽然贴的代码多,但是期中修改的是很少的一部分,为了方便理解上下文,我贴了完整代码,接下来处理CRUD+cache
|
||||
至此,API Gateway修改完成,虽然贴的代码多,但是期中修改的是很少的一部分,为了方便理解上下文,我贴了完整代码,接下来处理CRUD+cache
|
||||
|
||||
## 8. 定义数据库表结构,并生成CRUD+cache代码
|
||||
|
||||
@@ -433,7 +435,7 @@
|
||||
* 修改`rpc/transform/internal/logic/expandlogic.go`,如下:
|
||||
|
||||
```go
|
||||
func (l *ExpandLogic) Expand(in *expand.ExpandReq) (*expand.ExpandResp, error) {
|
||||
func (l *ExpandLogic) Expand(in *transform.ExpandReq) (*transform.ExpandResp, error) {
|
||||
// 手动代码开始
|
||||
res, err := l.svcCtx.Model.FindOne(in.Shorten)
|
||||
if err != nil {
|
||||
@@ -450,7 +452,7 @@
|
||||
* 修改`rpc/shorten/internal/logic/shortenlogic.go`,如下:
|
||||
|
||||
```go
|
||||
func (l *ShortenLogic) Shorten(in *shorten.ShortenReq) (*shorten.ShortenResp, error) {
|
||||
func (l *ShortenLogic) Shorten(in *transform.ShortenReq) (*transform.ShortenResp, error) {
|
||||
// 手动代码开始,生成短链接
|
||||
key := hash.Md5Hex([]byte(in.Url))[:6]
|
||||
_, err := l.svcCtx.Model.Insert(model.Shorturl{
|
||||
@@ -514,6 +516,10 @@
|
||||
|
||||
可以看出在我的MacBook Pro上能达到3万+的qps。
|
||||
|
||||
## 12. 完整代码
|
||||
|
||||
[https://github.com/tal-tech/go-zero/tree/master/example/shorturl](https://github.com/tal-tech/go-zero/tree/master/example/shorturl)
|
||||
|
||||
## 12. 总结
|
||||
|
||||
我们一直强调**工具大于约定和文档**。
|
||||
|
||||
33
example/bookstore/api/bookstore.api
Normal file
@@ -0,0 +1,33 @@
|
||||
type (
|
||||
addReq struct {
|
||||
book string `form:"book"`
|
||||
price int64 `form:"price"`
|
||||
}
|
||||
|
||||
addResp struct {
|
||||
ok bool `json:"ok"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
checkReq struct {
|
||||
book string `form:"book"`
|
||||
}
|
||||
|
||||
checkResp struct {
|
||||
found bool `json:"found"`
|
||||
price int64 `json:"price"`
|
||||
}
|
||||
)
|
||||
|
||||
service bookstore-api {
|
||||
@server(
|
||||
handler: AddHandler
|
||||
)
|
||||
get /add(addReq) returns(addResp)
|
||||
|
||||
@server(
|
||||
handler: CheckHandler
|
||||
)
|
||||
get /check(checkReq) returns(checkResp)
|
||||
}
|
||||
27
example/bookstore/api/bookstore.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bookstore/api/internal/config"
|
||||
"bookstore/api/internal/handler"
|
||||
"bookstore/api/internal/svc"
|
||||
"flag"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/conf"
|
||||
"github.com/tal-tech/go-zero/rest"
|
||||
)
|
||||
|
||||
var configFile = flag.String("f", "etc/bookstore-api.yaml", "the config file")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var c config.Config
|
||||
conf.MustLoad(*configFile, &c)
|
||||
|
||||
ctx := svc.NewServiceContext(c)
|
||||
server := rest.MustNewServer(c.RestConf)
|
||||
defer server.Stop()
|
||||
|
||||
handler.RegisterHandlers(server, ctx)
|
||||
server.Start()
|
||||
}
|
||||
13
example/bookstore/api/etc/bookstore-api.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
Name: bookstore-api
|
||||
Host: 0.0.0.0
|
||||
Port: 8888
|
||||
Add:
|
||||
Etcd:
|
||||
Hosts:
|
||||
- localhost:2379
|
||||
Key: add.rpc
|
||||
Check:
|
||||
Etcd:
|
||||
Hosts:
|
||||
- localhost:2379
|
||||
Key: check.rpc
|
||||
12
example/bookstore/api/internal/config/config.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/rest"
|
||||
"github.com/tal-tech/go-zero/rpcx"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
rest.RestConf
|
||||
Add rpcx.RpcClientConf
|
||||
Check rpcx.RpcClientConf
|
||||
}
|
||||
28
example/bookstore/api/internal/handler/addhandler.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bookstore/api/internal/logic"
|
||||
"bookstore/api/internal/svc"
|
||||
"bookstore/api/internal/types"
|
||||
"net/http"
|
||||
|
||||
"github.com/tal-tech/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func addHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.AddReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
httpx.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := logic.NewAddLogic(r.Context(), ctx)
|
||||
resp, err := l.Add(req)
|
||||
if err != nil {
|
||||
httpx.Error(w, err)
|
||||
} else {
|
||||
httpx.WriteJson(w, http.StatusOK, resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
28
example/bookstore/api/internal/handler/checkhandler.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bookstore/api/internal/logic"
|
||||
"bookstore/api/internal/svc"
|
||||
"bookstore/api/internal/types"
|
||||
"net/http"
|
||||
|
||||
"github.com/tal-tech/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func checkHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.CheckReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
httpx.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := logic.NewCheckLogic(r.Context(), ctx)
|
||||
resp, err := l.Check(req)
|
||||
if err != nil {
|
||||
httpx.Error(w, err)
|
||||
} else {
|
||||
httpx.WriteJson(w, http.StatusOK, resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
24
example/bookstore/api/internal/handler/routes.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// DO NOT EDIT, generated by goctl
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bookstore/api/internal/svc"
|
||||
"net/http"
|
||||
|
||||
"github.com/tal-tech/go-zero/rest"
|
||||
)
|
||||
|
||||
func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
engine.AddRoutes([]rest.Route{
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/add",
|
||||
Handler: addHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/check",
|
||||
Handler: checkHandler(serverCtx),
|
||||
},
|
||||
})
|
||||
}
|
||||
38
example/bookstore/api/internal/logic/addlogic.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"bookstore/api/internal/svc"
|
||||
"bookstore/api/internal/types"
|
||||
"bookstore/rpc/add/adder"
|
||||
"context"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type AddLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewAddLogic(ctx context.Context, svcCtx *svc.ServiceContext) AddLogic {
|
||||
return AddLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AddLogic) Add(req types.AddReq) (*types.AddResp, error) {
|
||||
resp, err := l.svcCtx.Adder.Add(l.ctx, &adder.AddReq{
|
||||
Book: req.Book,
|
||||
Price: req.Price,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.AddResp{
|
||||
Ok: resp.Ok,
|
||||
}, nil
|
||||
}
|
||||
38
example/bookstore/api/internal/logic/checklogic.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"bookstore/api/internal/svc"
|
||||
"bookstore/api/internal/types"
|
||||
"bookstore/rpc/check/checker"
|
||||
"context"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type CheckLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewCheckLogic(ctx context.Context, svcCtx *svc.ServiceContext) CheckLogic {
|
||||
return CheckLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *CheckLogic) Check(req types.CheckReq) (*types.CheckResp, error) {
|
||||
resp, err := l.svcCtx.Checker.Check(l.ctx, &checker.CheckReq{
|
||||
Book: req.Book,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.CheckResp{
|
||||
Found: resp.Found,
|
||||
Price: resp.Price,
|
||||
}, nil
|
||||
}
|
||||
23
example/bookstore/api/internal/svc/servicecontext.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"bookstore/api/internal/config"
|
||||
"bookstore/rpc/add/adder"
|
||||
"bookstore/rpc/check/checker"
|
||||
|
||||
"github.com/tal-tech/go-zero/rpcx"
|
||||
)
|
||||
|
||||
type ServiceContext struct {
|
||||
Config config.Config
|
||||
Adder adder.Adder
|
||||
Checker checker.Checker
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{
|
||||
Config: c,
|
||||
Adder: adder.NewAdder(rpcx.MustNewClient(c.Add)),
|
||||
Checker: checker.NewChecker(rpcx.MustNewClient(c.Check)),
|
||||
}
|
||||
}
|
||||
20
example/bookstore/api/internal/types/types.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// DO NOT EDIT, generated by goctl
|
||||
package types
|
||||
|
||||
type AddReq struct {
|
||||
Book string `form:"book"`
|
||||
Price int64 `form:"price"`
|
||||
}
|
||||
|
||||
type AddResp struct {
|
||||
Ok bool `json:"ok"`
|
||||
}
|
||||
|
||||
type CheckReq struct {
|
||||
Book string `form:"book"`
|
||||
}
|
||||
|
||||
type CheckResp struct {
|
||||
Found bool `json:"found"`
|
||||
Price int64 `json:"price"`
|
||||
}
|
||||
11
example/bookstore/go.mod
Normal file
@@ -0,0 +1,11 @@
|
||||
module bookstore
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/golang/mock v1.4.3
|
||||
github.com/golang/protobuf v1.4.2
|
||||
github.com/tal-tech/go-zero v1.0.11
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
||||
google.golang.org/grpc v1.29.1
|
||||
)
|
||||
356
example/bookstore/go.sum
Normal file
@@ -0,0 +1,356 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
||||
github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/StackExchange/wmi v0.0.0-20170410192909-ea383cf3ba6e/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
|
||||
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28=
|
||||
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819/go.mod h1:MvzMVHq8BH2Ji/o8TGDocVA70byvLrAgFTxkEnmjO4Y=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||
github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U=
|
||||
github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-xorm/builder v0.3.4 h1:FxkeGB4Cggdw3tPwutLCpfjng2jugfkg6LDMrd/KsoY=
|
||||
github.com/go-xorm/builder v0.3.4/go.mod h1:KxkQkNN1DpPKTedxXyTQcmH+rXfvk4LZ9SOOBoZBAxw=
|
||||
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gops v0.3.7/go.mod h1:bj0cwMmX1X4XIJFTjR99R5sCxNssNJ8HebFNvoQlmgY=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
|
||||
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/keybase/go-ps v0.0.0-20161005175911-668c8856d999/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4 v2.5.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA=
|
||||
github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/tal-tech/go-zero v1.0.11 h1:vy5jfTVZZdlrycZS9PVrveKEytz4e5hReT7yuh4Oa6o=
|
||||
github.com/tal-tech/go-zero v1.0.11/go.mod h1:y2wBHTkxNJw79K9/wCSeDKzv2pCT6x45oOmXEsJdQK8=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
|
||||
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698 h1:jWtjCJX1qxhHISBMLRztWwR+EXkI7MJAF2HjHAE/x/I=
|
||||
go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698/go.mod h1:YoUyTScD3Vcv2RBm3eGVOq7i1ULiz3OuXoQFWOirmAM=
|
||||
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/automaxprocs v1.3.0 h1:II28aZoGdaglS5vVNnspf28lnZpXScxtIozx1lAjdb0=
|
||||
go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA=
|
||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
|
||||
go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20171017063910-8dbc5d05d6ed/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200410132612-ae9902aceb98/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f h1:ohwtWcCwB/fZUxh/vjazHorYmBnua3NmY3CAjwC7mEA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/goversion v1.0.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
39
example/bookstore/rpc/add/add.go
Executable file
@@ -0,0 +1,39 @@
|
||||
// Code generated by goctl. DO NOT EDIT!
|
||||
// Source: add.proto
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bookstore/rpc/add/internal/config"
|
||||
"bookstore/rpc/add/internal/server"
|
||||
"bookstore/rpc/add/internal/svc"
|
||||
add "bookstore/rpc/add/pb"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/conf"
|
||||
"github.com/tal-tech/go-zero/rpcx"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var configFile = flag.String("f", "etc/add.yaml", "the config file")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var c config.Config
|
||||
conf.MustLoad(*configFile, &c)
|
||||
ctx := svc.NewServiceContext(c)
|
||||
adderSrv := server.NewAdderServer(ctx)
|
||||
|
||||
s, err := rpcx.NewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
|
||||
add.RegisterAdderServer(grpcServer, adderSrv)
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
|
||||
s.Start()
|
||||
}
|
||||
16
example/bookstore/rpc/add/add.proto
Executable file
@@ -0,0 +1,16 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package add;
|
||||
|
||||
message addReq {
|
||||
string book = 1;
|
||||
int64 price = 2;
|
||||
}
|
||||
|
||||
message addResp {
|
||||
bool ok = 1;
|
||||
}
|
||||
|
||||
service adder {
|
||||
rpc add(addReq) returns(addResp);
|
||||
}
|
||||
62
example/bookstore/rpc/add/adder/adder.go
Executable file
@@ -0,0 +1,62 @@
|
||||
// Code generated by goctl. DO NOT EDIT!
|
||||
// Source: add.proto
|
||||
|
||||
//go:generate mockgen -destination ./adder_mock.go -package adder -source $GOFILE
|
||||
|
||||
package adder
|
||||
|
||||
import (
|
||||
add "bookstore/rpc/add/pb"
|
||||
"context"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/jsonx"
|
||||
"github.com/tal-tech/go-zero/rpcx"
|
||||
)
|
||||
|
||||
type (
|
||||
Adder interface {
|
||||
Add(ctx context.Context, in *AddReq) (*AddResp, error)
|
||||
}
|
||||
|
||||
defaultAdder struct {
|
||||
cli rpcx.Client
|
||||
}
|
||||
)
|
||||
|
||||
func NewAdder(cli rpcx.Client) Adder {
|
||||
return &defaultAdder{
|
||||
cli: cli,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultAdder) Add(ctx context.Context, in *AddReq) (*AddResp, error) {
|
||||
var request add.AddReq
|
||||
bts, err := jsonx.Marshal(in)
|
||||
if err != nil {
|
||||
return nil, errJsonConvert
|
||||
}
|
||||
|
||||
err = jsonx.Unmarshal(bts, &request)
|
||||
if err != nil {
|
||||
return nil, errJsonConvert
|
||||
}
|
||||
|
||||
client := add.NewAdderClient(m.cli.Conn())
|
||||
resp, err := client.Add(ctx, &request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ret AddResp
|
||||
bts, err = jsonx.Marshal(resp)
|
||||
if err != nil {
|
||||
return nil, errJsonConvert
|
||||
}
|
||||
|
||||
err = jsonx.Unmarshal(bts, &ret)
|
||||
if err != nil {
|
||||
return nil, errJsonConvert
|
||||
}
|
||||
|
||||
return &ret, nil
|
||||
}
|
||||
49
example/bookstore/rpc/add/adder/adder_mock.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: adder.go
|
||||
|
||||
// Package adder is a generated GoMock package.
|
||||
package adder
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockAdder is a mock of Adder interface
|
||||
type MockAdder struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockAdderMockRecorder
|
||||
}
|
||||
|
||||
// MockAdderMockRecorder is the mock recorder for MockAdder
|
||||
type MockAdderMockRecorder struct {
|
||||
mock *MockAdder
|
||||
}
|
||||
|
||||
// NewMockAdder creates a new mock instance
|
||||
func NewMockAdder(ctrl *gomock.Controller) *MockAdder {
|
||||
mock := &MockAdder{ctrl: ctrl}
|
||||
mock.recorder = &MockAdderMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockAdder) EXPECT() *MockAdderMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Add mocks base method
|
||||
func (m *MockAdder) Add(ctx context.Context, in *AddReq) (*AddResp, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Add", ctx, in)
|
||||
ret0, _ := ret[0].(*AddResp)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Add indicates an expected call of Add
|
||||
func (mr *MockAdderMockRecorder) Add(ctx, in interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockAdder)(nil).Add), ctx, in)
|
||||
}
|
||||
19
example/bookstore/rpc/add/adder/types.go
Executable file
@@ -0,0 +1,19 @@
|
||||
// Code generated by goctl. DO NOT EDIT!
|
||||
// Source: add.proto
|
||||
|
||||
package adder
|
||||
|
||||
import "errors"
|
||||
|
||||
var errJsonConvert = errors.New("json convert error")
|
||||
|
||||
type (
|
||||
AddReq struct {
|
||||
Book string `json:"book,omitempty"`
|
||||
Price int64 `json:"price,omitempty"`
|
||||
}
|
||||
|
||||
AddResp struct {
|
||||
Ok bool `json:"ok,omitempty"`
|
||||
}
|
||||
)
|
||||
10
example/bookstore/rpc/add/etc/add.yaml
Executable file
@@ -0,0 +1,10 @@
|
||||
Name: add.rpc
|
||||
ListenOn: 127.0.0.1:8080
|
||||
Etcd:
|
||||
Hosts:
|
||||
- 127.0.0.1:2379
|
||||
Key: add.rpc
|
||||
DataSource: root:@tcp(localhost:3306)/gozero
|
||||
Table: book
|
||||
Cache:
|
||||
- Host: localhost:6379
|
||||
13
example/bookstore/rpc/add/internal/config/config.go
Executable file
@@ -0,0 +1,13 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||
"github.com/tal-tech/go-zero/rpcx"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
rpcx.RpcServerConf
|
||||
DataSource string
|
||||
Table string
|
||||
Cache cache.CacheConf
|
||||
}
|
||||
38
example/bookstore/rpc/add/internal/logic/addlogic.go
Executable file
@@ -0,0 +1,38 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"bookstore/rpc/add/internal/svc"
|
||||
add "bookstore/rpc/add/pb"
|
||||
"bookstore/rpc/model"
|
||||
"context"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type AddLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func NewAddLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AddLogic {
|
||||
return &AddLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logx.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AddLogic) Add(in *add.AddReq) (*add.AddResp, error) {
|
||||
_, err := l.svcCtx.Model.Insert(model.Book{
|
||||
Book: in.Book,
|
||||
Price: in.Price,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &add.AddResp{
|
||||
Ok: true,
|
||||
}, nil
|
||||
}
|
||||
26
example/bookstore/rpc/add/internal/server/adderserver.go
Executable file
@@ -0,0 +1,26 @@
|
||||
// Code generated by goctl. DO NOT EDIT!
|
||||
// Source: add.proto
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"bookstore/rpc/add/internal/logic"
|
||||
"bookstore/rpc/add/internal/svc"
|
||||
add "bookstore/rpc/add/pb"
|
||||
"context"
|
||||
)
|
||||
|
||||
type AdderServer struct {
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewAdderServer(svcCtx *svc.ServiceContext) *AdderServer {
|
||||
return &AdderServer{
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AdderServer) Add(ctx context.Context, in *add.AddReq) (*add.AddResp, error) {
|
||||
l := logic.NewAddLogic(ctx, s.svcCtx)
|
||||
return l.Add(in)
|
||||
}
|
||||
20
example/bookstore/rpc/add/internal/svc/servicecontext.go
Executable file
@@ -0,0 +1,20 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"bookstore/rpc/add/internal/config"
|
||||
"bookstore/rpc/model"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
type ServiceContext struct {
|
||||
c config.Config
|
||||
Model *model.BookModel
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{
|
||||
c: c,
|
||||
Model: model.NewBookModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table),
|
||||
}
|
||||
}
|
||||
167
example/bookstore/rpc/add/pb/add.pb.go
Normal file
@@ -0,0 +1,167 @@
|
||||
// Code generated by protoc-gen-go.
|
||||
// source: add.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package add is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
add.proto
|
||||
|
||||
It has these top-level messages:
|
||||
AddReq
|
||||
AddResp
|
||||
*/
|
||||
package add
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type AddReq struct {
|
||||
Book string `protobuf:"bytes,1,opt,name=book" json:"book,omitempty"`
|
||||
Price int64 `protobuf:"varint,2,opt,name=price" json:"price,omitempty"`
|
||||
}
|
||||
|
||||
func (m *AddReq) Reset() { *m = AddReq{} }
|
||||
func (m *AddReq) String() string { return proto.CompactTextString(m) }
|
||||
func (*AddReq) ProtoMessage() {}
|
||||
func (*AddReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *AddReq) GetBook() string {
|
||||
if m != nil {
|
||||
return m.Book
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *AddReq) GetPrice() int64 {
|
||||
if m != nil {
|
||||
return m.Price
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type AddResp struct {
|
||||
Ok bool `protobuf:"varint,1,opt,name=ok" json:"ok,omitempty"`
|
||||
}
|
||||
|
||||
func (m *AddResp) Reset() { *m = AddResp{} }
|
||||
func (m *AddResp) String() string { return proto.CompactTextString(m) }
|
||||
func (*AddResp) ProtoMessage() {}
|
||||
func (*AddResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||
|
||||
func (m *AddResp) GetOk() bool {
|
||||
if m != nil {
|
||||
return m.Ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*AddReq)(nil), "add.addReq")
|
||||
proto.RegisterType((*AddResp)(nil), "add.addResp")
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// Client API for Adder service
|
||||
|
||||
type AdderClient interface {
|
||||
Add(ctx context.Context, in *AddReq, opts ...grpc.CallOption) (*AddResp, error)
|
||||
}
|
||||
|
||||
type adderClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewAdderClient(cc *grpc.ClientConn) AdderClient {
|
||||
return &adderClient{cc}
|
||||
}
|
||||
|
||||
func (c *adderClient) Add(ctx context.Context, in *AddReq, opts ...grpc.CallOption) (*AddResp, error) {
|
||||
out := new(AddResp)
|
||||
err := grpc.Invoke(ctx, "/add.adder/add", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Adder service
|
||||
|
||||
type AdderServer interface {
|
||||
Add(context.Context, *AddReq) (*AddResp, error)
|
||||
}
|
||||
|
||||
func RegisterAdderServer(s *grpc.Server, srv AdderServer) {
|
||||
s.RegisterService(&_Adder_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Adder_Add_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AddReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AdderServer).Add(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/add.adder/Add",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AdderServer).Add(ctx, req.(*AddReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Adder_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "add.adder",
|
||||
HandlerType: (*AdderServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "add",
|
||||
Handler: _Adder_Add_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "add.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("add.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 136 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x4c, 0x4c, 0x49, 0xd1,
|
||||
0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x4e, 0x4c, 0x49, 0x51, 0x32, 0xe2, 0x62, 0x4b, 0x4c,
|
||||
0x49, 0x09, 0x4a, 0x2d, 0x14, 0x12, 0xe2, 0x62, 0x49, 0xca, 0xcf, 0xcf, 0x96, 0x60, 0x54, 0x60,
|
||||
0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x85, 0x44, 0xb8, 0x58, 0x0b, 0x8a, 0x32, 0x93, 0x53, 0x25, 0x98,
|
||||
0x14, 0x18, 0x35, 0x98, 0x83, 0x20, 0x1c, 0x25, 0x49, 0x2e, 0x76, 0xb0, 0x9e, 0xe2, 0x02, 0x21,
|
||||
0x3e, 0x2e, 0x26, 0xa8, 0x16, 0x8e, 0x20, 0xa6, 0xfc, 0x6c, 0x23, 0x4d, 0x2e, 0xd6, 0xc4, 0x94,
|
||||
0x94, 0xd4, 0x22, 0x21, 0x05, 0x2e, 0x90, 0xf1, 0x42, 0xdc, 0x7a, 0x20, 0xfb, 0x20, 0x36, 0x48,
|
||||
0xf1, 0x20, 0x38, 0xc5, 0x05, 0x49, 0x6c, 0x60, 0x57, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff,
|
||||
0xe2, 0x6d, 0xb5, 0x91, 0x92, 0x00, 0x00, 0x00,
|
||||
}
|
||||
39
example/bookstore/rpc/check/check.go
Executable file
@@ -0,0 +1,39 @@
|
||||
// Code generated by goctl. DO NOT EDIT!
|
||||
// Source: check.proto
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bookstore/rpc/check/internal/config"
|
||||
"bookstore/rpc/check/internal/server"
|
||||
"bookstore/rpc/check/internal/svc"
|
||||
check "bookstore/rpc/check/pb"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/conf"
|
||||
"github.com/tal-tech/go-zero/rpcx"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var configFile = flag.String("f", "etc/check.yaml", "the config file")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var c config.Config
|
||||
conf.MustLoad(*configFile, &c)
|
||||
ctx := svc.NewServiceContext(c)
|
||||
checkerSrv := server.NewCheckerServer(ctx)
|
||||
|
||||
s, err := rpcx.NewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
|
||||
check.RegisterCheckerServer(grpcServer, checkerSrv)
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
|
||||
s.Start()
|
||||
}
|
||||
16
example/bookstore/rpc/check/check.proto
Executable file
@@ -0,0 +1,16 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package check;
|
||||
|
||||
message checkReq {
|
||||
string book = 1;
|
||||
}
|
||||
|
||||
message checkResp {
|
||||
bool found = 1;
|
||||
int64 price = 2;
|
||||
}
|
||||
|
||||
service checker {
|
||||
rpc check(checkReq) returns(checkResp);
|
||||
}
|
||||
62
example/bookstore/rpc/check/checker/checker.go
Executable file
@@ -0,0 +1,62 @@
|
||||
// Code generated by goctl. DO NOT EDIT!
|
||||
// Source: check.proto
|
||||
|
||||
//go:generate mockgen -destination ./checker_mock.go -package checker -source $GOFILE
|
||||
|
||||
package checker
|
||||
|
||||
import (
|
||||
check "bookstore/rpc/check/pb"
|
||||
"context"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/jsonx"
|
||||
"github.com/tal-tech/go-zero/rpcx"
|
||||
)
|
||||
|
||||
type (
|
||||
Checker interface {
|
||||
Check(ctx context.Context, in *CheckReq) (*CheckResp, error)
|
||||
}
|
||||
|
||||
defaultChecker struct {
|
||||
cli rpcx.Client
|
||||
}
|
||||
)
|
||||
|
||||
func NewChecker(cli rpcx.Client) Checker {
|
||||
return &defaultChecker{
|
||||
cli: cli,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultChecker) Check(ctx context.Context, in *CheckReq) (*CheckResp, error) {
|
||||
var request check.CheckReq
|
||||
bts, err := jsonx.Marshal(in)
|
||||
if err != nil {
|
||||
return nil, errJsonConvert
|
||||
}
|
||||
|
||||
err = jsonx.Unmarshal(bts, &request)
|
||||
if err != nil {
|
||||
return nil, errJsonConvert
|
||||
}
|
||||
|
||||
client := check.NewCheckerClient(m.cli.Conn())
|
||||
resp, err := client.Check(ctx, &request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ret CheckResp
|
||||
bts, err = jsonx.Marshal(resp)
|
||||
if err != nil {
|
||||
return nil, errJsonConvert
|
||||
}
|
||||
|
||||
err = jsonx.Unmarshal(bts, &ret)
|
||||
if err != nil {
|
||||
return nil, errJsonConvert
|
||||
}
|
||||
|
||||
return &ret, nil
|
||||
}
|
||||
49
example/bookstore/rpc/check/checker/checker_mock.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: checker.go
|
||||
|
||||
// Package checker is a generated GoMock package.
|
||||
package checker
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockChecker is a mock of Checker interface
|
||||
type MockChecker struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockCheckerMockRecorder
|
||||
}
|
||||
|
||||
// MockCheckerMockRecorder is the mock recorder for MockChecker
|
||||
type MockCheckerMockRecorder struct {
|
||||
mock *MockChecker
|
||||
}
|
||||
|
||||
// NewMockChecker creates a new mock instance
|
||||
func NewMockChecker(ctrl *gomock.Controller) *MockChecker {
|
||||
mock := &MockChecker{ctrl: ctrl}
|
||||
mock.recorder = &MockCheckerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockChecker) EXPECT() *MockCheckerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Check mocks base method
|
||||
func (m *MockChecker) Check(ctx context.Context, in *CheckReq) (*CheckResp, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Check", ctx, in)
|
||||
ret0, _ := ret[0].(*CheckResp)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Check indicates an expected call of Check
|
||||
func (mr *MockCheckerMockRecorder) Check(ctx, in interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Check", reflect.TypeOf((*MockChecker)(nil).Check), ctx, in)
|
||||
}
|
||||
19
example/bookstore/rpc/check/checker/types.go
Executable file
@@ -0,0 +1,19 @@
|
||||
// Code generated by goctl. DO NOT EDIT!
|
||||
// Source: check.proto
|
||||
|
||||
package checker
|
||||
|
||||
import "errors"
|
||||
|
||||
var errJsonConvert = errors.New("json convert error")
|
||||
|
||||
type (
|
||||
CheckReq struct {
|
||||
Book string `json:"book,omitempty"`
|
||||
}
|
||||
|
||||
CheckResp struct {
|
||||
Found bool `json:"found,omitempty"`
|
||||
Price int64 `json:"price,omitempty"`
|
||||
}
|
||||
)
|
||||
10
example/bookstore/rpc/check/etc/check.yaml
Executable file
@@ -0,0 +1,10 @@
|
||||
Name: check.rpc
|
||||
ListenOn: 127.0.0.1:8081
|
||||
Etcd:
|
||||
Hosts:
|
||||
- 127.0.0.1:2379
|
||||
Key: check.rpc
|
||||
DataSource: root:@tcp(localhost:3306)/gozero
|
||||
Table: book
|
||||
Cache:
|
||||
- Host: localhost:6379
|
||||
13
example/bookstore/rpc/check/internal/config/config.go
Executable file
@@ -0,0 +1,13 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||
"github.com/tal-tech/go-zero/rpcx"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
rpcx.RpcServerConf
|
||||
DataSource string
|
||||
Table string
|
||||
Cache cache.CacheConf
|
||||
}
|
||||
35
example/bookstore/rpc/check/internal/logic/checklogic.go
Executable file
@@ -0,0 +1,35 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"bookstore/rpc/check/internal/svc"
|
||||
check "bookstore/rpc/check/pb"
|
||||
"context"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type CheckLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func NewCheckLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CheckLogic {
|
||||
return &CheckLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logx.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *CheckLogic) Check(in *check.CheckReq) (*check.CheckResp, error) {
|
||||
resp, err := l.svcCtx.Model.FindOne(in.Book)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &check.CheckResp{
|
||||
Found: true,
|
||||
Price: resp.Price,
|
||||
}, nil
|
||||
}
|
||||
26
example/bookstore/rpc/check/internal/server/checkerserver.go
Executable file
@@ -0,0 +1,26 @@
|
||||
// Code generated by goctl. DO NOT EDIT!
|
||||
// Source: check.proto
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"bookstore/rpc/check/internal/logic"
|
||||
"bookstore/rpc/check/internal/svc"
|
||||
check "bookstore/rpc/check/pb"
|
||||
"context"
|
||||
)
|
||||
|
||||
type CheckerServer struct {
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewCheckerServer(svcCtx *svc.ServiceContext) *CheckerServer {
|
||||
return &CheckerServer{
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CheckerServer) Check(ctx context.Context, in *check.CheckReq) (*check.CheckResp, error) {
|
||||
l := logic.NewCheckLogic(ctx, s.svcCtx)
|
||||
return l.Check(in)
|
||||
}
|
||||
20
example/bookstore/rpc/check/internal/svc/servicecontext.go
Executable file
@@ -0,0 +1,20 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"bookstore/rpc/check/internal/config"
|
||||
"bookstore/rpc/model"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
type ServiceContext struct {
|
||||
c config.Config
|
||||
Model *model.BookModel
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{
|
||||
c: c,
|
||||
Model: model.NewBookModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table),
|
||||
}
|
||||
}
|
||||
167
example/bookstore/rpc/check/pb/check.pb.go
Normal file
@@ -0,0 +1,167 @@
|
||||
// Code generated by protoc-gen-go.
|
||||
// source: check.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package check is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
check.proto
|
||||
|
||||
It has these top-level messages:
|
||||
CheckReq
|
||||
CheckResp
|
||||
*/
|
||||
package check
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type CheckReq struct {
|
||||
Book string `protobuf:"bytes,1,opt,name=book" json:"book,omitempty"`
|
||||
}
|
||||
|
||||
func (m *CheckReq) Reset() { *m = CheckReq{} }
|
||||
func (m *CheckReq) String() string { return proto.CompactTextString(m) }
|
||||
func (*CheckReq) ProtoMessage() {}
|
||||
func (*CheckReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *CheckReq) GetBook() string {
|
||||
if m != nil {
|
||||
return m.Book
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type CheckResp struct {
|
||||
Found bool `protobuf:"varint,1,opt,name=found" json:"found,omitempty"`
|
||||
Price int64 `protobuf:"varint,2,opt,name=price" json:"price,omitempty"`
|
||||
}
|
||||
|
||||
func (m *CheckResp) Reset() { *m = CheckResp{} }
|
||||
func (m *CheckResp) String() string { return proto.CompactTextString(m) }
|
||||
func (*CheckResp) ProtoMessage() {}
|
||||
func (*CheckResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||
|
||||
func (m *CheckResp) GetFound() bool {
|
||||
if m != nil {
|
||||
return m.Found
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *CheckResp) GetPrice() int64 {
|
||||
if m != nil {
|
||||
return m.Price
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*CheckReq)(nil), "check.checkReq")
|
||||
proto.RegisterType((*CheckResp)(nil), "check.checkResp")
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// Client API for Checker service
|
||||
|
||||
type CheckerClient interface {
|
||||
Check(ctx context.Context, in *CheckReq, opts ...grpc.CallOption) (*CheckResp, error)
|
||||
}
|
||||
|
||||
type checkerClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewCheckerClient(cc *grpc.ClientConn) CheckerClient {
|
||||
return &checkerClient{cc}
|
||||
}
|
||||
|
||||
func (c *checkerClient) Check(ctx context.Context, in *CheckReq, opts ...grpc.CallOption) (*CheckResp, error) {
|
||||
out := new(CheckResp)
|
||||
err := grpc.Invoke(ctx, "/check.checker/check", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Checker service
|
||||
|
||||
type CheckerServer interface {
|
||||
Check(context.Context, *CheckReq) (*CheckResp, error)
|
||||
}
|
||||
|
||||
func RegisterCheckerServer(s *grpc.Server, srv CheckerServer) {
|
||||
s.RegisterService(&_Checker_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Checker_Check_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CheckReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CheckerServer).Check(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/check.checker/Check",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CheckerServer).Check(ctx, req.(*CheckReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Checker_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "check.checker",
|
||||
HandlerType: (*CheckerServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "check",
|
||||
Handler: _Checker_Check_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "check.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("check.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 136 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x4e, 0xce, 0x48, 0x4d,
|
||||
0xce, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x73, 0x94, 0xe4, 0xb8, 0x38, 0xc0,
|
||||
0x8c, 0xa0, 0xd4, 0x42, 0x21, 0x21, 0x2e, 0x96, 0xa4, 0xfc, 0xfc, 0x6c, 0x09, 0x46, 0x05, 0x46,
|
||||
0x0d, 0xce, 0x20, 0x30, 0x5b, 0xc9, 0x9c, 0x8b, 0x13, 0x2a, 0x5f, 0x5c, 0x20, 0x24, 0xc2, 0xc5,
|
||||
0x9a, 0x96, 0x5f, 0x9a, 0x97, 0x02, 0x56, 0xc1, 0x11, 0x04, 0xe1, 0x80, 0x44, 0x0b, 0x8a, 0x32,
|
||||
0x93, 0x53, 0x25, 0x98, 0x14, 0x18, 0x35, 0x98, 0x83, 0x20, 0x1c, 0x23, 0x53, 0x2e, 0x76, 0xb0,
|
||||
0xc6, 0xd4, 0x22, 0x21, 0x2d, 0x2e, 0x88, 0x65, 0x42, 0xfc, 0x7a, 0x10, 0x17, 0xc0, 0x6c, 0x94,
|
||||
0x12, 0x40, 0x15, 0x28, 0x2e, 0x48, 0x62, 0x03, 0xbb, 0xce, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff,
|
||||
0x6e, 0x6f, 0xa7, 0x1d, 0xac, 0x00, 0x00, 0x00,
|
||||
}
|
||||
6
example/bookstore/rpc/model/book.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE `book`
|
||||
(
|
||||
`book` varchar(255) NOT NULL COMMENT 'book name',
|
||||
`price` int NOT NULL COMMENT 'book price',
|
||||
PRIMARY KEY(`book`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
82
example/bookstore/rpc/model/bookmodel.go
Executable file
@@ -0,0 +1,82 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlc"
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
"github.com/tal-tech/go-zero/core/stringx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx"
|
||||
)
|
||||
|
||||
var (
|
||||
bookFieldNames = builderx.FieldNames(&Book{})
|
||||
bookRows = strings.Join(bookFieldNames, ",")
|
||||
bookRowsExpectAutoSet = strings.Join(stringx.Remove(bookFieldNames, "create_time", "update_time"), ",")
|
||||
bookRowsWithPlaceHolder = strings.Join(stringx.Remove(bookFieldNames, "book", "create_time", "update_time"), "=?,") + "=?"
|
||||
|
||||
cacheBookBookPrefix = "cache#Book#book#"
|
||||
)
|
||||
|
||||
type (
|
||||
BookModel struct {
|
||||
sqlc.CachedConn
|
||||
table string
|
||||
}
|
||||
|
||||
Book struct {
|
||||
Book string `db:"book"` // book name
|
||||
Price int64 `db:"price"` // book price
|
||||
}
|
||||
)
|
||||
|
||||
func NewBookModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *BookModel {
|
||||
return &BookModel{
|
||||
CachedConn: sqlc.NewConn(conn, c),
|
||||
table: table,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *BookModel) Insert(data Book) (sql.Result, error) {
|
||||
query := `insert into ` + m.table + ` (` + bookRowsExpectAutoSet + `) values (?, ?)`
|
||||
return m.ExecNoCache(query, data.Book, data.Price)
|
||||
}
|
||||
|
||||
func (m *BookModel) FindOne(book string) (*Book, error) {
|
||||
bookBookKey := fmt.Sprintf("%s%v", cacheBookBookPrefix, book)
|
||||
var resp Book
|
||||
err := m.QueryRow(&resp, bookBookKey, func(conn sqlx.SqlConn, v interface{}) error {
|
||||
query := `select ` + bookRows + ` from ` + m.table + ` where book = ? limit 1`
|
||||
return conn.QueryRow(v, query, book)
|
||||
})
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *BookModel) Update(data Book) error {
|
||||
bookBookKey := fmt.Sprintf("%s%v", cacheBookBookPrefix, data.Book)
|
||||
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := `update ` + m.table + ` set ` + bookRowsWithPlaceHolder + ` where book = ?`
|
||||
return conn.Exec(query, data.Price, data.Book)
|
||||
}, bookBookKey)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *BookModel) Delete(book string) error {
|
||||
|
||||
bookBookKey := fmt.Sprintf("%s%v", cacheBookBookPrefix, book)
|
||||
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := `delete from ` + m.table + ` where book = ?`
|
||||
return conn.Exec(query, book)
|
||||
}, bookBookKey)
|
||||
return err
|
||||
}
|
||||
5
example/bookstore/rpc/model/vars.go
Executable file
@@ -0,0 +1,5 @@
|
||||
package model
|
||||
|
||||
import "github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
|
||||
var ErrNotFound = sqlx.ErrNotFound
|
||||
40
example/mapreduce/deadlock/main.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/mr"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Uid int
|
||||
Name string
|
||||
}
|
||||
|
||||
func main() {
|
||||
uids := []int{111, 222, 333}
|
||||
res, err := mr.MapReduce(func(source chan<- interface{}) {
|
||||
for _, uid := range uids {
|
||||
source <- uid
|
||||
}
|
||||
}, func(item interface{}, writer mr.Writer, cancel func(error)) {
|
||||
uid := item.(int)
|
||||
user := &User{
|
||||
Uid: uid,
|
||||
Name: strconv.Itoa(uid),
|
||||
}
|
||||
writer.Write(user)
|
||||
}, func(pipe <-chan interface{}, writer mr.Writer, cancel func(error)) {
|
||||
var users []*User
|
||||
for p := range pipe {
|
||||
users = append(users, p.(*User))
|
||||
}
|
||||
// missing writer.Write(...), should not panic
|
||||
})
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
log.Print(len(res.([]*User)))
|
||||
}
|
||||
114
example/mapreduce/mr/mr.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/mr"
|
||||
"github.com/tal-tech/go-zero/core/timex"
|
||||
)
|
||||
|
||||
type user struct{}
|
||||
|
||||
func (u *user) User(uid int64) (interface{}, error) {
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type store struct{}
|
||||
|
||||
func (s *store) Store(pid int64) (interface{}, error) {
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type order struct{}
|
||||
|
||||
func (o *order) Order(pid int64) (interface{}, error) {
|
||||
time.Sleep(time.Millisecond * 40)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
userRpc user
|
||||
storeRpc store
|
||||
orderRpc order
|
||||
)
|
||||
|
||||
func main() {
|
||||
start := timex.Now()
|
||||
_, err := productDetail(123, 345)
|
||||
if err != nil {
|
||||
log.Printf("product detail error: %v", err)
|
||||
return
|
||||
}
|
||||
log.Printf("productDetail time: %v", timex.Since(start))
|
||||
|
||||
// the data processing
|
||||
res, err := checkLegal([]int64{1, 2, 3})
|
||||
if err != nil {
|
||||
log.Printf("check error: %v", err)
|
||||
return
|
||||
}
|
||||
log.Printf("check res: %v", res)
|
||||
}
|
||||
|
||||
type ProductDetail struct {
|
||||
User interface{}
|
||||
Store interface{}
|
||||
Order interface{}
|
||||
}
|
||||
|
||||
func productDetail(uid, pid int64) (*ProductDetail, error) {
|
||||
var pd ProductDetail
|
||||
err := mr.Finish(func() (err error) {
|
||||
pd.User, err = userRpc.User(uid)
|
||||
return
|
||||
}, func() (err error) {
|
||||
pd.Store, err = storeRpc.Store(pid)
|
||||
return
|
||||
}, func() (err error) {
|
||||
pd.Order, err = orderRpc.Order(pid)
|
||||
return
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pd, nil
|
||||
}
|
||||
|
||||
func checkLegal(uids []int64) ([]int64, error) {
|
||||
r, err := mr.MapReduce(func(source chan<- interface{}) {
|
||||
for _, uid := range uids {
|
||||
source <- uid
|
||||
}
|
||||
}, func(item interface{}, writer mr.Writer, cancel func(error)) {
|
||||
uid := item.(int64)
|
||||
ok, err := check(uid)
|
||||
if err != nil {
|
||||
cancel(err)
|
||||
}
|
||||
if ok {
|
||||
writer.Write(uid)
|
||||
}
|
||||
}, func(pipe <-chan interface{}, writer mr.Writer, cancel func(error)) {
|
||||
var uids []int64
|
||||
for p := range pipe {
|
||||
uids = append(uids, p.(int64))
|
||||
}
|
||||
writer.Write(uids)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.([]int64), nil
|
||||
}
|
||||
|
||||
func check(uid int64) (bool, error) {
|
||||
// do something check user legal
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
return true, nil
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
Name: shorturl-api
|
||||
Host: 0.0.0.0
|
||||
Port: 8888
|
||||
Transform:
|
||||
Etcd:
|
||||
Hosts:
|
||||
- localhost:2379
|
||||
Key: transform.rpc
|
||||
@@ -1,7 +1,11 @@
|
||||
package config
|
||||
|
||||
import "github.com/tal-tech/go-zero/rest"
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/rest"
|
||||
"github.com/tal-tech/go-zero/rpcx"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
rest.RestConf
|
||||
Transform rpcx.RpcClientConf
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"shorturl/api/internal/svc"
|
||||
"shorturl/api/internal/types"
|
||||
"shorturl/rpc/transform/transformer"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
)
|
||||
@@ -24,5 +25,14 @@ func NewExpandLogic(ctx context.Context, svcCtx *svc.ServiceContext) ExpandLogic
|
||||
}
|
||||
|
||||
func (l *ExpandLogic) Expand(req types.ExpandReq) (*types.ExpandResp, error) {
|
||||
return &types.ExpandResp{}, nil
|
||||
resp, err := l.svcCtx.Transformer.Expand(l.ctx, &transformer.ExpandReq{
|
||||
Shorten: req.Shorten,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.ExpandResp{
|
||||
Url: resp.Url,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"shorturl/api/internal/svc"
|
||||
"shorturl/api/internal/types"
|
||||
"shorturl/rpc/transform/transformer"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
)
|
||||
@@ -24,5 +25,14 @@ func NewShortenLogic(ctx context.Context, svcCtx *svc.ServiceContext) ShortenLog
|
||||
}
|
||||
|
||||
func (l *ShortenLogic) Shorten(req types.ShortenReq) (*types.ShortenResp, error) {
|
||||
return &types.ShortenResp{}, nil
|
||||
resp, err := l.svcCtx.Transformer.Shorten(l.ctx, &transformer.ShortenReq{
|
||||
Url: req.Url,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.ShortenResp{
|
||||
Shorten: resp.Shorten,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
package svc
|
||||
|
||||
import "shorturl/api/internal/config"
|
||||
import (
|
||||
"shorturl/api/internal/config"
|
||||
"shorturl/rpc/transform/transformer"
|
||||
|
||||
"github.com/tal-tech/go-zero/rpcx"
|
||||
)
|
||||
|
||||
type ServiceContext struct {
|
||||
Config config.Config
|
||||
Config config.Config
|
||||
Transformer transformer.Transformer
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{Config: c}
|
||||
return &ServiceContext{
|
||||
Config: c,
|
||||
Transformer: transformer.NewTransformer(rpcx.MustNewClient(c.Transform)),
|
||||
}
|
||||
}
|
||||
|
||||
206
readme-en.md
Normal file
@@ -0,0 +1,206 @@
|
||||
English | [简体中文](readme.md)
|
||||
|
||||
# go-zero
|
||||
|
||||
[](https://github.com/tal-tech/go-zero/actions)
|
||||
[](https://codecov.io/gh/tal-tech/go-zero)
|
||||
[](https://goreportcard.com/report/github.com/tal-tech/go-zero)
|
||||
[](https://github.com/tal-tech/go-zero)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
## 0. what is go-zero?
|
||||
|
||||
go-zero is a web and rpc framework that with lots of engineering practices builtin. It’s born to ensure the stability of the busy services with resilience design, and has been serving sites with tens of millions users for years.
|
||||
|
||||
go-zero contains simple API description syntax and code generation tool called `goctl`. You can generate Go, iOS, Android, Kotlin, Dart, TypeScript, JavaScript from .api files with `goctl`.
|
||||
|
||||
Advantages of go-zero:
|
||||
|
||||
* improve the stability of the services with tens of millions of daily active users
|
||||
* builtin chained timeout control, concurrency control, rate limit, adaptive circuit breaker, adaptive load shedding, even no configuration needed
|
||||
* builtin middlewares also can be integrated into your frameworks
|
||||
* simple API syntax, one command to generate couple different languages
|
||||
* auto validate the request parameters from clients
|
||||
* plenty of builtin microservice management and concurrent toolkits
|
||||
|
||||
<img src="doc/images/architecture-en.png" alt="Architecture" width="1500" />
|
||||
|
||||
## 1. Backgrounds of go-zero
|
||||
|
||||
At the beginning of 2018, we heavily suffered from frequent downtime. We decided to re-design our system, from monolithic architecture with Java+MongoDB to microservice architecture. After researches and comparison, we chose to:
|
||||
|
||||
* Golang based
|
||||
* great performance
|
||||
* simple syntax
|
||||
* proven engineering efficiency
|
||||
* extreme deployment experience
|
||||
* less server resource consumption
|
||||
* Self-designed microservice architecture
|
||||
* I have rich experience on designing microservice architectures
|
||||
* easy to location the problems
|
||||
* easy to extend the features
|
||||
|
||||
## 2. Design considerations on go-zero
|
||||
|
||||
By designing the microservice architecture, we expected to ensure the stability, as well as the productivity. And from just the beginning, we have the following design principles:
|
||||
|
||||
* keep it simple
|
||||
* high availability
|
||||
* stable on high concurrency
|
||||
* easy to extend
|
||||
* resilience design, failure-oriented programming
|
||||
* try best to be friendly to the business logic development, encapsulate the complexity
|
||||
* one thing, one way
|
||||
|
||||
After almost half a year, we finished the transfer from monolithic system to microservice system, and deployed on August 2018. The new system guaranteed the business growth, and the system stability.
|
||||
|
||||
## 3. The implementation and features of go-zero
|
||||
|
||||
go-zero is a web and rpc framework that integrates lots of engineering practices. The features are mainly listed below:
|
||||
|
||||
* powerful tool included, less code to write
|
||||
* simple interfaces
|
||||
* fully compatible with net/http
|
||||
* middlewares are supported, easy to extend
|
||||
* high performance
|
||||
* failure-oriented programming, resilience design
|
||||
* builtin service discovery, load balancing
|
||||
* builtin concurrency control, adaptive circuit breaker, adaptive load shedding, auto trigger, auto recover
|
||||
* auto validation of API request parameters
|
||||
* chained timeout control
|
||||
* auto management of data caching
|
||||
* call tracing, metrics and monitoring
|
||||
* high concurrency protected
|
||||
|
||||
As below, go-zero protects the system with couple layers and mechanisms:
|
||||
|
||||

|
||||
|
||||
## 4. Future development plans of go-zero
|
||||
|
||||
* auto generate API mock server, make the client debugging eaisier
|
||||
* auto generate the simple integration test for the server side just from the .api files
|
||||
|
||||
## 5. Installation
|
||||
|
||||
Run the following command under your project:
|
||||
|
||||
```shell
|
||||
go get -u github.com/tal-tech/go-zero
|
||||
```
|
||||
|
||||
## 6. Quick Start
|
||||
|
||||
0. full examples can be checked out from below:
|
||||
|
||||
[Rapid development of microservice systems](doc/shorturl-en.md)
|
||||
|
||||
[Rapid development of microservice systems - multiple RPCs](doc/bookstore-en.md)
|
||||
|
||||
1. install goctl
|
||||
|
||||
`goctl`can be read as `go control`. `goctl means not to be controlled by code, instead, we control it. The inside `go` is not `golang`. At the very beginning, I was expecting it to help us improve the productivity, and make our lives easier.
|
||||
|
||||
```shell
|
||||
GO111MODULE=on go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
|
||||
make sure goctl is executable.
|
||||
|
||||
2. create the API file, like greet.api, you can install the plugin of goctl in vs code, api syntax is supported.
|
||||
|
||||
```go
|
||||
type Request struct {
|
||||
Name string `path:"name,options=you|me"` // parameters are auto validated
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
service greet-api {
|
||||
@server(
|
||||
handler: GreetHandler
|
||||
)
|
||||
get /greet/from/:name(Request) returns (Response);
|
||||
}
|
||||
```
|
||||
|
||||
the .api files also can be generate by goctl, like below:
|
||||
|
||||
```shell
|
||||
goctl api -o greet.api
|
||||
```
|
||||
|
||||
3. generate the go server side code
|
||||
|
||||
```shell
|
||||
goctl api go -api greet.api -dir greet
|
||||
```
|
||||
|
||||
the generated files look like:
|
||||
|
||||
```
|
||||
├── greet
|
||||
│ ├── etc
|
||||
│ │ └── greet-api.yaml // configuration file
|
||||
│ ├── greet.go // main file
|
||||
│ └── internal
|
||||
│ ├── config
|
||||
│ │ └── config.go // configuration definition
|
||||
│ ├── handler
|
||||
│ │ ├── greethandler.go // get/put/post/delete routes are defined here
|
||||
│ │ └── routes.go // routes list
|
||||
│ ├── logic
|
||||
│ │ └── greetlogic.go // request logic can be written here
|
||||
│ ├── svc
|
||||
│ │ └── servicecontext.go // service context, mysql/redis can be passed in here
|
||||
│ └── types
|
||||
│ └── types.go // request/response defined here
|
||||
└── greet.api // api description file
|
||||
```
|
||||
the generated code can be run directly:
|
||||
|
||||
```shell
|
||||
cd greet
|
||||
go run greet.go -f etc/greet-api.yaml
|
||||
```
|
||||
|
||||
by default, it’s listening on port 8888, while it can be changed in configuration file.
|
||||
|
||||
you can check it by curl:
|
||||
|
||||
```shell
|
||||
curl -i http://localhost:8888/greet/from/you
|
||||
```
|
||||
|
||||
the response looks like:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Date: Sun, 30 Aug 2020 15:32:35 GMT
|
||||
Content-Length: 0
|
||||
```
|
||||
|
||||
4. Write the business logic code
|
||||
|
||||
* the dependencies can be passed into the logic within servicecontext.go, like mysql, reds etc.
|
||||
* add the logic code in logic package according to .api file
|
||||
5. Generate code like Java, TypeScript, Dart, JavaScript etc. just from the api file
|
||||
|
||||
```shell
|
||||
goctl api java -api greet.api -dir greet
|
||||
goctl api dart -api greet.api -dir greet
|
||||
...
|
||||
```
|
||||
|
||||
## 7. Benchmark
|
||||
|
||||

|
||||
|
||||
[Checkout the test code](https://github.com/smallnest/go-web-framework-benchmark)
|
||||
|
||||
## 8. Documents (adding)
|
||||
|
||||
* [Rapid development of microservice systems](doc/shorturl-en.md)
|
||||
* [Rapid development of microservice systems - multiple RPCs](doc/bookstore-en.md)
|
||||
73
readme.md
@@ -1,3 +1,5 @@
|
||||
[English](readme-en.md) | 简体中文
|
||||
|
||||
# go-zero
|
||||
|
||||
[](https://github.com/tal-tech/go-zero/actions)
|
||||
@@ -91,71 +93,24 @@ go get -u github.com/tal-tech/go-zero
|
||||
|
||||
0. 完整示例请查看
|
||||
|
||||
[快速构建高并发微服务](doc/shorturl.md)
|
||||
[快速构建高并发微服务](doc/shorturl.md)
|
||||
|
||||
[快速构建高并发微服务-多RPC版](doc/bookstore.md)
|
||||
|
||||
1. 安装goctl工具
|
||||
|
||||
`goctl`读作`go control`,不要读成`go C-T-L`。`goctl`的意思是不要被代码控制,而是要去控制它。其中的`go`不是指`golang`。在设计`goctl`之初,我就希望通过`她`来解放我们的双手👈
|
||||
|
||||
```shell
|
||||
export GO111MODULE=on export GOPROXY=https://goproxy.cn/,direct go get github.com/tal-tech/go-zero/tools/goctl
|
||||
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
|
||||
确保goctl可执行
|
||||
|
||||
2. 定义API文件,比如greet.api,可以在vs code里安装`goctl`插件,支持api语法
|
||||
|
||||
```go
|
||||
type Request struct {
|
||||
Name string `path:"name,options=you|me"` // 框架自动验证请求参数是否合法
|
||||
}
|
||||
2. 快速生成api服务
|
||||
|
||||
type Response struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
service greet-api {
|
||||
@server(
|
||||
handler: GreetHandler
|
||||
)
|
||||
get /greet/from/:name(Request) returns (Response);
|
||||
}
|
||||
```
|
||||
|
||||
也可以通过goctl生成api模本文件,命令如下:
|
||||
|
||||
```shell
|
||||
goctl api -o greet.api
|
||||
```
|
||||
|
||||
3. 生成go服务端代码
|
||||
|
||||
```shell
|
||||
goctl api go -api greet.api -dir greet
|
||||
```
|
||||
|
||||
生成的文件结构如下:
|
||||
|
||||
```
|
||||
├── greet
|
||||
│ ├── etc
|
||||
│ │ └── greet-api.yaml // 配置文件
|
||||
│ ├── greet.go // main文件
|
||||
│ └── internal
|
||||
│ ├── config
|
||||
│ │ └── config.go // 配置定义
|
||||
│ ├── handler
|
||||
│ │ ├── greethandler.go // get/put/post/delete等路由定义文件
|
||||
│ │ └── routes.go // 路由列表
|
||||
│ ├── logic
|
||||
│ │ └── greetlogic.go // 请求逻辑处理文件
|
||||
│ ├── svc
|
||||
│ │ └── servicecontext.go // 请求上下文,可以传入mysql, redis等依赖
|
||||
│ └── types
|
||||
│ └── types.go // 请求、返回等类型定义
|
||||
└── greet.api // api描述文件
|
||||
```
|
||||
生成的代码可以直接运行:
|
||||
|
||||
```shell
|
||||
goctl api new greet
|
||||
cd greet
|
||||
go run greet.go -f etc/greet-api.yaml
|
||||
```
|
||||
@@ -176,10 +131,11 @@ Content-Length: 0
|
||||
|
||||
编写业务代码:
|
||||
|
||||
* api文件定义了服务对外暴露的路由,可参考[api规范](https://github.com/tal-tech/go-zero/blob/master/doc/goctl.md)
|
||||
* 可以在servicecontext.go里面传递依赖给logic,比如mysql, redis等
|
||||
* 在api定义的get/post/put/delete等请求对应的logic里增加业务处理逻辑
|
||||
|
||||
4. 可以根据api文件生成前端需要的Java, TypeScript, Dart, JavaScript代码
|
||||
3. 可以根据api文件生成前端需要的Java, TypeScript, Dart, JavaScript代码
|
||||
|
||||
```shell
|
||||
goctl api java -api greet.api -dir greet
|
||||
@@ -196,9 +152,12 @@ Content-Length: 0
|
||||
## 8. 文档 (逐步完善中)
|
||||
|
||||
* [快速构建高并发微服务](doc/shorturl.md)
|
||||
* [快速构建高并发微服务-多RPC版](doc/bookstore.md)
|
||||
* [goctl使用帮助](doc/goctl.md)
|
||||
* [通过MapReduce降低服务响应时间](doc/mapreduce.md)
|
||||
* [关键字替换和敏感词过滤工具](doc/keywords.md)
|
||||
* [进程内缓存使用方法](doc/collection.md)
|
||||
|
||||
## 9. 微信交流群
|
||||
|
||||
添加我的微信:kevwan,请注明go-zero,我拉进go-zero社区群🤝
|
||||
<img src="doc/images/wechat.jpg" alt="wechat" width="300" />
|
||||
|
||||
@@ -36,6 +36,10 @@ func GoCommand(c *cli.Context) error {
|
||||
return errors.New("missing -dir")
|
||||
}
|
||||
|
||||
return DoGenProject(apiFile, dir)
|
||||
}
|
||||
|
||||
func DoGenProject(apiFile, dir string) error {
|
||||
p, err := parser.NewParser(apiFile)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"fmt"
|
||||
goformat "go/format"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -13,32 +11,27 @@ import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||
goctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/project"
|
||||
)
|
||||
|
||||
func getParentPackage(dir string) (string, error) {
|
||||
absDir, err := filepath.Abs(dir)
|
||||
p, err := project.Prepare(dir, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
absDir = strings.ReplaceAll(absDir, `\`, `/`)
|
||||
rootPath, hasGoMod := goctlutil.FindGoModPath(dir)
|
||||
if hasGoMod {
|
||||
return rootPath, nil
|
||||
if len(p.GoMod.Path) > 0 {
|
||||
goModePath := filepath.Clean(filepath.Dir(p.GoMod.Path))
|
||||
absPath, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
parent := filepath.Clean(goctlutil.JoinPackages(p.GoMod.Module, absPath[len(goModePath):]))
|
||||
parent = strings.ReplaceAll(parent, "\\", "/")
|
||||
return parent, nil
|
||||
}
|
||||
|
||||
gopath := os.Getenv("GOPATH")
|
||||
parent := path.Join(gopath, "src")
|
||||
pos := strings.Index(absDir, parent)
|
||||
if pos < 0 {
|
||||
fmt.Printf("%s not in go.mod project path, or not in GOPATH of %s directory\n", absDir, gopath)
|
||||
tempPath := filepath.Dir(absDir)
|
||||
rootPath = absDir[len(tempPath)+1:]
|
||||
} else {
|
||||
rootPath = absDir[len(parent)+1:]
|
||||
}
|
||||
|
||||
return rootPath, nil
|
||||
return p.Package, nil
|
||||
}
|
||||
|
||||
func writeIndent(writer io.Writer, indent int) {
|
||||
|
||||
58
tools/goctl/api/new/newservice.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package new
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/gogen"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const apiTemplate = `
|
||||
type Request struct {
|
||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + ` // 框架自动验证请求参数是否合法
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Message string ` + "`" + `json:"message"` + "`" + `
|
||||
}
|
||||
|
||||
service {{.name}}-api {
|
||||
@server(
|
||||
handler: GreetHandler
|
||||
)
|
||||
get /greet/from/:name(Request) returns (Response);
|
||||
}
|
||||
`
|
||||
|
||||
func NewService(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
name := "greet"
|
||||
if len(args) > 0 {
|
||||
name = args.First()
|
||||
}
|
||||
location := name
|
||||
err := os.MkdirAll(location, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filename := name + ".api"
|
||||
apiFilePath := filepath.Join(location, filename)
|
||||
fp, err := os.Create(apiFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer fp.Close()
|
||||
t := template.Must(template.New("template").Parse(apiTemplate))
|
||||
if err := t.Execute(fp, map[string]string{
|
||||
"name": name,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = gogen.DoGenProject(apiFilePath, location)
|
||||
return err
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/gogen"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/javagen"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/ktgen"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/new"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/tsgen"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/validate"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/configgen"
|
||||
@@ -36,6 +37,11 @@ var (
|
||||
},
|
||||
Action: apigen.ApiCommand,
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "new",
|
||||
Usage: "fast create api service",
|
||||
Action: new.NewService,
|
||||
},
|
||||
{
|
||||
Name: "format",
|
||||
Usage: "format api files",
|
||||
@@ -193,6 +199,17 @@ var (
|
||||
Name: "rpc",
|
||||
Usage: "generate rpc code",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "new",
|
||||
Usage: `generate rpc demo service`,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "idea",
|
||||
Usage: "whether the command execution environment is from idea plugin. [option]",
|
||||
},
|
||||
},
|
||||
Action: rpc.RpcNew,
|
||||
},
|
||||
{
|
||||
Name: "template",
|
||||
Usage: `generate proto template`,
|
||||
@@ -224,10 +241,6 @@ var (
|
||||
Name: "service, srv",
|
||||
Usage: `the name of rpc service. [option]`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "shared",
|
||||
Usage: `the dir of the shared file,default path is "${pwd}/shared. [option]`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "idea",
|
||||
Usage: "whether the command execution environment is from idea plugin. [option]",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
## goctl用途
|
||||
* 定义api请求
|
||||
* 根据定义的api自动生成golang(后端), java(iOS & Android), typescript(web & 晓程序),dart(flutter)
|
||||
* 生成MySQL CURD 详情见[goctl model模块](https://github.com/tal-tech/go-zero/tools/goctl/model)
|
||||
* 生成MySQL CURD 详情见[goctl model模块](model/sql)
|
||||
## goctl使用说明
|
||||
#### goctl参数说明
|
||||
|
||||
|
||||
@@ -22,9 +22,6 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
||||
```shell script
|
||||
$ goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model"
|
||||
```
|
||||
|
||||
|
||||
> 详情用法请参考[example](https://github.com/tal-tech/go-zero/tree/master/tools/goctl/model/sql/example)
|
||||
|
||||
|
||||
* 生成代码示例
|
||||
|
||||
@@ -22,7 +22,7 @@ func genDelete(table Table, withCache bool) (string, error) {
|
||||
}
|
||||
var containsIndexCache = false
|
||||
for _, item := range table.Fields {
|
||||
if item.IsKey && !item.IsPrimaryKey {
|
||||
if item.IsUniqueKey {
|
||||
containsIndexCache = true
|
||||
break
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ func genFineOneByField(table Table, withCache bool) (string, error) {
|
||||
var list []string
|
||||
camelTableName := table.Name.ToCamel()
|
||||
for _, field := range table.Fields {
|
||||
if field.IsPrimaryKey || !field.IsKey {
|
||||
if field.IsPrimaryKey || !field.IsUniqueKey {
|
||||
continue
|
||||
}
|
||||
camelFieldName := field.Name.ToCamel()
|
||||
|
||||
@@ -29,22 +29,21 @@ func genCacheKeys(table parser.Table) (map[string]Key, error) {
|
||||
camelTableName := table.Name.ToCamel()
|
||||
lowerStartCamelTableName := stringx.From(camelTableName).UnTitle()
|
||||
for _, field := range fields {
|
||||
if !field.IsKey {
|
||||
continue
|
||||
}
|
||||
camelFieldName := field.Name.ToCamel()
|
||||
lowerStartCamelFieldName := stringx.From(camelFieldName).UnTitle()
|
||||
left := fmt.Sprintf("cache%s%sPrefix", camelTableName, camelFieldName)
|
||||
right := fmt.Sprintf("cache#%s#%s#", camelTableName, lowerStartCamelFieldName)
|
||||
variable := fmt.Sprintf("%s%sKey", lowerStartCamelTableName, camelFieldName)
|
||||
m[field.Name.Source()] = Key{
|
||||
VarExpression: fmt.Sprintf(`%s = "%s"`, left, right),
|
||||
Left: left,
|
||||
Right: right,
|
||||
Variable: variable,
|
||||
KeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("%s%s", %s,%s)`, variable, "%s", "%v", left, lowerStartCamelFieldName),
|
||||
DataKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("%s%s",%s, data.%s)`, variable, "%s", "%v", left, camelFieldName),
|
||||
RespKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("%s%s", %s,resp.%s)`, variable, "%s", "%v", left, camelFieldName),
|
||||
if field.IsUniqueKey || field.IsPrimaryKey {
|
||||
camelFieldName := field.Name.ToCamel()
|
||||
lowerStartCamelFieldName := stringx.From(camelFieldName).UnTitle()
|
||||
left := fmt.Sprintf("cache%s%sPrefix", camelTableName, camelFieldName)
|
||||
right := fmt.Sprintf("cache#%s#%s#", camelTableName, lowerStartCamelFieldName)
|
||||
variable := fmt.Sprintf("%s%sKey", lowerStartCamelTableName, camelFieldName)
|
||||
m[field.Name.Source()] = Key{
|
||||
VarExpression: fmt.Sprintf(`%s = "%s"`, left, right),
|
||||
Left: left,
|
||||
Right: right,
|
||||
Variable: variable,
|
||||
KeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("%s%s", %s,%s)`, variable, "%s", "%v", left, lowerStartCamelFieldName),
|
||||
DataKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("%s%s",%s, data.%s)`, variable, "%s", "%v", left, camelFieldName),
|
||||
RespKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("%s%s", %s,resp.%s)`, variable, "%s", "%v", left, camelFieldName),
|
||||
}
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
|
||||
@@ -36,6 +36,7 @@ type (
|
||||
DataType string
|
||||
IsKey bool
|
||||
IsPrimaryKey bool
|
||||
IsUniqueKey bool
|
||||
Comment string
|
||||
}
|
||||
|
||||
@@ -124,6 +125,7 @@ func Parse(ddl string) (*Table, error) {
|
||||
if ok {
|
||||
field.IsKey = true
|
||||
field.IsPrimaryKey = key == primary
|
||||
field.IsUniqueKey = key == unique
|
||||
if field.IsPrimaryKey {
|
||||
primaryKey.Field = field
|
||||
if column.Type.Autoincrement {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Change log
|
||||
|
||||
# 2020-08-29
|
||||
* rpc greet服务一键生成
|
||||
* 修复相对路径生成rpc服务package引入错误bug
|
||||
* 移除`--shared`参数
|
||||
|
||||
# 2020-08-29
|
||||
* 新增支持windows生成
|
||||
|
||||
|
||||
@@ -8,74 +8,118 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块,支持prot
|
||||
|
||||
# 快速开始
|
||||
|
||||
### 生成proto模板
|
||||
### 方式一:快速生成greet服务
|
||||
|
||||
```shell script
|
||||
$ goctl rpc template -o=user.proto
|
||||
```
|
||||
通过命令 `goctl rpc new ${servieName}`生成
|
||||
|
||||
```golang
|
||||
syntax = "proto3";
|
||||
如生成greet rpc服务:
|
||||
|
||||
package remote;
|
||||
```shell script
|
||||
$ goctl rpc new greet
|
||||
```
|
||||
|
||||
message Request {
|
||||
// 用户名
|
||||
string username = 1;
|
||||
// 用户密码
|
||||
string password = 2;
|
||||
}
|
||||
执行后代码结构如下:
|
||||
|
||||
message Response {
|
||||
// 用户名称
|
||||
string name = 1;
|
||||
// 用户性别
|
||||
string gender = 2;
|
||||
}
|
||||
|
||||
service User {
|
||||
// 登录
|
||||
rpc Login(Request)returns(Response);
|
||||
}
|
||||
```
|
||||
### 生成rpc服务代码
|
||||
|
||||
生成user rpc服务
|
||||
```
|
||||
$ goctl rpc proto -src=user.proto
|
||||
```
|
||||
|
||||
代码tree
|
||||
|
||||
```
|
||||
user
|
||||
```golang
|
||||
└── greet
|
||||
├── etc
|
||||
│ └── user.json
|
||||
│ └── greet.yaml
|
||||
├── go.mod
|
||||
├── go.sum
|
||||
├── greet
|
||||
│ ├── greet.go
|
||||
│ ├── greet_mock.go
|
||||
│ └── types.go
|
||||
├── greet.go
|
||||
├── greet.proto
|
||||
├── internal
|
||||
│ ├── config
|
||||
│ │ └── config.go
|
||||
│ ├── handler
|
||||
│ │ ├── loginhandler.go
|
||||
│ ├── logic
|
||||
│ │ └── loginlogic.go
|
||||
│ │ └── pinglogic.go
|
||||
│ ├── server
|
||||
│ │ └── greetserver.go
|
||||
│ └── svc
|
||||
│ └── servicecontext.go
|
||||
├── pb
|
||||
│ └── user.pb.go
|
||||
├── shared
|
||||
│ ├── mockusermodel.go
|
||||
│ ├── types.go
|
||||
│ └── usermodel.go
|
||||
├── user.go
|
||||
└── user.proto
|
||||
└── pb
|
||||
└── greet.pb.go
|
||||
```
|
||||
|
||||
```
|
||||
rpc一键生成常见问题解决见 <a href="#常见问题解决">常见问题解决</a>
|
||||
### 方式二:通过指定proto生成rpc服务
|
||||
|
||||
* 生成proto模板
|
||||
|
||||
```shell script
|
||||
$ goctl rpc template -o=user.proto
|
||||
```
|
||||
|
||||
```golang
|
||||
syntax = "proto3";
|
||||
|
||||
package remote;
|
||||
|
||||
message Request {
|
||||
// 用户名
|
||||
string username = 1;
|
||||
// 用户密码
|
||||
string password = 2;
|
||||
}
|
||||
|
||||
message Response {
|
||||
// 用户名称
|
||||
string name = 1;
|
||||
// 用户性别
|
||||
string gender = 2;
|
||||
}
|
||||
|
||||
service User {
|
||||
// 登录
|
||||
rpc Login(Request)returns(Response);
|
||||
}
|
||||
```
|
||||
* 生成rpc服务代码
|
||||
|
||||
```
|
||||
$ goctl rpc proto -src=user.proto
|
||||
```
|
||||
|
||||
代码tree
|
||||
|
||||
```
|
||||
user
|
||||
├── etc
|
||||
│ └── user.json
|
||||
├── internal
|
||||
│ ├── config
|
||||
│ │ └── config.go
|
||||
│ ├── handler
|
||||
│ │ ├── loginhandler.go
|
||||
│ ├── logic
|
||||
│ │ └── loginlogic.go
|
||||
│ └── svc
|
||||
│ └── servicecontext.go
|
||||
├── pb
|
||||
│ └── user.pb.go
|
||||
├── shared
|
||||
│ ├── mockusermodel.go
|
||||
│ ├── types.go
|
||||
│ └── usermodel.go
|
||||
├── user.go
|
||||
└── user.proto
|
||||
|
||||
```
|
||||
# 准备工作
|
||||
|
||||
* 安装了go环境
|
||||
* 安装了protoc&protoc-gen-go,并且已经设置环境变量
|
||||
* mockgen(可选)
|
||||
* mockgen(可选,将移除)
|
||||
* 更多问题请见 <a href="#注意事项">注意事项</a>
|
||||
|
||||
# 用法
|
||||
|
||||
### rpc服务生成用法
|
||||
|
||||
```shell script
|
||||
$ goctl rpc proto -h
|
||||
```
|
||||
@@ -91,27 +135,28 @@ OPTIONS:
|
||||
--src value, -s value the file path of the proto source file
|
||||
--dir value, -d value the target path of the code,default path is "${pwd}". [option]
|
||||
--service value, --srv value the name of rpc service. [option]
|
||||
--shared value the dir of the shared file,default path is "${pwd}/shared. [option]"
|
||||
--shared[已废弃] value the dir of the shared file,default path is "${pwd}/shared. [option]"
|
||||
--idea whether the command execution environment is from idea plugin. [option]
|
||||
|
||||
```
|
||||
|
||||
* 参数说明
|
||||
* --src 必填,proto数据源,目前暂时支持单个proto文件生成,这里不支持(不建议)外部依赖
|
||||
* --dir 非必填,默认为proto文件所在目录,生成代码的目标目录
|
||||
* --service 服务名称,非必填,默认为proto文件所在目录名称,但是,如果proto所在目录为一下结构:
|
||||
```shell script
|
||||
user
|
||||
├── cmd
|
||||
│ └── rpc
|
||||
│ └── user.proto
|
||||
```
|
||||
则服务名称亦为user,而非proto所在文件夹名称了,这里推荐使用这种结构,可以方便在同一个服务名下建立不同类型的服务(api、rpc、mq等),便于代码管理与维护。
|
||||
* --shared 非必填,默认为$dir(xxx.proto)/shared,rpc client逻辑代码存放目录。
|
||||
|
||||
> 注意:这里的shared文件夹名称将会是代码中的package名称。
|
||||
|
||||
* --idea 非必填,是否为idea插件中执行,保留字段,终端执行可以忽略
|
||||
### 参数说明
|
||||
|
||||
* --src 必填,proto数据源,目前暂时支持单个proto文件生成,这里不支持(不建议)外部依赖
|
||||
* --dir 非必填,默认为proto文件所在目录,生成代码的目标目录
|
||||
* --service 服务名称,非必填,默认为proto文件所在目录名称,但是,如果proto所在目录为一下结构:
|
||||
```shell script
|
||||
user
|
||||
├── cmd
|
||||
│ └── rpc
|
||||
│ └── user.proto
|
||||
```
|
||||
则服务名称亦为user,而非proto所在文件夹名称了,这里推荐使用这种结构,可以方便在同一个服务名下建立不同类型的服务(api、rpc、mq等),便于代码管理与维护。
|
||||
* --shared[⚠️已废弃] 非必填,默认为$dir(xxx.proto)/shared,rpc client逻辑代码存放目录。
|
||||
|
||||
> 注意:这里的shared文件夹名称将会是代码中的package名称。
|
||||
|
||||
* --idea 非必填,是否为idea插件中执行,保留字段,终端执行可以忽略
|
||||
|
||||
# 开发人员需要做什么
|
||||
|
||||
@@ -124,6 +169,10 @@ OPTIONS:
|
||||
对于需要进行rpc mock的开发人员,在安装了`mockgen`工具的前提下可以在rpc的shared文件中生成好对应的mock文件。
|
||||
|
||||
# 注意事项
|
||||
* `google.golang.org/grpc`需要降级到v1.26.0,且protoc-gen-go版本不能高于v1.3.2(see [https://github.com/grpc/grpc-go/issues/3347](https://github.com/grpc/grpc-go/issues/3347))即
|
||||
```
|
||||
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0
|
||||
```
|
||||
* proto不支持暂多文件同时生成
|
||||
* proto不支持外部依赖包引入,message不支持inline
|
||||
* 目前main文件、shared文件、handler文件会被强制覆盖,而和开发人员手动需要编写的则不会覆盖生成,这一类在代码头部均有
|
||||
@@ -133,8 +182,42 @@ OPTIONS:
|
||||
```
|
||||
的标识,请注意不要将也写业务性代码写在里面。
|
||||
|
||||
# 常见问题解决(go mod工程)
|
||||
|
||||
* 错误一:
|
||||
|
||||
```golang
|
||||
pb/xx.pb.go:220:7: undefined: grpc.ClientConnInterface
|
||||
pb/xx.pb.go:224:11: undefined: grpc.SupportPackageIsVersion6
|
||||
pb/xx.pb.go:234:5: undefined: grpc.ClientConnInterface
|
||||
pb/xx.pb.go:237:24: undefined: grpc.ClientConnInterface
|
||||
```
|
||||
解决方法:请将`protoc-gen-go`版本降至v1.3.2及一下
|
||||
|
||||
* 错误二:
|
||||
|
||||
```golang
|
||||
|
||||
# go.etcd.io/etcd/clientv3/balancer/picker
|
||||
../../../go/pkg/mod/go.etcd.io/etcd@v0.0.0-20200402134248-51bdeb39e698/clientv3/balancer/picker/err.go:25:9: cannot use &errPicker literal (type *errPicker) as type Picker in return argument:*errPicker does not implement Picker (wrong type for Pick method)
|
||||
have Pick(context.Context, balancer.PickInfo) (balancer.SubConn, func(balancer.DoneInfo), error)
|
||||
want Pick(balancer.PickInfo) (balancer.PickResult, error)
|
||||
../../../go/pkg/mod/go.etcd.io/etcd@v0.0.0-20200402134248-51bdeb39e698/clientv3/balancer/picker/roundrobin_balanced.go:33:9: cannot use &rrBalanced literal (type *rrBalanced) as type Picker in return argument:
|
||||
*rrBalanced does not implement Picker (wrong type for Pick method)
|
||||
have Pick(context.Context, balancer.PickInfo) (balancer.SubConn, func(balancer.DoneInfo), error)
|
||||
want Pick(balancer.PickInfo) (balancer.PickResult, error)
|
||||
#github.com/tal-tech/go-zero/rpcx/internal/balancer/p2c
|
||||
../../../go/pkg/mod/github.com/tal-tech/go-zero@v1.0.12/rpcx/internal/balancer/p2c/p2c.go:41:32: not enough arguments in call to base.NewBalancerBuilder
|
||||
have (string, *p2cPickerBuilder)
|
||||
want (string, base.PickerBuilder, base.Config)
|
||||
../../../go/pkg/mod/github.com/tal-tech/go-zero@v1.0.12/rpcx/internal/balancer/p2c/p2c.go:58:9: cannot use &p2cPicker literal (type *p2cPicker) as type balancer.Picker in return argument:
|
||||
*p2cPicker does not implement balancer.Picker (wrong type for Pick method)
|
||||
have Pick(context.Context, balancer.PickInfo) (balancer.SubConn, func(balancer.DoneInfo), error)
|
||||
want Pick(balancer.PickInfo) (balancer.PickResult, error)
|
||||
```
|
||||
|
||||
解决方法:
|
||||
|
||||
```golang
|
||||
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0
|
||||
```
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/ctx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/gen"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -17,6 +22,39 @@ func RpcTemplate(c *cli.Context) error {
|
||||
out := c.String("out")
|
||||
idea := c.Bool("idea")
|
||||
generator := gen.NewRpcTemplate(out, idea)
|
||||
generator.MustGenerate()
|
||||
generator.MustGenerate(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func RpcNew(c *cli.Context) error {
|
||||
idea := c.Bool("idea")
|
||||
arg := c.Args().First()
|
||||
if len(arg) == 0 {
|
||||
arg = "greet"
|
||||
}
|
||||
abs, err := filepath.Abs(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = os.Stat(abs)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
err = util.MkdirIfNotExist(abs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
dir := filepath.Base(filepath.Clean(abs))
|
||||
|
||||
protoSrc := filepath.Join(abs, fmt.Sprintf("%v.proto", dir))
|
||||
templateGenerator := gen.NewRpcTemplate(protoSrc, idea)
|
||||
templateGenerator.MustGenerate(false)
|
||||
|
||||
rpcCtx := ctx.MustCreateRpcContext(protoSrc, "", "", idea)
|
||||
generator := gen.NewDefaultRpcGenerator(rpcCtx)
|
||||
rpcCtx.Must(generator.Generate())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/project"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@@ -29,13 +30,12 @@ type RpcContext struct {
|
||||
ProtoFileSrc string
|
||||
ProtoSource string
|
||||
TargetDir string
|
||||
IsInGoEnv bool
|
||||
console.Console
|
||||
}
|
||||
|
||||
func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *RpcContext {
|
||||
log := console.NewConsole(idea)
|
||||
info, err := prepare(log)
|
||||
log.Must(err)
|
||||
|
||||
if stringx.From(protoSrc).IsEmptyOrSpace() {
|
||||
log.Fatalln("expected proto source, but nothing found")
|
||||
@@ -61,6 +61,9 @@ func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *R
|
||||
log.Fatalln("service name is not found")
|
||||
}
|
||||
|
||||
info, err := project.Prepare(targetDir, true)
|
||||
log.Must(err)
|
||||
|
||||
return &RpcContext{
|
||||
ProjectPath: info.Path,
|
||||
ProjectName: stringx.From(info.Name),
|
||||
@@ -70,6 +73,7 @@ func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *R
|
||||
ProtoFileSrc: srcFp,
|
||||
ProtoSource: filepath.Base(srcFp),
|
||||
TargetDir: targetDirFp,
|
||||
IsInGoEnv: info.IsInGoEnv,
|
||||
Console: log,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func Run(arg string) (string, error) {
|
||||
func Run(arg string, dir string) (string, error) {
|
||||
goos := runtime.GOOS
|
||||
var cmd *exec.Cmd
|
||||
switch goos {
|
||||
@@ -19,7 +19,9 @@ func Run(arg string) (string, error) {
|
||||
default:
|
||||
return "", fmt.Errorf("unexpected os: %v", goos)
|
||||
}
|
||||
|
||||
if len(dir) > 0 {
|
||||
cmd.Dir = dir
|
||||
}
|
||||
dtsout := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
cmd.Stdout = dtsout
|
||||
|
||||
@@ -42,6 +42,11 @@ func (g *defaultRpcGenerator) Generate() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.initGoMod()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.genEtc()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -82,5 +87,5 @@ func (g *defaultRpcGenerator) Generate() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
@@ -113,10 +113,7 @@ func (g *defaultRpcGenerator) genCall() error {
|
||||
}
|
||||
|
||||
service := file.Service[0]
|
||||
callPath, err := filepath.Abs(service.Name.Lower())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
callPath := filepath.Join(g.dirM[dirTarget], service.Name.Lower())
|
||||
|
||||
if err = util.MkdirIfNotExist(callPath); err != nil {
|
||||
return err
|
||||
@@ -152,7 +149,7 @@ func (g *defaultRpcGenerator) genCall() error {
|
||||
}
|
||||
|
||||
mockFile := filepath.Join(callPath, fmt.Sprintf("%s_mock.go", service.Name.Lower()))
|
||||
os.Remove(mockFile)
|
||||
_ = os.Remove(mockFile)
|
||||
err = util.With("shared").GoFmt(true).Parse(callTemplateText).SaveTo(map[string]interface{}{
|
||||
"name": service.Name.Lower(),
|
||||
"head": head,
|
||||
@@ -167,9 +164,9 @@ func (g *defaultRpcGenerator) genCall() error {
|
||||
return err
|
||||
}
|
||||
// if mockgen is already installed, it will generate code of gomock for shared files
|
||||
_, err = exec.LookPath("mockgen")
|
||||
if mockGenInstalled {
|
||||
execx.Run(fmt.Sprintf("go generate %s", filename))
|
||||
// Deprecated: it will be removed
|
||||
if mockGenInstalled && g.Ctx.IsInGoEnv {
|
||||
_, _ = execx.Run(fmt.Sprintf("go generate %s", filename), "")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -74,7 +74,7 @@ func (g *defaultRpcGenerator) genPb() error {
|
||||
func (g *defaultRpcGenerator) protocGenGo(target string) error {
|
||||
src := filepath.Dir(g.Ctx.ProtoFileSrc)
|
||||
sh := fmt.Sprintf(`protoc -I=%s --go_out=plugins=grpc:%s %s`, src, target, g.Ctx.ProtoFileSrc)
|
||||
stdout, err := execx.Run(sh)
|
||||
stdout, err := execx.Run(sh, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
22
tools/goctl/rpc/gen/gomod.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
|
||||
)
|
||||
|
||||
func (g *defaultRpcGenerator) initGoMod() error {
|
||||
if !g.Ctx.IsInGoEnv {
|
||||
projectDir := g.dirM[dirTarget]
|
||||
cmd := fmt.Sprintf("go mod init %s", g.Ctx.ProjectName.Source())
|
||||
output, err := execx.Run(fmt.Sprintf(cmd), projectDir)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return err
|
||||
}
|
||||
g.Ctx.Info(output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,26 +1,28 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
const rpcTemplateText = `syntax = "proto3";
|
||||
|
||||
package remote;
|
||||
package {{.package}};
|
||||
|
||||
message Request {
|
||||
string username = 1;
|
||||
string password = 2;
|
||||
string ping = 1;
|
||||
}
|
||||
|
||||
message Response {
|
||||
string name = 1;
|
||||
string gender = 2;
|
||||
string pong = 1;
|
||||
}
|
||||
|
||||
service User {
|
||||
rpc Login(Request) returns(Response);
|
||||
service {{.serviceName}} {
|
||||
rpc Ping(Request) returns(Response);
|
||||
}
|
||||
`
|
||||
|
||||
@@ -36,8 +38,15 @@ func NewRpcTemplate(out string, idea bool) *rpcTemplate {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rpcTemplate) MustGenerate() {
|
||||
err := util.With("t").Parse(rpcTemplateText).SaveTo(nil, r.out, false)
|
||||
func (r *rpcTemplate) MustGenerate(showState bool) {
|
||||
protoFilename := filepath.Base(r.out)
|
||||
serviceName := stringx.From(strings.TrimSuffix(protoFilename, filepath.Ext(protoFilename)))
|
||||
err := util.With("t").Parse(rpcTemplateText).SaveTo(map[string]string{
|
||||
"package": serviceName.UnTitle(),
|
||||
"serviceName": serviceName.Title(),
|
||||
}, r.out, false)
|
||||
r.Must(err)
|
||||
r.Success("Done.")
|
||||
if showState {
|
||||
r.Success("Done.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package ctx
|
||||
package project
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -10,7 +9,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,51 +21,63 @@ const (
|
||||
|
||||
type (
|
||||
Project struct {
|
||||
Path string
|
||||
Name string
|
||||
GoMod GoMod
|
||||
Path string // Project path name
|
||||
Name string // Project name
|
||||
Package string // The service related package
|
||||
// true-> project in go path or project init with go mod,or else->false
|
||||
IsInGoEnv bool
|
||||
GoMod GoMod
|
||||
}
|
||||
|
||||
GoMod struct {
|
||||
Module string
|
||||
Module string // The gomod module name
|
||||
Path string // The gomod related path
|
||||
}
|
||||
)
|
||||
|
||||
func prepare(log console.Console) (*Project, error) {
|
||||
log.Info("checking go env...")
|
||||
func Prepare(projectDir string, checkGrpcEnv bool) (*Project, error) {
|
||||
_, err := exec.LookPath(constGo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = exec.LookPath(constProtoC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = exec.LookPath(constProtoCGenGo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if checkGrpcEnv {
|
||||
_, err = exec.LookPath(constProtoC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = exec.LookPath(constProtoCGenGo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
goMod, module string
|
||||
goPath string
|
||||
name, path string
|
||||
pkg string
|
||||
)
|
||||
|
||||
ret, err := execx.Run(constGoMod)
|
||||
ret, err := execx.Run(constGoMod, projectDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
goMod = strings.TrimSpace(ret)
|
||||
|
||||
ret, err = execx.Run(constGoPath)
|
||||
goMod = strings.TrimSpace(ret)
|
||||
if goMod == os.DevNull {
|
||||
goMod = ""
|
||||
}
|
||||
|
||||
ret, err = execx.Run(constGoPath, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
goPath = strings.TrimSpace(ret)
|
||||
src := filepath.Join(goPath, "src")
|
||||
var isInGoEnv = true
|
||||
if len(goMod) > 0 {
|
||||
path = filepath.Dir(goMod)
|
||||
name = filepath.Base(path)
|
||||
@@ -81,28 +91,42 @@ func prepare(log console.Console) (*Project, error) {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
pwd, err := os.Getwd()
|
||||
pwd, err := execx.Run("pwd", projectDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pwd = filepath.Clean(strings.TrimSpace(pwd))
|
||||
|
||||
if !strings.HasPrefix(pwd, src) {
|
||||
return nil, fmt.Errorf("%s: project is not in go mod and go path", pwd)
|
||||
absPath, err := filepath.Abs(projectDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name = filepath.Clean(filepath.Base(absPath))
|
||||
path = projectDir
|
||||
pkg = name
|
||||
isInGoEnv = false
|
||||
} else {
|
||||
r := strings.TrimPrefix(pwd, src+string(filepath.Separator))
|
||||
name = filepath.Dir(r)
|
||||
if name == "." {
|
||||
name = r
|
||||
}
|
||||
path = filepath.Join(src, name)
|
||||
pkg = r
|
||||
}
|
||||
r := strings.TrimPrefix(pwd, src+string(filepath.Separator))
|
||||
name = filepath.Dir(r)
|
||||
if name == "." {
|
||||
name = r
|
||||
}
|
||||
path = filepath.Join(src, name)
|
||||
module = name
|
||||
}
|
||||
|
||||
return &Project{
|
||||
Name: name,
|
||||
Path: path,
|
||||
Name: name,
|
||||
Path: path,
|
||||
Package: pkg,
|
||||
IsInGoEnv: isInGoEnv,
|
||||
GoMod: GoMod{
|
||||
Module: module,
|
||||
Path: goMod,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||