/trinity

Primary LanguageGoMIT LicenseMIT

trinity

Build Status GitHub Actions codecov Go Report Card GoDoc Release

golang restframework plugin with gin+gorm, 5 lines to generate rest api with high security
p.s: django restframework like :)

特性

  • 集成gorm
  • 集成gin
  • 快速注册路由
  • 链路追踪,返回错误代码行数,及sql
  • 快速migrate表并生成权限
  • 支持快速开发rest风格api并实现增删改查,开箱即用
  • JWT token生成,认证,刷新中间件
  • json log风格中间件
  • 支持请求事务
  • 支持自定义用户权限查询
  • 支持自定义接口访问权限
  • 支持自定义接口数据访问权限
  • 自定义分页
  • 自定义过滤查询
  • 自定义搜索
  • 自定义预加载(gorm preload)
  • 自定义排序
  • 自定义查询包含字段
  • 自定义http方法重写
  • 添加了change log

安装

打开终端输入

$ go get -u github.com/PolarPanda611/trinity

done.

文档

// ViewSetCfg for viewset config
type ViewSetCfg struct {
	sync.RWMutex
	// global config
	Db *gorm.DB
	// HasAuthCtl
	// if do the auth check ,default false
	HasAuthCtl bool
	// AuthenticationBackendMap
	// if HasAuthCtl == false ; pass... customize the authentication check , default jwt  ;
	// please set UserID in context
	// e.g : c.Set("UserID", tokenClaims.UID)
	AuthenticationBackendMap map[string]func(c *gin.Context) error
	// GetCurrentUserAuth
	// must be type : func(c *gin.Context, db *gorm.DB) error
	// if HasAuthCtl == false ; pass...
	// get user auth func with UserID if you set in AuthenticationBackend
	// please set UserPermission and UserKey in context
	// e.g : c.Set("UserKey",UserKey) with c.GetString("UserID")
	// e.g : c.Set("UserPermission", UserPermission) with c.GetString("UserID")
	GetCurrentUserAuth interface{}
	// AccessBackendReqMap
	// if HasAuthCtl == false ; pass... customize the access require permission
	AccessBackendRequireMap map[string][]string
	// AccessBackendCheckMap
	// if HasAuthCtl == false ; pass... customize the access check , check user permission
	// e.g : userPermission :=  c.GetString("UserPermission")
	// e.g : requiredPermission := []string{"123"} get with AccessBackendReqMap by default
	// e.g : trinity.CheckAccessAuthorization(requiredPermission , userPermission) , true?allow:deny
	AccessBackendCheckMap map[string]func(v *ViewSetRunTime) error
	// PreloadListMap gorm preload list
	PreloadListMap map[string]map[string]func(db *gorm.DB) *gorm.DB
	// FilterBackendMap : all the query will with this filter backend
	FilterBackendMap map[string]func(c *gin.Context, db *gorm.DB) *gorm.DB
	// FilterByList : only in FilterByList will do the filter
	FilterByList []string
	// FilterCustomizeFunc : can do the customize filter ,mapping with FilterByList
	FilterCustomizeFunc map[string]func(db *gorm.DB, queryValue string) *gorm.DB
	// SearchingByList : with keyword "SearchBy" on url query ,
	// will do the where (xxx =? or xxx=?)
	SearchingByList []string
	// OrderingByList : with keyword "OrderBy" on url query ,
	// only define in OrderingByList will do the order by
	// e.g: OrderBy=xxx-   ==> order by xxx desc
	// e.g: OrderBy=xxx   ==> order by xxx asc
	OrderingByList map[string]bool
	// PageSize default 10
	// keyword : PageNum , PageSize to do the limit and offset
	PageSize int
	// Retrieve: customize retrieve func
	Retrieve func(r *ViewSetRunTime) *ViewSetRunTime
	// Get: customize Get func
	Get func(r *ViewSetRunTime) *ViewSetRunTime
	// Post: customize Post func
	Post func(r *ViewSetRunTime) *ViewSetRunTime
	// Put: customize Put func
	Put func(r *ViewSetRunTime) *ViewSetRunTime
	// Patch: customize Patch func
	Patch func(r *ViewSetRunTime) *ViewSetRunTime
	// Delete: customize Delete func
	Delete func(r *ViewSetRunTime) *ViewSetRunTime
}

例子

  • 准备条件,建立model
//Country model Country
type Country struct {
	Model
	Code        string `json:"code" gorm:"type:varchar(50);index;unique;not null;"`
	Name        string `json:"name" gorm:"type:varchar(50);"`
	Description string `json:"description" gorm:"type:varchar(100);index;"`
}

