# Dolphin, Go code generate Framework
Dolphin是一个Golang代码生成工具, 及ERP基础架构平台, 内部提供多种基本功能, 适合k8s中构建微服务
使用Dolphin-ui作为前端与基于Dolphin构建的后台rest交互, 部分截图
<img align="center" width="400px" src="https://img-blog.csdnimg.cn/2021043013451981.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTM1NzEyNDM=,size_16,color_FFFFFF,t_70">
<img align="center" width="400px" src="https://img-blog.csdnimg.cn/20210430134858440.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTM1NzEyNDM=,size_16,color_FFFFFF,t_70">
<img align="center" width="400px" src="https://img-blog.csdnimg.cn/20210430134623869.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTM1NzEyNDM=,size_16,color_FFFFFF,t_70">
# Features
```
- 基于XML配置生成代码
- 基于XML配置生成DOC文档
- 基于XML配置生成SQL
- 默认处理NULL空值问题
- 支持多租户分库
- 登录/退出, 或者单点登录
- 支持路由权限认证
- 快速EXCEL报表和EXCEL解析
- 支持路由缓存
- 支持数据权限控制
- 支持日子记录
- 支持RPC远程服务
- 默认生成K8s部署配置
- 支持基于数据库生成XML配置
```
## 可以前往github查看更多功能, 开源不易, 有兴趣就给个star吧 ^_^
https://github.com/2637309949/dolphin
## Quick start
<img width="580px" src="https://img-blog.csdnimg.cn/20210430135242138.gif"/>
1. The first need [Go](https://golang.org/) installed, then you can use the below Go command to install Dolphin.
```shell
$ go get -u github.com/2637309949/dolphin/cmd/dolphin
```
2. Create project dir and run dolphin
```shell
$ mkdir example && cd example && dolphin init && dolphin build && go run main.go
```
Output:
```shell
time="2020/06/13 11:55:58" level=info msg="grpc listen on port:9081"
time="2020/06/13 11:55:58" level=info msg="http listen on port:8082"
```
## Directory structure
> The quasi-directory structure of the project is shown below, The project structure has been simplified as a guideline, such as managing large-scale projects and recommending new sub-projects
```shell
.
├── app
│ ├── app.auto.go
│ ├── app.go
│ ├── article.go
│ └── article.go.new
├── app.properties
├── doc
│ └── swagger.yaml
├── go.mod
├── go.sum
├── log
│ └── demo.2020071400
├── main.go
├── model
│ ├── article.auto.go
│ └── article_info.auto.go
├── rpc
│ ├── message.cli.go
│ ├── message.go
│ ├── message.go.new
│ └── proto
│ ├── message.pb.go
│ ├── message.proto
│ └── message.proto.new
├── script
│ ├── apis
│ │ ├── article.js
│ │ └── index.js
│ └── axios.js
├── sql
│ ├── article
│ │ ├── article_page_count.tpl
│ │ └── article_page_select.tpl
│ └── sqlmap
│ └── article.xml
├── srv
│ ├── article.go
│ └── worker_hello.go
├── static
│ ├── files
│ │ ├── 6b7ead55-f663-4340-a594-d282d5baf753.xlsx
│ │ └── 6dc88052-54e0-4aa9-a344-fb2b3c30f9b6.xlsx
│ └── web
│ ├── affirm.html
│ └── login.html
├── util
│ └── tool.go
└── xml
├── application.xml
├── bean
│ └── article_info.xml
├── controller
│ └── article.xml
├── rpc
│ └── message.xml
└── table
└── article.xml
```
## CMD
Command options
```shell
dolphin, a cli tools for generate golang code
Usage:
dolphin [command]
Available Commands:
build Build project from xml
clean Removing intermediate files
help Help about any command
init Initialize a empty project
Flags:
-h, --help help for dolphin
Use "dolphin [command] --help" for more information about a command.
```
### build
The build command generates the preset function by executing the built-in Pipeline function, You can specify that only a pipeline will be executed via the @ symbol
```shell
dolphin build @table xml/test
```
Existing built-in Pipeline function:
| Function | Action |
|----------|:-------------:|
| main | create main file source |
| app | create engine template source |
| ctr | create controller source |
| proto | create proto3 source |
| srv | create server source |
| model | create model source |
| bean | create bean source |
| auto | create register source |
| tool | create tool source |
| sql | create sql source, .sql to .go |
| sqlmap | create table sqlmap |
| oauth | create oauth h5 template |
| script | create js api |
| deploy | create k8s template |
| doc | create swagger api doc |
| table | create table from datasource |
### clean
The clean command clears temporary files
```shell
dolphin clean
```
### init
The init command, as stated, generates a series of initialization files
```shell
mkdir demo && cd demo && dolphin init
```
## API Examples
### dolphin-ui
> An erp template that uses dolphin background support.
You can find a number of ready-to-run examples at [dolphin examples repository.](https://github.com/2637309949/dolphin-ui)
## XML Label
### application
> application label contain app infomation, such as name, package
Example:
```xml
<?xml version="1.0" encoding="utf-8" ?>
<application name="demo" desc="template" packagename="demo"/>
```
application
| LabelName | LabelMeaning |
|----------|:-------------:|
| name | required, application name |
| desc | application desc |
| packagename | required, application packagename |
### bean
> bean, you can declare object in bean, just like spring bean. all bean and model will be placed in the model directory, so you needs another name if the conflict
Example:
```xml
<bean name="activity_info" desc="desc" packages="xxx" extends="$applet_activity">
<prop name="code" desc="编码" type="xx.String" />
<prop name="name" desc="名称" type="xx.String" />
</bean>
```
Generate code:
```go
// Code generated by dol build. DO NOT EDIT.
package model
import (
"github.com/2637309949/dolphin/packages/null"
)
// ArticleInfo defined 文章信息
type ArticleInfo struct {
*Article
// 地址
URL null.String `json:"url" xml:"url"`
}
```
bean
| LabelName | LabelMeaning |
|----------|:-------------:|
| name | bean name |
| desc | bean desc |
| packagename | third party package name,use "," to split |
| extends | bean extends |
prop
| LabelName | LabelMeaning |
|----------|:-------------:|
| name | prop name |
| desc | prop desc |
| type | prop type |
### controller
> controller, a collect api, you can declare api prefix
Example:
```xml
<controller name="activity" desc="微信活动" />
```
controller
| LabelName | LabelMeaning |
|----------|:-------------:|
| name | controller name |
| desc | controller desc |
| prefix | controller desc |
### api example
> api, api func in controller. we has some built-in func such as 'add', 'delete', 'update', 'page', 'get', 'tree', or you can refined if you need.
#### add
##### one
```xml
<api name="add" func="add" table="sys_client" desc="添加客户端" method="post">
<param name="user" type="$sys_client" desc="客户端信息" />
<return>
<success type="$success"/>
<failure type="$fail"/>
</return>
</api>
```
Generate code:
```go
// SysClientAdd api implementation
// @Summary 添加客户端
// @Tags 客户端
// @Accept application/json
// @Param Authorization header string false "认证令牌"
// @Param user body model.SysClient false "客户端信息"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/sys/client/add [post]
func SysClientAdd(ctx *Context) {
var payload model.SysClient
if err := ctx.ShouldBindBodyWith(&payload, binding.JSON); err != nil {
ctx.Fail(err)
return
}
payload.ID = null.StringFromUUID()
payload.CreateTime = null.TimeFrom(time.Now().Value())
payload.Creater = null.StringFrom(ctx.GetToken().GetUserID())
payload.UpdateTime = null.TimeFrom(time.Now().Value())
payload.Updater = null.StringFrom(ctx.GetToken().GetUserID())
payload.IsDelete = null.IntFrom(0)
payload.AppName = null.StringFrom(viper.GetString("app.name"))
ret, err := ctx.PlatformDB.Insert(&payload)
if err != nil {
ctx.Fail(err)
return
}
ctx.Success(ret)
}
```
##### batch
> Of course, you can also specify array parameters, which will automatically generate batch added templates.
```xml
<api name="batch_add" func="add" table="sys_role_menu" method="post" desc="添加角色菜单">
<param name="role_menu" type="[]$sys_role_menu" desc="角色菜单信息" />
<return>
<success type="$success"/>
<failure type="$fail"/>
</return>
</api>
```
Generate code:
```go
// SysRoleMenuBatchAdd api implementation
// @Summary 添加角色菜单
// @Tags 角色菜单
// @Accept application/json
// @Param Authorization header string false "认证令牌"
// @Param role_menu body []model.SysRoleMenu false "角色菜单信息"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/sys/role/menu/batch_add [post]
func SysRoleMenuBatchAdd(ctx *Context) {
var payload []*model.SysRoleMenu
if err := ctx.ShouldBindBodyWith(&payload, binding.JSON); err != nil {
logrus.Error(err)
ctx.Fail(err)
return
}
funk.ForEach(payload, func(form *model.SysRoleMenu) {
form.ID = null.StringFromUUID()
form.CreateTime = null.TimeFrom(time.Now().Value())
form.Creater = null.StringFrom(ctx.GetToken().GetUserID())
form.UpdateTime = null.TimeFrom(time.Now().Value())
form.Updater = null.StringFrom(ctx.GetToken().GetUserID())
form.IsDelete = null.IntFrom(0)
})
payload = funk.Filter(payload, func(form *model.SysRoleMenu) bool {
ext, _ := ctx.DB.Where("role_id=? and menu_id=?", form.RoleId.String,
form.MenuId.String).Exist(new(model.SysRoleMenu))
return !ext
}).([]*model.SysRoleMenu)
ret, err := ctx.DB.Insert(&payload)
if err != nil {
logrus.Error(err)
ctx.Fail(err)
return
}
ctx.Success(ret)
}
```
#### delete
##### one
> The system default templates are soft delete logic, this is also highly recommended. If you need hard delete, please do it yourself
```xml
<api name="del" func="delete" table="sys_client" desc="删除客户端" method="delete">
<param name="sys_client" type="$sys_client" desc="客户端" />
<return>
<success type="$success"/>
<failure type="$fail"/>
</return>
</api>
```
Generate code:
```go
// SysClientDel api implementation
// @Summary 删除客户端
// @Tags 客户端
// @Accept application/json
// @Param Authorization header string false "认证令牌"
// @Param sys_client body model.SysClient false "客户端"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/sys/client/del [delete]
func SysClientDel(ctx *Context) {
var payload model.SysClient
if err := ctx.ShouldBindBodyWith(&payload, binding.JSON); err != nil {
ctx.Fail(err)
return
}
ret, err := ctx.PlatformDB.In("id", payload.ID.String).Update(&model.SysClient{
UpdateTime: null.TimeFrom(time.Now().Value()),
Updater: null.StringFrom(ctx.GetToken().GetUserID()),
IsDelete: null.IntFrom(1),
})
if err != nil {
ctx.Fail(err)
return
}
ctx.Success(ret)
}
```
##### batch
> Of course, you can also specify array parameters, which will automatically generate batch deleted templates.
```xml
<api name="batch_del" func="delete" table="sys_optionset" method="delete" desc="删除字典">
<param name="user" type="[]$sys_optionset" desc="字典" />
<return>
<success type="$success"/>
<failure type="$fail"/>
</return>
</api>
```
Generate code:
```go
// SysOptionsetBatchDel api implementation
// @Summary 删除字典
// @Tags 字典
// @Accept application/json
// @Param Authorization header string false "认证令牌"
// @Param user body []model.SysOptionset false "字典"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/sys/optionset/batch_del [delete]
func SysOptionsetBatchDel(ctx *Context) {
var payload []*model.SysOptionset
var ids []string
if err := ctx.ShouldBindBodyWith(&payload, binding.JSON); err != nil {
logrus.Error(err)
ctx.Fail(err)
return
}
funk.ForEach(payload, func(form model.SysOptionset) {
ids = append(ids, form.ID.String)
})
ret, err := ctx.DB.In("id", ids).Update(&model.SysOptionset{
UpdateTime: null.TimeFrom(time.Now().Value()),
Updater: null.StringFrom(ctx.GetToken().GetUserID()),
IsDelete: null.IntFrom(1),
})
if err != nil {
logrus.Error(err)
ctx.Fail(err)
return
}
ctx.Success(ret)
}
```
#### update
##### one
> The entire model field definition is null type, so don't worry about the default value types, as long as you reference packages/ xormplus (native xorm has been modified to fit null packages)
```xml
<api name="update" func="update" table="sys_client" desc="更新客户端" method="put">
<param name="user" type="$sys_role" desc="客户端信息" />
<return>
<success type="$success"/>
<failure type="$fail"/>
</return>
</api>
```
Generate code:
```go
// SysClientUpdate api implementation
// @Summary 更新客户端
// @Tags 客户端
// @Accept application/json
// @Param Authorization header string false "认证令牌"
// @Param user body model.SysRole false "客户端信息"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/sys/client/update [put]
func SysClientUpdate(ctx *Context) {
var payload model.SysRole
if err := ctx.ShouldBindBodyWith(&payload, binding.JSON); err != nil {
ctx.Fail(err)
return
}
payload.Updater = null.StringFrom(ctx.GetToken().GetUserID())
payload.UpdateTime = null.TimeFrom(time.Now().Value())
ret, err := ctx.PlatformDB.ID(payload.ID).Update(&payload)
if err != nil {
ctx.Fail(err)
return
}
ctx.Success(ret)
}
```
##### batch
> Of course, you can also specify array parameters, which will automatically generate batch updated templates.
```xml
<api name="batch_update" func="update" table="article" desc="更新文章" method="put">
<param name="article" type="[]$article" desc="文章信息" />
<return>
<success type="$success"/>
<failure type="$fail"/>
</return>
</api>
```
Generate code:
```go
// ArticleBatchUpdate api implementation
// @Summary 更新文章
// @Tags 文章
// @Accept application/json
// @Param Authorization header string false "认证令牌"
// @Param article body []model.Article false "文章信息"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/article/batch_update [put]
func ArticleBatchUpdate(ctx *Context) {
var payload []*model.Article
var err error
var ret []int64
var r int64
if err = ctx.ShouldBindBodyWith(&payload, binding.JSON); err != nil {
logrus.Error(err)
ctx.Fail(err)
return
}
s := ctx.DB.NewSession()
s.Begin()
defer s.Close()
funk.ForEach(payload, func(form model.Article) {
form.Updater = null.StringFrom(ctx.GetToken().GetUserID())
form.UpdateTime = null.TimeFrom(time.Now().Value())
r, err = s.ID(form.ID.String).Update(&form)
ret = append(ret, r)
})
if err != nil {
s.Rollback()
logrus.Error(err)
ctx.Fail(err)
return
}
ctx.Success(ret)
}
```
#### page
> The most basic paging interface template is also supported by default.
```xml
<api name="page" func="page" table="sys_client" desc="客户端分页查询" method="get">
<param name="page" type="int" value="1" desc="页码"/>
<param name="size" type="int" value="10" desc="单页数"/>
<param name="app_name" type="string" desc="所属应用"/>
<return>
<success type="$success"/>
<failure type="$fail"/>
</return>
</api>
```
Generate code:
```go
// SysClientPage api implementation
// @Summary 客户端分页查询
// @Tags 客户端
// @Param Authorization header string false "认证令牌"
// @Param page query int false "页码"
// @Param size query int false "单页数"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/sys/client/page [get]
func SysClientPage(ctx *Context) {
q := ctx.TypeQuery()
q.SetInt("page", 1)
q.SetInt("size", 10)
q.SetString("app_name", viper.GetString("app.name"))
q.SetTags()
ret, err := ctx.PageSearch(ctx.PlatformDB, "sys_client", "page", "sys_client", q.Value())
if err != nil {
ctx.Fail(err)
return
}
ctx.Success(ret)
}
```
#### tree
> Tree structure is also a common algorithm. The framework integrates a TreeSearch interface for tree retrieval.
```xml
<api name="page" func="page" table="sys_menu" desc="菜单分页查询" method="get">
<param name="page" type="int" value="1" desc="页码"/>
<param name="size" type="int" value="10" desc="单页数"/>
<return>
<success type="$success"/>
<failure type="$fail"/>
</return>
</api>
```
Generate code:
```go
// SysMenuTree api implementation
// @Summary 菜单树形结构
// @Tags 菜单
// @Param Authorization header string false "认证令牌"
// @Failure 403 {object} model.Fail
// @Router /api/sys/menu/tree [get]
func SysMenuTree(ctx *Context) {
q := ctx.TypeQuery()
q.SetString("name")
q.SetRule("sys_menu_tree")
q.SetTags()
ret, err := ctx.TreeSearch(ctx.DB, "sys_menu", "tree", "sys_menu", q.Value())
if err != nil {
ctx.Fail(err)
return
}
ctx.Success(ret)
}
```
> TreeSearch:
> Detailed instructions can be found in the TreeSearch source code
```go
// platform/app/app.ctx.go#TreeSearch
func (ctx *Context) TreeSearch(db *xorm.Engine, controller, api, table string,
q map[string]interface{}) (interface{}, error)
```
#### one
> Find a single piece of data by unique key.
```xml
<api name="get" func="one" table="sys_client" desc="获取客户端信息" method="get">
<param name="id" type="string" desc="客户端id" />
<return>
<success type="$success"/>
<failure type="$fail"/>
</return>
</api>
```
Generate code:
```go
// SysClientGet api implementation
// @Summary 获取客户端信息
// @Tags 客户端
// @Param Authorization header string false "认证令牌"
// @Param id query string false "客户端id"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/sys/client/get [get]
func SysClientGet(ctx *Context) {
var entity model.SysClient
id := ctx.Query("id")
_, err := ctx.PlatformDB.ID(id).Get(&entity)
if err != nil {
ctx.Fail(err)
return
}
ctx.Success(entity)
}
```
#### other
> In addition to the default interface, custom interfaces are also supported.
```xml
<api name="payment" method="post" desc="文章付费">
<param name="article" type="$article_info" desc="文章"/>
<return>
<success type="$success"/>
<failure type="$fail"/>
</return>
</api>
```
Generate code:
```go
// ArticlePayment api implementation
// @Summary 文章分页查询
// @Tags 文章
// @Accept application/json
// @Param Authorization header string false "认证令牌"
// @Param article body model.ArticleInfo false "文章"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/article/payment [post]
func ArticlePayment(ctx *Context) {
var payload model.ArticleInfo
if err := ctx.ShouldBindBodyWith(&payload, binding.JSON); err != nil {
ctx.Fail(err)
return
}
ret, err := srv.ArticleAction(payload)
if err != nil {
ctx.Fail(err)
return
}
ctx.Success(ret)
}
```
可以前往github查看更多功能, 有兴趣就给个star吧 ^_^
https://github.com/2637309949/dolphin