kevin 4 лет назад
Родитель
Сommit
77e23ad65d

BIN
doc/images/shorturl-arch.png


+ 182 - 0
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
+  ```
+
+## 未完待续
+

+ 1 - 0
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)
 

+ 5 - 5
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",

+ 2 - 2
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"
+goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2"  -dir="./model"

+ 1 - 3
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")

+ 1 - 1
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 {

+ 1 - 0
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}} = ?` + "`" + `

+ 1 - 4
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
 `