*初始化trinity设置

// GlobalViewSet global view set config by default value
var GlobalViewSet *trinity.ViewSetCfg


//Inittrinity get default setting
func Inittrinity() {
	trinity.Jwtexpirehour = setting.Cfg.Jwt.Jwtexpirehour
	// Jwtheaderprefix for jwt
	trinity.Jwtheaderprefix = setting.Cfg.Jwt.Jwtheaderprefix
	// Secretkey for jwt
	trinity.Secretkey = setting.Cfg.Secretkey
	// Jwtissuer for jwt
	trinity.Jwtissuer = setting.Cfg.Jwt.Jwtissuer

	GlobalViewSet = trinity.InitDefault(db.Dsource)

	// 在此覆盖默认全局设置
	GlobalViewSet.PageSize = setting.Cfg.Pagesize
}
  • 使用log,jwt中间件及注册路由
func Router() *gin.Engine {
    r := gin.New()
    ````
    r.Use(trinity.JWT())                 // jwt中间件
    r.Use(trinity.LoggerWithFormatter()) // log中间件,链路追踪需开启log中间件,获取TraceID *gin.Context.GetString("TraceID")
    v1 := r.Group("/api/v1")
    {
        // register RESTFUL API router by resouce name
	/*
	*@param RouterGroup :  the router group you want to register
	*@param Resource : the resource of the REST API
	*@param ViewSet : the service of the REST API
	*@param SupportedMethod : the service list of the REST API
	 */
	 // same as 
	 //r.GET("/"+resource+"/:key", viewset)
	 //r.GET("/"+resource, viewset)
	 //r.POST("/"+resource, viewset)
	 //r.PATCH("/"+resource+"/:key", viewset)
	 //r.PUT("/"+resource+"/:key", viewset)
	 //r.DELETE("/"+resource+"/:key", viewset)
        trinity.RegisterRestStyleRouter(v1, "users", servicev1.UserViewSet, []string{"Retrieve", "List", "Create", "Update", "Delete"})
    }
    ```
  • 注册路由处理
// CountryViewSet hanlde router
func CountryViewSet(c *gin.Context) {
	v := trinityinit.GlobalViewSet.New()
	// 在此覆盖默认局部设置
	v.HasAuthCtl = true
	v.FilterByList = []string{"trace_id"}
	// v.GetCurrentUserAuth = func(c *gin.Context, db *gorm.DB) error {
	// 	c.Set("UserPermission", []string{"system.view.Country"})
	// 	return nil
	// }
	utils.HandleResponse(v.NewRunTime(
		c,
		&model.Country{},
		&model.Country{},
		&[]model.Country{},
	).ViewSetServe())
}
  • 支持快速生成过滤

    • 过滤 (系统预置关键字) -关键字列表[]string{"like", "ilike", "in", "notin", "start", "end", "lt", "lte", "gt", "gte", "isnull", "isempty"} -支持关联关系过滤,以双下划线"__"分隔
	// Example :table user(id , name , dpp_id) table dpp {id , code , country_id}, table country {id , name ,create_time}
	// GET : http://127.0.0.1/countries?name__ilike=PolarPanda611&dpp__country_name__ilike=China&dpp__country__create_time__start=2019-01-01
	// ----FilterByList   filter condition must configured in FilterByList config  
	// SETTING : v.FilterByList=[]string{"name__ilike","dpp__country_name__ilike","dpp__country__create_time__start"}
	// EXECUTE:
	// name__ilike ==> name ilike '%PolarPanda611%'  
	// dpp__country__name__ilike => dpp_id in (select id from dpp where country_id in (select id from country where name ilike '%China%' ))
	// dpp__country__create_time__start => dpp_id in (select id from dpp where country_id in (select id from country where create_time > '2019-01-01 00:00:00' ))

	xxx__like=ooo 		=> xxx like '%ooo%'  
	xxx__ilike=ooo 		=> xxx like '%ooo%'  caps no sensitive
	xxx__in=aaa,bbb,ccc => xxx in ['aaa','bbb','ccc']  
	xxx__start=date1	=> xxx > 'date1 00:00:00'  
	xxx__end=date2		=> xxx < 'date2 23:59:59'  
	xxx__isnull=true 	=> xxx is null 
	xxx__isnull=false 	=> xxx is not null 
	xxx__isempty=true	=> (COALESCE("xxx"::varchar ,'') ='' )  
	xxx__isempty=false	=> (COALESCE("xxx"::varchar ,'') !='' )  
	xxx=ooo				=> xxx = ooo
  • 自定义过滤 (自定义关键字和查询方法)
	#Example  :http://127.0.0.1/countries?CustomizeFilterUser=PolarPanda611
	----FilterByList   filter condition must configured in FilterByList config  
	// v.FilterByList=[]string{"CustomizeFilterUser"}
	// v.FilterCustomizeFunc=map[string]func(db *gorm.DB, queryValue string) *gorm.DB{
	//		"CustomizeFilterUser":func(db *gorm.DB, queryValue string) *gorm.DB{
	//			db.Where("name = ?" , "PolarPanda611")
	//		}
	// }

	CustomizeFilterUser=PolarPanda611	=> name = 'PolarPanda611'  
	
  • 自定义搜索 (自定义关键字集合)
	#Example  :http://127.0.0.1/countries?SearchBy=xxx
	----SearchingByList   search condition must configured in in SearchingByList config  
	//v.SearchingByList=[]string{"name","address"}
	SearchBy=xxx		=>(name ilike '%xxx%' or address ilike '%xxx%')  
  • 自定义排序 (自定义排序关键字)
	#Example  :http://127.0.0.1/countries?OrderingBy=-id,name
	----OrderingByList  order by condition must configured in in OrderingByList config  
	OrderingBy=-id,name	=> order by id desc , name    
  • 自定义预加载 (自定义预加载)
	
// QuotationViewSet hanlde router
	func QuotationViewSet(c *gin.Context) {
		v := trinity.NewViewSet()
		v.HasAuthCtl = true
		v.PreloadListMap = map[string]map[string]func(db *gorm.DB) *gorm.DB{
			"RETRIEVE": map[string]func(db *gorm.DB) *gorm.DB{
				"Details":               nil,
				// "Details": func(db *gorm.DB) *gorm.DB {
				// 	return db.Where("id=1 ")
				// },
			},
			"GET": nil,
		}
		v.EnableChangeLog = true
		v.PostValidation = &QuotationPostValidation{}
		v.NewRunTime(
			c,
			&model.Quotation{},
			&model.Quotation{},
			&[]model.Quotation{},
		).ViewSetServe()
	}
  • 自定义分页 (自定义分页数量和页码)
	//默认开启分页
	#Example  :http://127.0.0.1/countries?PageSize=44&PageNum=2
	----queryByPagination By default , the list will be paged , default page size is configured in Pagination config  
	//offset := PageNumFieldInt * PageSizeFieldInt -1 
	//limit := PageSizeFieldInt  
	PageNum=1		=>PageNum= (1,2.....)  
	PageSize=10		=>PageSize= default value:10
	PaginationOn		=>by default :true , will open the pagination , if else , close the pagination, return all the list 
	//关闭分页
	#Example  :http://127.0.0.1/countries?PaginationOn=false
  • 自定义请求回复

// ResponseData http response
type ResponseData struct {
	Status  int         // the http response status  to return
	Result  interface{} // the response data  if req success
	TraceID string
}

// Response handle trinity return value
func Response(r *ViewSetRunTime) {
	var res ResponseData
	res.Status = r.Status
	res.TraceID = r.Gcontext.GetString("TraceID")
	if r.RealError != nil {
		r.Cfg.Logger.LogWriter(r)
		r.Gcontext.Error(r.RealError)
		r.Gcontext.Error(r.UserError)
		res.Result = r.UserError.Error()
		r.Gcontext.AbortWithStatusJSON(r.Status, res)
	} else {
		res.Result = r.ResBody
		r.Gcontext.JSON(r.Status, res)
	}
	return

}
  • 支持请求事务 整个request将会被同一个事物包裹, -如果返回err,则会request整体roll back -如果不返回err,则会request整体commit
	local:
		webapp:
			...
			atomicrequest: true
  • 添加change log 目前不支持嵌套map添加change log
	migrate  		&trinity.AppChangelog{}, 

	在viewset中
	// PartsViewSet hanlde router
	func PartsViewSet(c *gin.Context) {

		v := trinity.NewViewSet()
		v.HasAuthCtl = true
		v.EnableChangeLog = true
		v.NewRunTime(
			c,
			&model.Part{},
			&model.Part{},
			&[]model.Part{},
		).ViewSetServe()
	}

to do list :

支持字段级验证请求数据
外键字段过滤

post and patch not support assosiation update and create