|
|
hace 5 años | |
|---|---|---|
| assets | hace 5 años | |
| cmd | hace 5 años | |
| examples | hace 5 años | |
| packages | hace 5 años | |
| platform | hace 5 años | |
| .gitignore | hace 5 años | |
| README.md | hace 5 años | |
| go.mod | hace 5 años | |
| go.sum | hace 5 años |
Dolphin is a code generate tools and web Framework written in Go (Golang), Will reduce the repetitive workload of adding, deleting, revising, and conducting inspections
The first need Go installed, then you can use the below Go command to install Dolphin.
$ go get -u github.com/2637309949/dolphin/cmd/dolphin
$ mkdir example && cd example && dolphin init && dolphin build && go run main.go
Output:
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"
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
.
├── 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
- Generates the code base on XML configuration
- Generates doc base on XML configuration
- Generates SQL base on XML configuration
- Handles the serialization null problem
- Multi-tenant support
- Login / Logout, or single sign on
- Permission Authentication
- Quick excel reporting or parsing
- Support routing caching
- Data permission control
- Log trace record
- RPC remote service
- The k8S deployment file is generated by default
- Support database reverse XML generation
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
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 |
The clean command clears temporary files
dolphin clean
The init command, as stated, generates a series of initialization files
dolphin init
You can find a number of ready-to-run examples at dolphin examples repository.
application label contain app infomation, such as name, package
Example:
<?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, 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:
<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:
// 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, a collect api, you can declare api prefix
Example:
<controller name="activity" desc="微信活动" />
controller | LabelName | LabelMeaning | |----------|:-------------:| | name | controller name | | desc | controller desc | | prefix | controller desc |
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.
<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:
// 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.CreateBy = null.StringFrom(ctx.GetToken().GetUserID())
payload.UpdateTime = null.TimeFrom(time.Now().Value())
payload.UpdateBy = null.StringFrom(ctx.GetToken().GetUserID())
payload.DelFlag = 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)
}
Of course, you can also specify array parameters, which will automatically generate batch added templates.
<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:
// 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.CreateBy = null.StringFrom(ctx.GetToken().GetUserID())
form.UpdateTime = null.TimeFrom(time.Now().Value())
form.UpdateBy = null.StringFrom(ctx.GetToken().GetUserID())
form.DelFlag = 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)
}
The system default templates are soft delete logic, this is also highly recommended. If you need hard delete, please do it yourself
<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:
// 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()),
UpdateBy: null.StringFrom(ctx.GetToken().GetUserID()),
DelFlag: null.IntFrom(1),
})
if err != nil {
ctx.Fail(err)
return
}
ctx.Success(ret)
}
Of course, you can also specify array parameters, which will automatically generate batch deleted templates.
<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:
// 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()),
UpdateBy: null.StringFrom(ctx.GetToken().GetUserID()),
DelFlag: null.IntFrom(1),
})
if err != nil {
logrus.Error(err)
ctx.Fail(err)
return
}
ctx.Success(ret)
}
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)
<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:
// 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.UpdateBy = 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)
}
Of course, you can also specify array parameters, which will automatically generate batch updated templates.
<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:
// 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()
funk.ForEach(payload, func(form model.Article) {
form.UpdateBy = 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)
}
The most basic paging interface template is also supported by default.
<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="15" desc="单页数"/>
<param name="app_name" type="string" desc="所属应用"/>
<return>
<success type="$success"/>
<failure type="$fail"/>
</return>
</api>
Generate code:
// 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", 15)
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 structure is also a common algorithm. The framework integrates a TreeSearch interface for tree retrieval.
<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="15" desc="单页数"/>
<return>
<success type="$success"/>
<failure type="$fail"/>
</return>
</api>
Generate code:
// 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
// platform/app/app.ctx.go#TreeSearch
func (ctx *Context) TreeSearch(db *xorm.Engine, controller, api, table string,
q map[string]interface{}) (interface{}, error)
Find a single piece of data by unique key.
<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:
// 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)
}
In addition to the default interface, custom interfaces are also supported.
<api name="payment" method="post" desc="文章付费">
<param name="article" type="$article_info" desc="文章"/>
<return>
<success type="$success"/>
<failure type="$fail"/>
</return>
</api>
Generate code:
// 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)
}
api | LabelName | LabelMeaning | |----------|:-------------:| | name | api name | | desc | api desc | | func | built-in func, 'add', 'delete', 'update', 'page', 'get' | | table | table name if you use built-in func | | method | http method | | roles | roles middles | | cache | cache middles |
param | LabelName | LabelMeaning | |----------|:-------------:| | name | param name | | desc | param desc | | type | param type | | value | default value |
return
| LabelName | LabelMeaning |
|---|---|
| success | success tag |
| failure | success tag |
table, as you khnow, you can defined any table structure as you need. and you should use
nulltype if you wan to accept form data that avoid null value problems in golang.
table | LabelName | LabelMeaning | |----------|:-------------:| | name | table name | | desc | table desc | | packages | third party package name,use "," to split |
column | LabelName | LabelMeaning | |----------|:-------------:| | name | column name | | desc | column desc | | type | column type | | xorm | xorm tag, please refer to XORM for details |
Example:
<table name="article" desc="文章" packages="github.com/2637309949/dolphin/packages/null">
<column name="id" desc="主键" type="null.String" xorm="varchar(36) notnull unique pk" />
<column name="type" desc="类别" type="null.String" xorm="varchar(36)" />
<column name="create_by" desc="创建人" type="null.String" xorm="varchar(36)" />
<column name="create_time" desc="创建时间" type="null.Time" xorm="datetime" />
<column name="update_by" desc="最后更新人" type="null.String" xorm="varchar(36)" />
<column name="update_time" desc="最后更新时间" type="null.Time" xorm="datetime" />
<column name="del_flag" desc="删除标记" type="null.Int" xorm="notnull" />
<column name="remark" desc="备注" type="null.String" xorm="varchar(200)" />
</table>
Generate code:
// Code generated by dol build. DO NOT EDIT.
package model
import (
"github.com/2637309949/dolphin/packages/null"
)
// Article defined 文章
type Article struct {
// 主键
ID null.String `xorm:"varchar(36) notnull unique pk 'id'" json:"id" xml:"id"`
// 类别
Type null.String `xorm:"varchar(36) 'type'" json:"type" xml:"type"`
// 创建人
CreateBy null.String `xorm:"varchar(36) 'create_by'" json:"create_by" xml:"create_by"`
// 创建时间
CreateTime null.Time `xorm:"datetime 'create_time'" json:"create_time" xml:"create_time"`
// 最后更新人
UpdateBy null.String `xorm:"varchar(36) 'update_by'" json:"update_by" xml:"update_by"`
// 最后更新时间
UpdateTime null.Time `xorm:"datetime 'update_time'" json:"update_time" xml:"update_time"`
// 删除标记
DelFlag null.Int `xorm:"notnull 'del_flag'" json:"del_flag" xml:"del_flag"`
// 备注
Remark null.String `xorm:"varchar(200) 'remark'" json:"remark" xml:"remark"`
}
// TableName table name of defined Article
func (m *Article) TableName() string {
return "article"
}
The system adds the null and decimal packages by default, null is used to support data type default issues, and decimal is used to support financial data calculations.
Example:
<table name="article" desc="文章" packages="github.com/2637309949/dolphin/packages/null,github.com/2637309949/dolphin/packages/decimal">
<column name="id" desc="主键" type="null.String" xorm="varchar(36) notnull unique pk" />
<column name="reward" desc="打赏" type="decimal.Decimal" xorm="decimal(6,2)" />
<column name="create_by" desc="创建人" type="null.String" xorm="varchar(36)" />
<column name="create_time" desc="创建时间" type="null.Time" xorm="datetime" />
<column name="update_by" desc="最后更新人" type="null.String" xorm="varchar(36)" />
<column name="update_time" desc="最后更新时间" type="null.Time" xorm="datetime" />
<column name="del_flag" desc="删除标记" type="null.Int" xorm="notnull" />
<column name="remark" desc="备注" type="null.String" xorm="varchar(200)" />
</table>
It is recommended to write the helper tool class in the corresponding model directory.
Example:
//platform/model/sys_user.helper.go
// SetPassword Method to set salt and hash the password for a user
func (m *SysUser) SetPassword(password string) {
b := util.RandString(16, util.RandNumChar)
m.Salt = null.StringFrom(b)
dk, err := scrypt.Key([]byte(password), []byte(m.Salt.String), 512, 8, 1, 64)
if err != nil {
panic(err)
}
m.Password = null.StringFrom(fmt.Sprintf("%x", dk))
}
// ValidPassword Method to check the entered password is correct or not
func (m *SysUser) ValidPassword(password string) bool {
dk, err := scrypt.Key([]byte(password), []byte(m.Salt.String), 512, 8, 1, 64)
if err != nil {
panic(err)
}
return m.Password.String == fmt.Sprintf("%x", dk)
}
rpc, as a microservice interaction, the basic proto file can be generated here in rpc dir, as well as automatic registration in auto file.
<service name="message" desc="消息">
<rpc name="send_mail" desc="发送邮件">
<request type="$article" desc="文章信息"/>
<reply type="$success" desc="文章信息"/>
</rpc>
</service>
Generate code:
proto file:
// Code generated by dol build. Only Generate by tools if not existed,
// your can rewrite platform.App default action
// source: MessageSrv.proto
syntax = "proto3";
package proto;
// MessageSrv defined
service MessageSrv {
rpc SendMail (MessageMail) returns (MessageReply) {}
}
// MessageMail defined
message MessageMail {}
// MessageReply defined
message MessageReply {}
rpc srv impl:
// Code generated by dol build. Only Generate by tools if not existed.
// source: MessageSrv.go
package rpc
import (
"demo/rpc/proto"
"golang.org/x/net/context"
)
// MessageSrv defined
type MessageSrv struct{}
// SendMail defined
func (srv *MessageSrv) SendMail(
ctx context.Context,
in *proto.MessageMail) (*proto.MessageReply, error) {
return &proto.MessageReply{}, nil
}
rpc cli endpoint:
// Code generated by dol build. Only Generate by tools if not existed.
// source: MessageSrv.cli.go
package rpc
import (
"demo/rpc/proto"
"github.com/2637309949/dolphin/packages/logrus"
"github.com/2637309949/dolphin/packages/viper"
"google.golang.org/grpc"
)
// MessageSrvClient defined
var MessageSrvClient proto.MessageSrvClient
func init() {
opt := grpc.WithInsecure()
conn, err := grpc.Dial(viper.GetString("rpc.message_srv"), opt)
if err != nil {
logrus.Error("grpc dial failed: %v", err)
}
MessageSrvClient = proto.NewMessageSrvClient(conn)
}
rpc server will be automatically registered in app/app.auto.go
// MessageSrv defined
func MessageSrvService(engine *Engine) {
proto.RegisterMessageSrvServer(engine.GRPC, &rpc.MessageSrv{})
}
// SyncService defined
func SyncService() error {
MessageSrvService(App)
return nil
}
The quasi-directory structure of the rpc is shown below:
├── message.cli.go
├── message.go
├── message.go.new
└── proto
├── message.pb.go
├── message.proto
└── message.proto.new
Domain, a model of multi-tenant support core. Application splitting is also supported.
<table name="sys_domain" packages="xx/null" bind="platform">
<column name="id" type="null.String" xorm="varchar(36) notnull unique pk" />
<column name="name" type="null.String" xorm="varchar(36) notnull" />
<column name="app_name" type="null.String" xorm="varchar(36) notnull" />
<column name="domain" type="null.String" xorm="varchar(36) notnull" />
<column name="full_name" type="null.String" xorm="varchar(36)" />
<column name="contact_name" type="null.String" xorm="varchar(36)" />
<column name="contact_email" type="null.String" xorm="varchar(50) " />
<column name="contact_mobile" type="null.String" xorm="varchar(50) " />
<column name="data_source" type="null.String" xorm="varchar(200) notnull" />
<column name="driver_name" type="null.String" xorm="varchar(50) notnull" />
<column name="login_url" type="null.String" xorm="varchar(200)" />
<column name="api_url" type="null.String" xorm="varchar(200)" />
<column name="static_url" type="null.String" xorm="varchar(200)" />
<column name="theme" type="null.String" xorm="varchar(50) " />
<column name="type" type="null.Int" xorm="notnull" />
<column name="status" type="null.Int" xorm="notnull" />
<column name="auth_mode" type="null.Int" xorm="notnull" />
<column name="sync_flag" type="null.Int" xorm="notnull" />
<column name="create_by" type="null.String" xorm="varchar(36) notnull" />
<column name="create_time" type="null.Time" xorm="datetime notnull" />
<column name="update_by" type="null.String" xorm="varchar(36) notnull" />
<column name="update_time" type="null.Time" xorm="datetime notnull" />
<column name="del_flag" type="null.Int" xorm="notnull" />
<column name="remark" type="null.String" xorm="varchar(200)" />
</table>
app_name, desynchronize the model as a tag. if you connect same datasource url from localhost, and you would find all tables in
data_sourceof same app_name datasource would be created
Identify different tenants, the logged in user will use the matching domain to find the DB
As shown in the code below
// Auth middles
func Auth(ctx *Context) {
if !ctx.Auth(ctx.Request) {
ctx.Fail(util.ErrInvalidAccessToken, 401)
ctx.Abort()
return
}
if ctx.DB = App.Manager.GetBusinessDB(ctx.GetToken().GetDomain()); ctx.DB == nil {
ctx.Fail(util.ErrInvalidDomain)
ctx.Abort()
return
}
ctx.Set("DB", ctx.DB)
ctx.Set("AuthInfo", ctx.AuthInfo)
ctx.Next()
}
if you want to get datasource of xxx, you can do the following.
App.Manager.GetBusinessDB("xxx")
All projects that inherit the platform support single sign-on by default, you can deploy independently or directly as SSO Server
Your FrontEnd Project Your BackEnd Project SSO
|| || ||
|| || ||
|| 1. fetch api || ||
|| -------------------------\ || ||
|| unauthorized || ||
|| /------------------------- || ||
|| || ||
|| 2. fetch oauth url || ||
|| ------------------------\ || ||
|| /------------------------- || ||
|| || ||
|| 3. goto sso oauth || goto sso oauth ||
|| ------------------------- || -------------------------\ ||
|| || goto client with code ||
|| || /------------------------- ||
|| || ||
|| || ||
|| 4. redirect and set cookie || get token by code ||
|| || -------------------------\ ||
|| /------------------------- || /------------------------- ||
|| || ||
Code segment in platform, You can carry the status if needed.
// SysCasURL api implementation
// @Summary 授权地址
// @Tags 认证中心
// @Param redirect_uri query string false "定向URL"
// @Param state query string false "状态"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/sys/cas/url [get]
func SysCasURL(ctx *Context)
Code segment in platform, authentication logic
// SysCasLogin api implementation
// @Summary 用户认证
// @Tags 认证中心
// @Accept multipart/form-data
// @Param username formData string false "用户名称"
// @Param password formData string false "用户密码"
// @Param domain formData string false "用户域"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/sys/cas/login [post]
func SysCasLogin(ctx *Context)
Code segment in platform, you can rewrite this way if you want to skip Affirm
// SysCasAffirm api implementation
// @Summary 用户授权
// @Tags 认证中心
// @Accept application/json
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/sys/cas/affirm [post]
func SysCasAffirm(ctx *Context)
Code segment in platform, Generate Token by code
// SysCasToken api implementation
// @Summary 获取令牌
// @Tags 认证中心
// @Accept application/json
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/sys/cas/token [post]
func SysCasToken(ctx *Context)
Code segment in client, Fetch token from platform and set cookie
// SysCasOauth2 api implementation
// @Summary 授权回调
// @Tags 认证中心
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/sys/cas/oauth2 [get]
func SysCasOauth2(ctx *Context)
For more information, please check out dolphin-ui dolphin-ui
If you want to go sso auth then you can set auth_mode to 1
"auth_mode": 1
public/domain.js
window.Domain = {
"name": "localhost",
"full_name": "localhost",
"contact_name": null,
"contact_email": null,
"contact_mobile": null,
"login_url": "localhost",
"api_url": "http://localhost:8082",
"static_url": null,
"theme": "default",
"auth_mode": 1
}
High concurrent requests are processed with built-in load interfaces
// @Summary AddJobHandler
// @Tags worker
func (d *DefaultWorker) AddJobHandler(code string, funk func(model.Worker) (interface{}, error)) {
Example:
demo/srv/article.go
func init() {
// add hello topic handler
pApp.App.Manager
.Worker()
.AddJobHandler("hello", func(args pModel.Worker) (interface{}, error) {
fmt.Printf("topic=%v, payload=%v", "hello", args.Payload)
return map[string]interface{}{
"score": 99,
}, nil
})
}
// SysWorkerAdd api implementation
// @Summary 添加worker
// @Tags worker
// @Accept application/json
// @Param Authorization header string false "认证令牌"
// @Param worker body model.Worker false "worker信息"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/sys/worker/add [post]
func SysWorkerAdd(ctx *Context) {
Example:
Request:
POST /api/sys/worker/add HTTP/1.1
Host: localhost:8082
Content-Type: application/json
token: f4c9f457c82c9ee51e3dc50fea74562b64dd9269
Authorization: Bearer BY3KDUJNMWCN-NQJLKQVAW
Cache-Control: no-cache
Postman-Token: 0a39bb7e-d9bf-607d-120d-ffa59102dab8
{
"name": "hello",
"payload": { "user_id": "sdhfusd9f"}
}
Reponse:
{
"code": 200,
"data": {
"code": "fb9b4a91-c918-4d11-a8f7-878e4dd94f70",
"name": "hello",
"status": 100
}
}
// SysWorkerGet api implementation
// @Summary 获取worker信息
// @Tags worker
// @Param Authorization header string false "认证令牌"
// @Param code query string false "worker code"
// @Failure 403 {object} model.Fail
// @Success 200 {object} model.Success
// @Failure 500 {object} model.Fail
// @Router /api/sys/worker/get [get]
func SysWorkerGet(ctx *Context) {
Example:
GET /api/sys/worker/get?code=a4d13b27-4836-4a1b-b6fe-63473716bc4c HTTP/1.1
Host: localhost:8082
Authorization: Bearer BY3KDUJNMWCN-NQJLKQVAW
Cache-Control: no-cache
Postman-Token: 4f1595de-583b-fac6-06bc-2eafad956d40
Response:
{
"code": 200,
"data": {
"code": "a4d13b27-4836-4a1b-b6fe-63473716bc4c",
"name": "hello",
"result": {
"score": 99
},
"status": 103
}
}
Cron is not enabled by default, You can do this by modifying the configurationapp.cron = true if you need. But Cron is stateful, so it must be deployed separately when applying load.
Add a timer task via AddFunc.
AddFunc(string, func()) (int, error)
pApp.App.Manager.Cron().AddFunc("*/10 * * * * *", func() {
fmt.Println("hello")
})
Reflesh a timer task via RefreshFunc.
RefreshFunc(int, string) (int, error)
id, _ := pApp.App.Manager.Cron().AddFunc("*/10 * * * * *", func() {
fmt.Println("hello")
})
pApp.App.Manager.Cron().RefreshFunc(id, "*/3 * * * * *")
Remove a timer task via DelFunc.
DelFunc(int) error
id, _ := pApp.App.Manager.Cron().AddFunc("*/10 * * * * *", func() {
fmt.Println("hello")
})
pApp.App.Manager.Cron().DelFunc(id)
Try a timer task via TryFunc.
TryFunc(int) error
id, _ := pApp.App.Manager.Cron().AddFunc("*/10 * * * * *", func() {
fmt.Println("hello")
})
pApp.App.Manager.Cron().TryFunc(id)
Because user info db and the primary business db are separated, so user information needs to be loaded separately.
ctx.PlatformDB
You can load platform info from PlatformDB, such as sys_user, sys_client, sys_domain
ctx.DB
Else, You should load business info from ctx.DB
Create a new array from inside an array based on the field name.
uids := slice.GetFieldSliceByName(ret.Data, "id", "'%v'").([]string)
roles, err := srv.SysUserGetUserRolesByUID(ctx.DB, strings.Join(uids, ","))
if err != nil {
logrus.Error(err)
ctx.Fail(err)
return
}
Patch the field value to another array.
Example:
err = slice.PatchSliceByField(ret.Data, roles, "id", "user_id", "role_name")(&ret.Data)
if err != nil {
logrus.Error(err)
ctx.Fail(err)
return
}