Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a13b48c33e | ||
|
|
033525fea8 | ||
|
|
607fc3297a | ||
|
|
4287877b74 | ||
|
|
2b7545ce11 | ||
|
|
60925c1164 | ||
|
|
1c9e81aa28 | ||
|
|
db7dcaa120 | ||
|
|
099d44054d | ||
|
|
f5f873c6bd | ||
|
|
6dbd3eada9 | ||
|
|
cf2d20a211 | ||
|
|
91bfc093f4 | ||
|
|
cf33aae91d | ||
|
|
c9494c8bc7 |
10
readme-en.md
10
readme-en.md
@@ -1,3 +1,5 @@
|
|||||||
|
<img align="right" width="150px" src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/go-zero.png">
|
||||||
|
|
||||||
# go-zero
|
# go-zero
|
||||||
|
|
||||||
English | [简体中文](readme.md)
|
English | [简体中文](readme.md)
|
||||||
@@ -23,7 +25,7 @@ Advantages of go-zero:
|
|||||||
* auto validate the request parameters from clients
|
* auto validate the request parameters from clients
|
||||||
* plenty of builtin microservice management and concurrent toolkits
|
* plenty of builtin microservice management and concurrent toolkits
|
||||||
|
|
||||||
<img src="https://github.com/tal-tech/zero-doc/blob/main/doc/images/architecture-en.png" alt="Architecture" width="1500" />
|
<img src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/architecture-en.png" alt="Architecture" width="1500" />
|
||||||
|
|
||||||
## 1. Backgrounds of go-zero
|
## 1. Backgrounds of go-zero
|
||||||
|
|
||||||
@@ -74,7 +76,7 @@ go-zero is a web and rpc framework that integrates lots of engineering practices
|
|||||||
|
|
||||||
As below, go-zero protects the system with couple layers and mechanisms:
|
As below, go-zero protects the system with couple layers and mechanisms:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 4. Future development plans of go-zero
|
## 4. Future development plans of go-zero
|
||||||
|
|
||||||
@@ -164,6 +166,8 @@ go get -u github.com/tal-tech/go-zero
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
cd greet
|
cd greet
|
||||||
|
go mod init
|
||||||
|
go mod tidy
|
||||||
go run greet.go -f etc/greet-api.yaml
|
go run greet.go -f etc/greet-api.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -198,7 +202,7 @@ go get -u github.com/tal-tech/go-zero
|
|||||||
|
|
||||||
## 7. Benchmark
|
## 7. Benchmark
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
[Checkout the test code](https://github.com/smallnest/go-web-framework-benchmark)
|
[Checkout the test code](https://github.com/smallnest/go-web-framework-benchmark)
|
||||||
|
|
||||||
|
|||||||
125
readme.md
125
readme.md
@@ -1,3 +1,5 @@
|
|||||||
|
<img align="right" width="150px" src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/go-zero.png">
|
||||||
|
|
||||||
# go-zero
|
# go-zero
|
||||||
|
|
||||||
[English](readme-en.md) | 简体中文
|
[English](readme-en.md) | 简体中文
|
||||||
@@ -8,28 +10,28 @@
|
|||||||
[](https://github.com/tal-tech/go-zero)
|
[](https://github.com/tal-tech/go-zero)
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
## 0. go-zero介绍
|
## 0. go-zero 介绍
|
||||||
|
|
||||||
go-zero是一个集成了各种工程实践的web和rpc框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。
|
go-zero 是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。
|
||||||
|
|
||||||
go-zero 包含极简的 API 定义和生成工具 goctl,可以根据定义的 api 文件一键生成 Go, iOS, Android, Kotlin, Dart, TypeScript, JavaScript 代码,并可直接运行。
|
go-zero 包含极简的 API 定义和生成工具 goctl,可以根据定义的 api 文件一键生成 Go, iOS, Android, Kotlin, Dart, TypeScript, JavaScript 代码,并可直接运行。
|
||||||
|
|
||||||
使用go-zero的好处:
|
使用 go-zero 的好处:
|
||||||
|
|
||||||
* 轻松获得支撑千万日活服务的稳定性
|
* 轻松获得支撑千万日活服务的稳定性
|
||||||
* 内建级联超时控制、限流、自适应熔断、自适应降载等微服务治理能力,无需配置和额外代码
|
* 内建级联超时控制、限流、自适应熔断、自适应降载等微服务治理能力,无需配置和额外代码
|
||||||
* 微服务治理中间件可无缝集成到其它现有框架使用
|
* 微服务治理中间件可无缝集成到其它现有框架使用
|
||||||
* 极简的API描述,一键生成各端代码
|
* 极简的 API 描述,一键生成各端代码
|
||||||
* 自动校验客户端请求参数合法性
|
* 自动校验客户端请求参数合法性
|
||||||
* 大量微服务治理和并发工具包
|
* 大量微服务治理和并发工具包
|
||||||
|
|
||||||
<img src="https://github.com/tal-tech/zero-doc/blob/main/doc/images/architecture.png" alt="架构图" width="1500" />
|
<img src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/architecture.png" alt="架构图" width="1500" />
|
||||||
|
|
||||||
## 1. go-zero框架背景
|
## 1. go-zero 框架背景
|
||||||
|
|
||||||
18年初,我们决定从`Java+MongoDB`的单体架构迁移到微服务架构,经过仔细思考和对比,我们决定:
|
18 年初,我们决定从 `Java+MongoDB` 的单体架构迁移到微服务架构,经过仔细思考和对比,我们决定:
|
||||||
|
|
||||||
* 基于Go语言
|
* 基于 Go 语言
|
||||||
* 高效的性能
|
* 高效的性能
|
||||||
* 简洁的语法
|
* 简洁的语法
|
||||||
* 广泛验证的工程效率
|
* 广泛验证的工程效率
|
||||||
@@ -40,7 +42,7 @@ go-zero 包含极简的 API 定义和生成工具 goctl,可以根据定义的
|
|||||||
* 需要有更快速的问题定位能力
|
* 需要有更快速的问题定位能力
|
||||||
* 更便捷的增加新特性
|
* 更便捷的增加新特性
|
||||||
|
|
||||||
## 2. go-zero框架设计思考
|
## 2. go-zero 框架设计思考
|
||||||
|
|
||||||
对于微服务框架的设计,我们期望保障微服务稳定性的同时,也要特别注重研发效率。所以设计之初,我们就有如下一些准则:
|
对于微服务框架的设计,我们期望保障微服务稳定性的同时,也要特别注重研发效率。所以设计之初,我们就有如下一些准则:
|
||||||
|
|
||||||
@@ -53,21 +55,21 @@ go-zero 包含极简的 API 定义和生成工具 goctl,可以根据定义的
|
|||||||
* 对业务开发友好,封装复杂度
|
* 对业务开发友好,封装复杂度
|
||||||
* 约束做一件事只有一种方式
|
* 约束做一件事只有一种方式
|
||||||
|
|
||||||
我们经历不到半年时间,彻底完成了从`Java+MongoDB`到`Golang+MySQL`为主的微服务体系迁移,并于18年8月底完全上线,稳定保障了业务后续迅速增长,确保了整个服务的高可用。
|
我们经历不到半年时间,彻底完成了从 `Java+MongoDB` 到 `Golang+MySQL` 为主的微服务体系迁移,并于 18 年 8 月底完全上线,稳定保障了业务后续迅速增长,确保了整个服务的高可用。
|
||||||
|
|
||||||
## 3. go-zero项目实现和特点
|
## 3. go-zero 项目实现和特点
|
||||||
|
|
||||||
go-zero是一个集成了各种工程实践的包含web和rpc框架,有如下主要特点:
|
go-zero 是一个集成了各种工程实践的包含 web 和 rpc 框架,有如下主要特点:
|
||||||
|
|
||||||
* 强大的工具支持,尽可能少的代码编写
|
* 强大的工具支持,尽可能少的代码编写
|
||||||
* 极简的接口
|
* 极简的接口
|
||||||
* 完全兼容net/http
|
* 完全兼容 net/http
|
||||||
* 支持中间件,方便扩展
|
* 支持中间件,方便扩展
|
||||||
* 高性能
|
* 高性能
|
||||||
* 面向故障编程,弹性设计
|
* 面向故障编程,弹性设计
|
||||||
* 内建服务发现、负载均衡
|
* 内建服务发现、负载均衡
|
||||||
* 内建限流、熔断、降载,且自动触发,自动恢复
|
* 内建限流、熔断、降载,且自动触发,自动恢复
|
||||||
* API参数自动校验
|
* API 参数自动校验
|
||||||
* 超时级联控制
|
* 超时级联控制
|
||||||
* 自动缓存控制
|
* 自动缓存控制
|
||||||
* 链路跟踪、统计报警等
|
* 链路跟踪、统计报警等
|
||||||
@@ -75,7 +77,7 @@ go-zero是一个集成了各种工程实践的包含web和rpc框架,有如下
|
|||||||
|
|
||||||
如下图,我们从多个层面保障了整体服务的高可用:
|
如下图,我们从多个层面保障了整体服务的高可用:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 4. Installation
|
## 4. Installation
|
||||||
|
|
||||||
@@ -91,88 +93,95 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/
|
|||||||
|
|
||||||
[快速构建高并发微服务](https://github.com/tal-tech/zero-doc/blob/main/doc/shorturl.md)
|
[快速构建高并发微服务](https://github.com/tal-tech/zero-doc/blob/main/doc/shorturl.md)
|
||||||
|
|
||||||
[快速构建高并发微服务-多RPC版](https://github.com/tal-tech/zero-doc/blob/main/doc/bookstore.md)
|
[快速构建高并发微服务 - 多 RPC 版](https://github.com/tal-tech/zero-doc/blob/main/doc/bookstore.md)
|
||||||
|
|
||||||
1. 安装goctl工具
|
1. 安装 goctl 工具
|
||||||
|
|
||||||
`goctl`读作`go control`,不要读成`go C-T-L`。`goctl`的意思是不要被代码控制,而是要去控制它。其中的`go`不是指`golang`。在设计`goctl`之初,我就希望通过`她`来解放我们的双手👈
|
`goctl` 读作 `go control`,不要读成 `go C-T-L`。`goctl` 的意思是不要被代码控制,而是要去控制它。其中的 `go` 不是指 `golang`。在设计 `goctl` 之初,我就希望通过 ` 她 ` 来解放我们的双手👈
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u 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可执行
|
确保 goctl 可执行
|
||||||
|
|
||||||
2. 快速生成api服务
|
2. 快速生成 api 服务
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
goctl api new greet
|
goctl api new greet
|
||||||
cd greet
|
cd greet
|
||||||
go run greet.go -f etc/greet-api.yaml
|
go mod init
|
||||||
```
|
go mod tidy
|
||||||
|
go run greet.go -f etc/greet-api.yaml
|
||||||
|
```
|
||||||
|
|
||||||
默认侦听在8888端口(可以在配置文件里修改),可以通过curl请求:
|
默认侦听在 8888 端口(可以在配置文件里修改),可以通过 curl 请求:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl -i http://localhost:8888/greet/from/you
|
curl -i http://localhost:8888/greet/from/you
|
||||||
```
|
```
|
||||||
|
|
||||||
返回如下:
|
返回如下:
|
||||||
|
|
||||||
```http
|
```http
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
Date: Sun, 30 Aug 2020 15:32:35 GMT
|
Content-Type: application/json
|
||||||
Content-Length: 0
|
Date: Thu, 22 Oct 2020 14:03:18 GMT
|
||||||
```
|
Content-Length: 14
|
||||||
|
|
||||||
编写业务代码:
|
{"message":""}
|
||||||
|
```
|
||||||
|
|
||||||
* api文件定义了服务对外暴露的路由,可参考[api规范](https://github.com/tal-tech/zero-doc/blob/main/doc/goctl.md)
|
编写业务代码:
|
||||||
* 可以在servicecontext.go里面传递依赖给logic,比如mysql, redis等
|
|
||||||
* 在api定义的get/post/put/delete等请求对应的logic里增加业务处理逻辑
|
|
||||||
|
|
||||||
3. 可以根据api文件生成前端需要的Java, TypeScript, Dart, JavaScript代码
|
* api 文件定义了服务对外暴露的路由,可参考 [api 规范](https://github.com/tal-tech/zero-doc/blob/main/doc/goctl.md)
|
||||||
|
* 可以在 servicecontext.go 里面传递依赖给 logic,比如 mysql, redis 等
|
||||||
|
* 在 api 定义的 get/post/put/delete 等请求对应的 logic 里增加业务处理逻辑
|
||||||
|
|
||||||
```shell
|
3. 可以根据 api 文件生成前端需要的 Java, TypeScript, Dart, JavaScript 代码
|
||||||
goctl api java -api greet.api -dir greet
|
|
||||||
goctl api dart -api greet.api -dir greet
|
```shell
|
||||||
...
|
goctl api java -api greet.api -dir greet
|
||||||
```
|
goctl api dart -api greet.api -dir greet
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
## 6. Benchmark
|
## 6. Benchmark
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
[测试代码见这里](https://github.com/smallnest/go-web-framework-benchmark)
|
[测试代码见这里](https://github.com/smallnest/go-web-framework-benchmark)
|
||||||
|
|
||||||
## 7. 文档
|
## 7. 文档
|
||||||
|
|
||||||
* API文档 (逐步完善中)
|
* API 文档 (逐步完善中)
|
||||||
|
|
||||||
[https://www.yuque.com/tal-tech/go-zero](https://www.yuque.com/tal-tech/go-zero)
|
[https://www.yuque.com/tal-tech/go-zero](https://www.yuque.com/tal-tech/go-zero)
|
||||||
|
|
||||||
* awesome系列
|
* awesome 系列
|
||||||
* [快速构建高并发微服务](https://github.com/tal-tech/zero-doc/blob/main/doc/shorturl.md)
|
* [快速构建高并发微服务](https://github.com/tal-tech/zero-doc/blob/main/doc/shorturl.md)
|
||||||
* [快速构建高并发微服务-多RPC版](https://github.com/tal-tech/zero-doc/blob/main/doc/bookstore.md)
|
* [快速构建高并发微服务 - 多 RPC 版](https://github.com/tal-tech/zero-doc/blob/main/doc/bookstore.md)
|
||||||
* [goctl使用帮助](https://github.com/tal-tech/zero-doc/blob/main/doc/goctl.md)
|
* [goctl 使用帮助](https://github.com/tal-tech/zero-doc/blob/main/doc/goctl.md)
|
||||||
* [通过MapReduce降低服务响应时间](https://github.com/tal-tech/zero-doc/blob/main/doc/mapreduce.md)
|
* [通过 MapReduce 降低服务响应时间](https://github.com/tal-tech/zero-doc/blob/main/doc/mapreduce.md)
|
||||||
* [关键字替换和敏感词过滤工具](https://github.com/tal-tech/zero-doc/blob/main/doc/keywords.md)
|
* [关键字替换和敏感词过滤工具](https://github.com/tal-tech/zero-doc/blob/main/doc/keywords.md)
|
||||||
* [进程内缓存使用方法](https://github.com/tal-tech/zero-doc/blob/main/doc/collection.md)
|
* [进程内缓存使用方法](https://github.com/tal-tech/zero-doc/blob/main/doc/collection.md)
|
||||||
* [防止缓存击穿之进程内共享调用](https://github.com/tal-tech/zero-doc/blob/main/doc/sharedcalls.md)
|
* [防止缓存击穿之进程内共享调用](https://github.com/tal-tech/zero-doc/blob/main/doc/sharedcalls.md)
|
||||||
* [基于prometheus的微服务指标监控](https://github.com/tal-tech/zero-doc/blob/main/doc/metric.md)
|
* [基于 prometheus 的微服务指标监控](https://github.com/tal-tech/zero-doc/blob/main/doc/metric.md)
|
||||||
* [文本序列化和反序列化](https://github.com/tal-tech/zero-doc/blob/main/doc/mapping.md)
|
* [文本序列化和反序列化](https://github.com/tal-tech/zero-doc/blob/main/doc/mapping.md)
|
||||||
* [快速构建jwt鉴权认证](https://github.com/tal-tech/zero-doc/blob/main/doc/jwt.md)
|
* [快速构建 jwt 鉴权认证](https://github.com/tal-tech/zero-doc/blob/main/doc/jwt.md)
|
||||||
|
|
||||||
## 9. 微信交流群
|
## 9. 微信交流群
|
||||||
|
|
||||||
加群之前有劳给一个star,一个小小的star是作者们回答海量问题的动力。
|
加群之前有劳给一个 star,一个小小的 star 是作者们回答海量问题的动力。
|
||||||
|
|
||||||
如果文档中未能覆盖的任何疑问,欢迎您在群里提出,我们会尽快答复。
|
如果文档中未能覆盖的任何疑问,欢迎您在群里提出,我们会尽快答复。
|
||||||
|
|
||||||
您可以在群内提出使用中需要改进的地方,我们会考虑合理性并尽快修改。
|
您可以在群内提出使用中需要改进的地方,我们会考虑合理性并尽快修改。
|
||||||
|
|
||||||
如果您发现bug请及时提issue,我们会尽快确认并修改。
|
如果您发现 bug 请及时提 issue,我们会尽快确认并修改。
|
||||||
|
|
||||||
<!-- 扫码后请加群主,便于我邀请您进讨论群,并请退出扫码网关群,谢谢!-->
|
<!-- 扫码后请加群主,便于我邀请您进讨论群,并请退出扫码网关群,谢谢!-->
|
||||||
|
|
||||||
|
开源中国年度评选,给go-zero投上一票:[https://www.oschina.net/p/go-zero](https://www.oschina.net/p/go-zero)
|
||||||
|
|
||||||
<img src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/wechat.jpg" alt="wechat" width="300" />
|
<img src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/wechat.jpg" alt="wechat" width="300" />
|
||||||
@@ -15,8 +15,8 @@ import (
|
|||||||
const apiTemplate = `info(
|
const apiTemplate = `info(
|
||||||
title: // TODO: add title
|
title: // TODO: add title
|
||||||
desc: // TODO: add description
|
desc: // TODO: add description
|
||||||
author: {{.gitUser}}
|
author: "{{.gitUser}}"
|
||||||
email: {{.gitEmail}}
|
email: "{{.gitEmail}}"
|
||||||
)
|
)
|
||||||
|
|
||||||
type request struct {
|
type request struct {
|
||||||
@@ -28,14 +28,10 @@ type response struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
service {{.serviceName}} {
|
service {{.serviceName}} {
|
||||||
@server(
|
@handler // TODO: set handler name and delete this comment
|
||||||
handler: // TODO: set handler name and delete this comment
|
|
||||||
)
|
|
||||||
get /users/id/:userId(request) returns(response)
|
get /users/id/:userId(request) returns(response)
|
||||||
|
|
||||||
@server(
|
@handler // TODO: set handler name and delete this comment
|
||||||
handler: // TODO: set handler name and delete this comment
|
|
||||||
)
|
|
||||||
post /users/create(request)
|
post /users/create(request)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/format"
|
"go/format"
|
||||||
|
"go/scanner"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -22,39 +23,79 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func GoFormatApi(c *cli.Context) error {
|
func GoFormatApi(c *cli.Context) error {
|
||||||
dir := c.String("dir")
|
useStdin := c.Bool("stdin")
|
||||||
if len(dir) == 0 {
|
|
||||||
return errors.New("missing -dir")
|
|
||||||
}
|
|
||||||
|
|
||||||
printToConsole := c.Bool("p")
|
|
||||||
|
|
||||||
var be errorx.BatchError
|
var be errorx.BatchError
|
||||||
err := filepath.Walk(dir, func(path string, fi os.FileInfo, errBack error) (err error) {
|
if useStdin {
|
||||||
if strings.HasSuffix(path, ".api") {
|
if err := ApiFormatByStdin(); err != nil {
|
||||||
err := ApiFormat(path, printToConsole)
|
be.Add(err)
|
||||||
if err != nil {
|
|
||||||
be.Add(util.WrapErr(err, fi.Name()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
} else {
|
||||||
})
|
dir := c.String("dir")
|
||||||
be.Add(err)
|
if len(dir) == 0 {
|
||||||
|
return errors.New("missing -dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := os.Lstat(dir)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(dir + ": No such file or directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = filepath.Walk(dir, func(path string, fi os.FileInfo, errBack error) (err error) {
|
||||||
|
if strings.HasSuffix(path, ".api") {
|
||||||
|
if err := ApiFormatByPath(path); err != nil {
|
||||||
|
be.Add(util.WrapErr(err, fi.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
be.Add(err)
|
||||||
|
}
|
||||||
if be.NotNil() {
|
if be.NotNil() {
|
||||||
errs := be.Err().Error()
|
scanner.PrintError(os.Stderr, be.Err())
|
||||||
fmt.Println(errs)
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return be.Err()
|
return be.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ApiFormat(path string, printToConsole bool) error {
|
func ApiFormatByStdin() error {
|
||||||
data, err := ioutil.ReadFile(path)
|
data, err := ioutil.ReadAll(os.Stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
r := reg.ReplaceAllStringFunc(string(data), func(m string) string {
|
result, err := apiFormat(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Print(result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiFormatByPath(apiFilePath string) error {
|
||||||
|
data, err := ioutil.ReadFile(apiFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := apiFormat(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(apiFilePath, []byte(result), os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiFormat(data string) (string, error) {
|
||||||
|
|
||||||
|
r := reg.ReplaceAllStringFunc(data, func(m string) string {
|
||||||
parts := reg.FindStringSubmatch(m)
|
parts := reg.FindStringSubmatch(m)
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
return m
|
return m
|
||||||
@@ -67,11 +108,11 @@ func ApiFormat(path string, printToConsole bool) error {
|
|||||||
|
|
||||||
apiStruct, err := parser.ParseApi(r)
|
apiStruct, err := parser.ParseApi(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
info := strings.TrimSpace(apiStruct.Info)
|
info := strings.TrimSpace(apiStruct.Info)
|
||||||
if len(apiStruct.Service) == 0 {
|
if len(apiStruct.Service) == 0 {
|
||||||
return nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fs, err := format.Source([]byte(strings.TrimSpace(apiStruct.StructBody)))
|
fs, err := format.Source([]byte(strings.TrimSpace(apiStruct.StructBody)))
|
||||||
@@ -81,16 +122,16 @@ func ApiFormat(path string, printToConsole bool) error {
|
|||||||
if lineNumber > 0 {
|
if lineNumber > 0 {
|
||||||
ln, err := strconv.ParseInt(str[:lineNumber], 10, 64)
|
ln, err := strconv.ParseInt(str[:lineNumber], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
pn := 0
|
pn := 0
|
||||||
if len(info) > 0 {
|
if len(info) > 0 {
|
||||||
pn = countRune(info, '\n') + 1
|
pn = countRune(info, '\n') + 1
|
||||||
}
|
}
|
||||||
number := int(ln) + pn + 1
|
number := int(ln) + pn + 1
|
||||||
return errors.New(fmt.Sprintf("line: %d, %s", number, str[lineNumber+1:]))
|
return "", errors.New(fmt.Sprintf("line: %d, %s", number, str[lineNumber+1:]))
|
||||||
}
|
}
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var result string
|
var result string
|
||||||
@@ -107,11 +148,7 @@ func ApiFormat(path string, printToConsole bool) error {
|
|||||||
result += strings.TrimSpace(apiStruct.Service) + "\n\n"
|
result += strings.TrimSpace(apiStruct.Service) + "\n\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
if printToConsole {
|
return result, nil
|
||||||
_, err := fmt.Print(result)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ioutil.WriteFile(path, []byte(result), os.ModePerm)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func countRune(s string, r rune) int {
|
func countRune(s string, r rune) int {
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func DoGenProject(apiFile, dir string, force bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = apiformat.ApiFormat(apiFile, false); err != nil {
|
if err := apiformat.ApiFormatByPath(apiFile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -178,6 +178,13 @@ service A-api {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const apiHasNoRequest = `
|
||||||
|
service A-api {
|
||||||
|
@handler GreetHandler
|
||||||
|
post /greet/ping ()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
func TestParser(t *testing.T) {
|
func TestParser(t *testing.T) {
|
||||||
filename := "greet.api"
|
filename := "greet.api"
|
||||||
err := ioutil.WriteFile(filename, []byte(testApiTemplate), os.ModePerm)
|
err := ioutil.WriteFile(filename, []byte(testApiTemplate), os.ModePerm)
|
||||||
@@ -311,6 +318,21 @@ func TestApiHasJwtAndMiddleware(t *testing.T) {
|
|||||||
validate(t, filename)
|
validate(t, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApiHasNoRequestBody(t *testing.T) {
|
||||||
|
filename := "greet.api"
|
||||||
|
err := ioutil.WriteFile(filename, []byte(apiHasNoRequest), os.ModePerm)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(filename)
|
||||||
|
|
||||||
|
parser, err := parser.NewParser(filename)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
_, err = parser.Parse()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
validate(t, filename)
|
||||||
|
}
|
||||||
|
|
||||||
func validate(t *testing.T, api string) {
|
func validate(t *testing.T, api string) {
|
||||||
dir := "_go"
|
dir := "_go"
|
||||||
err := DoGenProject(api, dir, true)
|
err := DoGenProject(api, dir, true)
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ import (
|
|||||||
|
|
||||||
func {{.HandlerName}}(ctx *svc.ServiceContext) http.HandlerFunc {
|
func {{.HandlerName}}(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var req types.{{.RequestType}}
|
{{if .HasRequest}}var req types.{{.RequestType}}
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
httpx.Error(w, err)
|
httpx.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}{{end}}
|
||||||
|
|
||||||
l := logic.New{{.LogicType}}(r.Context(), ctx)
|
l := logic.New{{.LogicType}}(r.Context(), ctx)
|
||||||
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}(req)
|
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}req{{end}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpx.Error(w, err)
|
httpx.Error(w, err)
|
||||||
} else {
|
} else {
|
||||||
@@ -47,6 +47,7 @@ type Handler struct {
|
|||||||
LogicType string
|
LogicType string
|
||||||
Call string
|
Call string
|
||||||
HasResp bool
|
HasResp bool
|
||||||
|
HasRequest bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func genHandler(dir string, group spec.Group, route spec.Route) error {
|
func genHandler(dir string, group spec.Group, route spec.Route) error {
|
||||||
@@ -71,6 +72,7 @@ func genHandler(dir string, group spec.Group, route spec.Route) error {
|
|||||||
LogicType: strings.TrimSuffix(strings.Title(handler), "Handler") + "Logic",
|
LogicType: strings.TrimSuffix(strings.Title(handler), "Handler") + "Logic",
|
||||||
Call: strings.Title(strings.TrimSuffix(handler, "Handler")),
|
Call: strings.Title(strings.TrimSuffix(handler, "Handler")),
|
||||||
HasResp: len(route.ResponseType.Name) > 0,
|
HasResp: len(route.ResponseType.Name) > 0,
|
||||||
|
HasRequest: len(route.RequestType.Name) > 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
59
tools/goctl/api/gogen/genmiddleware.go
Normal file
59
tools/goctl/api/gogen/genmiddleware.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package gogen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var middlewareImplementCode = `
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type {{.name}} struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func New{{.name}}() *{{.name}} {
|
||||||
|
return &{{.name}}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *{{.name}})Handle(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// TODO generate middleware implement function, delete after code implementation
|
||||||
|
|
||||||
|
// Passthrough to next handler if need
|
||||||
|
next(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func genMiddleware(dir string, middlewares []string) error {
|
||||||
|
for _, item := range middlewares {
|
||||||
|
filename := strings.TrimSuffix(strings.ToLower(item), "middleware") + "middleware" + ".go"
|
||||||
|
fp, created, err := util.MaybeCreateFile(dir, middlewareDir, filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !created {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer fp.Close()
|
||||||
|
|
||||||
|
name := strings.TrimSuffix(item, "Middleware") + "Middleware"
|
||||||
|
t := template.Must(template.New("contextTemplate").Parse(middlewareImplementCode))
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
err = t.Execute(buffer, map[string]string{
|
||||||
|
"name": strings.Title(name),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
formatCode := formatCode(buffer.String())
|
||||||
|
_, err = fp.WriteString(formatCode)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package gogen
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||||
@@ -25,8 +26,12 @@ type ServiceContext struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewServiceContext(c {{.config}}) *ServiceContext {
|
func NewServiceContext(c {{.config}}) *ServiceContext {
|
||||||
return &ServiceContext{Config: c}
|
return &ServiceContext{
|
||||||
|
Config: c,
|
||||||
|
{{.middlewareAssignment}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,21 +62,32 @@ func genServiceContext(dir string, api *spec.ApiSpec) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var middlewareStr string
|
var middlewareStr string
|
||||||
for _, item := range getMiddleware(api) {
|
var middlewareAssignment string
|
||||||
|
var middlewares = getMiddleware(api)
|
||||||
|
err = genMiddleware(dir, middlewares)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range middlewares {
|
||||||
middlewareStr += fmt.Sprintf("%s rest.Middleware\n", item)
|
middlewareStr += fmt.Sprintf("%s rest.Middleware\n", item)
|
||||||
|
name := strings.TrimSuffix(item, "Middleware") + "Middleware"
|
||||||
|
middlewareAssignment += fmt.Sprintf("%s: %s,\n", item, fmt.Sprintf("middleware.New%s().%s", strings.Title(name), "Handle"))
|
||||||
}
|
}
|
||||||
|
|
||||||
var configImport = "\"" + ctlutil.JoinPackages(parentPkg, configDir) + "\""
|
var configImport = "\"" + ctlutil.JoinPackages(parentPkg, configDir) + "\""
|
||||||
if len(middlewareStr) > 0 {
|
if len(middlewareStr) > 0 {
|
||||||
configImport += fmt.Sprintf("\n\"%s/rest\"", vars.ProjectOpenSourceUrl)
|
configImport += "\n\t\"" + ctlutil.JoinPackages(parentPkg, middlewareDir) + "\""
|
||||||
|
configImport += fmt.Sprintf("\n\t\"%s/rest\"", vars.ProjectOpenSourceUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
t := template.Must(template.New("contextTemplate").Parse(text))
|
t := template.Must(template.New("contextTemplate").Parse(text))
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
err = t.Execute(buffer, map[string]string{
|
err = t.Execute(buffer, map[string]string{
|
||||||
"configImport": configImport,
|
"configImport": configImport,
|
||||||
"config": "config.Config",
|
"config": "config.Config",
|
||||||
"middleware": middlewareStr,
|
"middleware": middlewareStr,
|
||||||
|
"middlewareAssignment": middlewareAssignment,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const (
|
|||||||
contextDir = interval + "svc"
|
contextDir = interval + "svc"
|
||||||
handlerDir = interval + "handler"
|
handlerDir = interval + "handler"
|
||||||
logicDir = interval + "logic"
|
logicDir = interval + "logic"
|
||||||
|
middlewareDir = interval + "middleware"
|
||||||
typesDir = interval + typesPacket
|
typesDir = interval + typesPacket
|
||||||
groupProperty = "group"
|
groupProperty = "group"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ func isTypeBeginLine(line string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isServiceBeginLine(line string) bool {
|
func isServiceBeginLine(line string) bool {
|
||||||
return strings.HasPrefix(line, "@server(") || (strings.HasPrefix(line, "service") && strings.HasSuffix(line, "{"))
|
return strings.HasPrefix(line, "@server") || (strings.HasPrefix(line, "service") && strings.HasSuffix(line, "{"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func lineBeginOfService(api string) int {
|
func lineBeginOfService(api string) int {
|
||||||
|
|||||||
@@ -42,12 +42,14 @@ func GenConfigCommand(c *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("abs failed: " + c.String("path"))
|
return errors.New("abs failed: " + c.String("path"))
|
||||||
}
|
}
|
||||||
|
|
||||||
goModPath, hasFound := util.FindGoModPath(path)
|
goModPath, hasFound := util.FindGoModPath(path)
|
||||||
if !hasFound {
|
if !hasFound {
|
||||||
return errors.New("go mod not initial")
|
return errors.New("go mod not initial")
|
||||||
}
|
}
|
||||||
|
|
||||||
path = strings.TrimSuffix(path, "/config.go")
|
path = strings.TrimSuffix(path, "/config.go")
|
||||||
location := path + "/tmp"
|
location := filepath.Join(path, "tmp")
|
||||||
err = os.MkdirAll(location, os.ModePerm)
|
err = os.MkdirAll(location, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -76,10 +78,12 @@ func GenConfigCommand(c *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
path, err = os.Getwd()
|
path, err = os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Rename(filepath.Dir(goPath)+"/config.yaml", path+"/config.yaml")
|
err = os.Rename(filepath.Dir(goPath)+"/config.yaml", path+"/config.yaml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
@@ -51,15 +51,16 @@ var (
|
|||||||
Name: "dir",
|
Name: "dir",
|
||||||
Usage: "the format target dir",
|
Usage: "the format target dir",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "p",
|
|
||||||
Usage: "print result to console",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "iu",
|
Name: "iu",
|
||||||
Usage: "ignore update",
|
Usage: "ignore update",
|
||||||
Required: false,
|
Required: false,
|
||||||
},
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "stdin",
|
||||||
|
Usage: "use stdin to input api doc content, press \"ctrl + d\" to send EOF",
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Action: format.GoFormatApi,
|
Action: format.GoFormatApi,
|
||||||
},
|
},
|
||||||
@@ -269,12 +270,16 @@ var (
|
|||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "src, s",
|
Name: "src, s",
|
||||||
Usage: "the file path of the ddl source file",
|
Usage: "the path or path globbing patterns of the ddl",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "dir, d",
|
Name: "dir, d",
|
||||||
Usage: "the target dir",
|
Usage: "the target dir",
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "style",
|
||||||
|
Usage: "the file naming style, lower|camel|underline,default is lower",
|
||||||
|
},
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "cache, c",
|
Name: "cache, c",
|
||||||
Usage: "generate code with cache [optional]",
|
Usage: "generate code with cache [optional]",
|
||||||
@@ -296,7 +301,7 @@ var (
|
|||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "table, t",
|
Name: "table, t",
|
||||||
Usage: `source table,tables separated by commas,like "user,course`,
|
Usage: `the table or table globbing patterns in the database`,
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "cache, c",
|
Name: "cache, c",
|
||||||
@@ -306,6 +311,10 @@ var (
|
|||||||
Name: "dir, d",
|
Name: "dir, d",
|
||||||
Usage: "the target dir",
|
Usage: "the target dir",
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "style",
|
||||||
|
Usage: "the file naming style, lower|camel|underline,default is lower",
|
||||||
|
},
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "idea",
|
Name: "idea",
|
||||||
Usage: "for idea plugin [optional]",
|
Usage: "for idea plugin [optional]",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package k8s
|
package k8s
|
||||||
|
|
||||||
var apiRpcTmeplate = `apiVersion: apps/v1beta2
|
var apiRpcTmeplate = `apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: {{.name}}
|
name: {{.name}}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
|||||||
* 通过ddl生成
|
* 通过ddl生成
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c=true
|
goctl model mysql ddl -src="./*.sql" -dir="./sql/model" -c=true
|
||||||
```
|
```
|
||||||
|
|
||||||
执行上述命令后即可快速生成CURD代码。
|
执行上述命令后即可快速生成CURD代码。
|
||||||
@@ -21,7 +21,7 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
|||||||
* 通过datasource生成
|
* 通过datasource生成
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
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="*" -dir="./model"
|
||||||
```
|
```
|
||||||
|
|
||||||
* 生成代码示例
|
* 生成代码示例
|
||||||
@@ -205,14 +205,49 @@ OPTIONS:
|
|||||||
* ddl
|
* ddl
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
goctl model mysql -src={filename} -dir={dir} -cache=true
|
goctl model mysql -src={patterns} -dir={dir} -cache=true
|
||||||
|
```
|
||||||
|
|
||||||
|
help
|
||||||
|
|
||||||
|
```
|
||||||
|
NAME:
|
||||||
|
goctl model mysql ddl - generate mysql model from ddl
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
goctl model mysql ddl [command options] [arguments...]
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
--src value, -s value the path or path globbing patterns of the ddl
|
||||||
|
--dir value, -d value the target dir
|
||||||
|
--cache, -c generate code with cache [optional]
|
||||||
|
--idea for idea plugin [optional]
|
||||||
```
|
```
|
||||||
|
|
||||||
* datasource
|
* datasource
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=true
|
goctl model mysql datasource -url={datasource} -table={patterns} -dir={dir} -cache=true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
help
|
||||||
|
|
||||||
|
```
|
||||||
|
NAME:
|
||||||
|
goctl model mysql datasource - generate model from datasource
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
goctl model mysql datasource [command options] [arguments...]
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
--url value the data source of database,like "root:password@tcp(127.0.0.1:3306)/database
|
||||||
|
--table value, -t value the table or table globbing patterns in the database
|
||||||
|
--cache, -c generate code with cache [optional]
|
||||||
|
--dir value, -d value the target dir
|
||||||
|
--idea for idea plugin [optional]
|
||||||
|
```
|
||||||
|
|
||||||
|
示例用法请参考[用法](./example/generator.sh)
|
||||||
|
|
||||||
目前仅支持redis缓存,如果选择带缓存模式,即生成的`FindOne(ByXxx)`&`Delete`代码会生成带缓存逻辑的代码,目前仅支持单索引字段(除全文索引外),对于联合索引我们默认认为不需要带缓存,且不属于通用型代码,因此没有放在代码生成行列,如example中user表中的`id`、`name`、`mobile`字段均属于单字段索引。
|
目前仅支持redis缓存,如果选择带缓存模式,即生成的`FindOne(ByXxx)`&`Delete`代码会生成带缓存逻辑的代码,目前仅支持单索引字段(除全文索引外),对于联合索引我们默认认为不需要带缓存,且不属于通用型代码,因此没有放在代码生成行列,如example中user表中的`id`、`name`、`mobile`字段均属于单字段索引。
|
||||||
|
|
||||||
@@ -221,26 +256,26 @@ OPTIONS:
|
|||||||
* ddl
|
* ddl
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
goctl model -src={filename} -dir={dir}
|
goctl model -src={patterns} -dir={dir}
|
||||||
```
|
```
|
||||||
|
|
||||||
* datasource
|
* datasource
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
goctl model mysql datasource -url={datasource} -table={tables} -dir={dir}
|
goctl model mysql datasource -url={datasource} -table={patterns} -dir={dir}
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
* ddl
|
* ddl
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
goctl model -src={filename} -dir={dir} -cache=false
|
goctl model -src={patterns} -dir={dir} -cache=false
|
||||||
```
|
```
|
||||||
|
|
||||||
* datasource
|
* datasource
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=false
|
goctl model mysql datasource -url={datasource} -table={patterns} -dir={dir} -cache=false
|
||||||
```
|
```
|
||||||
|
|
||||||
生成代码仅基本的CURD结构。
|
生成代码仅基本的CURD结构。
|
||||||
@@ -265,8 +300,3 @@ OPTIONS:
|
|||||||
|
|
||||||
目前,我认为除了基本的CURD外,其他的代码均属于<i>业务型</i>代码,这个我觉得开发人员根据业务需要进行编写更好。
|
目前,我认为除了基本的CURD外,其他的代码均属于<i>业务型</i>代码,这个我觉得开发人员根据业务需要进行编写更好。
|
||||||
|
|
||||||
## QA
|
|
||||||
|
|
||||||
* goctl model除了命令行模式,支持插件模式吗?
|
|
||||||
|
|
||||||
很快支持idea插件。
|
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/collection"
|
"github.com/go-sql-driver/mysql"
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/gen"
|
"github.com/tal-tech/go-zero/tools/goctl/model/sql/gen"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/model"
|
"github.com/tal-tech/go-zero/tools/goctl/model/sql/model"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/model/sql/util"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
@@ -19,6 +22,7 @@ const (
|
|||||||
flagDir = "dir"
|
flagDir = "dir"
|
||||||
flagCache = "cache"
|
flagCache = "cache"
|
||||||
flagIdea = "idea"
|
flagIdea = "idea"
|
||||||
|
flagStyle = "style"
|
||||||
flagUrl = "url"
|
flagUrl = "url"
|
||||||
flagTable = "table"
|
flagTable = "table"
|
||||||
)
|
)
|
||||||
@@ -28,17 +32,35 @@ func MysqlDDL(ctx *cli.Context) error {
|
|||||||
dir := ctx.String(flagDir)
|
dir := ctx.String(flagDir)
|
||||||
cache := ctx.Bool(flagCache)
|
cache := ctx.Bool(flagCache)
|
||||||
idea := ctx.Bool(flagIdea)
|
idea := ctx.Bool(flagIdea)
|
||||||
|
namingStyle := strings.TrimSpace(ctx.String(flagStyle))
|
||||||
log := console.NewConsole(idea)
|
log := console.NewConsole(idea)
|
||||||
fileSrc, err := filepath.Abs(src)
|
src = strings.TrimSpace(src)
|
||||||
|
if len(src) == 0 {
|
||||||
|
return errors.New("expected path or path globbing patterns, but nothing found")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch namingStyle {
|
||||||
|
case gen.NamingLower, gen.NamingCamel, gen.NamingUnderline:
|
||||||
|
case "":
|
||||||
|
namingStyle = gen.NamingLower
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpected naming style: %s", namingStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := util.MatchFiles(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
data, err := ioutil.ReadFile(fileSrc)
|
|
||||||
if err != nil {
|
var source []string
|
||||||
return err
|
for _, file := range files {
|
||||||
|
data, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
source = append(source, string(data))
|
||||||
}
|
}
|
||||||
source := string(data)
|
generator := gen.NewDefaultGenerator(strings.Join(source, "\n"), dir, namingStyle, gen.WithConsoleOption(log))
|
||||||
generator := gen.NewDefaultGenerator(source, dir, gen.WithConsoleOption(log))
|
|
||||||
err = generator.Start(cache)
|
err = generator.Start(cache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("%v", err)
|
log.Error("%v", err)
|
||||||
@@ -51,36 +73,72 @@ func MyDataSource(ctx *cli.Context) error {
|
|||||||
dir := strings.TrimSpace(ctx.String(flagDir))
|
dir := strings.TrimSpace(ctx.String(flagDir))
|
||||||
cache := ctx.Bool(flagCache)
|
cache := ctx.Bool(flagCache)
|
||||||
idea := ctx.Bool(flagIdea)
|
idea := ctx.Bool(flagIdea)
|
||||||
table := strings.TrimSpace(ctx.String(flagTable))
|
namingStyle := strings.TrimSpace(ctx.String(flagStyle))
|
||||||
|
pattern := strings.TrimSpace(ctx.String(flagTable))
|
||||||
log := console.NewConsole(idea)
|
log := console.NewConsole(idea)
|
||||||
if len(url) == 0 {
|
if len(url) == 0 {
|
||||||
log.Error("%v", "expected data source of mysql, but is empty")
|
log.Error("%v", "expected data source of mysql, but nothing found")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if len(table) == 0 {
|
|
||||||
log.Error("%v", "expected table(s), but nothing found")
|
if len(pattern) == 0 {
|
||||||
|
log.Error("%v", "expected table or table globbing patterns, but nothing found")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch namingStyle {
|
||||||
|
case gen.NamingLower, gen.NamingCamel, gen.NamingUnderline:
|
||||||
|
case "":
|
||||||
|
namingStyle = gen.NamingLower
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpected naming style: %s", namingStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := mysql.ParseDSN(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
logx.Disable()
|
logx.Disable()
|
||||||
conn := sqlx.NewMysql(url)
|
conn := sqlx.NewMysql(url)
|
||||||
|
databaseSource := strings.TrimSuffix(url, "/"+cfg.DBName) + "/information_schema"
|
||||||
|
db := sqlx.NewMysql(databaseSource)
|
||||||
m := model.NewDDLModel(conn)
|
m := model.NewDDLModel(conn)
|
||||||
tables := collection.NewSet()
|
im := model.NewInformationSchemaModel(db)
|
||||||
for _, item := range strings.Split(table, ",") {
|
|
||||||
item = strings.TrimSpace(item)
|
tables, err := im.GetAllTables(cfg.DBName)
|
||||||
if len(item) == 0 {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchTables []string
|
||||||
|
for _, item := range tables {
|
||||||
|
match, err := filepath.Match(pattern, item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !match {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tables.AddStr(item)
|
|
||||||
|
matchTables = append(matchTables, item)
|
||||||
}
|
}
|
||||||
ddl, err := m.ShowDDL(tables.KeysStr()...)
|
if len(matchTables) == 0 {
|
||||||
|
return errors.New("no tables matched")
|
||||||
|
}
|
||||||
|
|
||||||
|
ddl, err := m.ShowDDL(matchTables...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("%v", err)
|
log.Error("%v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
generator := gen.NewDefaultGenerator(strings.Join(ddl, "\n"), dir, gen.WithConsoleOption(log))
|
|
||||||
|
generator := gen.NewDefaultGenerator(strings.Join(ddl, "\n"), dir, namingStyle, gen.WithConsoleOption(log))
|
||||||
err = generator.Start(cache)
|
err = generator.Start(cache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("%v", err)
|
log.Error("%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# generate model with cache from ddl
|
# generate model with cache from ddl
|
||||||
goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c
|
goctl model mysql ddl -src="./sql/*.sql" -dir="./sql/model/user" -c
|
||||||
|
|
||||||
# generate model with cache from data source
|
# 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"
|
#user=root
|
||||||
|
#password=password
|
||||||
|
#datasource=127.0.0.1:3306
|
||||||
|
#database=test
|
||||||
|
#goctl model mysql datasource -url="${user}:${password}@tcp(${datasource})/${database}" -table="*" -dir ./model
|
||||||
15
tools/goctl/model/sql/example/sql/user_1.sql
Normal file
15
tools/goctl/model/sql/example/sql/user_1.sql
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
-- 用户表 --
|
||||||
|
CREATE TABLE `user1` (
|
||||||
|
`id` bigint(10) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',
|
||||||
|
`password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',
|
||||||
|
`mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',
|
||||||
|
`gender` char(5) COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开',
|
||||||
|
`nickname` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称',
|
||||||
|
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `name_index` (`name`),
|
||||||
|
UNIQUE KEY `mobile_index` (`mobile`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
@@ -17,6 +17,9 @@ import (
|
|||||||
const (
|
const (
|
||||||
pwd = "."
|
pwd = "."
|
||||||
createTableFlag = `(?m)^(?i)CREATE\s+TABLE` // ignore case
|
createTableFlag = `(?m)^(?i)CREATE\s+TABLE` // ignore case
|
||||||
|
NamingLower = "lower"
|
||||||
|
NamingCamel = "camel"
|
||||||
|
NamingUnderline = "underline"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -24,15 +27,17 @@ type (
|
|||||||
source string
|
source string
|
||||||
dir string
|
dir string
|
||||||
console.Console
|
console.Console
|
||||||
|
pkg string
|
||||||
|
namingStyle string
|
||||||
}
|
}
|
||||||
Option func(generator *defaultGenerator)
|
Option func(generator *defaultGenerator)
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDefaultGenerator(source, dir string, opt ...Option) *defaultGenerator {
|
func NewDefaultGenerator(source, dir, namingStyle string, opt ...Option) *defaultGenerator {
|
||||||
if dir == "" {
|
if dir == "" {
|
||||||
dir = pwd
|
dir = pwd
|
||||||
}
|
}
|
||||||
generator := &defaultGenerator{source: source, dir: dir}
|
generator := &defaultGenerator{source: source, dir: dir, namingStyle: namingStyle}
|
||||||
var optionList []Option
|
var optionList []Option
|
||||||
optionList = append(optionList, newDefaultOption())
|
optionList = append(optionList, newDefaultOption())
|
||||||
optionList = append(optionList, opt...)
|
optionList = append(optionList, opt...)
|
||||||
@@ -59,6 +64,8 @@ func (g *defaultGenerator) Start(withCache bool) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
g.dir = dirAbs
|
||||||
|
g.pkg = filepath.Base(dirAbs)
|
||||||
err = util.MkdirIfNotExist(dirAbs)
|
err = util.MkdirIfNotExist(dirAbs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -69,7 +76,14 @@ func (g *defaultGenerator) Start(withCache bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for tableName, code := range modelList {
|
for tableName, code := range modelList {
|
||||||
name := fmt.Sprintf("%smodel.go", strings.ToLower(stringx.From(tableName).ToCamel()))
|
tn := stringx.From(tableName)
|
||||||
|
name := fmt.Sprintf("%smodel.go", strings.ToLower(tn.ToCamel()))
|
||||||
|
switch g.namingStyle {
|
||||||
|
case NamingCamel:
|
||||||
|
name = fmt.Sprintf("%sModel.go", tn.ToCamel())
|
||||||
|
case NamingUnderline:
|
||||||
|
name = fmt.Sprintf("%s_model.go", tn.ToSnake())
|
||||||
|
}
|
||||||
filename := filepath.Join(dirAbs, name)
|
filename := filepath.Join(dirAbs, name)
|
||||||
if util.FileExists(filename) {
|
if util.FileExists(filename) {
|
||||||
g.Warning("%s already exists, ignored.", name)
|
g.Warning("%s already exists, ignored.", name)
|
||||||
@@ -82,12 +96,18 @@ func (g *defaultGenerator) Start(withCache bool) error {
|
|||||||
}
|
}
|
||||||
// generate error file
|
// generate error file
|
||||||
filename := filepath.Join(dirAbs, "vars.go")
|
filename := filepath.Join(dirAbs, "vars.go")
|
||||||
if !util.FileExists(filename) {
|
text, err := util.LoadTemplate(category, errTemplateFile, template.Error)
|
||||||
err = ioutil.WriteFile(filename, []byte(template.Error), os.ModePerm)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = util.With("vars").Parse(text).SaveTo(map[string]interface{}{
|
||||||
|
"pkg": g.pkg,
|
||||||
|
}, filename, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
g.Success("Done.")
|
g.Success("Done.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -119,8 +139,12 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, error) {
|
func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, error) {
|
||||||
|
text, err := util.LoadTemplate(category, modelTemplateFile, template.Model)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
t := util.With("model").
|
t := util.With("model").
|
||||||
Parse(template.Model).
|
Parse(text).
|
||||||
GoFmt(true)
|
GoFmt(true)
|
||||||
|
|
||||||
m, err := genCacheKeys(in)
|
m, err := genCacheKeys(in)
|
||||||
@@ -188,6 +212,7 @@ func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
output, err := t.Execute(map[string]interface{}{
|
output, err := t.Execute(map[string]interface{}{
|
||||||
|
"pkg": g.pkg,
|
||||||
"imports": importsCode,
|
"imports": importsCode,
|
||||||
"vars": varsCode,
|
"vars": varsCode,
|
||||||
"types": typesCode,
|
"types": typesCode,
|
||||||
|
|||||||
34
tools/goctl/model/sql/gen/gen_test.go
Normal file
34
tools/goctl/model/sql/gen/gen_test.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package gen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
source = "CREATE TABLE `test_user_info` (\n `id` bigint NOT NULL AUTO_INCREMENT,\n `nanosecond` bigint NOT NULL DEFAULT '0',\n `data` varchar(255) DEFAULT '',\n `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n UNIQUE KEY `nanosecond_unique` (`nanosecond`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCacheModel(t *testing.T) {
|
||||||
|
logx.Disable()
|
||||||
|
_ = Clean()
|
||||||
|
g := NewDefaultGenerator(source, "./testmodel/cache", NamingLower)
|
||||||
|
err := g.Start(true)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
g = NewDefaultGenerator(source, "./testmodel/nocache", NamingLower)
|
||||||
|
err = g.Start(false)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNamingModel(t *testing.T) {
|
||||||
|
logx.Disable()
|
||||||
|
_ = Clean()
|
||||||
|
g := NewDefaultGenerator(source, "./testmodel/camel", NamingCamel)
|
||||||
|
err := g.Start(true)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
g = NewDefaultGenerator(source, "./testmodel/snake", NamingUnderline)
|
||||||
|
err = g.Start(true)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ func genNew(table Table, withCache bool) (string, error) {
|
|||||||
output, err := util.With("new").
|
output, err := util.With("new").
|
||||||
Parse(text).
|
Parse(text).
|
||||||
Execute(map[string]interface{}{
|
Execute(map[string]interface{}{
|
||||||
|
"table": table.Name.Source(),
|
||||||
"withCache": withCache,
|
"withCache": withCache,
|
||||||
"upperStartCamelObject": table.Name.ToCamel(),
|
"upperStartCamelObject": table.Name.ToCamel(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const (
|
|||||||
typesTemplateFile = "types.tpl"
|
typesTemplateFile = "types.tpl"
|
||||||
updateTemplateFile = "update.tpl"
|
updateTemplateFile = "update.tpl"
|
||||||
varTemplateFile = "var.tpl"
|
varTemplateFile = "var.tpl"
|
||||||
|
errTemplateFile = "err.tpl"
|
||||||
)
|
)
|
||||||
|
|
||||||
var templates = map[string]string{
|
var templates = map[string]string{
|
||||||
@@ -41,6 +42,7 @@ var templates = map[string]string{
|
|||||||
typesTemplateFile: template.Types,
|
typesTemplateFile: template.Types,
|
||||||
updateTemplateFile: template.Update,
|
updateTemplateFile: template.Update,
|
||||||
varTemplateFile: template.Vars,
|
varTemplateFile: template.Vars,
|
||||||
|
errTemplateFile: template.Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenTemplates(_ *cli.Context) error {
|
func GenTemplates(_ *cli.Context) error {
|
||||||
|
|||||||
25
tools/goctl/model/sql/model/informationschemamodel.go
Normal file
25
tools/goctl/model/sql/model/informationschemamodel.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
InformationSchemaModel struct {
|
||||||
|
conn sqlx.SqlConn
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewInformationSchemaModel(conn sqlx.SqlConn) *InformationSchemaModel {
|
||||||
|
return &InformationSchemaModel{conn: conn}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *InformationSchemaModel) GetAllTables(database string) ([]string, error) {
|
||||||
|
query := `select TABLE_NAME from TABLES where TABLE_SCHEMA = ?`
|
||||||
|
var tables []string
|
||||||
|
err := m.conn.QueryRows(&tables, query, database)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tables, nil
|
||||||
|
}
|
||||||
@@ -7,5 +7,5 @@ import (
|
|||||||
var (
|
var (
|
||||||
unSupportDDL = errors.New("unexpected type")
|
unSupportDDL = errors.New("unexpected type")
|
||||||
tableBodyIsNotFound = errors.New("create table spec not found")
|
tableBodyIsNotFound = errors.New("create table spec not found")
|
||||||
errPrimaryKey = errors.New("unexpected joint primary key")
|
errPrimaryKey = errors.New("unexpected join primary key")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ func (m *{{.upperStartCamelObject}}Model) Delete({{.lowerStartCamelPrimaryKey}}
|
|||||||
|
|
||||||
{{.keys}}
|
{{.keys}}
|
||||||
_, err {{if .containsIndexCache}}={{else}}:={{end}} m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
_, err {{if .containsIndexCache}}={{else}}:={{end}} m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||||
query := ` + "`" + `delete from ` + "` +" + ` m.table + ` + " `" + ` where {{.originalPrimaryKey}} = ?` + "`" + `
|
query := fmt.Sprintf("delete from %s where {{.originalPrimaryKey}} = ?", m.table)
|
||||||
return conn.Exec(query, {{.lowerStartCamelPrimaryKey}})
|
return conn.Exec(query, {{.lowerStartCamelPrimaryKey}})
|
||||||
}, {{.keyValues}}){{else}}query := ` + "`" + `delete from ` + "` +" + ` m.table + ` + " `" + ` where {{.originalPrimaryKey}} = ?` + "`" + `
|
}, {{.keyValues}}){{else}}query := fmt.Sprintf("delete from %s where {{.originalPrimaryKey}} = ?", m.table)
|
||||||
_,err:=m.conn.Exec(query, {{.lowerStartCamelPrimaryKey}}){{end}}
|
_,err:=m.conn.Exec(query, {{.lowerStartCamelPrimaryKey}}){{end}}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package template
|
package template
|
||||||
|
|
||||||
var Error = `package model
|
var Error = `package {{.pkg}}
|
||||||
|
|
||||||
import "github.com/tal-tech/go-zero/core/stores/sqlx"
|
import "github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ func (m *{{.upperStartCamelObject}}Model) FindOne({{.lowerStartCamelPrimaryKey}}
|
|||||||
{{if .withCache}}{{.cacheKey}}
|
{{if .withCache}}{{.cacheKey}}
|
||||||
var resp {{.upperStartCamelObject}}
|
var resp {{.upperStartCamelObject}}
|
||||||
err := m.QueryRow(&resp, {{.cacheKeyVariable}}, func(conn sqlx.SqlConn, v interface{}) error {
|
err := m.QueryRow(&resp, {{.cacheKeyVariable}}, func(conn sqlx.SqlConn, v interface{}) error {
|
||||||
query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalPrimaryKey}} = ? limit 1` + "`" + `
|
query := fmt.Sprintf("select %s from %s where {{.originalPrimaryKey}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table)
|
||||||
return conn.QueryRow(v, query, {{.lowerStartCamelPrimaryKey}})
|
return conn.QueryRow(v, query, {{.lowerStartCamelPrimaryKey}})
|
||||||
})
|
})
|
||||||
switch err {
|
switch err {
|
||||||
@@ -16,7 +16,7 @@ func (m *{{.upperStartCamelObject}}Model) FindOne({{.lowerStartCamelPrimaryKey}}
|
|||||||
return nil, ErrNotFound
|
return nil, ErrNotFound
|
||||||
default:
|
default:
|
||||||
return nil, err
|
return nil, err
|
||||||
}{{else}}query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalPrimaryKey}} = ? limit 1` + "`" + `
|
}{{else}}query := fmt.Sprintf("select %s from %s where {{.originalPrimaryKey}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table)
|
||||||
var resp {{.upperStartCamelObject}}
|
var resp {{.upperStartCamelObject}}
|
||||||
err := m.conn.QueryRow(&resp, query, {{.lowerStartCamelPrimaryKey}})
|
err := m.conn.QueryRow(&resp, query, {{.lowerStartCamelPrimaryKey}})
|
||||||
switch err {
|
switch err {
|
||||||
@@ -36,7 +36,7 @@ func (m *{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}}) (*{{
|
|||||||
{{if .withCache}}{{.cacheKey}}
|
{{if .withCache}}{{.cacheKey}}
|
||||||
var resp {{.upperStartCamelObject}}
|
var resp {{.upperStartCamelObject}}
|
||||||
err := m.QueryRowIndex(&resp, {{.cacheKeyVariable}}, m.formatPrimary, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
|
err := m.QueryRowIndex(&resp, {{.cacheKeyVariable}}, m.formatPrimary, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
|
||||||
query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalField}} = ? limit 1` + "`" + `
|
query := fmt.Sprintf("select %s from %s where {{.originalField}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table)
|
||||||
if err := conn.QueryRow(&resp, query, {{.lowerStartCamelField}}); err != nil {
|
if err := conn.QueryRow(&resp, query, {{.lowerStartCamelField}}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ func (m *{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}}) (*{{
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}{{else}}var resp {{.upperStartCamelObject}}
|
}{{else}}var resp {{.upperStartCamelObject}}
|
||||||
query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalField}} limit 1` + "`" + `
|
query := fmt.Sprintf("select %s from %s where {{.originalField}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table )
|
||||||
err := m.conn.QueryRow(&resp, query, {{.lowerStartCamelField}})
|
err := m.conn.QueryRow(&resp, query, {{.lowerStartCamelField}})
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
@@ -69,7 +69,7 @@ func (m *{{.upperStartCamelObject}}Model) formatPrimary(primary interface{}) str
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *{{.upperStartCamelObject}}Model) queryPrimary(conn sqlx.SqlConn, v, primary interface{}) error {
|
func (m *{{.upperStartCamelObject}}Model) queryPrimary(conn sqlx.SqlConn, v, primary interface{}) error {
|
||||||
query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalPrimaryField}} = ? limit 1` + "`" + `
|
query := fmt.Sprintf("select %s from %s where {{.originalPrimaryField}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table )
|
||||||
return conn.QueryRow(v, query, primary)
|
return conn.QueryRow(v, query, primary)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ var (
|
|||||||
`
|
`
|
||||||
ImportsNoCache = `import (
|
ImportsNoCache = `import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
{{if .time}}"time"{{end}}
|
{{if .time}}"time"{{end}}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ var Insert = `
|
|||||||
func (m *{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) (sql.Result,error) {
|
func (m *{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) (sql.Result,error) {
|
||||||
{{if .withCache}}{{if .containsIndexCache}}{{.keys}}
|
{{if .withCache}}{{if .containsIndexCache}}{{.keys}}
|
||||||
ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||||
query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "` (` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) values ({{.expression}})` " + `
|
query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
|
||||||
return conn.Exec(query, {{.expressionValues}})
|
return conn.Exec(query, {{.expressionValues}})
|
||||||
}, {{.keyValues}}){{else}}query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "` (` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) values ({{.expression}})` " + `
|
}, {{.keyValues}}){{else}}query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
|
||||||
ret,err:=m.ExecNoCache(query, {{.expressionValues}})
|
ret,err:=m.ExecNoCache(query, {{.expressionValues}})
|
||||||
{{end}}{{else}}query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "` (` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) values ({{.expression}})` " + `
|
{{end}}{{else}}query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
|
||||||
ret,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
|
ret,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
|
||||||
return ret,err
|
return ret,err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package template
|
package template
|
||||||
|
|
||||||
var Model = `package model
|
var Model = `package {{.pkg}}
|
||||||
{{.imports}}
|
{{.imports}}
|
||||||
{{.vars}}
|
{{.vars}}
|
||||||
{{.types}}
|
{{.types}}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package template
|
package template
|
||||||
|
|
||||||
var New = `
|
var New = `
|
||||||
func New{{.upperStartCamelObject}}Model(conn sqlx.SqlConn,{{if .withCache}} c cache.CacheConf,{{end}} table string) *{{.upperStartCamelObject}}Model {
|
func New{{.upperStartCamelObject}}Model(conn sqlx.SqlConn{{if .withCache}}, c cache.CacheConf{{end}}) *{{.upperStartCamelObject}}Model {
|
||||||
return &{{.upperStartCamelObject}}Model{
|
return &{{.upperStartCamelObject}}Model{
|
||||||
{{if .withCache}}CachedConn: sqlc.NewConn(conn, c){{else}}conn:conn{{end}},
|
{{if .withCache}}CachedConn: sqlc.NewConn(conn, c){{else}}conn:conn{{end}},
|
||||||
table: table,
|
table: "{{.table}}",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package template
|
package template
|
||||||
|
|
||||||
var Update = `
|
var Update = `
|
||||||
func (m *{{.upperStartCamelObject}}Model) Update(data {{.upperStartCamelObject}}) (sql.Result,error) {
|
func (m *{{.upperStartCamelObject}}Model) Update(data {{.upperStartCamelObject}}) error {
|
||||||
{{if .withCache}}{{.primaryCacheKey}}
|
{{if .withCache}}{{.primaryCacheKey}}
|
||||||
ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||||
query := ` + "`" + `update ` + "` +" + `m.table +` + "` " + `set ` + "` +" + `{{.lowerStartCamelObject}}RowsWithPlaceHolder` + " + `" + ` where {{.originalPrimaryKey}} = ?` + "`" + `
|
query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = ?", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder)
|
||||||
return conn.Exec(query, {{.expressionValues}})
|
return conn.Exec(query, {{.expressionValues}})
|
||||||
}, {{.primaryKeyVariable}}){{else}}query := ` + "`" + `update ` + "` +" + `m.table +` + "` " + `set ` + "` +" + `{{.lowerStartCamelObject}}RowsWithPlaceHolder` + " + `" + ` where {{.originalPrimaryKey}} = ?` + "`" + `
|
}, {{.primaryKeyVariable}}){{else}}query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = ?", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder)
|
||||||
ret,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
|
_,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
|
||||||
return ret,err
|
return err
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
29
tools/goctl/model/sql/util/match_test.go
Normal file
29
tools/goctl/model/sql/util/match_test.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMatchFiles(t *testing.T) {
|
||||||
|
dir, err := filepath.Abs("./")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
files, err := MatchFiles("./*.sql")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, []string{filepath.Join(dir, "studeat.sql"), filepath.Join(dir, "student.sql"), filepath.Join(dir, "xx.sql")}, files)
|
||||||
|
|
||||||
|
files, err = MatchFiles("./??.sql")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, []string{filepath.Join(dir, "xx.sql")}, files)
|
||||||
|
|
||||||
|
files, err = MatchFiles("./*.sq*")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, []string{filepath.Join(dir, "studeat.sql"), filepath.Join(dir, "student.sql"), filepath.Join(dir, "xx.sql"), filepath.Join(dir, "xx.sql1")}, files)
|
||||||
|
|
||||||
|
files, err = MatchFiles("./student.sql")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, []string{filepath.Join(dir, "student.sql")}, files)
|
||||||
|
}
|
||||||
38
tools/goctl/model/sql/util/matcher.go
Normal file
38
tools/goctl/model/sql/util/matcher.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// expression: globbing patterns
|
||||||
|
func MatchFiles(in string) ([]string, error) {
|
||||||
|
dir, pattern := filepath.Split(in)
|
||||||
|
abs, err := filepath.Abs(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := ioutil.ReadDir(abs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var res []string
|
||||||
|
for _, file := range files {
|
||||||
|
if file.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := file.Name()
|
||||||
|
match, err := filepath.Match(pattern, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !match {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, filepath.Join(abs, name))
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
0
tools/goctl/model/sql/util/studeat.sql
Normal file
0
tools/goctl/model/sql/util/studeat.sql
Normal file
0
tools/goctl/model/sql/util/student.sql
Normal file
0
tools/goctl/model/sql/util/student.sql
Normal file
0
tools/goctl/model/sql/util/sub/sub.sql
Normal file
0
tools/goctl/model/sql/util/sub/sub.sql
Normal file
0
tools/goctl/model/sql/util/xx.sql
Normal file
0
tools/goctl/model/sql/util/xx.sql
Normal file
0
tools/goctl/model/sql/util/xx.sql1
Normal file
0
tools/goctl/model/sql/util/xx.sql1
Normal file
@@ -70,7 +70,7 @@ func (g *defaultRpcGenerator) genMain() error {
|
|||||||
"srv": srv,
|
"srv": srv,
|
||||||
"registers": registers,
|
"registers": registers,
|
||||||
"imports": strings.Join(imports, util.NL),
|
"imports": strings.Join(imports, util.NL),
|
||||||
}, fileName, true)
|
}, fileName, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *defaultRpcGenerator) genServer(pkg string, list []*parser.RpcService) (string, string) {
|
func (g *defaultRpcGenerator) genServer(pkg string, list []*parser.RpcService) (string, string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user