diff --git a/doc/images/shorturl-arch.png b/doc/images/shorturl-arch.png new file mode 100644 index 00000000..160b0b92 Binary files /dev/null and b/doc/images/shorturl-arch.png differ diff --git a/doc/shorturl.md b/doc/shorturl.md new file mode 100644 index 00000000..592c1297 --- /dev/null +++ b/doc/shorturl.md @@ -0,0 +1,182 @@ +# 使用go-zero从0到1快速构建高并发的短链服务 + +## 0. 什么是短链服务? + +短链服务就是将长的URL网址,通过程序计算等方式,转换为简短的网址字符串。 + +写此短链服务是为了从整体上演示go-zero构建完整微服务的过程,算法和实现细节尽可能简化了,所以这不是一个高阶的短链服务。 + +## 1. 短链微服务架构图 + +![架构图](images/shorturl-arch.png) + +## 2. 创建工作目录并初始化go.mod + +* 创建工作目录`shorturl` +* 在`shorturl`目录下执行`go mod init shorturl`初始化`go.mod` + +## 3. 编写API Gateway代码 + +* 通过goctl生成`shorturl.api`并编辑,为了简洁,去除了文件开头的`info`,代码如下: + + ```go + type ( + shortenReq struct { + url string `form:"url"` + } + + shortenResp struct { + shortUrl string `json:"shortUrl"` + } + ) + + type ( + expandReq struct { + key string `form:"key"` + } + + expandResp struct { + url string `json:"url"` + } + ) + + service shorturl-api { + @server( + handler: ShortenHandler + ) + get /shorten(shortenReq) returns(shortenResp) + + @server( + handler: ExpandHandler + ) + get /expand(expandReq) returns(expandResp) + } + ``` + + type用法和go一致,service用来定义get/post/head/delete等api请求,解释如下: + + * `service shorturl-api {`这一行定义了service名字 + * `@server`部分用来定义server端用到的属性 + * `handler`定义了服务端handler名字 + * `get /shorten(shortenReq) returns(shortenResp)`定义了get方法的路由、请求参数、返回参数等 + +* 使用goctl生成API Gateway代码 + + ```shell + goctl api go -api shorturl.api -dir api + ``` + + 生成的文件结构如下: + + ``` + . + ├── api + │   ├── etc + │   │   └── shorturl-api.yaml // 配置文件 + │   ├── internal + │   │   ├── config + │   │   │   └── config.go // 定义配置 + │   │   ├── handler + │   │   │   ├── expandhandler.go // 实现expandHandler + │   │   │   ├── routes.go // 定义路由处理 + │   │   │   └── shortenhandler.go // 实现shortenHandler + │   │   ├── logic + │   │   │   ├── expandlogic.go // 实现ExpandLogic + │   │   │   └── shortenlogic.go // 实现ShortenLogic + │   │   ├── svc + │   │   │   └── servicecontext.go // 定义ServiceContext + │   │   └── types + │   │   └── types.go // 定义请求、返回结构体 + │   └── shorturl.go // main入口定义 + ├── go.mod + ├── go.sum + └── shorturl.api + ``` + +* 启动API Gateway服务,默认侦听在8888端口 + + ```shell + go run api/shorturl.go -f api/etc/shorturl-api.yaml + ``` + +* 测试API Gateway服务 + + ```shell + curl -i "http://localhost:8888/shorten?url=a" + ``` + + 返回如下: + + ```http + HTTP/1.1 200 OK + Content-Type: application/json + Date: Thu, 27 Aug 2020 14:31:39 GMT + Content-Length: 15 + + {"shortUrl":""} + ``` + +* 可以修改`internal/svc/servicecontext.go`来传递服务依赖(如果需要) +* 实现逻辑可以修改`internal/logic`下的对应文件 + +## 4. 编写shorten/expand rpc服务(未完) + +## 5. 定义数据库表结构 + +* shorturl下创建rpc/model目录:`mkdir -p rpc/model` +* 在roc/model目录下编写创建shorturl表的sql文件`shorturl.sql`,如下: + + ```sql + CREATE TABLE `shorturl` + ( + `id` bigint(10) NOT NULL AUTO_INCREMENT, + `key` varchar(255) NOT NULL DEFAULT '' COMMENT 'shorten key', + `url` varchar(255) DEFAULT '' COMMENT 'original url', + PRIMARY KEY(`id`), + UNIQUE KEY `key_index`(`key`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + ``` + +## 6. 自动生成CRUD+cache代码 + +* 在`rpc/model`目录下执行如下命令生成CRUD+cache代码,`-c`表示使用`redis cache` + + ```shell + goctl model mysql ddl -c -src shorturl.sql -dir . + ``` + + 生成后的文件结构如下: + + ``` + . + ├── api + │   ├── etc + │   │   └── shorturl-api.yaml + │   ├── internal + │   │   ├── config + │   │   │   └── config.go + │   │   ├── handler + │   │   │   ├── expandhandler.go + │   │   │   ├── routes.go + │   │   │   └── shortenhandler.go + │   │   ├── logic + │   │   │   ├── expandlogic.go + │   │   │   └── shortenlogic.go + │   │   ├── svc + │   │   │   └── servicecontext.go + │   │   └── types + │   │   └── types.go + │   └── shorturl.go + ├── go.mod + ├── go.sum + ├── rpc + │   └── model + │   ├── shorturl.sql + │   ├── shorturlmodel.go // CRUD+cache代码 + │   └── vars.go // 定义常量和变量 + ├── shorturl.api + └── shorturl.sql + ``` + +## 未完待续 + diff --git a/readme.md b/readme.md index 7106b5c3..0f532f89 100644 --- a/readme.md +++ b/readme.md @@ -185,6 +185,7 @@ go get -u github.com/tal-tech/go-zero ## 8. 文档 (逐步完善中) +* [从0到1快速构建一个高并发的微服务系统](doc/shorturl.md) * [goctl使用帮助](doc/goctl.md) * [关键字替换和敏感词过滤工具](doc/keywords.md) diff --git a/tools/goctl/goctl.go b/tools/goctl/goctl.go index 3a1bf3c3..287b35da 100644 --- a/tools/goctl/goctl.go +++ b/tools/goctl/goctl.go @@ -194,11 +194,11 @@ var ( Subcommands: []cli.Command{ { Name: "mysql", - Usage: `generate mysql model"`, + Usage: `generate mysql model`, Subcommands: []cli.Command{ { Name: "ddl", - Usage: `generate mysql model from ddl"`, + Usage: `generate mysql model from ddl`, Flags: []cli.Flag{ cli.StringFlag{ Name: "src, s", @@ -221,15 +221,15 @@ var ( }, { Name: "datasource", - Usage: `generate model from datasource"`, + Usage: `generate model from datasource`, Flags: []cli.Flag{ cli.StringFlag{ Name: "url", - Usage: `the data source of database,like "root:password@tcp(127.0.0.1:3306)/database"`, + Usage: `the data source of database,like "root:password@tcp(127.0.0.1:3306)/database`, }, cli.StringFlag{ Name: "table, t", - Usage: `source table,tables separated by commas,like "user,course"`, + Usage: `source table,tables separated by commas,like "user,course`, }, cli.BoolFlag{ Name: "cache, c", diff --git a/tools/goctl/model/sql/example/generator.sh b/tools/goctl/model/sql/example/generator.sh index 2e121423..fb43430b 100644 --- a/tools/goctl/model/sql/example/generator.sh +++ b/tools/goctl/model/sql/example/generator.sh @@ -1,7 +1,7 @@ #!/bin/bash # generate model with cache from ddl - goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c=true +goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c # generate model with cache from data source - goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model" \ No newline at end of file +goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model" \ No newline at end of file diff --git a/tools/goctl/model/sql/gen/error.go b/tools/goctl/model/sql/gen/error.go index 95b730bd..be9cf947 100644 --- a/tools/goctl/model/sql/gen/error.go +++ b/tools/goctl/model/sql/gen/error.go @@ -2,6 +2,4 @@ package gen import "errors" -var ( - ErrCircleQuery = errors.New("circle query with other fields") -) +var ErrCircleQuery = errors.New("circle query with other fields") diff --git a/tools/goctl/model/sql/gen/gen.go b/tools/goctl/model/sql/gen/gen.go index 45fc8656..e103ee78 100644 --- a/tools/goctl/model/sql/gen/gen.go +++ b/tools/goctl/model/sql/gen/gen.go @@ -82,7 +82,7 @@ func (g *defaultGenerator) Start(withCache bool) error { } } // generate error file - filename := filepath.Join(dirAbs, "error.go") + filename := filepath.Join(dirAbs, "vars.go") if !util.FileExists(filename) { err = ioutil.WriteFile(filename, []byte(template.Error), os.ModePerm) if err != nil { diff --git a/tools/goctl/model/sql/template/delete.go b/tools/goctl/model/sql/template/delete.go index 1830043e..72d5db36 100644 --- a/tools/goctl/model/sql/template/delete.go +++ b/tools/goctl/model/sql/template/delete.go @@ -6,6 +6,7 @@ func (m *{{.upperStartCamelObject}}Model) Delete({{.lowerStartCamelPrimaryKey}} if err!=nil{ return err }{{end}} + {{.keys}} _, err {{if .containsIndexCache}}={{else}}:={{end}} m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { query := ` + "`" + `delete from ` + "` +" + ` m.table + ` + " `" + ` where {{.originalPrimaryKey}} = ?` + "`" + ` diff --git a/tools/goctl/model/sql/template/errors.go b/tools/goctl/model/sql/template/errors.go index a24ff3fa..f35e8942 100644 --- a/tools/goctl/model/sql/template/errors.go +++ b/tools/goctl/model/sql/template/errors.go @@ -4,8 +4,5 @@ var Error = `package model import "github.com/tal-tech/go-zero/core/stores/sqlx" -var ( - ErrNotFound = sqlx.ErrNotFound -) - +var ErrNotFound = sqlx.ErrNotFound `