kuu

package module
v0.0.0-...-cc73645 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 22, 2024 License: Apache-2.0 Imports: 67 Imported by: 5

README

Modular Go Web Framework

GoDoc Build Status codecov

Modular Go Web Framework based on GORM and Gin.

Contents

Installation

go get -u github.com/kuuland/kuu

Quick start

# assume the following codes in kuu.json file
$ cat kuu.json
{
  "prefix": "/api",
  "db": {
    "dialect": "postgres",
    "args": "host=127.0.0.1 port=5432 user=root dbname=kuu password=hello sslmode=disable"
  },
  "redis": {
    "addr": "127.0.0.1:6379"
  }
}
# assume the following codes in main.go file
$ cat main.go
package main

import (
	"github.com/gin-gonic/gin"
	_ "github.com/jinzhu/gorm/dialects/postgres"
	"github.com/kuuland/kuu"
)

func main() {
	r := kuu.Default()
	r.Import(kuu.Acc(), kuu.Sys())
	r.Run()
}
# run main.go and visit 0.0.0.0:8080 on browser
$ go run main.go

Features

Global configuration

# assume the following codes in kuu.json file
$ cat kuu.json
{
  "prefix": "/api",
  "cors": true,
  "gzip": true,
  "gorm:migrate": false,
  "db": {
    "dialect": "postgres",
    "args": "host=127.0.0.1 port=5432 user=root dbname=kuu password=hello sslmode=disable"
  },
  "redis": {
    "addr": "127.0.0.1:6379"
  },
  "statics": {
    "/assets": "assets/",
    "/drone_yml": ".drone.yml"
  }
}
func main() {
    kuu.C().Get("prefix")              // output "/api"
    kuu.C().GetBool("cors")            // output true
    kuu.C().GetBool("gorm:migrate")    // output true
    // load config from kuu.Param store on databases
    // only work when the Param.type is json and the Param.value is json object
    kuu.C().LoadFromParams("whitelist") // whitelist value is {"enable": true, "lable":"app", "items": ["item-1", "item-2"]}
    // and the key startwith: params
    kuu.C().GetBool("params.whilelist.enable") // output true
    kuu.C().GetString("params.whilelist.label") // output "app"
    var items []string
    kuu.C().GetInterface("params.whilelist.items", &items)
}

List of preset config:

  • prefix - Global routes prefix for kuu.Mod's Routes.
  • gorm:migrate - Enable GORM's auto migration for Mod's Models.
  • audit:callbacks - Register audit callbacks, default is true.
  • db - DB configs.
  • redis - Redis configs.
    • use kuu.GetRedisClient() get full redis client
  • cors - Attaches the official CORS gin's middleware.
  • gzip - Attaches the gin middleware to enable GZIP support.
  • statics - Static serves files from the given file system root or serve a single file.
  • whitelist:prefix - Let whitelist also matches paths with global prefix, default is true.
  • ignoreDefaultRootRoute - Do not mount the default root route, default is false.
  • logs - Log dir.

Notes: Static paths are automatically added to the whitelist.

Data Source Management

Single data source:

{
  "db": {
    "dialect": "postgres",
    "args": "host=127.0.0.1 port=5432 user=root dbname=db1 password=hello sslmode=disable"
  }
}
r.GET("/ping", func(c *kuu.Context) {
    var users []user
    kuu.DB().Find(&users)
    c.STD(&users)
})

Multiple data source:

{
  "db": [
    {
      "name": "ds1",
      "dialect": "postgres",
      "args": "host=127.0.0.1 port=5432 user=root dbname=db1 password=hello sslmode=disable"
    },
    {
      "name": "ds2",
      "dialect": "postgres",
      "args": "host=127.0.0.1 port=5432 user=root dbname=db1 password=hello sslmode=disable"
    }
  ]
}
r.GET("/ping", func(c *kuu.Context) {
    var users []user
    kuu.DB("ds1").Find(&users)
    c.STD(&users)
})

Use transaction

err := kuu.WithTransaction(func(tx *gorm.DB) error {
	// ...
    tx.Create(&memberDoc)
    if tx.NewRecord(memberDoc) {
        return errors.New("Failed to create member profile")
    }
    // ...
    tx.Create(...)
    return tx.Error
})

Notes: Remember to return tx.Error!!!

RESTful APIs for struct

Automatically mount RESTful APIs for struct:

type User struct {
	kuu.Model `rest:"*"`
	Code string
	Name string
}

func main() {
	kuu.RESTful(r, &User{})
}
[GIN-debug] POST   /api/user  --> github.com/kuuland/kuu.RESTful.func1 (4 handlers)
[GIN-debug] DELETE /api/user  --> github.com/kuuland/kuu.RESTful.func2 (4 handlers)
[GIN-debug] GET    /api/user  --> github.com/kuuland/kuu.RESTful.func3 (4 handlers)
[GIN-debug] PUT    /api/user  --> github.com/kuuland/kuu.RESTful.func4 (4 handlers)

On other fields:

type User struct {
	kuu.Model
	Code string `rest:"*"`
	Name string
}

You can also change the default request method:

type User struct {
	kuu.Model `rest:"C:POST;U:PUT;R:GET;D:DELETE"`
	Code string
	Name string
}

func main() {
	kuu.RESTful(r, &User{})
}

Or change route path:

type User struct {
	kuu.Model `rest:"*" route:"profile"`
	Code string
	Name string
}

func main() {
	kuu.RESTful(r, &User{})
}
[GIN-debug] POST   /api/profile  --> github.com/kuuland/kuu.RESTful.func1 (4 handlers)
[GIN-debug] DELETE /api/profile  --> github.com/kuuland/kuu.RESTful.func2 (4 handlers)
[GIN-debug] GET    /api/profile  --> github.com/kuuland/kuu.RESTful.func3 (4 handlers)
[GIN-debug] PUT    /api/profile  --> github.com/kuuland/kuu.RESTful.func4 (4 handlers)

Or unmount:

type User struct {
	kuu.Model `rest:"C:-;U:PUT;R:GET;D:-"` // unmount all: `rest:"-"`
	Code string
	Name string
}

func main() {
	kuu.RESTful(r, &User{})
}
Create Record
curl -X POST \
  http://localhost:8080/api/user \
  -H 'Content-Type: application/json' \
  -d '{
    "user": "test",
    "pass": "123"
}'
Batch Create
curl -X POST \
  http://localhost:8080/api/user \
  -H 'Content-Type: application/json' \
  -d '[
    {
        "user": "test1",
        "pass": "123456"
    },
    {
        "user": "test2",
        "pass": "123456"
    },
    {
        "user": "test3",
        "pass": "123456"
    }
]'
Query

Request querystring parameters:

curl -X GET \
  'http://localhost:8080/api/user?cond={"user":"test"}&sort=id&project=pass'
Key Desc Default Example
range data range, allow ALL and PAGE PAGE range=ALL
cond query condition, JSON string - cond={"user":"test"}
sort order fields - sort=id,-user
project select fields - project=user,pass
preload preload fields - preload=CreditCards,UserAddresses
export export data - export=true
page current page(required in PAGE mode) 1 page=2
size record size per page(required in PAGE mode) 30 size=100

Query operators:

Operator Desc Example
$regex LIKE cond={"user":{"$regex":"^test$"}}
$in IN cond={"id":{"$in":[1,2,5]}}
$nin NOT IN cond={"id":{"$nin":[1,2,5]}}
$eq Equal cond={"id":{"$eq":5}} equivalent to cond={"id":5}
$ne NOT Equal cond={"id":{"$ne":5}}
$exists IS NOT NULL cond={"pass":{"$exists":true}}
$gt Greater Than cond={"id":{"$gt":5}}
$gte Greater Than or Equal cond={"id":{"$gte":5}}
$lt Less Than cond={"id":{"$lt":20}}
$lte Less Than or Equal cond={"id":{"$lte":20}}, cond={"id":{"$gte":5,"$lte":20}}
$and AND cond={"user":"root","$and":[{"pass":"123"},{"pass":{"$regex":"^333"}}]}
$or OR cond={"user":"root","$or":[{"pass":"123"},{"pass":{"$regex":"^333"}}]}

Response JSON body:

{
    "data": {
        "cond": {
            "user": "test"
        },
        "list": [
            {
                "ID": 3,
                "CreatedAt": "2019-05-10T09:19:40.437816Z",
                "UpdatedAt": "2019-05-12T07:04:13.583093Z",
                "DeletedAt": null,
                "User": "test",
                "Pass": "123456"
            },
            {
                "ID": 5,
                "CreatedAt": "2019-05-10T10:31:43.203526Z",
                "UpdatedAt": "2019-05-12T07:04:13.583093Z",
                "DeletedAt": null,
                "User": "test",
                "Pass": "111222333"
            }
        ],
        "page": 1,
        "range": "PAGE",
        "size": 30,
        "sort": "id",
        "totalpages": 1,
        "totalrecords": 2
    },
    "code": 0,
    "msg": ""
}
Key Desc Default
list data list []
range data range, same as request PAGE
cond query condition, same as request -
sort order fields, same as request -
project select fields, same as request -
preload preload fields, same as request -
totalrecords total records 0
page current page, exist in PAGE mode 1
size record size per page, exist in PAGE mode 30
totalpages total pages, exist in PAGE mode 0
Update Fields
curl -X PUT \
  http://localhost:8080/api/user \
  -H 'Content-Type: application/json' \
  -d '{
    "cond": {
        "id": 5
    },
    "doc": {
        "user": "new username"
    }
}'

Notes: Pass only the fields that need to be updated!!!

Batch Updates
curl -X PUT \
  http://localhost:8080/api/user \
  -H 'Content-Type: application/json' \
  -d '{
    "cond": {
        "user": "test"
    },
    "doc": {
        "pass": "newpass"
    },
    "multi": true
}'

Notes: Pass only the fields that need to be updated!!!

Delete Record
curl -X DELETE \
  http://localhost:8080/api/user \
  -H 'Content-Type: application/json' \
  -d '{
    "cond": {
        "id": 5
    }
}'
Batch Delete
curl -X DELETE \
  http://localhost:8080/api/user \
  -H 'Content-Type: application/json' \
  -d '{
    "cond": {
        "user": "test"
    },
    "multi": true
}'
UnSoft Delete
curl -X DELETE \
  http://localhost:8080/api/user \
  -H 'Content-Type: application/json' \
  -d '{
    "cond": {
        "user": "test"
    },
    "unsoft": true
}'

Associations

Associations

  1. if association has a primary key, Kuu will call Update to save it, otherwise it will be created
  2. If the association has both ID and DeletedAt, Kuu will delete it.
  3. set "preload=field1,field2" to preload associations
Create associations
curl -X PUT \
  http://localhost:8080/api/user \
  -H 'Content-Type: application/json' \
  -d '{
    "cond": {
        "ID": 50
    },
    "doc": {
        "Emails": [
            {
                "Email": "test1@example.com"
            },
            {
                "Email": "test2@example.com"
            }
        ]
    }
}'
Update associations

ID is required:

curl -X PUT \
  http://localhost:8080/api/user \
  -d '{
    "cond": {
        "ID": 50
    },
    "doc": {
        "Emails": [
            {
                "ID": 101,
                "Email": "test111@example.com"
            },
            {
                "ID": 159,
                "Email": "test222@example.com"
            }
        ]
    }
}'

Notes: Pass only the fields that need to be updated!!!

Delete associations

ID and DeletedAt are required:

curl -X PUT \
  http://localhost:8080/api/user \
  -H 'Content-Type: application/json' \
  -d '{
    "cond": {
        "ID": 50
    },
    "doc": {
        "Emails": [
            {
                "ID": 101,
                "DeletedAt": "2019-06-25T17:05:06.000Z",
                "Email": "test111@example.com"
            },
            {
                "ID": 159,
                "DeletedAt": "2019-06-25T17:05:06.000Z",
                "Email": "test222@example.com"
            }
        ]
    }
}'

Notes: DeletedAt is only used as a flag, and will be re-assigned using server time when deleted.

Query associations

set "preload=Emails" to preload associations:

curl -X GET \
  'http://localhost:8080/api/user?cond={"ID":115}&preload=Emails'

Password field filter

type User struct {
	Model   `rest:"*" displayName:"用户" kuu:"password"`
	Username    string  `name:"账号"`
	Password    string  `name:"密码"`
}

users := []User{
    {Username: "root", Password: "xxx"},
    {Username: "admin", Password: "xxx"},
} 
users = kuu.Meta("User").OmitPassword(users) // => []User{ { Username: "root" }, { Username: "admin" } } 

Global default callbacks

You can override the default callbacks:

// Default create callback
kuu.CreateCallback = func(scope *gorm.Scope) {
	if !scope.HasError() {
		if desc := GetRoutinePrivilegesDesc(); desc != nil {
			var (
				hasOrgIDField       bool = false
				orgID               uint
				hasCreatedByIDField bool = false
				createdByID         uint
			)
			if field, ok := scope.FieldByName("OrgID"); ok {
				if field.IsBlank {
					if err := scope.SetColumn(field.DBName, desc.SignOrgID); err != nil {
						_ = scope.Err(fmt.Errorf("自动设置组织ID失败:%s", err.Error()))
						return
					}
				}
				hasOrgIDField = ok
				orgID = field.Field.Interface().(uint)
			}
			if field, ok := scope.FieldByName("CreatedByID"); ok {
				if err := scope.SetColumn(field.DBName, desc.UID); err != nil {
					_ = scope.Err(fmt.Errorf("自动设置创建人ID失败:%s", err.Error()))
					return
				}
				hasCreatedByIDField = ok
				createdByID = field.Field.Interface().(uint)
			}
			if field, ok := scope.FieldByName("UpdatedByID"); ok {
				if err := scope.SetColumn(field.DBName, desc.UID); err != nil {
					_ = scope.Err(fmt.Errorf("自动设置修改人ID失败:%s", err.Error()))
					return
				}
			}
			// 写权限判断
			if orgID == 0 {
				if hasCreatedByIDField && createdByID != desc.UID {
					_ = scope.Err(fmt.Errorf("用户 %d 只拥有个人可写权限", desc.UID))
					return
				}
			} else if hasOrgIDField && !desc.IsWritableOrgID(orgID) {
				_ = scope.Err(fmt.Errorf("用户 %d 在组织 %d 中无可写权限", desc.UID, orgID))
				return
			}
		}
	}
}


// Default delete callback
kuu.DeleteCallback = func(scope *gorm.Scope) {
	if !scope.HasError() {
		var extraOption string
		if str, ok := scope.Get("gorm:delete_option"); ok {
			extraOption = fmt.Sprint(str)
		}

		deletedAtField, hasDeletedAtField := scope.FieldByName("DeletedAt")
		var desc *PrivilegesDesc
		if desc = GetRoutinePrivilegesDesc(); desc != nil {
			AddDataScopeWritableSQL(scope, desc)
		}

		if !scope.Search.Unscoped && hasDeletedAtField {
			var sql string
			if desc != nil {
				deletedByField, hasDeletedByField := scope.FieldByName("DeletedByID")
				if !scope.Search.Unscoped && hasDeletedByField {
					sql = fmt.Sprintf(
						"UPDATE %v SET %v=%v,%v=%v%v%v",
						scope.QuotedTableName(),
						scope.Quote(deletedByField.DBName),
						scope.AddToVars(desc.UID),
						scope.Quote(deletedAtField.DBName),
						scope.AddToVars(gorm.NowFunc()),
						AddExtraSpaceIfExist(scope.CombinedConditionSql()),
						AddExtraSpaceIfExist(extraOption),
					)
				}
			}
			if sql == "" {
				sql = fmt.Sprintf(
					"UPDATE %v SET %v=%v%v%v",
					scope.QuotedTableName(),
					scope.Quote(deletedAtField.DBName),
					scope.AddToVars(gorm.NowFunc()),
					AddExtraSpaceIfExist(scope.CombinedConditionSql()),
					AddExtraSpaceIfExist(extraOption),
				)
			}
			scope.Raw(sql).Exec()
		} else {
			scope.Raw(fmt.Sprintf(
				"DELETE FROM %v%v%v",
				scope.QuotedTableName(),
				AddExtraSpaceIfExist(scope.CombinedConditionSql()),
				AddExtraSpaceIfExist(extraOption),
			)).Exec()
		}
		if scope.DB().RowsAffected < 1 {
			_ = scope.Err(errors.New("未删除任何记录,请检查更新条件或数据权限"))
			return
		}
	}
}

// Default update callback
kuu.UpdateCallback = func(scope *gorm.Scope) {
	if !scope.HasError() {
		if desc := GetRoutinePrivilegesDesc(); desc != nil {
			// 添加可写权限控制
			AddDataScopeWritableSQL(scope, desc)
			if err := scope.SetColumn("UpdatedByID", desc.UID); err != nil {
				ERROR("自动设置修改人ID失败:%s", err.Error())
			}
		}
	}
}

// Default query callback
kuu.QueryCallback = func(scope *gorm.Scope) {
	if !scope.HasError() {
		desc := GetRoutinePrivilegesDesc()
		if desc == nil {
			// 无登录登录态时
			return
		}

		caches := GetRoutineCaches()
		if caches != nil {
			// 有忽略标记时
			if _, ignoreAuth := caches[GLSIgnoreAuthKey]; ignoreAuth {
				return
			}
			// 查询用户菜单时
			if _, queryUserMenus := caches[GLSUserMenusKey]; queryUserMenus {
				if desc.NotRootUser() {
					_, hasCodeField := scope.FieldByName("Code")
					_, hasCreatedByIDField := scope.FieldByName("CreatedByID")
					if hasCodeField && hasCreatedByIDField {
						// 菜单数据权限控制与组织无关,且只有两种情况:
						// 1.自己创建的,一定看得到
						// 2.别人创建的,必须通过分配操作权限才能看到
						scope.Search.Where("(code in (?)) OR (created_by_id = ?)", desc.Codes, desc.UID)
					}
				}
				return
			}
		}
		AddDataScopeReadableSQL(scope, desc)
	}
}

// Default validate callback
kuu.ValidateCallback = func(scope *gorm.Scope) {
	if !scope.HasError() {
		if _, ok := scope.Get("gorm:update_column"); !ok {
			result, ok := scope.DB().Get(skipValidations)
			if !(ok && result.(bool)) {
				scope.CallMethod("Validate")
				if scope.Value == nil {
					return
				}
				resource := scope.IndirectValue().Interface()
				_, validatorErrors := govalidator.ValidateStruct(resource)
				if validatorErrors != nil {
					if errs, ok := validatorErrors.(govalidator.Errors); ok {
						for _, err := range FlatValidatorErrors(errs) {
							if err := scope.DB().AddError(formattedValidError(err, resource)); err != nil {
								ERROR("添加验证错误信息失败:%s", err.Error())
							}

						}
					} else {
						if err := scope.DB().AddError(validatorErrors); err != nil {
							ERROR("添加验证错误信息失败:%s", err.Error())
						}
					}
				}
			}
		}
	}
}

Inject custom authentication

Specify the token type, the default is ADMIN:

secret, err := kuu.GenToken(kuu.GenTokenDesc{
    Payload: jwt.MapClaims{
        "MemberID":  member.ID,
        "CreatedAt": member.CreatedAt,
    },
    UID:      member.UID,
    SubDocID: member.ID,
    Exp:      time.Now().Add(time.Second * time.Duration(kuu.ExpiresSeconds)).Unix(),
    Type:     "MY_SIGN_TYPE",
})

Inject your rules:

kuu.InjectCreateAuth = func(signType string, auth kuu.AuthProcessorDesc) (replace bool, err error) {
    return
}
kuu.InjectWritableAuth = func(signType string, auth kuu.AuthProcessorDesc) (replace bool, err error) {
    return
}
kuu.InjectReadableAuth = func(signType string, auth kuu.AuthProcessorDesc) (replace bool, err error) {
    return
}

Struct validation

base on govalidator:

// this struct definition will fail govalidator.ValidateStruct() (and the field values do not matter):
type exampleStruct struct {
  Name  string ``
  Email string `valid:"email"`
}

// this, however, will only fail when Email is empty or an invalid email address:
type exampleStruct2 struct {
  Name  string `valid:"-"`
  Email string `valid:"email"`
}

// lastly, this will only fail when Email is an invalid email address but not when it's empty:
type exampleStruct2 struct {
  Name  string `valid:"-"`
  Email string `valid:"email,optional"`
}

// Validate
func (e *exampleStruct2) Validate () error {
}

Modular project structure

Kuu will automatically mount routes, middlewares and struct RESTful APIs after Import:

type User struct {
	kuu.Model `rest`
	Username string
	Password string
}

type Profile struct {
	kuu.Model `rest`
	Nickname string
	Age int
}

func MyMod() *kuu.Mod {
	return &kuu.Mod{
		Models: []interface{}{
			&User{},
			&Profile{},
		},
		Middlewares: gin.HandlersChain{
			func(c *gin.Context) {
				// Auth middleware
			},
		},
		Routes: kuu.RoutesInfo{
			kuu.RouteInfo{
				Method: "POST",
				Path:   "/login",
				HandlerFunc: func(c *kuu.Context) {
					// POST /login
				},
			},
			kuu.RouteInfo{
				Method: "POST",
				Path:   "/logout",
				HandlerFunc: func(c *kuu.Context) {
					// POST /logout
				},
			},
		},
	}
}

func main() {
	r := kuu.Default()
	r.Import(kuu.Acc(), kuu.Sys())     // import preset modules
	r.Import(MyMod())                       // import custom module
}

Global log API

func main() {
	kuu.PRINT("Hello Kuu")  // PRINT[0000] Hello Kuu
	kuu.DEBUG("Hello Kuu")  // DEBUG[0000] Hello Kuu
	kuu.WARN("Hello Kuu")   // WARN[0000] Hello Kuu
	kuu.INFO("Hello Kuu")   // INFO[0000] Hello Kuu
	kuu.FATAL("Hello Kuu")  // FATAL[0000] Hello Kuu
	kuu.PANIC("Hello Kuu")  // PANIC[0000] Hello Kuu
}

Or with params:

func main() {
	kuu.INFO("Hello %s", "Kuu")  // INFO[0000] Hello Kuu
}

Standard response format

func main() {
	r := kuu.Default()
	r.GET("/ping", func(c *kuu.Context) {
        c.STD("hello")                                  // response: {"data":"hello","code":0}
        c.STD("hello", c.L("ping_success", "Success"))  // response: {"data":"hello","code":0,"msg":"Success"}
        c.STD(1800)                                     // response: {"data":1800,"code":0}
        c.STDErr(c.L("ping_failed_new", "New record failed"))       // response: {"code":-1,"msg":"New record failed"}
        c.STDErr(c.L("ping_failed_new", "New record failed"), err)  // response: {"code":-1,"msg":"New record failed","data":"错误详细描述信息,对应err.Error()"}
        c.STDErrHold(c.L("ping_failed_token", "Token decoding failed"), errors.New("token not found")).Code(555).Render() // response: {"code":555,"msg":"Token decoding failed","data":"token not found"}
    })
}

Notes:

  • If data == error, Kuu will call ERROR(data) to output the log.
  • All message will call kuu.L(c, msg) for i18n before the response.

Get login context

r.GET(func (c *kuu.Context){
	c.SignInfo // Login user info
	c.PrisDesc // Login user privileges
})

Goroutine local storage

// preset caches
kuu.GetRoutinePrivilegesDesc()
kuu.GetRoutineValues()
kuu.GetRoutineRequestContext()

// custom caches
kuu.GetRoutineCaches()
kuu.SetRoutineCache(key, value)
kuu.GetRoutineCache(key)
kuu.DelRoutineCache(key)

// Ignore default data filters
kuu.IgnoreAuth() // Equivalent to c.IgnoreAuth/kuu.GetRoutineValues().IgnoreAuth

Whitelist

All routes are blocked by the authentication middleware by default. If you want to ignore some routes, please configure the whitelist:

kuu.AddWhitelist("GET /", "GET /user")
kuu.AddWhitelist(regexp.MustCompile("/user"))

Notes: Whitelist also matches paths with global prefix. If you don't want this feature, please set "whitelist:prefix":false.

Hooks

Rest api hooks
  • hooks keys format: StructName:Operation, operation list:
    • BizBeforeFind
    • BizAfterFind
    • BizBeforeCreate
    • BizAfterCreate
    • BizBeforeUpdate
    • BizAfterUpdate
    • BizBeforeDelete
    • BizAfterDelete
kuu.RegisterBizHook("User:BizBeforeCreate", func (scope *kuu.Scope) error {
    db := scope.DB // db instance
    user, ok := scope.Value.(*User)
	fmt.Println(user, ok)
    return nil
})
Gorm global hooks

Mainly used to solve circular dependency problems

  • is same as gorm hooks, the hooks key format: StructName:Operation, operation list:
    • BeforeSave
    • BeforeCreate
    • BeforeUpdate
    • AfterUpdate
    • AfterSave
    • AfterCreate
kuu.RegisterGormHook("User:BeforeSave", func(scope *gorm.Scope) error {
    return nil
})

i18n

kuu.L("acc_logout_failed", "Logout failed").Render()                             // => Logout failed
kuu.L("fano_table_total", "Total {{total}} items", kuu.M{"total": 500}).Render() // => Total 500 items
  • Use a unique key
  • Always set defaultMessage
Best Practices
func singleMessage(c *kuu.Context) {
	failedMessage := c.L("import_failed", "Import failed")
	file, _ := c.FormFile("file")
	if file == nil {
		c.STDErr(failedMessage, errors.New("no 'file' key in form-data"))
		return
	}
	src, err := file.Open()
	if err != nil {
		c.STDErr(failedMessage, err)
		return
	}
	defer src.Close()
}

func multiMessage(c *kuu.Context) {
	var (
		phoneIncorrect = c.L("phone_incorrect", "Phone number is incorrect")
		passwordIncorrect = c.L("password_incorrect", "The password is incorrect")
	)
	if err := checkPhoneNumber(...); err != nil {
		c.STDErr(phoneIncorrect, err)
		return
	}
	if err := checkPassword(...); err != nil {
		c.STDErr(passwordIncorrect, err)
		return
	}
	c.STD(...)
}
Manual Registration
register := kuu.NewLangRegister(kuu.DB())
register.SetKey("acc_please_login").Add("Please login", "请登录", "請登錄")
register.SetKey("auth_failed").Add("Authentication failed", "鉴权失败", "鑒權失敗")
register.SetKey("acc_logout_failed").Add("Logout failed", "登出失败", "登出失敗")
register.SetKey("kuu_welcome").Add("Welcome {{name}}", "欢迎{{name}}", "歡迎{{name}}")

Common utils

func main() {
	r := kuu.Default()
	// Parse JSON from string
	var params map[string]string
	kuu.Parse(`{"user":"kuu","pass":"123"}`, &params)
	// Formatted as JSON
	kuu.Stringify(&params)
}
  • IsBlank - Check if value is empty
  • Stringify - Converts value to a JSON string
  • Parse - Parses a JSON string to the value
  • EnsureDir - Ensures that the directory exists
  • Copy - Copy values
  • RandCode - Generate random code
  • If - Conditional expression

Preset modules

Security framework

Kuu Security framework

FAQ

Why called Kuu?

Kuu and Shino

Kuu and Shino

License

Kuu is available under the Apache License, Version 2.0.

Thanks to

  • JetBrains for their open source license(s).

JetBrains

Documentation

Index

Examples

Constants

View Source
const (
	SignMethodLogin  = "LOGIN"
	SignMethodLogout = "LOGOUT"
)
View Source
const (
	// BizCreateKind
	BizCreateKind = "create"
	// BizUpdateKind
	BizUpdateKind = "update"
	// BizDeleteKind
	BizDeleteKind = "delete"
	// BizQueryKind
	BizQueryKind = "query"
)
View Source
const (
	ImportStatusImporting = "importing"
	ImportStatusSuccess   = "success"
	ImportStatusFailed    = "failed"
)
View Source
const (
	MessageStatusDraft = 100
	MessageStatusSent  = 200
)
View Source
const (
	ASCIISignerAlgMD5  = "MD5"
	ASCIISignerAlgSHA1 = "SHA1"
)
View Source
const (
	// DataScopePersonal
	DataScopePersonal = "PERSONAL"
	// DataScopeCurrent
	DataScopeCurrent = "CURRENT"
	// DataScopeCurrentFollowing
	DataScopeCurrentFollowing = "CURRENT_FOLLOWING"
)
View Source
const (
	AdminSignType = "ADMIN"
)

Variables

View Source
var (
	TokenKey  = "Token"
	LangKey   = "Lang"
	Whitelist = []interface{}{
		"GET /",
		"GET /favicon.ico",
		"POST /login",
		"GET /model/docs",
		"GET /model/ws",
		"GET /language",
		"GET /langmsgs",
		"GET /captcha",
		"GET /intl/languages",
		"GET /intl/messages",
		regexp.MustCompile("GET /assets"),
	}
	ExpiresSeconds = 86400
)
View Source
var (
	// CaptchaIDKey
	CaptchaIDKey = "captcha_id"
	// CaptchaValKey
	CaptchaValKey = "captcha_val"
)
View Source
var (
	ErrTokenNotFound       = errors.New("token not found")
	ErrSecretNotFound      = errors.New("secret not found")
	ErrInvalidToken        = errors.New("invalid token")
	ErrAffectedSaveToken   = errors.New("未新增或修改任何记录,请检查更新条件或数据权限")
	ErrAffectedDeleteToken = errors.New("未删除任何记录,请检查更新条件或数据权限")
)
View Source
var (
	// GLSPrisDescKey
	GLSPrisDescKey = "PrisDesc"
	// GLSSignInfoKey
	GLSSignInfoKey = "SignInfo"
	// GLSIgnoreAuthKey
	GLSIgnoreAuthKey = "IgnoreAuth"
	// GLSRoutineCachesKey
	GLSRoutineCachesKey = "RoutineCaches"
	// GLSRequestContextKey
	GLSRequestContextKey = "RequestContext"
	GLSRequestIDKey      = "Request ID"
	// Uptime
	Uptime time.Time
	// IsProduction
	IsProduction = os.Getenv("GIN_MODE") == "release" || os.Getenv("KUU_PROD") == "true"
)
View Source
var (
	Logger        = logrus.New()
	DailyFileName = fmt.Sprintf("kuu-%s.log", time.Now().Format("2006-01-02"))
	DailyFile     *os.File
	LogDir        string
)
View Source
var (

	// CreateCallback
	CreateCallback = createCallback
	// BeforeQueryCallback
	BeforeQueryCallback = beforeQueryCallback
	// DeleteCallback
	DeleteCallback = deleteCallback
	// UpdateCallback
	UpdateCallback = updateCallback
	// AfterSaveCallback
	AfterSaveCallback = afterSaveCallback
	// QueryCallback
	QueryCallback = queryCallback
	// ValidateCallback
	ValidateCallback = validateCallback
)
View Source
var APIKeyRoute = RouteInfo{
	Name:   "令牌生成接口",
	Method: "POST",
	Path:   "/apikeys",
	IntlMessages: map[string]string{
		"apikeys_failed": "Create API Keys failed.",
	},
	HandlerFunc: func(c *Context) *STDReply {
		var body GenTokenDesc
		if err := c.ShouldBindJSON(&body); err != nil {
			return c.STDErr(err, "apikeys_failed")
		}
		body.Payload = c.SignInfo.Payload
		body.UID = c.SignInfo.UID
		body.IsAPIKey = true
		body.Type = AdminSignType
		secretData, err := GenToken(body)
		if err != nil {
			return c.STDErr(err, "apikeys_failed")
		}
		return c.STD(secretData.Token)
	},
}

APIKeyRoute

View Source
var ActiveAuthProcessor = DefaultAuthProcessor{}

ActiveAuthProcessor

View Source
var AuthRoute = RouteInfo{
	Name:   "操作权限鉴权接口",
	Method: "GET",
	Path:   "/auth",
	IntlMessages: map[string]string{
		"auth_failed": "Authentication failed",
	},
	HandlerFunc: func(c *Context) *STDReply {
		ps := c.Query("p")
		split := strings.Split(ps, ",")

		if len(split) == 0 {
			return c.STDErr(errors.New("param 'p' is required"), "auth_failed")
		}

		ret := make(map[string]bool)
		for _, s := range split {
			_, has := c.PrisDesc.PermissionMap[s]
			ret[s] = has
		}

		return c.STD(ret)
	},
}

AuthRoute

View Source
var CaptchaRoute = RouteInfo{
	Name:   "查询验证码",
	Path:   "/captcha",
	Method: "GET",
	HandlerFunc: func(c *Context) *STDReply {
		var (
			user  = c.Query("user")
			valid bool
		)
		if user != "" {
			times := GetCacheInt(getFailedTimesKey(user))
			valid = failedTimesValid(times)
		}
		if valid == false {
			return c.STD(null.BoolFrom(valid))
		}

		id, base64Str := GenerateCaptcha()
		c.SetCookie(CaptchaIDKey, id, ExpiresSeconds, "/", "", false, true)
		return c.STD(D{
			"id":        id,
			"base64Str": base64Str,
		})
	},
}

CaptchaRoute

View Source
var ChangePassword = RouteInfo{
	Name:   "修改密码",
	Method: "POST",
	Path:   "/changepasswd",
	IntlMessages: map[string]string{
		"parse_body_failed": "解析请求参数失败",
		"oldpasswd_error":   "旧密码错误",
		"newpasswd_error":   "新密码错误",
	},
	HandlerFunc: func(c *Context) *STDReply {
		var body = struct {
			OldPasswd string `binding:"required"`
			NewPasswd string `binding:"required"`
		}{}
		if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil {
			return c.STDErr(err, "parse_body_failed")
		}
		var user User
		DB().Model(&User{}).Where("id = ?", c.PrisDesc.UID).First(&user)
		if user.ID == 0 {
			return c.STDErr(errors.New("用户不存在"), "parse_body_failed")
		}
		body.OldPasswd = strings.ToLower(body.OldPasswd)
		if err := CompareHashAndPassword(user.Password, body.OldPasswd); err != nil {
			return c.STDErr(err, "oldpasswd_error")
		}
		passwd, err := GenerateFromPassword(strings.ToLower(body.NewPasswd))
		if err != nil {
			return c.STDErr(err, "newpasswd_error")
		}
		DB().Model(&User{}).
			Where("id = ?", c.PrisDesc.UID).
			Updates(map[string]any{
				"Password":               passwd,
				"LastChangePasswordTime": time.Now(),
			})
		return c.STDOK()
	},
}
View Source
var DataDictRoute = RouteInfo{
	Name:   "查询数据字典",
	Method: "GET",
	Path:   "/datadict",
	HandlerFunc: func(c *Context) *STDReply {
		modCode := c.Query("modCode")
		var buff strings.Builder
		buff.WriteString(fmt.Sprintf("# %s数据字典\n\n", C().GetString("name")))
		var modname string
		bookmap := map[bool]string{true: "是", false: "否"}
		m := DefaultCache.HGetAll(BuildKey("datadict"))
		var keys []string
		for k, _ := range m {
			keys = append(keys, k)
		}
		sort.Strings(keys)
		for _, key := range keys {
			item := m[key]
			var meta Metadata
			err := JSONParse(item, &meta)
			if err != nil {
				return c.STDErr(err)
			}
			if meta.ModCode == "" {
				continue
			}
			if modCode != "" && meta.ModCode != modCode {
				continue
			}
			if modname != meta.ModCode {
				modname = meta.ModCode
				buff.WriteString(fmt.Sprintf("## %s\n\n", meta.ModCode))
			}
			buff.WriteString(fmt.Sprintf("### %s_%s %s\n\n", meta.ModCode, meta.NativeName, meta.DisplayName))
			buff.WriteString("|字段名|字段类型|是否可空|是否主键|注释|\n")
			buff.WriteString("| :--- | :--- | :--- | :--- | :--- |\n")
			for _, field := range meta.Fields {
				IsBland := bookmap[field.IsBland]
				IsPrimaryKey := bookmap[field.IsPrimaryKey]
				line := fmt.Sprintf("| %s | %s | %s | %s | %s |\n", field.NativeName, field.DBType, IsBland, IsPrimaryKey, field.Name)
				buff.WriteString(line)
			}
			buff.WriteString("\n\n")
		}
		c.String(http.StatusOK, buff.String())
		return nil
	},
}

DataDictRoute

View Source
var DefaultCallback = &Callback{}

DefaultCallback

View Source
var DefaultCron = cron.New(cron.WithSeconds())

DefaultCron (set option 5 cron to convet 6 cron)

View Source
var EnumKey = BuildKey("EMUM")
View Source
var EnumRoute = RouteInfo{
	Name:   "查询枚举列表",
	Path:   "/enum",
	Method: "GET",
	HandlerFunc: func(c *Context) *STDReply {
		json := c.Query("json")
		name := c.Query("name")

		em := EnumMap()
		var list []*EnumDesc
		if name != "" {
			for _, name := range strings.Split(name, ",") {
				if v, ok := em[name]; ok && v != nil {
					list = append(list, v)
				}
			}
		} else {
			list = EnumList()
		}
		if json != "" {
			return c.STD(list)
		} else {
			var buffer bytes.Buffer
			for _, desc := range list {
				if desc.ClassName != "" {
					buffer.WriteString(fmt.Sprintf("%s(%s) {\n", desc.ClassCode, desc.ClassName))
				} else {
					buffer.WriteString(fmt.Sprintf("%s {\n", desc.ClassCode))
				}
				index := 0
				for value, label := range desc.Values {
					if len(label) < 20 {
						for i := 0; i < 20-len(label); i++ {
							label += " "
						}
					}
					buffer.WriteString(fmt.Sprintf("\t%s\t%v(%s)", label, value, reflect.ValueOf(value).Type().Kind().String()))
					if index != len(desc.Values)-1 {
						buffer.WriteString("\n")
					}
					index++
				}
				buffer.WriteString(fmt.Sprintf("\n}\n\n"))
			}
			c.String(http.StatusOK, buffer.String())
			return nil
		}
	},
}

EnumRoute

View Source
var GetUserWithRoles = func(uid uint) (*User, error) {
	// 查询用户档案
	var user User
	if err := DB().Where("id = ?", uid).Preload("RoleAssigns").First(&user).Error; err != nil {
		return &user, err
	}
	// 过滤有效的角色分配
	var roleIDs []uint
	for _, assign := range user.RoleAssigns {
		if assign.ExpireUnix <= 0 || time.Now().Before(time.Unix(assign.ExpireUnix, 0)) {
			roleIDs = append(roleIDs, assign.RoleID)
		}
	}
	// 查询角色档案
	var (
		roles   []Role
		roleMap = make(map[uint]Role)
	)
	if err := DB().Where("id in (?)", roleIDs).Preload("OperationPrivileges").Preload("DataPrivileges").Find(&roles).Error; err != nil {
		return &user, err
	}
	for _, role := range roles {
		roleMap[role.ID] = role
	}

	for index, assign := range user.RoleAssigns {
		role := roleMap[assign.RoleID]
		assign.Role = &role
		user.RoleAssigns[index] = assign
	}
	return &user, nil
}

GetUserWithRoles

View Source
var ImportRoute = RouteInfo{
	Name:   "统一导入路由",
	Method: "POST",
	Path:   "/import",
	IntlMessages: map[string]string{
		"import_failed": "Import failed",
		"import_empty":  "Import data is empty",
	},
	HandlerFunc: func(c *Context) *STDReply {

		file, _ := c.FormFile("file")
		if file == nil {
			return c.STDErr(errors.New("no 'file' key in form-data"), "import_failed")
		}
		channel := c.PostForm("channel")
		if channel == "" {
			return c.STDErr(errors.New("no 'channel' key in form-data"), "import_failed")
		}
		if importCallbackMap[channel] == nil {
			return c.STDErr(fmt.Errorf("no import callback registered for this channel: %s", channel), "import_failed")
		}
		var (
			sheetIndex int
			sheetName  string
		)
		sheetName = c.PostForm("sheet_name")
		if v := c.PostForm("sheet_idx"); v != "" {
			idx, err := strconv.Atoi(v)
			if err == nil {
				sheetIndex = idx
			}
		}
		rows, err := ParseExcelFromFileHeader(file, sheetIndex, sheetName)
		if err != nil {
			return c.STDErr(err, "import_failed")
		}
		if len(rows) == 0 {
			return c.STDErr(err, "import_empty")
		}

		args := importCallbackMap[channel]
		if args.Validator != nil {
			if std, err := args.Validator(c, rows); err != nil {
				return std
			}
		}

		record := ImportRecord{
			Channel: channel,
			Data:    JSONStringify(rows),
			Sync:    c.PostForm("sync") != "",
			Context: JSONStringify(&ImportContext{
				Token:      c.SignInfo.Token,
				SignType:   c.SignInfo.Type,
				Lang:       c.SignInfo.Lang,
				UID:        c.SignInfo.UID,
				SubDocID:   c.SignInfo.SubDocID,
				ActOrgID:   c.PrisDesc.ActOrgID,
				ActOrgCode: c.PrisDesc.ActOrgCode,
				ActOrgName: c.PrisDesc.ActOrgName,
			}),
		}
		if err := c.DB().Create(&record).Error; err != nil {
			return c.STDErr(err, "import_failed")
		}

		if record.Sync {
			if _, err := CallImportCallback(c, &record); err != nil {
				if _, ok := err.(*IntlError); ok {
					return c.STDErr(err)
				}
				return c.STDErr(err, "import_failed")
			}
		} else {
			go func() {
				_, _ = CallImportCallback(c, &record)
			}()
		}

		return c.STD(record.ImportSn)
	},
}

ImportRoute

View Source
var ImportTemplateRoute = RouteInfo{
	Name:   "导入模板下载",
	Method: "GET",
	Path:   "/import/template",
	IntlMessages: map[string]string{
		"import_template_failed": "Import template download failed",
	},
	HandlerFunc: func(c *Context) *STDReply {
		channel := c.Query("channel")
		format := strings.ToLower(c.DefaultQuery("format", "file"))
		if channel == "" {
			return c.STDErr(errors.New("no 'channel' key in query parameters"), "import_template_failed")
		}
		callback := importCallbackMap[channel]
		if callback == nil {
			return c.STDErr(fmt.Errorf("no import callback registered for this channel: %s", channel), "import_template_failed")
		}
		if callback.TemplateGenerator == nil {
			return c.STDErr(fmt.Errorf("no template generator registered for this channel: %s", channel), "import_template_failed")
		}
		fileName, headers := callback.TemplateGenerator(c)
		switch format {
		case "json":
			c.STD(headers)
		case "file":
			if !strings.HasSuffix(fileName, ".xlsx") {
				fileName = fmt.Sprintf("%s.xlsx", fileName)
			}
			fileName = url.QueryEscape(fileName)
			f := excelize.NewFile()
			if err := f.SetSheetRow("Sheet1", "A1", &headers); err != nil {
				return c.STDErr(err, "import_template_failed")
			}
			c.Header("Content-Transfer-Encoding", "binary")
			c.Header("Content-Disposition", "attachment; filename="+fileName)
			c.Header("Content-Type", "application/octet-stream")
			f.SetActiveSheet(1)
			if err := f.Write(c.Writer); err != nil {
				return c.STDErr(err, "import_template_failed")
			}
		}
		return c.STDOK()
	},
}

ImportTemplateRoute

View Source
var InjectCreateAuth = func(signType string, auth AuthProcessorDesc) (replace bool, err error) {
	return
}

InjectCreateAuth

View Source
var InjectReadableAuth = func(signType string, auth AuthProcessorDesc) (replace bool, err error) {
	return
}

InjectReadableAuth

View Source
var InjectWritableAuth = func(signType string, auth AuthProcessorDesc) (replace bool, err error) {
	return
}

InjectWritableAuth

View Source
var IntlLanguagesRoute = RouteInfo{
	Name:   "查询语言列表",
	Method: http.MethodGet,
	Path:   "/intl/languages",
	HandlerFunc: func(c *Context) *STDReply {
		list := intl.LanguageList()
		return c.STD(list)
	},
}
View Source
var IntlMessagesRoute = RouteInfo{
	Name:   "查询消息列表",
	Method: http.MethodGet,
	Path:   "/intl/messages",
	IntlMessages: map[string]string{
		"intl_messages_failed": "You must and only specify one language code, like 'langs=zh-Hans'",
	},
	HandlerFunc: func(c *Context) *STDReply {
		var query struct {
			LanguageCodes string `form:"langs"`
			Prefix        string `form:"prefix"`
			Suffix        string `form:"suffix"`
			Contains      string `form:"contains"`
			Description   string `form:"desc"`
			Keys          string `form:"keys"`
			Simple        string `form:"simple"`
		}
		_ = c.ShouldBindQuery(&query)
		var simple bool
		if v, err := strconv.ParseBool(query.Simple); err == nil {
			simple = v
		}
		if simple {
			if query.LanguageCodes == "" {
				query.LanguageCodes = c.Lang()
			}
			if strings.Contains(query.LanguageCodes, ",") {
				query.LanguageCodes = strings.Split(query.LanguageCodes, ",")[0]
			}
			query.LanguageCodes = intl.ConvertLanguageCode(query.LanguageCodes)
		}
		opts := IntlMessagesOptions{
			LanguageCodes: query.LanguageCodes,
			Prefix:        query.Prefix,
			Suffix:        query.Suffix,
			Contains:      query.Contains,
			Description:   query.Description,
			Keys:          query.Keys,
		}
		var ret interface{}
		if simple {
			ret = getIntlMessagesByLang(&opts)
		} else {
			ret = getIntlMessages(&opts)
		}
		return c.STD(ret)
	},
}
View Source
var IntlMessagesSaveRoute = RouteInfo{
	Name:   "修改/新增翻译键",
	Method: http.MethodPost,
	Path:   "/intl/messages/save",
	IntlMessages: map[string]string{
		"intl_messages_save_failed": "Save failed.",
	},
	HandlerFunc: func(c *Context) *STDReply {
		var messages map[string]map[string]string
		if err := c.ShouldBindJSON(&messages); err != nil {
			return c.STDErr(err, "intl_messages_save_failed")
		}
		if err := saveIntlMessages(messages, false); err != nil {
			return c.STDErr(err, "intl_messages_save_failed")
		}
		return c.STDOK()
	},
}
View Source
var IntlMessagesUploadRoute = RouteInfo{
	Name:   "批量上传翻译文件",
	Method: http.MethodPost,
	Path:   "/intl/messages/upload",
	IntlMessages: map[string]string{
		"intl_messages_upload_failed": "Upload failed.",
	},
	HandlerFunc: func(c *Context) *STDReply {
		updateMethod := c.DefaultPostForm("method", "incr")
		fh, err := c.FormFile("file")
		if err != nil {
			return c.STDErr(err, "intl_messages_upload_failed")
		}
		var (
			sheetIndex int
			sheetName  string
		)
		sheetName = c.PostForm("sheet_name")
		if v := c.PostForm("sheet_idx"); v != "" {
			idx, err := strconv.Atoi(v)
			if err == nil {
				sheetIndex = idx
			}
		}
		rows, err := ParseExcelFromFileHeader(fh, sheetIndex, sheetName)
		if err != nil {
			return c.STDErr(err, "intl_messages_upload_failed")
		}
		if len(rows) == 0 {
			return c.STDOK()
		}
		languages := intl.LanguageList()
		indexLangCodeMap := map[int]string{
			0: "key",
			1: "default",
			2: "en",
			3: "zh-Hans",
			4: "zh-Hant",
		}
		i := len(indexLangCodeMap)
		for _, item := range languages {
			if item.Code == "en" || item.Code == "zh-Hans" || item.Code == "zh-Hant" {
				continue
			}
			indexLangCodeMap[i] = item.Code
			i++
		}
		messages := make(map[string]map[string]string)
		for i := 1; i < len(rows); i++ {
			row := rows[i]
			key := strings.TrimSpace(row[0])
			if key == "" {
				continue
			}
			for j := 1; j < len(row); j++ {
				value := strings.TrimSpace(row[j])
				if value == "" {
					continue
				}
				lang := indexLangCodeMap[j]
				if messages[key] == nil {
					messages[key] = make(map[string]string)
				}
				messages[key][lang] = value
			}
		}
		if err := saveIntlMessages(messages, updateMethod == "overwrite"); err != nil {
			return c.STDErr(err, "intl_messages_upload_failed")
		}
		return c.STDOK()
	},
}
View Source
var JobRunRoute = RouteInfo{
	Name:   "触发定时任务立即运行接口",
	Method: http.MethodPost,
	Path:   "/job/run",
	HandlerFunc: func(c *Context) *STDReply {
		if err := RunJob(c.Query("code")); err != nil {
			return c.STDErr(err)
		}
		return c.STDOK()
	},
}

JobRunRoute

View Source
var LangSwitchRoute = RouteInfo{
	Name:   "切换用户语言环境",
	Method: "POST",
	Path:   "/lang/switch",
	IntlMessages: map[string]string{
		"lang_switch_failed": "Switching language failed",
	},
	HandlerFunc: func(c *Context) *STDReply {
		var body struct {
			Lang string
		}
		if err := c.ShouldBindJSON(&body); err != nil {
			return c.STDErr(err, "lang_switch_failed")
		}

		err := c.IgnoreAuth().DB().
			Model(&User{ID: c.SignInfo.UID}).
			Update(User{Lang: body.Lang}).Error

		if err != nil {
			return c.STDErr(err, "lang_switch_failed")
		}
		return c.STDOK()
	},
}

LangSwitchRoute

View Source
var LoginAsOutRoute = RouteInfo{
	Name:   "退出模拟登录(该接口仅限root调用)",
	Method: "DELETE",
	Path:   "/login_as",
	HandlerFunc: func(c *Context) *STDReply {
		c.SetCookie(TokenKey, c.SignInfo.Token, -1, "/", "", false, true)
		c.SetCookie(LangKey, "", -1, "/", "", false, true)
		return c.STDOK()
	},
}

LoginAsOutRoute

View Source
var LoginAsRoute = RouteInfo{
	Name:   "以用户身份登录(该接口仅限root调用)",
	Method: "POST",
	Path:   "/login_as",
	IntlMessages: map[string]string{
		"login_as_unauthorized": "Unauthorized operation",
		"login_as_failed":       "Login failed",
	},
	HandlerFunc: func(c *Context) *STDReply {
		var body struct {
			UID uint
		}

		if c.SignInfo.UID != RootUID() {
			return c.STDErr(fmt.Errorf("unauthorized operation: uid=%v", c.SignInfo.UID), "login_as_unauthorized")
		}

		if err := c.ShouldBindJSON(&body); err != nil {
			return c.STDErr(err, "login_as_failed")
		}

		var (
			secret SignSecret
			user   User
			db     = c.DB()
		)
		if err := db.Where(&SignSecret{UID: body.UID, Type: AdminSignType, Method: "LOGIN"}).Where(fmt.Sprintf("%s > ?", db.Dialect().Quote("exp")), time.Now().Unix()).Order("created_at desc").First(&secret).Error; err != nil {
			return c.STDErr(err, "login_as_failed")
		}
		if err := db.Where(fmt.Sprintf("%s = ?", db.Dialect().Quote("id")), secret.UID).First(&user).Error; err != nil {
			return c.STDErr(err, "login_as_failed")
		}
		c.SetCookie(LangKey, user.Lang, ExpiresSeconds, "/", "", false, true)
		c.SetCookie(TokenKey, secret.Token, ExpiresSeconds, "/", "", false, true)
		return c.STDOK()
	},
}

LoginAsRoute

View Source
var LoginAsUsersRoute = RouteInfo{
	Name:   "查询可模拟登录的用户列表(该接口仅限root调用)",
	Method: "GET",
	Path:   "/login_as/users",
	IntlMessages: map[string]string{
		"login_as_unauthorized": "Unauthorized operation",
		"login_as_failed":       "Login failed",
	},
	HandlerFunc: func(c *Context) *STDReply {
		if c.SignInfo.UID != RootUID() {
			return c.STDErr(fmt.Errorf("unauthorized operation: uid=%v", c.SignInfo.UID), "login_as_unauthorized")
		}

		var (
			secrets []SignSecret
			uids    []uint
			users   []User
			db      = c.DB()
		)
		if err := db.Model(&SignSecret{}).Where(&SignSecret{Type: AdminSignType, Method: "LOGIN"}).Where(fmt.Sprintf("%s > ?", db.Dialect().Quote("exp")), time.Now().Unix()).Find(&secrets).Error; err != nil {
			return c.STDErr(err, "login_as_failed")
		}
		secretMap := make(map[uint]SignSecret)
		for _, item := range secrets {
			uids = append(uids, item.UID)
			secretMap[item.UID] = item
		}
		if err := db.Model(&User{}).Where(fmt.Sprintf("%s IN (?)", db.Dialect().Quote("id")), uids).Find(&users).Error; err != nil {
			return c.STDErr(err, "login_as_failed")
		}
		type record struct {
			ID       uint
			Name     string
			Username string
			Exp      int64
		}
		var records []record
		for _, item := range users {
			records = append(records, record{
				ID:       item.ID,
				Name:     item.Name,
				Username: item.Username,
				Exp:      secretMap[item.ID].Exp,
			})
		}
		return c.STD(records)
	},
}

LoginAsUsersRoute

View Source
var LoginRoute = RouteInfo{
	Name:   "默认登录接口",
	Method: "POST",
	Path:   "/login",
	IntlMessages: map[string]string{
		"acc_login_failed": "Login failed",
	},
	HandlerFunc: func(c *Context) *STDReply {

		if loginHandler == nil {
			PANIC("login handler not configured")
		}
		resp := loginHandler(c)
		if resp.Error != nil {
			if resp.LocaleMessageID != "" {
				return c.STDErr(resp.Error, resp.LocaleMessageID, resp.LocaleMessageDefaultText, resp.LocaleMessageContextValues)
			}
			return c.STDErr(resp.Error, "acc_login_failed")
		}

		secretData, err := GenToken(GenTokenDesc{
			UID:      resp.UID,
			Username: resp.Username,
			Payload:  resp.Payload,
			Exp:      time.Now().Add(time.Second * time.Duration(ExpiresSeconds)).Unix(),
			Type:     AdminSignType,
		})
		if err != nil {
			return c.STDErr(err, "acc_login_failed")
		}

		c.Set("__kuu_sign_context__", &SignContext{
			Token:   secretData.Token,
			UID:     secretData.UID,
			Payload: resp.Payload,
			Secret:  secretData,
		})

		c.SetCookie(LangKey, resp.Lang, ExpiresSeconds, "/", "", false, true)
		c.SetCookie(TokenKey, secretData.Token, ExpiresSeconds, "/", "", false, true)

		c.SetCookie(CaptchaIDKey, "", -1, "/", "", false, true)
		DelCache(getFailedTimesKey(resp.Username))
		return c.STD(resp.Payload)
	},
}

LoginRoute

View Source
var LogoutRoute = RouteInfo{
	Name:   "默认登出接口",
	Method: "POST",
	Path:   "/logout",
	IntlMessages: map[string]string{
		"acc_logout_failed": "Logout failed",
	},
	HandlerFunc: func(c *Context) *STDReply {
		if err := Logout(c, c.DB()); err != nil {
			return c.STDErr(err, "acc_logout_failed")
		}
		return c.STDOK()
	},
}

LogoutRoute

View Source
var MessagesLatestRoute = RouteInfo{
	Name:   "查询当前用户最新消息",
	Method: http.MethodGet,
	Path:   "/messages/latest",
	IntlMessages: map[string]string{
		"messages_latest_failed": "Get latest messages failed.",
	},
	HandlerFunc: func(c *Context) *STDReply {
		c.IgnoreAuth()
		defer c.IgnoreAuth(true)

		var query struct {
			Limit        string `form:"limit"`
			RecipientIDs string `form:"recipient_ids"`
		}
		if err := c.ShouldBindQuery(&query); err != nil {
			return c.STDErr(err, "messages_latest_failed")
		}
		var (
			limit        int
			recipientIDs []uint
		)
		if s := c.DefaultQuery("limit", "10"); s != "" {
			if v, err := strconv.Atoi(s); err == nil {
				limit = v
			}
		}
		if s := c.Query("recipient_ids"); s != "" {
			ss := strings.Split(s, ",")
			for _, item := range ss {
				item = strings.TrimSpace(item)
				if item == "" {
					continue
				}
				if v, err := strconv.Atoi(item); err == nil {
					recipientIDs = append(recipientIDs, uint(v))
				}
			}
		}
		type replyItem struct {
			Messages    Messages
			UnreadCount int
		}
		var reply struct {
			replyItem
			RecipientMap map[uint]replyItem `json:",omitempty"`
		}
		if len(recipientIDs) > 0 {
			reply.RecipientMap = make(map[uint]replyItem)
			for _, itemId := range recipientIDs {
				baseMessageDB := c.DB().Model(&Message{}).Where("sender_id = ? OR recipient_user_ids LIKE ?", itemId, "%"+fmt.Sprintf("%d", itemId)+"%")
				messsagesDB := GetMessageCommonDB(baseMessageDB, c.SignInfo.UID, c.PrisDesc.ReadableOrgIDs, c.PrisDesc.RolesCode, 1, limit)
				if _, v, err := c.ParseCond(&Message{}, messsagesDB); err != nil {
					return c.STDErr(err, "messages_latest_failed")
				} else {
					messsagesDB = v
				}
				var messages Messages
				if err := messsagesDB.Preload("Attachments").Find(&messages).Error; err != nil {
					return c.STDErr(err, "messages_latest_failed")
				}
				sort.Sort(messages)
				item := replyItem{Messages: messages}
				count, err := FindUnreadMessagesCount(baseMessageDB, c.SignInfo.UID, c.PrisDesc.ReadableOrgIDs, c.PrisDesc.RolesCode)
				if err != nil {
					return c.STDErr(err, "messages_latest_failed")
				}
				item.UnreadCount = count
				reply.RecipientMap[itemId] = item
			}
		} else {
			messsagesDB := GetMessageCommonDB(c.DB().Model(&Message{}), c.SignInfo.UID, c.PrisDesc.ReadableOrgIDs, c.PrisDesc.RolesCode, 1, limit)
			if _, v, err := c.ParseCond(&Message{}, messsagesDB); err != nil {
				return c.STDErr(err, "messages_latest_failed")
			} else {
				messsagesDB = v
			}
			var messages Messages
			if err := messsagesDB.Preload("Attachments").Find(&messages).Error; err != nil {
				return c.STDErr(err, "messages_latest_failed")
			}
			sort.Sort(messages)
			reply.Messages = messages
		}
		count, err := FindUnreadMessagesCount(c.DB().Model(&Message{}), c.SignInfo.UID, c.PrisDesc.ReadableOrgIDs, c.PrisDesc.RolesCode)
		if err != nil {
			return c.STDErr(err, "messages_latest_failed")
		}
		reply.UnreadCount = count
		return c.STD(&reply)
	},
}
View Source
var MessagesReadRoute = RouteInfo{
	Name:   "阅读消息",
	Method: http.MethodPost,
	Path:   "/messages/read",
	IntlMessages: map[string]string{
		"messages_read_failed": "Update message status failed.",
	},
	HandlerFunc: func(c *Context) *STDReply {
		c.IgnoreAuth()
		defer c.IgnoreAuth(true)

		var body struct {
			MessageIDs   []uint
			RecipientIDs []uint
			All          bool
		}
		if err := c.ShouldBindJSON(&body); err != nil {
			return c.STDErr(err, "messages_read_failed")
		}
		if !body.All && len(body.MessageIDs) == 0 && len(body.RecipientIDs) == 0 {
			return c.STDOK()
		}
		err := c.WithTransaction(func(tx *gorm.DB) error {
			messageDB := tx.Model(&Message{})
			if !body.All {
				if len(body.MessageIDs) > 0 {
					messageDB = messageDB.Where(fmt.Sprintf("%s IN (?)", messageDB.Dialect().Quote("id")), body.MessageIDs)
				}
				if len(body.RecipientIDs) > 0 {
					var (
						sqls  []string
						attrs []interface{}
					)
					for _, itemId := range body.RecipientIDs {
						sqls = append(sqls, "(sender_id = ? OR recipient_user_ids LIKE ?)")
						attrs = append(attrs, itemId, "%"+fmt.Sprintf("%d", itemId)+"%")
					}
					messageDB = messageDB.Where(strings.Join(sqls, " OR "), attrs...)
				}
			}
			unreadMessageIDs, err := FindUnreadMessageIDs(messageDB, c.SignInfo.UID, c.PrisDesc.ReadableOrgIDs, c.PrisDesc.RolesCode, 0, 0)
			if err != nil {
				return err
			}
			for _, item := range unreadMessageIDs {
				if err := tx.Model(&MessageReceipt{}).Create(&MessageReceipt{
					MessageID:         item,
					RecipientID:       c.SignInfo.UID,
					RecipientUsername: c.SignInfo.Username,
					RecipientSourceIP: c.ClientIP(),
					ReadAt:            null.TimeFrom(time.Now()),
				}).Error; err != nil {
					return err
				}
			}
			return tx.Error
		})
		if err != nil {
			return c.STDErr(err, "messages_read_failed")
		}
		return c.STDOK()
	},
}
View Source
var MetaRoute = RouteInfo{
	Name:   "查询元数据列表",
	Method: "GET",
	Path:   "/meta",
	HandlerFunc: func(c *Context) *STDReply {
		json := c.Query("json")
		name := c.Query("name")
		mod := c.Query("mod")

		var list []*Metadata
		if name != "" {
			for _, name := range strings.Split(name, ",") {
				if v, ok := metadataMap[name]; ok && v != nil {
					list = append(list, v)
				}
			}
		} else if mod != "" {
			for _, item := range strings.Split(mod, ",") {
				for _, meta := range metadataList {
					if meta.ModCode == item {
						list = append(list, meta)
					}
				}
			}
		} else {
			list = metadataList
		}
		if json != "" {
			return c.STD(list)
		} else {
			var (
				hashKey = fmt.Sprintf("meta_%s_%s", name, mod)
				result  string
			)
			if v, ok := valueCacheMap.Load(hashKey); ok {
				result = v.(string)
			} else {
				var buffer bytes.Buffer
				for _, m := range list {
					if len(m.Fields) > 0 {
						if m.DisplayName != "" {
							buffer.WriteString(fmt.Sprintf("%s(%s) {\n", m.Name, m.DisplayName))
						} else {
							buffer.WriteString(fmt.Sprintf("%s {\n", m.Name))
						}
						for index, field := range m.Fields {
							if field.Enum != "" {
								buffer.WriteString(fmt.Sprintf("\t%s %s ENUM(%s)", field.Code, field.Name, field.Enum))
							} else {
								buffer.WriteString(fmt.Sprintf("\t%s %s %s", field.Code, field.Name, field.Type))
							}

							if index != len(m.Fields)-1 {
								buffer.WriteString("\n")
							}
						}
						buffer.WriteString(fmt.Sprintf("\n}\n\n"))
					}
				}
				result = buffer.String()
				valueCacheMap.Store(hashKey, result)
			}
			c.String(http.StatusOK, result)
			return nil
		}
	},
}

MetaRoute

View Source
var ModelDocsRoute = RouteInfo{
	Name:   "查询默认接口文档",
	Method: "GET",
	Path:   "/model/docs",
	IntlMessages: map[string]string{
		"model_docs_failed": "Model document query failed",
	},
	HandlerFunc: func(c *Context) *STDReply {
		var (
			hashKeyYAML = "model_docs_yaml"
			hashKeyJSON = "model_docs_json"
		)

		json := c.Query("json") != ""

		if json {
			if v, ok := valueCacheMap.Load(hashKeyJSON); ok {
				c.String(http.StatusOK, v.(string))
				return nil
			}
		} else {
			if v, ok := valueCacheMap.Load(hashKeyYAML); ok {
				c.String(http.StatusOK, v.(string))
				return nil
			}
		}
		// 重新生成
		var validMeta []*Metadata
		for _, m := range metadataList {
			if m == nil || m.RestDesc == nil || !m.RestDesc.IsValid() || len(m.Fields) == 0 {
				continue
			}
			validMeta = append(validMeta, m)
		}

		name := GetAppName()
		doc := Doc{
			Openapi: "3.0.1",
			Info: DocInfo{
				Title: fmt.Sprintf("%s 模型默认接口文档", name),
				Description: "调用说明:\n" +
					"1. 本文档仅包含数据模型默认开放的增删改查RESTful接口\n" +
					"1. 接口请求/响应的Content-Type默认为application/json,UTF-8编码\n" +
					"1. 如未额外说明,接口响应格式默认为以下JSON格式:\n" +
					"\t- `code` - **业务状态码**,0表成功,非0表失败(错误码默认为-1,令牌失效为555),该值一定存在,请按照该值判断业务操作是否成功,`integer`\n" +
					"\t- `msg` - **提示信息**,表正常或异常情况下的提示信息,有值才存在,`string`\n" +
					"\t- `data` - **数据部分**,正常时返回请求数据,异常时返回错误详情,有值才存在,`类型视具体接口而定`\n" +
					"1. 日期格式为`2019-06-04T02:42:01.472Z`,js代码:`new Date().toISOString()`\n" +
					"1. 用户密码等信息统一为MD5加密后的32位小写字符串,npm推荐使用blueimp-md5" +
					"",
				Version: "1.0.0",
				Contact: DocInfoContact{
					Email: "yinfxs@dexdev.me",
				},
			},
			Servers: []DocServer{
				{Url: fmt.Sprintf("%s%s", c.Origin(), C().GetString("prefix")), Description: "默认服务器"},
			},
			Tags: func() (tags []DocTag) {
				tags = []DocTag{{Name: "辅助接口"}}
				for _, m := range validMeta {
					tags = append(tags, DocTag{
						Name:        m.Name,
						Description: m.DisplayName,
					})
				}
				return
			}(),
			Paths: func() (paths map[string]DocPathItems) {
				paths = map[string]DocPathItems{
					"/meta": {
						"get": {
							Tags:        []string{"辅助接口"},
							Summary:     "查询模型列表",
							OperationID: "meta",
							Responses: map[int]DocPathResponse{
								200: {
									Description: "查询模型列表成功",
									Content: map[string]DocPathContentItem{
										"text/plain": {
											Schema: DocPathSchema{Type: "string"},
										},
									},
								},
							},
						},
					},
					"/enum": {
						"get": {
							Tags:        []string{"辅助接口"},
							Summary:     "查询枚举列表",
							OperationID: "enum",
							Responses: map[int]DocPathResponse{
								200: {
									Description: "查询枚举列表成功",
									Content: map[string]DocPathContentItem{
										"text/plain": {
											Schema: DocPathSchema{Type: "string"},
										},
									},
								},
							},
						},
					},
					"/upload": {
						"post": {
							Tags:        []string{"辅助接口"},
							Summary:     "上传文件",
							OperationID: "upload",
							RequestBody: DocPathRequestBody{
								Content: map[string]DocPathContentItem{
									"multipart/form-data": {
										Schema: DocPathSchema{
											Type: "object",
											Properties: map[string]DocPathSchema{
												"file": {
													Type:        "string",
													Format:      "binary",
													Description: "文件",
												},
											},
										},
									},
								},
							},
							Responses: map[int]DocPathResponse{
								200: {
									Description: "上传成功",
									Content: map[string]DocPathContentItem{
										"application/json": {
											Schema: DocPathSchema{Type: "string"},
										},
									},
								},
							},
							Security: []DocPathItemSecurity{
								map[string][]string{
									"api_key": {},
								},
							},
						},
					},
					"/whitelist": {
						"get": {
							Tags:        []string{"辅助接口"},
							Summary:     "接口白名单",
							Description: "接口白名单是指`不需要任何令牌`,可直接访问的接口,请前往在线链接查看最新列表",
							OperationID: "whitelist",
							Responses: map[int]DocPathResponse{
								200: {
									Description: "查询接口白名单成功",
									Content: map[string]DocPathContentItem{
										"text/plain": {
											Schema: DocPathSchema{Type: "string"},
										},
									},
								},
							},
						},
					},
				}
				for _, m := range validMeta {
					key := strings.ToLower(path.Join(GetModPrefix(m.ModCode), fmt.Sprintf("/%s", m.Name)))
					items := make(DocPathItems)
					displayName := m.DisplayName
					if displayName == "" {
						displayName = m.Name
					}

					if m.RestDesc.Create {
						items["post"] = DocPathItem{
							Tags:        []string{m.Name},
							Summary:     fmt.Sprintf("新增%s", displayName),
							Description: "注意:\n1. 如需批量新增,请传递对象数组\n1. 当你请求体为对象格式时,返回数据也为对象格式\n1. 当你请求体为对象数组时,返回数据也为对象数组",
							OperationID: fmt.Sprintf("create%s", m.Name),
							RequestBody: DocPathRequestBody{
								Required:    true,
								Description: fmt.Sprintf("%s对象", displayName),
								Content: map[string]DocPathContentItem{
									"application/json": {
										Schema: DocPathSchema{
											Ref: fmt.Sprintf("#/components/schemas/%s", m.Name),
										},
									},
								},
							},
							Responses: map[int]DocPathResponse{
								200: {
									Description: fmt.Sprintf("新增%s成功", displayName),
									Content: map[string]DocPathContentItem{
										"application/json": {
											Schema: DocPathSchema{
												Ref: fmt.Sprintf("#/components/schemas/%s", m.Name),
											},
										},
									},
								},
							},
							Security: []DocPathItemSecurity{
								map[string][]string{
									"api_key": {},
								},
							},
						}
					}

					if m.RestDesc.Delete {
						items["delete"] = DocPathItem{
							Tags:        []string{m.Name},
							Summary:     fmt.Sprintf("删除%s", displayName),
							Description: "注意:\n如需批量删除,请指定multi=true",
							OperationID: fmt.Sprintf("delete%s", m.Name),
							Parameters: []DocPathParameter{
								{
									Name:        "cond",
									In:          "query",
									Required:    true,
									Description: "删除条件,JSON格式的字符串",
									Schema: DocPathSchema{
										Type: "string",
									},
								},
								{
									Name:        "multi",
									In:          "query",
									Description: "是否批量删除",
									Schema: DocPathSchema{
										Type: "boolean",
									},
								},
							},
							Responses: map[int]DocPathResponse{
								200: {
									Description: fmt.Sprintf("删除%s成功", displayName),
									Content: map[string]DocPathContentItem{
										"application/json": {
											Schema: DocPathSchema{
												Ref: fmt.Sprintf("#/components/schemas/%s", m.Name),
											},
										},
									},
								},
							},
							Security: []DocPathItemSecurity{
								map[string][]string{
									"api_key": {},
								},
							},
						}
					}

					if m.RestDesc.Update {
						items["put"] = DocPathItem{
							Tags:        []string{m.Name},
							Summary:     fmt.Sprintf("修改%s", displayName),
							Description: "注意:\n如需批量修改,请指定multi=true",
							OperationID: fmt.Sprintf("update%s", m.Name),
							RequestBody: DocPathRequestBody{
								Required:    true,
								Description: fmt.Sprintf("%s对象", displayName),
								Content: map[string]DocPathContentItem{
									"application/json": {
										Schema: DocPathSchema{
											Type: "object",
											Properties: map[string]DocPathSchema{
												"cond": {
													Ref:      fmt.Sprintf("#/components/schemas/%s", m.Name),
													Required: true,
												},
												"doc": {
													Ref:      fmt.Sprintf("#/components/schemas/%s", m.Name),
													Required: true,
												},
												"multi": {
													Type: "boolean",
												},
											},
										},
									},
								},
							},
							Responses: map[int]DocPathResponse{
								200: {
									Description: fmt.Sprintf("修改%s成功", displayName),
									Content: map[string]DocPathContentItem{
										"application/json": {
											Schema: DocPathSchema{
												Ref: fmt.Sprintf("#/components/schemas/%s", m.Name),
											},
										},
									},
								},
							},
							Security: []DocPathItemSecurity{
								map[string][]string{
									"api_key": {},
								},
							},
						}
					}

					if m.RestDesc.Query {
						items["get"] = DocPathItem{
							Tags:        []string{m.Name},
							Summary:     fmt.Sprintf("查询%s", displayName),
							OperationID: fmt.Sprintf("query%s", m.Name),
							Parameters: []DocPathParameter{
								{
									Name:        "range",
									In:          "query",
									Description: "查询数据范围,分页(PAGE)或全量(ALL)",
									Schema: DocPathSchema{
										Type: "string",
										Enum: []interface{}{
											"PAGE",
											"ALL",
										},
										Default: "PAGE",
									},
								},
								{
									Name:        "cond",
									In:          "query",
									Description: fmt.Sprintf("查询条件,%s对象的JSON字符串", displayName),
									Schema: DocPathSchema{
										Type: "string",
									},
								},
								{
									Name:        "sort",
									In:          "query",
									Description: "排序字段,多字段排序以英文逗号分隔,逆序以负号开头",
									Schema: DocPathSchema{
										Type: "string",
									},
								},
								{
									Name:        "project",
									In:          "query",
									Description: "查询字段,注意字段依然返回,只是不查询",
									Schema: DocPathSchema{
										Type: "string",
									},
								},
								{
									Name:        "page",
									In:          "query",
									Description: "当前页码(仅PAGE模式有效)",
									Schema: DocPathSchema{
										Type:    "integer",
										Default: 1,
									},
								},
								{
									Name:        "size",
									In:          "query",
									Description: "每页条数(仅PAGE模式有效)",
									Schema: DocPathSchema{
										Type:    "integer",
										Default: 30,
									},
								},
							},
							Responses: map[int]DocPathResponse{
								200: {
									Description: fmt.Sprintf("查询%s成功", displayName),
									Content: map[string]DocPathContentItem{
										"application/json": {
											Schema: DocPathSchema{
												Type: "object",
												Properties: map[string]DocPathSchema{
													"list": {
														Type: "array",
														Items: &DocPathSchema{
															Ref: fmt.Sprintf("#/components/schemas/%s", m.Name),
														},
													},
													"totalrecords": {
														Type:        "integer",
														Description: "当前查询条件下的总记录数",
													},
													"totalpages": {
														Type:        "integer",
														Description: "当前查询条件下的总页数(仅PAGE模式存在)",
													},
												},
											},
										},
									},
								},
							},
							Security: []DocPathItemSecurity{
								map[string][]string{
									"api_key": {},
								},
							},
						}
					}
					if len(items) > 0 {
						paths[key] = items
					}
				}
				return
			}(),
			Components: DocComponent{
				Schemas: func() (schemas map[string]DocComponentSchema) {
					schemas = make(map[string]DocComponentSchema)
					em := EnumMap()
					for _, m := range validMeta {
						props := make(map[string]DocSchemaProperty)
						for _, f := range m.Fields {
							prop := DocSchemaProperty{}
							if f.Name != "" {
								prop.Title = f.Name
							}
							if f.IsRef {
								if f.IsArray {
									prop.Type = "array"
									prop.Items = &DocSchemaProperty{
										Ref: fmt.Sprintf("#/components/schemas/%s", f.Type),
									}
								} else {
									prop.Ref = fmt.Sprintf("#/components/schemas/%s", f.Type)
								}
							} else {
								prop.Type = f.Type
							}
							if f.Enum != "" && em[f.Enum] != nil {
								for value, _ := range em[f.Enum].Values {
									prop.Enum = append(prop.Enum, value)
								}
							}
							props[f.Code] = prop
						}
						schemas[m.Name] = DocComponentSchema{
							Type:       "object",
							Properties: props,
						}
					}
					return
				}(),
				SecuritySchemes: map[string]DocSecurityScheme{
					"api_key": {
						Type: "apiKey",
						Name: "api_key",
						In:   "header",
					},
				},
			},
		}
		yml := doc.Marshal()
		if json {
			data, err := yaml.YAMLToJSON([]byte(yml))
			if err != nil {
				return c.STDErr(err, "model_docs_failed")
			}
			json := string(data)
			valueCacheMap.Store(hashKeyJSON, json)
			c.String(http.StatusOK, json)
		} else {
			valueCacheMap.Store(hashKeyYAML, yml)
			c.String(http.StatusOK, yml)
		}
		return nil
	},
}

ModelDocsRoute

View Source
var ModelWSRoute = RouteInfo{
	Name:   "模型变更通知WebSocket接口",
	Method: "GET",
	Path:   "/model/ws",
	HandlerFunc: func(c *Context) *STDReply {
		conn, err := modelUpgrader.Upgrade(c.Writer, c.Request, nil)
		if err != nil {
			ERROR("websocket.upgrade: %v", err)
			return nil
		}
		defer func() {
			if _, ok := modelWSConns.Load(conn); ok {
				modelWSConns.Delete(conn)
			}
			conn.Close()
			INFO("websocket.close: %p", conn)
		}()
		modelWSConns.Store(conn, conn)
		INFO("websocket.connect: %p", conn)
		for {
			mt, message, err := conn.ReadMessage()
			if err != nil {
				ERROR(err)
				break
			}
			INFO("websocket.recv: %s", message)
			err = conn.WriteMessage(mt, message)
			if err != nil {
				ERROR(err)
				break
			}
		}
		return nil
	},
}

ModelWSRoute

View Source
var OrgLoginableRoute = RouteInfo{
	Name:   "查询可登录组织",
	Method: "GET",
	Path:   "/org/loginable",
	IntlMessages: map[string]string{
		"org_query_failed": "Query organization failed",
	},
	HandlerFunc: func(c *Context) *STDReply {
		c.IgnoreAuth()
		data, err := GetLoginableOrgs(c, c.SignInfo.UID)
		if err != nil {
			return c.STDErr(err, "org_query_failed")
		}
		return c.STD(data)
	},
}

OrgLoginableRoute

View Source
var OrgSwitchRoute = RouteInfo{
	Name:   "切换当前登录组织",
	Method: "POST",
	Path:   "/org/switch",
	IntlMessages: map[string]string{
		"org_switch_failed": "Switching organization failed",
	},
	HandlerFunc: func(c *Context) *STDReply {
		var body struct {
			ActOrgID uint
		}
		if err := c.ShouldBindJSON(&body); err != nil {
			return c.STDErr(err, "org_switch_failed")
		}

		err := c.IgnoreAuth().DB().
			Model(&User{ID: c.SignInfo.UID}).
			Update(User{ActOrgID: body.ActOrgID}).Error

		if err != nil {
			return c.STDErr(err, "org_switch_failed")
		}
		return c.STDOK()
	},
}

OrgSwitchRoute

View Source
var Recovery gin.HandlerFunc

Recovery defined kuu.Engine recovery from panic Rewrite `Recovery` if you need Tag: 在rest不处理error,除非业务需求(如事物),直接抛出来

View Source
var RoleUserAssigns = RouteInfo{
	Name:   "查询角色关联用户",
	Method: "GET",
	Path:   "/role/user_assigns/:roleid",
	IntlMessages: map[string]string{
		"role_users_assigns_failed": "Role assigns users query failed",
	},
	HandlerFunc: func(c *Context) *STDReply {
		raw := c.Param("roleid")
		if raw == "" {
			return c.STDErr(errors.New("roleid is required"), "role_users_assigns_failed")
		}
		id := ParseID(raw)
		var userids []uint
		DB().Model(&RoleAssign{}).Where("role_id = ?", id).Pluck("user_id", &userids)
		var users []User
		DB().Model(&User{}).Where("id in (?)", userids).Find(&users)
		var result []map[string]any
		for _, user := range users {
			item := map[string]any{
				"ID":        user.ID,
				"Name":      user.Name,
				"Username":  user.Username,
				"Mobile":    user.Mobile,
				"Disable":   user.Disable,
				"CreatedAt": user.CreatedAt,
			}
			if os.Getenv("HIDDEN_MOBILE") == "true" {
				item["Mobile"] = hideMobile(user.Mobile)
			}
			result = append(result, item)
		}
		return c.STD(result)
	},
}

RoleUserAssigns

View Source
var RoleUserAssignsList = RouteInfo{
	Name:   "查询角色关联用户",
	Method: "GET",
	Path:   "/role/user_assigns_list",
	IntlMessages: map[string]string{
		"role_users_assigns_failed": "Role assigns users query failed",
	},
	HandlerFunc: func(c *Context) *STDReply {
		var roles []Role
		DB().Model(&Role{}).Find(&roles)
		return c.STDOK()
	},
}

RoleUserAssigns

View Source
var SetPayloadAttrs = func(payload jwt.MapClaims, user *User) jwt.MapClaims {
	return payload
}

SetPayloadAttrs

View Source
var TriggerRepeatEvent = RouteInfo{
	Name:   "触发执行可重复事件",
	Method: "GET",
	Path:   "/TriggerRepeatEvent",
	HandlerFunc: func(c *Context) *STDReply {
		c.IgnoreAuth()
		name := c.Query("name")
		q := DB().Model(&RepeatEvent{}).Where("status = 0 and next_time <= ?", time.Now())
		if name != "" {
			q = q.Where("name = ?", name)
		}
		var repeatEvents []RepeatEvent
		q.Find(&repeatEvents)
		for _, event := range repeatEvents {
			context := &REContext{Event: event}
			JSONParse(context.Event.Data, &context.Data)
			intervals := strings.Split(event.RetryInterval, "/")
			context.Max = len(intervals)
			context.Current = event.RetryCount + 1
			if processer, has := repeatEventProcesserMap[event.Name]; has {
				go processRepeatEvent(processer, context)
			}
		}
		return c.STDOK()
	},
}
View Source
var UploadRoute = RouteInfo{
	Name:   "默认文件上传接口",
	Method: "POST",
	Path:   "/upload",
	IntlMessages: map[string]string{
		"upload_failed": "Upload file failed",
	},
	HandlerFunc: func(c *Context) *STDReply {
		var (
			save2db = true
		)
		if v, ok := c.GetPostForm("save2db"); ok {
			if b, err := strconv.ParseBool(v); err == nil {
				save2db = b
			}
		}
		extra, err := getFileExtraData(c)
		if err != nil {
			return c.STDErr(err, "upload_failed")
		}
		fh, err := c.FormFile("file")
		if err != nil {
			return c.STDErr(err, "upload_failed")
		}
		file, err := SaveUploadedFile(fh, save2db, extra)
		if err != nil {
			return c.STDErr(err, "upload_failed")
		}
		return c.STD(file)
	},
}

UploadRoute

View Source
var UserMenusRoute = RouteInfo{
	Name:   "查询用户菜单",
	Method: "GET",
	Path:   "/user/menus",
	IntlMessages: map[string]string{
		"user_menus_failed": "User menus query failed",
	},
	HandlerFunc: func(c *Context) *STDReply {
		var menus MenuList

		if err := c.DB().Find(&menus).Error; err != nil {
			return c.STDErr(err, "user_menus_failed")
		}
		// 补全父级菜单
		var total MenuList
		if err := c.IgnoreAuth().DB().Find(&total).Error; err != nil {
			return c.STDErr(err, "user_menus_failed")
		}

		if c.PrisDesc.HasPermission("sys_menu") {
			sort.Sort(total)
			return c.STD(total)
		}
		var (
			codeMap   = make(map[string]Menu)
			existsMap = make(map[uint]bool)
			finded    = make(map[uint]bool)
		)
		for _, item := range total {
			codeMap[item.Code] = item
		}
		for _, item := range menus {
			existsMap[item.ID] = true
		}
		var fall func(result MenuList) MenuList
		fall = func(result MenuList) MenuList {
			recall := false
			for _, item := range result {
				if !finded[item.ID] {
					pitem := codeMap[item.ParentCode.String]
					if item.ParentCode.String != "" && pitem.ID != 0 && !existsMap[pitem.ID] {
						result = append(result, pitem)
						recall = true
						existsMap[pitem.ID] = true
					}
					finded[item.ID] = true
				}
			}
			if recall {
				return fall(result)
			}
			return result
		}
		menus = fall(menus)
		if strings.ToLower(c.DefaultQuery("default", "true")) == "false" {
			var filtered MenuList
			for _, item := range menus {
				if item.Code != "default" {
					filtered = append(filtered, item)
				}
			}
			menus = filtered
		}
		sort.Sort(menus)
		return c.STD(menus)
	},
}

UserMenusRoute

View Source
var UserRoleAssigns = RouteInfo{
	Name:   "查询用户已分配角色",
	Method: "GET",
	Path:   "/user/role_assigns/:uid",
	IntlMessages: map[string]string{
		"role_assigns_failed": "User roles query failed",
	},
	HandlerFunc: func(c *Context) *STDReply {
		raw := c.Param("uid")
		if raw == "" {
			return c.STDErr(errors.New("UID is required"), "role_assigns_failed")
		}
		uid := ParseID(raw)
		user, err := GetUserWithRoles(uid)
		if err != nil {
			return c.STDErr(err, "role_assigns_failed")
		}
		return c.STD(user.RoleAssigns)
	},
}

UserRoleAssigns

View Source
var ValidRoute = RouteInfo{
	Name:   "令牌有效性验证接口",
	Method: "POST",
	Path:   "/valid",
	IntlMessages: map[string]string{
		"acc_invalid_token": "Invalid token.",
	},
	HandlerFunc: func(c *Context) *STDReply {
		// 查询用户
		var user User
		if err := c.IgnoreAuth().DB().Select("lang, act_org_id").First(&user, "id = ?", c.SignInfo.UID).Error; err != nil {
			return c.STDErr(err, "acc_invalid_token")
		}

		if user.Lang == "" {
			user.Lang = c.Lang()
		}
		c.SetCookie(LangKey, user.Lang, ExpiresSeconds, "/", "", false, true)
		c.SignInfo.Payload["Lang"] = user.Lang
		c.SignInfo.Payload["ActOrgID"] = c.PrisDesc.ActOrgID
		c.SignInfo.Payload["ActOrgCode"] = c.PrisDesc.ActOrgCode
		c.SignInfo.Payload["ActOrgName"] = c.PrisDesc.ActOrgName
		c.SignInfo.Payload[TokenKey] = c.SignInfo.Token
		if c.PrisDesc != nil {
			c.SignInfo.Payload["Permissions"] = c.PrisDesc.Permissions
			c.SignInfo.Payload["RolesCode"] = c.PrisDesc.RolesCode
		}
		return c.STD(c.SignInfo.Payload)
	},
}

ValidRoute

View Source
var WhitelistRoute = RouteInfo{
	Name:   "查询白名单列表",
	Method: "GET",
	Path:   "/whitelist",
	HandlerFunc: func(c *Context) *STDReply {
		var list []string
		for _, item := range Whitelist {
			if v, ok := item.(string); ok {
				list = append(list, v)
			} else if v, ok := item.(*regexp.Regexp); ok {
				list = append(list, v.String())
			}
		}
		return c.STD(list)
	},
}

WhitelistRoute

Functions

func AESCBCDecrypt

func AESCBCDecrypt(encryptData, key []byte) ([]byte, error)

AESCBCDecrypt

func AESCBCEncrypt

func AESCBCEncrypt(rawData, key []byte) ([]byte, error)

AESCBCEncrypt 填充秘钥key的16位(24、32分别对应AES-128、AES-192或AES-256)

func ASCIISort

func ASCIISort(data interface{}, includeEmpty bool, omitKeys ...[]string) (sortedKeys []string)

ASCIISort

func AddDefaultIntlMessage

func AddDefaultIntlMessage(messages map[string]string)

func AddExtraSpaceIfExist

func AddExtraSpaceIfExist(str string) string

AddExtraSpaceIfExist

func AddJob

func AddJob(spec string, name string, cmd func(c *JobContext)) (cron.EntryID, error)

AddJob

func AddJobEntry

func AddJobEntry(j *Job) error

AddJobEntry

func AddPresetIntlMessage

func AddPresetIntlMessage(messages map[string]map[string]string)

func AddWhitelist

func AddWhitelist(rules ...interface{})

AddWhitelist support string and *regexp.Regexp.

func AutoMigrate

func AutoMigrate(values ...interface{}) *gorm.DB

AutoMigrate

func Base64Decode

func Base64Decode(p string) (v string)

Base64Decode Base64解码

func Base64Encode

func Base64Encode(p string) (v string)

Base64Encode Base64编码

func Base64URLDecode

func Base64URLDecode(p string) (v string)

Base64URLDecode Base64 URL解码

func Base64URLEncode

func Base64URLEncode(p string) (v string)

Base64URLEncode Base64 URL编码

func BatchInsert

func BatchInsert(tx *gorm.DB, insertBase string, items []BatchInsertItem, batchSize int) error

BatchInsert

func BeforeUse

func BeforeUse(handlers ...HandlerFunc)

func BeforeUseGin

func BeforeUseGin(handlers ...gin.HandlerFunc)

func BuildKey

func BuildKey(keys ...string) string

BuildKey

func CORSMiddleware

func CORSMiddleware() gin.HandlerFunc

CORSMiddleware

func CompareHashAndPassword

func CompareHashAndPassword(hashedPassword, password string) error

CompareHashAndPassword 密码比对

func ContainsCache

func ContainsCache(key string, limit int) (val map[string]string)

ContainsCache

func Copy

func Copy(src interface{}, dest interface{}) (err error)

Copy

func DB

func DB() *gorm.DB

DB

func DEBUG

func DEBUG(v ...interface{})

DEBUG

func DEBUGWithFields

func DEBUGWithFields(fields logrus.Fields, v ...interface{})

func DS

func DS(name string) *gorm.DB

DS

func DecodeURIComponent

func DecodeURIComponent(str string) string

DecodeURIComponent

func DecodedToken

func DecodedToken(tokenString string, secret string) (jwt.MapClaims, error)

DecodedToken

func DelCache

func DelCache(keys ...string)

DelCache

func DelRoutineCache

func DelRoutineCache(key string)

DelRoutineCache

func ERROR

func ERROR(v ...interface{})

ERROR

func ERRORWithFields

func ERRORWithFields(fields logrus.Fields, v ...interface{})

func EncodeURIComponent

func EncodeURIComponent(str string) string

EncodeURIComponent

func EncodedToken

func EncodedToken(claims jwt.MapClaims, secret string) (signed string, err error)

EncodedToken

func EndOfDay

func EndOfDay(t ...time.Time) time.Time

func EnsureDir

func EnsureDir(dir string)

EnsureDir

func EnumMap

func EnumMap() map[string]*EnumDesc

func FATAL

func FATAL(v ...interface{})

FATAL

func FATALWithFields

func FATALWithFields(fields logrus.Fields, v ...interface{})

func FindReadableMessageIDs

func FindReadableMessageIDs(messageDB *gorm.DB, uid uint, orgIDs []uint, rolesCode []string, page, size int) ([]uint, error)

func FindUnreadMessageIDs

func FindUnreadMessageIDs(messageDB *gorm.DB, uid uint, orgIDs []uint, rolesCode []string, page, size int) ([]uint, error)

func FindUnreadMessagesCount

func FindUnreadMessagesCount(messageDB *gorm.DB, uid uint, orgIDs []uint, rolesCode []string) (int, error)

func FindUnreadMessagesPrepareDB

func FindUnreadMessagesPrepareDB(messageDB *gorm.DB, uid uint, orgIDs []uint, rolesCode []string) (*gorm.DB, error)

func FlatValidatorErrors

func FlatValidatorErrors(validatorErrors govalidator.Errors) []govalidator.Error

FlatValidatorErrors

func GenPassword

func GenPassword(passwordSize ...int) string

GenPassword Gen random password

func GenRSAKey

func GenRSAKey() (prvKey, pubKey []byte)

GenRSAKey 生成RSA密钥

func GenerateCaptcha

func GenerateCaptcha() (id string, base64Str string)

GenerateCaptcha create a digit captcha.

func GenerateFromPassword

func GenerateFromPassword(p string) (string, error)

GenerateFromPassword 生成新密码

func GetAppName

func GetAppName() string

GetAppName

func GetCacheInt

func GetCacheInt(key string) (val int)

GetCacheInt

func GetCacheString

func GetCacheString(key string) (val string)

GetCacheString

func GetDataScopeWheres

func GetDataScopeWheres(scope *gorm.Scope, desc *PrivilegesDesc, orgIDs []uint, personalOrgIDMap map[uint]Org) (sqls []string, attrs []interface{})

GetDataScopeWheres

func GetEnumLabel

func GetEnumLabel(classCode string, value interface{}) (label string)

func GetGLSValue

func GetGLSValue(key interface{}) (value interface{}, ok bool)

GetGLSValue

func GetIntlMessages

func GetIntlMessages() map[string]map[string]string

func GetIntlMessagesByLang

func GetIntlMessagesByLang(lang string) map[string]string

func GetMessageCommonDB

func GetMessageCommonDB(messageDB *gorm.DB, uid uint, orgIDs []uint, rolesCode []string, page, size int) *gorm.DB

func GetModPrefix

func GetModPrefix(modCode string) string

GetModPrefix

func GetPagination

func GetPagination(ginContextOrKuuContext interface{}, ignoreDefault ...bool) (page int, size int)

Pagination

func GetPresetIntlMessage

func GetPresetIntlMessage() map[string]map[string]string

func GetRedisClient

func GetRedisClient() redis.UniversalClient

GetRedisClient

func GetRoutineCache

func GetRoutineCache(key string) interface{}

GetRoutineCache

func GetRoutineRequestID

func GetRoutineRequestID() string

GetRoutineRequestID

func GetUploadDir

func GetUploadDir() string

func HasPrefixCache

func HasPrefixCache(key string, limit int) (val map[string]string)

HasPrefixCache

func HasSuffixCache

func HasSuffixCache(key string, limit int) (val map[string]string)

HasSuffixCache

func INFO

func INFO(v ...interface{})

INFO

func INFOWithFields

func INFOWithFields(fields logrus.Fields, v ...interface{})

func If

func If(condition bool, trueVal, falseVal interface{}) interface{}

If

func IgnoreAuth

func IgnoreAuth(cancel ...bool) (success bool)

IgnoreAuth

func IncrCache

func IncrCache(key string) (val int)

IncrCache

func IsBlank

func IsBlank(value interface{}) bool

IsBlank

func IsNil

func IsNil(i interface{}) bool

IsNil

func JSON

func JSON() jsoniter.API

JSON

func JSONParse

func JSONParse(v string, r interface{}) error

JSONParse

func JSONStringify

func JSONStringify(v interface{}, format ...bool) string

JSONStringify

func LoadParmsFroSubApp

func LoadParmsFroSubApp()

func Logout

func Logout(c *Context, tx *gorm.DB) error

func MD5

func MD5(p string, upper ...bool) (v string)

MD5 加密

func NewCaptcha

func NewCaptcha() *base64Captcha.Captcha

NewCaptcha creates a captcha instance from driver and store

func NewDateSn

func NewDateSn(keyPrefix string, valPrefix string, minLen ...int) string

NewDateSn 创建以日期递增的序列化

func NewIntlError

func NewIntlError(err error, id string, args ...interface{}) error

func NewValidError

func NewValidError(resource interface{}, column, err string) error

NewValidError generate a new error for a model's field

func NotifyModelChange

func NotifyModelChange(modelName string)

NotifyModelChange

func OmitFields

func OmitFields(src interface{}, fieldNames []string) (omitted map[string]interface{})

OmitFields

func OrgIDMap

func OrgIDMap(list []Org) map[uint]Org

OrgIDMap

func PANIC

func PANIC(v ...interface{})

PANIC

func PANICWithFields

func PANICWithFields(fields logrus.Fields, v ...interface{})

func PKCS7Padding

func PKCS7Padding(ciphertext []byte, blockSize int) []byte

PKCS7Padding

func PKCS7UnPadding

func PKCS7UnPadding(origData []byte) []byte

PKCS7UnPadding

func PRINT

func PRINT(v ...interface{})

PRINT

func PRINTWithFields

func PRINTWithFields(fields logrus.Fields, v ...interface{})

func PSubscribeCache

func PSubscribeCache(patterns []string, handler func(string, string)) error

PSubscribeCache

func ParseCaptchaID

func ParseCaptchaID(c interface{}) string

ParseCaptchaID

func ParseCaptchaValue

func ParseCaptchaValue(c interface{}) string

ParseCaptchaValue

func ParseExcelDate

func ParseExcelDate(text string) (*time.Time, error)

func ParseExcelFromFileHeader

func ParseExcelFromFileHeader(fh *multipart.FileHeader, index int, sheetName ...string) (rows [][]string, err error)

ParseExcelFromFileHeader

func ParseID

func ParseID(id string) uint

ParseID

func ParseJSONPath

func ParseJSONPath(path string) []string

ParseJSONPath

func ProjectFields

func ProjectFields(data interface{}, project string) interface{}

ProjectFields

func PublishCache

func PublishCache(channel string, message interface{}) error

PublishCache

func QueryCI

func QueryCI(c *gin.Context, key string) (v string)

QueryCI means to get case-insensitive query value

func RSADecrypt

func RSADecrypt(ciphertext, pubKeyBytes, privKeyBytes []byte) ([]byte, error)

RSADecrypt 私钥解密(此处传入公钥的作用是为了解析不限长度的明文)

func RSAEncrypt

func RSAEncrypt(data, keyBytes []byte) ([]byte, error)

RSAEncrypt 公钥加密(支持不限长度的明文)

func RSASignWithSha256

func RSASignWithSha256(data []byte, keyBytes []byte) ([]byte, error)

RSASignWithSha256 签名

func RSAVerySignWithSha256

func RSAVerySignWithSha256(data, signData, keyBytes []byte) error

RSAVerySignWithSha256 验签

func RandCode

func RandCode(size ...int) string

RandCode

func ReadData

func ReadData(file *excelize.File, sheet string, headers Headers) ([]map[string]string, error)

read data

Example
file, err := excelize.OpenFile("data.xlsx")
if err != nil {
	panic(err)
}
headers := Headers{
	{
		Label: "字段0",
		Field: "Field0",
		Index: 0,
	},
	{
		Label: "字段3",
		Field: "Field3",
		Index: 3,
	},
	{
		Label: "字段1",
		Field: "Field1",
		Index: 1,
	},
	{
		Label: "字段2",
		Field: "Field2",
		Index: 2,
	},
}
sheet := file.GetSheetName(1)
list, err := ReadData(file, sheet, headers)
if err != nil {
	panic(err)
}
for _, m := range list {
	fmt.Println(m)
}
Output:

func RegisterBizHook

func RegisterBizHook(name string, handler func(scope *Scope) error)

模型名称:BizBeforeCreate

func RegisterGormHook

func RegisterGormHook(name string, handler func(scope *gorm.Scope) error)

模型名称:BeforeCreate

func RegisterImportCallback

func RegisterImportCallback(callback *ImportCallback)

RegisterImportCallback 注册导入回调

func RegisterMeta

func RegisterMeta()

RegisterMeta

func RegisterRepeatEvent

func RegisterRepeatEvent(name string, interval string, data map[string]interface{}) error

ReigsterRepeatEvent

func RegisterRepeatEventProcesser

func RegisterRepeatEventProcesser(name string, processer RepeatEventProcesser)

ReigsterRepeatEventProcesser

func Release

func Release()

Release

func ReloadIntlMessages

func ReloadIntlMessages() map[string]map[string]string

func RootOrgID

func RootOrgID() uint

RootOrgID

func RootRoleID

func RootRoleID() uint

RootRoleID

func RootUID

func RootUID() uint

RootUID

func RunAllRunAfterJobs

func RunAllRunAfterJobs()

func RunJob

func RunJob(codesOrNames string, sync ...bool) error

func SetCacheInt

func SetCacheInt(key string, val int, expiration ...time.Duration)

SetCacheInt

func SetCacheString

func SetCacheString(key, val string, expiration ...time.Duration)

SetCacheString

func SetData

func SetData(file *excelize.File, sheet string, headers Headers, list []map[string]interface{})

set header and data row

Example
headers := Headers{
	{
		Label: "字段0",
		Field: "Field0",
		Index: 0,
	},
	{
		Label: "字段3",
		Field: "Field3",
		Index: 3,
	},
	{
		Label: "字段1",
		Field: "Field1",
		Index: 1,
	},
	{
		Label: "字段2",
		Field: "Field2",
		Index: 2,
	},
}
list := []map[string]interface{}{
	{
		"Field0": "221",
		"Field1": "221",
		"Field2": "221",
		"Field3": "221",
	},
	{
		"Field0": "2211",
		"Field1": "221",
		"Field2": "221",
		"Field3": "221",
	},
	{
		"Field0": "2212",
		"Field1": "221",
		"Field2": "221",
		"Field3": "221",
	},
	{
		"Field0": "2213",
		"Field1": "221",
		"Field2": "221",
		"Field3": "221",
	},
	{
		"Field0": "2214",
		"Field1": "221",
		"Field2": "221",
		"Field3": "221",
	},
	{
		"Field0": "2215",
		"Field1": "221",
		"Field2": "221",
		"Field3": "221",
	},
	{
		"Field0": "2216",
		"Field1": "221",
		"Field2": "221",
		"Field3": "221",
	},
}
file2 := excelize.NewFile()
SetData(file2, "Sheet1", headers, list)
file2.SaveAs("data.xlsx")
Output:

func SetGLSValues

func SetGLSValues(values gls.Values, call func())

SetGLSValues

func SetHeader

func SetHeader(file *excelize.File, sheet string, headers Headers)

only set header label

Example
var headers = Headers{}
for i := 0; i < 500; i++ {
	h := Header{
		Label: fmt.Sprintf("Label%d", i),
		Field: fmt.Sprintf("Field%d", i),
		Index: i,
	}
	v := h.GetExcelCol()
	fmt.Println(v)
	headers = append(headers, h)
}
file := excelize.NewFile()
SetHeader(file, "Sheet1", headers)
file.SaveAs("500-col-headers.xlsx")
Output:

func SetRoutineCache

func SetRoutineCache(key string, value interface{})

SetRoutineCache

func SetUrlQuery

func SetUrlQuery(rawUrl string, values map[string]interface{}, replace ...bool) string

SetUrlQuery

func Sha1

func Sha1(p string, upper ...bool) (v string)

Sha1 加密

func StartOfDay

func StartOfDay(t ...time.Time) time.Time

func SubscribeCache

func SubscribeCache(channels []string, handler func(string, string)) error

SubscribeCache

func TimeFromExcelTime

func TimeFromExcelTime(excelTime float64, date1904 bool) time.Time

timeFromExcelTime provides a function to convert an excelTime representation (stored as a floating point number) to a time.Time.

func TimeToExcelTime

func TimeToExcelTime(t time.Time) float64

timeToExcelTime provides a function to convert time to Excel time.

func VerifyCaptcha

func VerifyCaptcha(idKey, value string) bool

VerifyCaptcha Verify captcha value.

func VerifyCaptchaWithClear

func VerifyCaptchaWithClear(idKey, value string, clear bool) bool

VerifyCaptchaWithClear Verify captcha value.

func WARN

func WARN(v ...interface{})

WARN

func WARNWithFields

func WARNWithFields(fields logrus.Fields, v ...interface{})

func WithTransaction

func WithTransaction(fn func(*gorm.DB) error) (err error)

WithTransaction

Types

type ASCIISigner

type ASCIISigner struct {
	Value        interface{}
	Alg          string
	OmitKeys     []string
	IncludeEmpty bool
	PrintRaw     bool
	FieldString  func(string, interface{}, bool) string
	Prefix       string
	Suffix       string
	ToUpper      bool
}

ASCIISigner

func (*ASCIISigner) Sign

func (s *ASCIISigner) Sign(value ...interface{}) (signature string)

Sign

type AuthProcessor

type AuthProcessor interface {
	AllowCreate(AuthProcessorDesc) error
	AddWritableWheres(AuthProcessorDesc) error
	AddReadableWheres(AuthProcessorDesc) error
}

AuthProcessor

type AuthProcessorDesc

type AuthProcessorDesc struct {
	Meta                *Metadata
	SubDocIDNames       []string
	Scope               *gorm.Scope
	PrisDesc            *PrivilegesDesc
	HasCreatedByIDField bool
	HasOrgIDField       bool
	CreatedByIDField    *gorm.Field
	OrgIDFieldField     *gorm.Field
	CreatedByID         uint
	OrgID               uint
}

AuthProcessorDesc

func GetAuthProcessorDesc

func GetAuthProcessorDesc(scope *gorm.Scope, desc *PrivilegesDesc) (auth AuthProcessorDesc)

GetAuthProcessorDesc

type BatchInsertItem

type BatchInsertItem struct {
	SQL  string
	Vars []interface{}
}

BatchInsertItem

type BizPreloadInterface

type BizPreloadInterface interface {
	BizPreloadHandlers() map[string]func(*gorm.DB) *gorm.DB
}

type BizQueryResult

type BizQueryResult struct {
	Cond         map[string]interface{} `json:"cond,omitempty"`
	Project      string                 `json:"project,omitempty"`
	Preload      string                 `json:"preload,omitempty"`
	Sort         string                 `json:"sort,omitempty"`
	Range        string                 `json:"range,omitempty"`
	Page         int                    `json:"page,omitempty"`
	Size         int                    `json:"size,omitempty"`
	TotalRecords int                    `json:"totalrecords,omitempty"`
	TotalPages   int                    `json:"totalpages,omitempty"`
	List         interface{}            `json:"list,omitempty"`
}

BizQueryResult

type BizUpdateParams

type BizUpdateParams struct {
	All   bool
	Multi bool
	Cond  map[string]interface{}
	Doc   map[string]interface{}
}

BizUpdateParams

type Cache

type Cache interface {
	SetString(string, string, ...time.Duration)
	HasPrefix(string, int) map[string]string
	HasSuffix(string, int) map[string]string
	Contains(string, int) map[string]string
	GetString(string) string
	SetInt(string, int, ...time.Duration)
	GetInt(string) int
	Incr(string) int
	Del(...string)
	Close()

	HGetAll(string) map[string]string
	HGet(string, string) string
	HSet(string, ...string)

	Publish(channel string, message interface{}) error
	Subscribe(channels []string, handler func(string, string)) error
	PSubscribe(patterns []string, handler func(string, string)) error
}

Cache todo

var DefaultCache Cache

DefaultCache

type CacheBolt

type CacheBolt struct {
	// contains filtered or unexported fields
}

CacheBolt

func NewCacheBolt

func NewCacheBolt() *CacheBolt

NewCacheBolt

func (*CacheBolt) Close

func (c *CacheBolt) Close()

Close

func (*CacheBolt) Contains

func (c *CacheBolt) Contains(pattern string, limit int) (values map[string]string)

Contains

func (*CacheBolt) Del

func (c *CacheBolt) Del(keys ...string)

Del

func (*CacheBolt) GetInt

func (c *CacheBolt) GetInt(key string) (val int)

GetInt

func (*CacheBolt) GetString

func (c *CacheBolt) GetString(key string) (val string)

GetString

func (*CacheBolt) HGet

func (c *CacheBolt) HGet(key, field string) string

func (*CacheBolt) HGetAll

func (c *CacheBolt) HGetAll(key string) (val map[string]string)

func (*CacheBolt) HSet

func (c *CacheBolt) HSet(key string, values ...string)

func (*CacheBolt) HasPrefix

func (c *CacheBolt) HasPrefix(prefix string, limit int) (values map[string]string)

HasPrefix

func (*CacheBolt) HasSuffix

func (c *CacheBolt) HasSuffix(suffix string, limit int) (values map[string]string)

HasSuffix

func (*CacheBolt) Incr

func (c *CacheBolt) Incr(key string) (val int)

Incr

func (*CacheBolt) PSubscribe

func (c *CacheBolt) PSubscribe(patterns []string, handler func(string, string)) error

func (*CacheBolt) Publish

func (c *CacheBolt) Publish(channel string, message interface{}) error

func (*CacheBolt) SetInt

func (c *CacheBolt) SetInt(key string, val int, expiration ...time.Duration)

SetInt

func (*CacheBolt) SetString

func (c *CacheBolt) SetString(key, val string, expiration ...time.Duration)

SetString

func (*CacheBolt) Subscribe

func (c *CacheBolt) Subscribe(channels []string, handler func(string, string)) error

type CacheRedis

type CacheRedis struct {
	// contains filtered or unexported fields
}

CacheRedis

func NewCacheRedis

func NewCacheRedis() *CacheRedis

NewCacheRedis

func (*CacheRedis) Close

func (c *CacheRedis) Close()

Close

func (*CacheRedis) Contains

func (c *CacheRedis) Contains(rawKey string, limit int) map[string]string

Contains

func (*CacheRedis) Del

func (c *CacheRedis) Del(keys ...string)

Del

func (*CacheRedis) GetInt

func (c *CacheRedis) GetInt(key string) (val int)

GetInt

func (*CacheRedis) GetString

func (c *CacheRedis) GetString(rawKey string) (val string)

GetString

func (*CacheRedis) HGet

func (c *CacheRedis) HGet(key, field string) string

func (*CacheRedis) HGetAll

func (c *CacheRedis) HGetAll(key string) map[string]string

func (*CacheRedis) HSet

func (c *CacheRedis) HSet(key string, values ...string)

func (*CacheRedis) HasPrefix

func (c *CacheRedis) HasPrefix(rawKey string, limit int) map[string]string

HasPrefix

func (*CacheRedis) HasSuffix

func (c *CacheRedis) HasSuffix(rawKey string, limit int) map[string]string

HasSuffix

func (*CacheRedis) Incr

func (c *CacheRedis) Incr(rawKey string) (val int)

Incr

func (*CacheRedis) PSubscribe

func (c *CacheRedis) PSubscribe(patterns []string, handler func(string, string)) error

func (*CacheRedis) Publish

func (c *CacheRedis) Publish(channel string, message interface{}) error

func (*CacheRedis) SetInt

func (c *CacheRedis) SetInt(rawKey string, val int, expiration ...time.Duration)

SetInt

func (*CacheRedis) SetString

func (c *CacheRedis) SetString(rawKey, val string, expiration ...time.Duration)

SetString

func (*CacheRedis) Subscribe

func (c *CacheRedis) Subscribe(channels []string, handler func(string, string)) error

type Callback

type Callback struct {
	// contains filtered or unexported fields
}

Callback

func (*Callback) Create

func (c *Callback) Create() *CallbackProcessor

Create

func (*Callback) Delete

func (c *Callback) Delete() *CallbackProcessor

Delete

func (*Callback) Query

func (c *Callback) Query() *CallbackProcessor

Query

func (*Callback) Update

func (c *Callback) Update() *CallbackProcessor

Update

type CallbackProcessor

type CallbackProcessor struct {
	// contains filtered or unexported fields
}

CallbackProcessor contains callback informations

func (*CallbackProcessor) After

func (cp *CallbackProcessor) After(callbackName string) *CallbackProcessor

After insert a new callback after callback `callbackName`, refer `Callbacks.Create`

func (*CallbackProcessor) Before

func (cp *CallbackProcessor) Before(callbackName string) *CallbackProcessor

Before insert a new callback before callback `callbackName`, refer `Callbacks.Create`

func (*CallbackProcessor) Get

func (cp *CallbackProcessor) Get(callbackName string) (callback func(scope *Scope))

Get registered callback

db.Callback().Create().Get("gorm:create")

func (*CallbackProcessor) Register

func (cp *CallbackProcessor) Register(callbackName string, callback func(scope *Scope))

Register a new callback, refer `Callbacks.Create`

func (*CallbackProcessor) Remove

func (cp *CallbackProcessor) Remove(callbackName string)

Remove a registered callback

db.Callback().Create().Remove("gorm:update_time_stamp_when_create")

func (*CallbackProcessor) Replace

func (cp *CallbackProcessor) Replace(callbackName string, callback func(scope *Scope))

Replace a registered callback with new callback

    db.Callback().Create().Replace("gorm:update_time_stamp_when_create", func(*Scope) {
		   scope.SetColumn("Created", now)
		   scope.SetColumn("Updated", now)
    })

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a middleman between the websocket connection and the hub.

type CondDesc

type CondDesc struct {
	AndSQLs  []string
	AndAttrs []interface{}
	OrSQLs   []string
	OrAttrs  []interface{}
}

CondDesc

func ParseCond

func ParseCond(cond interface{}, model interface{}, with ...*gorm.DB) (desc *CondDesc, db *gorm.DB)

ParseCond parse the cond parameter.

type Config

type Config struct {
	// contains filtered or unexported fields
}

func C

func C(newConfig ...map[string]interface{}) *Config

func ReloadC

func ReloadC(jsonStr string) *Config

func (*Config) DefaultGetBool

func (c *Config) DefaultGetBool(path string, defaultValue bool) bool

DefaultGetBool returns the value associated with the key as a boolean.

func (*Config) DefaultGetFloat64

func (c *Config) DefaultGetFloat64(path string, defaultValue float64) float64

DefaultGetInt returns the value associated with the key as a float64.

func (*Config) DefaultGetInt

func (c *Config) DefaultGetInt(path string, defaultValue int) int

DefaultGetInt returns the value associated with the key as a integer.

func (*Config) DefaultGetString

func (c *Config) DefaultGetString(path string, defaultValue string) string

DefaultGetString returns the value associated with the key as a string.

func (*Config) Get

func (c *Config) Get(path string) (val []byte, exists bool)

func (*Config) GetBool

func (c *Config) GetBool(path string) (b bool)

GetBool returns the value associated with the key as a boolean.

func (*Config) GetFloat32

func (c *Config) GetFloat32(path string) float32

GetFloat64 returns the value associated with the key as a float32.

func (*Config) GetFloat64

func (c *Config) GetFloat64(path string) (f64 float64)

GetFloat64 returns the value associated with the key as a float64.

func (*Config) GetInt

func (c *Config) GetInt(path string) (i int)

GetInt returns the value associated with the key as an integer.

func (*Config) GetInt32

func (c *Config) GetInt32(path string) int32

GetInt64 returns the value associated with the key as an integer.

func (*Config) GetInt64

func (c *Config) GetInt64(path string) int64

GetInt64 returns the value associated with the key as an integer.

func (*Config) GetInterface

func (c *Config) GetInterface(path string, out interface{})

GetInterface returns the value associated with the key.

func (*Config) GetString

func (c *Config) GetString(path string) (s string)

GetString returns the value associated with the key as a string.

func (*Config) Has

func (c *Config) Has(path string) bool

func (*Config) LoadFromParams

func (c *Config) LoadFromParams(keys ...string)

type Context

type Context struct {
	*gin.Context

	SignInfo      *SignContext
	PrisDesc      *PrivilegesDesc
	RoutineCaches RoutineCaches
	RouteInfo     *RouteInfo
	// contains filtered or unexported fields
}

Context

func GetRoutineRequestContext

func GetRoutineRequestContext() *Context

GetRoutineRequestContext

func (*Context) Abort

func (c *Context) Abort(data interface{}, args ...interface{}) *STDReply

c.Abort(data, localeMessageName, defaultLocaleMessageText, localeMessageValues)

func (*Context) AbortErr

func (c *Context) AbortErr(err error, args ...interface{}) *STDReply

c.AbortErr(err, localeMessageName, defaultLocaleMessageText, localeMessageValues)

func (*Context) AbortErrWithCode

func (c *Context) AbortErrWithCode(errData error, code int, args ...interface{}) *STDReply

c.AbortErrWithCode(errData, code, localeMessageName, defaultLocaleMessageText, localeMessageValues)

func (*Context) DB

func (c *Context) DB() *gorm.DB

DB

func (*Context) DEBUG

func (c *Context) DEBUG(v ...interface{}) *Context

DEBUG

func (*Context) DecodedContext

func (c *Context) DecodedContext() (sign *SignContext, err error)

DecodedContext

func (*Context) DelRoutineCache

func (c *Context) DelRoutineCache(key string)

DelValue

func (*Context) ERROR

func (c *Context) ERROR(v ...interface{}) *Context

ERROR

func (*Context) FATAL

func (c *Context) FATAL(v ...interface{}) *Context

FATAL

func (*Context) FormatMessage

func (c *Context) FormatMessage(id, defaultMessage string, contextValues ...interface{}) string

func (*Context) GetIntlMessages

func (c *Context) GetIntlMessages() map[string]string

func (*Context) GetKey

func (c *Context) GetKey(names ...string) (value string)

func (*Context) GetPagination

func (c *Context) GetPagination(ignoreDefault ...bool) (int, int)

GetPagination

func (*Context) GetRoutineCache

func (c *Context) GetRoutineCache(key string) interface{}

GetValue

func (*Context) INFO

func (c *Context) INFO(v ...interface{}) *Context

INFO

func (*Context) IgnoreAuth

func (c *Context) IgnoreAuth(cancel ...bool) *Context

IgnoreAuth

func (*Context) InWhitelist

func (c *Context) InWhitelist() bool

InWhitelist

func (*Context) L

func (c *Context) L(id, defaultMessage string, contextValues ...interface{}) string

func (*Context) Lang

func (c *Context) Lang() (lang string)

func (*Context) Origin

func (c *Context) Origin() string

Origin

func (*Context) PANIC

func (c *Context) PANIC(v ...interface{}) *Context

PANIC

func (*Context) PRINT

func (c *Context) PRINT(v ...interface{}) *Context

PRINT

func (*Context) ParseCond

func (c *Context) ParseCond(model interface{}, db *gorm.DB) (map[string]interface{}, *gorm.DB, error)

ParseCond

func (*Context) QueryCI

func (c *Context) QueryCI(key string) (v string)

QueryCI

func (*Context) RequestCode

func (c *Context) RequestCode(withSign ...bool) string

func (*Context) RequestID

func (c *Context) RequestID() string

func (*Context) STD

func (c *Context) STD(data interface{}, args ...interface{}) *STDReply

STD render a JSON body with code(default is 0), data and message.

c.STD(data, localeMessageName, defaultLocaleMessageText, localeMessageValues)

Examples:

c.STD(data) c.STD(data, "hello") c.STD(data, "hello", "Hello") c.STD(data, "welcome", "Welcome {{name}}", mot.D{"name": "xxx"})

func (*Context) STDErr

func (c *Context) STDErr(err interface{}, args ...interface{}) *STDReply

STDErr render a JSON body with error message, error code(default is -1) and error detail.

c.STDErr(err, localeMessageName, defaultLocaleMessageText, localeMessageValues)

Examples:

c.STDErr(err, "hello") c.STDErr(err, "hello", "Hello") c.STDErr(err, "welcome", "Welcome {{name}}", D{"name": "xxx"})

func (*Context) STDErrWithCode

func (c *Context) STDErrWithCode(errData interface{}, code int, args ...interface{}) *STDReply

STDErrWithCode render a JSON body with error message, custom error code and error detail.

c.STDErrWithCode(errData, code, localeMessageName, defaultLocaleMessageText, localeMessageValues)

Examples:

c.STDErrWithCode(errData, 555, "hello") c.STDErrWithCode(errData, 501, "hello", "Hello") c.STDErrWithCode(errData, 500, "welcome", "Welcome {{name}}", D{"name": "xxx"})

func (*Context) STDOK

func (c *Context) STDOK() *STDReply

func (*Context) Scheme

func (c *Context) Scheme() string

Scheme

func (*Context) SetRoutineCache

func (c *Context) SetRoutineCache(key string, value interface{})

SetValue

func (*Context) Token

func (c *Context) Token() string

func (*Context) WARN

func (c *Context) WARN(v ...interface{}) *Context

WARN

func (*Context) WithTransaction

func (c *Context) WithTransaction(fn func(*gorm.DB) error) error

WithTransaction

type CreateOrgArgs

type CreateOrgArgs struct {
	Tx               *gorm.DB
	OrgCode          string
	OrgName          string
	ParentOrgID      uint
	ParentOrgCode    string
	AdminUID         uint
	AdminUsername    string
	AdminPassword    string
	GeneratePassword bool

	ExtraAdminUserInfo   User
	ExtraAdminRoleInfo   Role
	ExtraAssignRoleCodes []string
}

type CreateOrgReply

type CreateOrgReply struct {
	OrgID                      uint
	Org                        *Org
	AdminUID                   uint
	AdminUser                  *User
	GeneratedPlaintextPassword string
}

func CreateOrg

func CreateOrg(args *CreateOrgArgs) (reply *CreateOrgReply, err error)

type D

type D map[string]interface{}

M is a shortcut for map[string]interface{}

type DBTypeRepairer

type DBTypeRepairer interface {
	RepairDBTypes()
}

type DataPrivileges

type DataPrivileges struct {
	Model `rest:"*" displayName:"角色数据权限"`
	ExtendField
	RoleID        uint   `name:"角色ID"`
	TargetOrgID   uint   `name:"目标组织ID"`
	ReadableRange string `name:"可读范围" enum:"DataScope"`
	WritableRange string `name:"可写范围" enum:"DataScope"`
}

DataPrivileges

func (*DataPrivileges) BeforeSave

func (m *DataPrivileges) BeforeSave(scope *gorm.Scope)

BeforeSave

type DefaultAuthProcessor

type DefaultAuthProcessor struct{}

DefaultAuthProcessor

func (*DefaultAuthProcessor) AddReadableWheres

func (por *DefaultAuthProcessor) AddReadableWheres(auth AuthProcessorDesc) (err error)

AddReadableWheres

func (*DefaultAuthProcessor) AddWritableWheres

func (por *DefaultAuthProcessor) AddWritableWheres(auth AuthProcessorDesc) (err error)

AddWritableWheres

func (*DefaultAuthProcessor) AllowCreate

func (por *DefaultAuthProcessor) AllowCreate(auth AuthProcessorDesc) (err error)

AllowCreate

type Doc

type Doc struct {
	Openapi    string
	Info       DocInfo
	Servers    []DocServer
	Tags       []DocTag
	Paths      map[string](DocPathItems)
	Components DocComponent
}

func (*Doc) Marshal

func (d *Doc) Marshal() (result string)

Marshal

type DocComponent

type DocComponent struct {
	Schemas         map[string]DocComponentSchema
	SecuritySchemes map[string]DocSecurityScheme `yaml:"securitySchemes"`
}

type DocComponentSchema

type DocComponentSchema struct {
	Required   []string
	Type       string
	Properties map[string]DocSchemaProperty
}

type DocInfo

type DocInfo struct {
	Title       string
	Description string
	Contact     DocInfoContact
	Version     string
}

type DocInfoContact

type DocInfoContact struct {
	Email string
}

type DocPathContentItem

type DocPathContentItem struct {
	Schema DocPathSchema
}

type DocPathItem

type DocPathItem struct {
	Tags        []string
	Summary     string
	Description string
	OperationID string             `yaml:"operationId"`
	RequestBody DocPathRequestBody `yaml:"requestBody"`
	Responses   map[int]DocPathResponse
	Parameters  []DocPathParameter
	Deprecated  bool
	Security    []DocPathItemSecurity
}

type DocPathItemSecurity

type DocPathItemSecurity map[string]([]string)

type DocPathItems

type DocPathItems map[string]DocPathItem

type DocPathParameter

type DocPathParameter struct {
	Name            string
	In              string
	Description     string
	Required        bool
	Style           string
	Explode         bool
	Schema          DocPathSchema
	AllowEmptyValue bool `yaml:"allowEmptyValue"`
}

type DocPathRequestBody

type DocPathRequestBody struct {
	Description string
	Content     map[string]DocPathContentItem
	Required    bool
}

type DocPathResponse

type DocPathResponse struct {
	Description string
	Content     map[string]DocPathContentItem
}

type DocPathSchema

type DocPathSchema struct {
	Type        string
	Description string
	Format      string
	Ref         string `yaml:"$ref"`
	Properties  map[string]DocPathSchema
	Items       *DocPathSchema
	Enum        []interface{}
	Default     interface{}
	Required    bool
}

type DocSchemaProperty

type DocSchemaProperty struct {
	Title      string
	Type       string
	Ref        string `yaml:"$ref"`
	Properties *DocPathSchema
	Items      *DocSchemaProperty
	Format     string
	Enum       []interface{}
	Default    interface{}
}

type DocSecurityScheme

type DocSecurityScheme struct {
	Type string
	Name string
	In   string
}

type DocServer

type DocServer struct {
	Url         string
	Description string
}

type DocTag

type DocTag struct {
	Name        string
	Description string
}

type Engine

type Engine struct {
	*gin.Engine
}

Engine

func Default

func Default() (app *Engine)

Default

func New

func New() (app *Engine)

New

func (*Engine) Any

func (app *Engine) Any(relativePath string, handlers ...HandlerFunc) gin.IRoutes

Overrite r.Any

func (*Engine) DELETE

func (app *Engine) DELETE(relativePath string, handlers ...HandlerFunc) gin.IRoutes

Overrite r.DELETE

func (*Engine) GET

func (app *Engine) GET(relativePath string, handlers ...HandlerFunc) gin.IRoutes

Overrite r.GET

func (*Engine) Group

func (app *Engine) Group(relativePath string, handlers ...HandlerFunc) *gin.RouterGroup

Overrite r.Group

func (*Engine) HEAD

func (app *Engine) HEAD(relativePath string, handlers ...HandlerFunc) gin.IRoutes

Overrite r.HEAD

func (*Engine) Handle

func (app *Engine) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) gin.IRoutes

Overrite r.Handle

func (*Engine) Import

func (app *Engine) Import(mods ...*Mod)

Import

func (*Engine) OPTIONS

func (app *Engine) OPTIONS(relativePath string, handlers ...HandlerFunc) gin.IRoutes

Overrite r.OPTIONS

func (*Engine) PATCH

func (app *Engine) PATCH(relativePath string, handlers ...HandlerFunc) gin.IRoutes

Overrite r.PATCH

func (*Engine) POST

func (app *Engine) POST(relativePath string, handlers ...HandlerFunc) gin.IRoutes

Overrite r.POST

func (*Engine) PUT

func (app *Engine) PUT(relativePath string, handlers ...HandlerFunc) gin.IRoutes

Overrite r.PUT

func (*Engine) RegisterWhitelist

func (app *Engine) RegisterWhitelist(rules ...interface{})

RegisterWhitelist

func (*Engine) Run

func (app *Engine) Run(addr ...string)

Run

func (*Engine) RunTLS

func (app *Engine) RunTLS(addr, certFile, keyFile string)

RunTLS

func (*Engine) Use

func (app *Engine) Use(handlers ...HandlerFunc) *Engine

func (*Engine) UseGin

func (app *Engine) UseGin(handlers ...gin.HandlerFunc) *Engine

type EnumDesc

type EnumDesc struct {
	ClassCode string
	ClassName string
	Values    map[interface{}]string `json:"-"`
	Items     []EnumItem             `json:"Values"`
}

func Enum

func Enum(classCode string, className ...string) *EnumDesc

func EnumList

func EnumList() (list []*EnumDesc)

func GetEnumItem

func GetEnumItem(classCode string) *EnumDesc

func (*EnumDesc) Add

func (d *EnumDesc) Add(value interface{}, label ...string) *EnumDesc

type EnumItem

type EnumItem struct {
	Label string
	Value interface{}
}

type EventLog

type EventLog struct {
	Model     `rest:"*" displayName:"事件日志"`
	EventID   string `name:"事件ID(UUID)" gorm:"NOT NULL"`
	EventTime int64  `name:"事件时间" gorm:"NOT NULL"`
	SourceIP  string `name:"来源IP" gorm:"NOT NULL"`

	UserID           uint   `name:"操作人ID" gorm:"NOT NULL"`
	UserName         string `name:"操作人名称" gorm:"NOT NULL"`
	UserEmailAddress string `name:"操作人邮箱"`
	UserPhoneNumber  string `name:"操作人手机"`

	EventClass   string          `name:"事件分类" gorm:"NOT NULL" enum:"EventLogClass"`
	EventSubject string          `name:"事件主题" gorm:"NOT NULL"`
	EventSummary string          `name:"事件摘要" gorm:"NOT NULL"`
	EventLabels  []EventLogLabel `name:"关联事件标签"`
	EventData    string          `name:"事件详情(JSON-String)"`
}

func (*EventLog) BindData

func (log *EventLog) BindData(dst interface{}) error

type EventLogLabel

type EventLogLabel struct {
	Model      `rest:"*" displayName:"事件日志标签"`
	EventLogID uint      `name:"关联事件日志ID" gorm:"NOT NULL"`
	EventLog   *EventLog `name:"关联事件日志"`

	EventClass string `name:"事件分类" gorm:"NOT NULL;INDEX:event_log_label" enum:"EventLogClass"`
	LabelKey   string `name:"标签名" gorm:"NOT NULL;INDEX:event_log_label"`
	LabelValue string `name:"标签值" gorm:"NOT NULL;INDEX:event_log_label"`
}

type ExtendField

type ExtendField struct {
	Def1 uint   `name:"扩展字段1(默认字段)"`
	Def2 string `name:"扩展字段2(默认字段)" gorm:"type:text"`
	Def3 string `name:"扩展字段3(默认字段)"`
	Def4 string `name:"扩展字段4(默认字段)"`
	Def5 string `name:"扩展字段5(默认字段)"`
}

ExtendField

type File

type File struct {
	Model `rest:"*" displayName:"文件"`
	ExtendField
	Class     string `name:"文件分类"`
	OwnerID   uint   `name:"归属数据ID"`
	OwnerType string `name:"归属数据类型"`

	UID  string `name:"文件唯一ID" json:"uid" gorm:"not null"`
	Type string `name:"文件Mine-Type" json:"type" gorm:"not null"`
	Size int64  `name:"文件大小" json:"size" gorm:"not null"`
	Name string `name:"文件名称" json:"name" gorm:"not null"`
	URL  string `name:"文件下载路径" json:"url" gorm:"not null"`
	Path string `name:"文件存储路径" json:"path" gorm:"not null"`
}

File

func SaveUploadedFile

func SaveUploadedFile(fh *multipart.FileHeader, save2db bool, extraData ...*File) (f *File, err error)

SaveUploadedFile

func (*File) BeforeCreate

func (f *File) BeforeCreate()

BeforeCreate

type GenTokenDesc

type GenTokenDesc struct {
	UID          uint
	Username     string
	Exp          int64 `binding:"required"`
	Type         string
	Desc         string
	Payload      jwt.MapClaims
	IsAPIKey     bool
	ForcePayload bool // 是否强制使用Payload参数值作为jwt的payload
}

type HandlerFunc

type HandlerFunc func(*Context) *STDReply

HandlerFunc defines the handler used by ok middleware as return value.

type HandlersChain

type HandlersChain []HandlerFunc

HandlersChain defines a HandlerFunc array.

type Header struct {
	Label string // header column name
	Field string // fieldname
	Index int    // index
}

func (*Header) GetExcelAxis

func (h *Header) GetExcelAxis(rowIndex int) string

get excel axis e.g: A1, A2, A4

func (*Header) GetExcelCol

func (h *Header) GetExcelCol() string

get excel column name,support 26*26=676 columns。 e.g: A,B,C,D,E org AA, AB

type Headers

type Headers []Header

func (Headers) GetColByField

func (cols Headers) GetColByField(field string) *Header

get column by fieldname

func (Headers) GetColByIndex

func (cols Headers) GetColByIndex(index int) *Header

get column by index

func (Headers) GetField

func (cols Headers) GetField(index int) string

get filename by index

func (Headers) GetIndex

func (cols Headers) GetIndex(field string) int

get index by fieldname

type HookHandler

type HookHandler func(args ...interface{}) error

type Hub

type Hub struct {
	// contains filtered or unexported fields
}

Hub maintains the set of active clients and broadcasts messages to the clients.

type IPInfo

type IPInfo struct {
	IP        string `json:"ip"`
	Country   string `json:"country"`
	CountryID string `json:"country_id"`
	Area      string `json:"area"`
	Region    string `json:"region"`
	City      string `json:"city"`
	ISP       string `json:"isp"`
}

IPInfo

func GetIPInfo

func GetIPInfo(ip string) (*IPInfo, error)

GetIPInfo

type ImportCallback

type ImportCallback struct {
	Channel           string
	Description       string
	TemplateGenerator func(*Context) (string, []string)
	Validator         ImportCallbackValidator
	Processor         ImportCallbackProcessor
}

type ImportCallbackProcessor

type ImportCallbackProcessor func(c *Context, rows [][]string) *ImportCallbackResult

ImportCallbackProcessor

type ImportCallbackResult

type ImportCallbackResult struct {
	Feedback []ImportFeedback
	Message  string
	Error    error
	Extra    map[string]interface{}
}

ImportCallbackResult

type ImportCallbackValidator

type ImportCallbackValidator func(c *Context, rows [][]string) (*STDReply, error)

ImportCallbackValidator

type ImportContext

type ImportContext struct {
	Token      string
	SignType   string
	Lang       string
	UID        uint
	SubDocID   uint
	ActOrgID   uint
	ActOrgCode string
	ActOrgName string
}

type ImportFeedback

type ImportFeedback struct {
	Success bool   `json:"success"`
	Message string `json:"message"`
}

type ImportRecord

type ImportRecord struct {
	Model    `rest:"*" displayName:"导入记录"`
	Sync     bool   `name:"是否同步执行"`
	ImportSn string `name:"批次编号" sql:"index"`
	Context  string `name:"导入时上下文数据" gorm:"type:text"`
	Channel  string `name:"导入渠道"`
	Data     string `name:"导入数据([][]string)" gorm:"type:text"`
	Feedback string `name:"反馈记录([]string)" gorm:"type:text"`
	Status   string `name:"导入状态" enum:"ImportStatus"`
	Message  string `name:"导入结果"`
	Error    string `name:"错误详情"`
	Extra    string `name:"额外数据(JSON)" gorm:"type:text"`
}

ImportRecord

func CallImportCallback

func CallImportCallback(c *Context, info *ImportRecord) (*ImportRecord, error)

CallImportCallback

func (*ImportRecord) BeforeCreate

func (imp *ImportRecord) BeforeCreate()

BeforeCreate

type IntlError

type IntlError struct {
	Err           error
	ID            string
	DefaultText   string
	ContextValues interface{}
}

func (*IntlError) Error

func (e *IntlError) Error() string

type IntlMessagesOptions

type IntlMessagesOptions struct {
	LanguageCodes string
	Prefix        string
	Suffix        string
	Contains      string
	Description   string
	Keys          string
}

type Job

type Job struct {
	Spec        string              `json:"spec" valid:"required"`
	Cmd         func(c *JobContext) `json:"-,omitempty"`
	Code        string              `json:"code"`
	Name        string              `json:"name" valid:"required"`
	RunAfterAdd bool                `json:"runAfterAdd"`
	EntryID     cron.EntryID        `json:"entryID,omitempty"`
	// contains filtered or unexported fields
}

Job

func (*Job) NewJobContext

func (j *Job) NewJobContext() *JobContext

type JobContext

type JobContext struct {
	// contains filtered or unexported fields
}

JobContext

func (*JobContext) Error

func (c *JobContext) Error(err error)

func (*JobContext) Name

func (c *JobContext) Name() string

type LogDailyFileHook

type LogDailyFileHook struct{}

LogDailyFileHook

func (*LogDailyFileHook) Fire

func (h *LogDailyFileHook) Fire(entry *logrus.Entry) error

Fire

func (*LogDailyFileHook) Levels

func (h *LogDailyFileHook) Levels() []logrus.Level

Levels

type LoginHandlerFunc

type LoginHandlerFunc func(*Context) *LoginHandlerResponse

LoginHandlerFunc

type LoginHandlerResponse

type LoginHandlerResponse struct {
	Username                   string
	Password                   string
	Payload                    jwt.MapClaims
	Lang                       string
	UID                        uint
	Error                      error
	LocaleMessageID            string
	LocaleMessageDefaultText   string
	LocaleMessageContextValues interface{}
}

LoginHandlerResponse

type Menu struct {
	ModelExOrg `rest:"*" displayName:"菜单"`
	ExtendField
	Code          string      `name:"菜单编码" gorm:"not null;UNIQUE_INDEX:kuu_unique"`
	Dr            int64       `name:"删除标记(0表示未删除,非0表示删除时间戳)" gorm:"DEFAULT:0;UNIQUE_INDEX:kuu_unique"`
	Name          string      `name:"菜单名称" gorm:"not null"`
	URI           null.String `name:"菜单地址"`
	Icon          null.String `name:"菜单图标"`
	ParentCode    null.String `name:"直接父菜单编号"`
	ParentCodes   null.String `name:"所有父菜单编号"`
	Group         null.String `name:"菜单分组名"`
	Disable       null.Bool   `name:"是否禁用"`
	IsLink        null.Bool   `name:"是否外链"`
	Sort          null.Int    `name:"排序值"`
	IsDefaultOpen null.Bool   `name:"是否默认打开"`
	Closeable     null.Bool   `name:"是否可关闭"`
	LocaleKey     null.String `name:"国际化语言键"`
	IsVirtual     null.Bool   `name:"是否虚菜单"`
	AuthPattern   null.String `name:"权限表达式"` // json数组
}

Menu

func (m *Menu) AfterDelete(tx *gorm.DB) error

AfterDelete

func (m *Menu) AfterSave(tx *gorm.DB) error

AfterSave

func (m *Menu) BeforeCreate(scope *gorm.Scope) error

BeforeSave

func (m *Menu) BeforeDelete(scope *gorm.Scope) error

BeforeDelete

func (m *Menu) BeforeUpdate(scope *gorm.Scope) error

BeforeUpdate

type MenuList []Menu
func (ml MenuList) Len() int
func (ml MenuList) Less(i, j int) bool
func (ml MenuList) Swap(i, j int)

type Message

type Message struct {
	Model `rest:"*" displayName:"系统消息"`

	Subject     string      `name:"消息标题"`
	Content     null.String `name:"消息内容" gorm:"not null"`
	Attachments []File      `name:"消息附件" gorm:"polymorphic:Owner;polymorphic_value:Message.Attachments"`

	Status int `name:"消息状态" enum:"MessageStatus"`

	SenderID       uint      `name:"发送人ID"`
	SenderUsername string    `name:"发送人账号"`
	Sender         *User     `name:"发送人" gorm:"foreignkey:SenderID"`
	SenderSourceIP string    `name:"发送人IP地址"`
	SentAt         time.Time `name:"发送时间"`

	RecipientOrgIDs    string           `name:"关联接收组织ID(多个以英文逗号分隔)"`
	RecipientUserIDs   string           `name:"关联接收人ID(多个以英文逗号分隔)"`
	RecipientRoleCodes string           `name:"关联接收角色编码(多个以英文逗号分隔)"`
	RecipientReceipts  []MessageReceipt `name:"消息接收/阅读回执"`
}

func FindUnreadMessages

func FindUnreadMessages(messageDB *gorm.DB, uid uint, orgIDs []uint, rolesCode []string, page, size int) ([]Message, error)

func (*Message) BeforeCreate

func (m *Message) BeforeCreate()

BeforeCreate

type MessageReceipt

type MessageReceipt struct {
	Model     `rest:"*" displayName:"消息回执"`
	MessageID uint     `name:"关联消息ID" gorm:"not null;UNIQUE_INDEX:kuu_unique"`
	Message   *Message `name:"关联消息"`

	RecipientID       uint      `name:"接收人ID" gorm:"not null;UNIQUE_INDEX:kuu_unique"`
	RecipientUsername string    `name:"接收人账号"`
	Recipient         *User     `name:"接收人" gorm:"foreignkey:RecipientID"`
	RecipientSourceIP string    `name:"阅读人IP地址"`
	ReadAt            null.Time `name:"阅读时间"`
}

type Messages

type Messages []Message

func (Messages) Len

func (m Messages) Len() int

func (Messages) Less

func (m Messages) Less(i, j int) bool

func (Messages) Swap

func (m Messages) Swap(i, j int)

type Metadata

type Metadata struct {
	ModCode     string
	Name        string
	NativeName  string
	DisplayName string
	LocaleKey   string
	FullName    string
	Fields      []MetadataField
	RestDesc    *RestDesc `json:"-"`

	SubDocIDNames []string          `json:"-" gorm:"-"`
	UIDNames      []string          `json:"-" gorm:"-"`
	OrgIDNames    []string          `json:"-" gorm:"-"`
	TagSettings   map[string]string `json:"-" gorm:"-"`
	// contains filtered or unexported fields
}

Metadata

func Meta

func Meta(valueOrName interface{}) (m *Metadata)

Meta

func Metalist

func Metalist() []*Metadata

Metalist

func (*Metadata) NewValue

func (m *Metadata) NewValue() interface{}

NewValue

func (*Metadata) OmitPassword

func (m *Metadata) OmitPassword(data interface{}) interface{}

OmitPassword

type MetadataField

type MetadataField struct {
	Code         string
	Name         string
	NativeName   string
	DBType       string
	IsBland      bool
	IsPrimaryKey bool
	LocaleKey    string
	Kind         string
	Type         string
	Enum         string
	IsRef        bool
	IsPassword   bool
	IsArray      bool
	Value        interface{}       `json:"-" gorm:"-"`
	Tag          reflect.StructTag `json:"-" gorm:"-"`
}

MetadataField

type Mod

type Mod struct {
	Code             string
	Prefix           string
	Middleware       HandlersChain
	Routes           RoutesInfo
	Models           []interface{}
	IntlMessages     map[string]string
	OnImport         func() error
	OnInit           func() error
	TablePrefix      null.String
	IgnoreCrudRoutes bool
	IgnoreModRoutes  bool
	IgnoreDataDict   bool
}

Mod

func Acc

func Acc(handler ...LoginHandlerFunc) *Mod

Acc

func Sys

func Sys() *Mod

Sys

type Model

type Model struct {
	ID          uint        `gorm:"primary_key"`
	CreatedAt   time.Time   `name:"创建时间,ISO字符串(默认字段)"`
	UpdatedAt   time.Time   `name:"修改时间,ISO字符串(默认字段)"`
	DeletedAt   *time.Time  `name:"删除时间,ISO字符串(默认字段)" sql:"index"`
	OrgID       uint        `name:"所属组织ID(默认字段)"`
	CreatedByID uint        `name:"创建人ID(默认字段)"`
	UpdatedByID uint        `name:"修改人ID(默认字段)"`
	DeletedByID uint        `name:"删除人ID(默认字段)"`
	Remark      null.String `name:"备注" gorm:"text"`
	Ts          time.Time   `name:"时间戳"`
	Org         *Org        `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:org_id"`
	CreatedBy   *User       `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:created_by_id"`
	UpdatedBy   *User       `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:updated_by_id"`
	DeletedBy   *User       `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:deleted_by_id"`
}

Model

type ModelExDel

type ModelExDel struct {
	ID          uint        `gorm:"primary_key"`
	CreatedAt   time.Time   `name:"创建时间,ISO字符串(默认字段)"`
	UpdatedAt   time.Time   `name:"修改时间,ISO字符串(默认字段)"`
	OrgID       uint        `name:"所属组织ID(默认字段)"`
	CreatedByID uint        `name:"创建人ID(默认字段)"`
	UpdatedByID uint        `name:"修改人ID(默认字段)"`
	Remark      null.String `name:"备注" gorm:"text"`
	Ts          time.Time   `name:"时间戳"`
	Org         *Org        `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:org_id"`
	CreatedBy   *User       `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:created_by_id"`
	UpdatedBy   *User       `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:updated_by_id"`
}

ModelExDel

type ModelExOrg

type ModelExOrg struct {
	ID          uint        `gorm:"primary_key"`
	CreatedAt   time.Time   `name:"创建时间,ISO字符串(默认字段)"`
	UpdatedAt   time.Time   `name:"修改时间,ISO字符串(默认字段)"`
	DeletedAt   *time.Time  `name:"删除时间,ISO字符串(默认字段)" sql:"index"`
	CreatedByID uint        `name:"创建人ID(默认字段)"`
	UpdatedByID uint        `name:"修改人ID(默认字段)"`
	DeletedByID uint        `name:"删除人ID(默认字段)"`
	Remark      null.String `name:"备注" gorm:"text"`
	Ts          time.Time   `name:"时间戳"`
	CreatedBy   *User       `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:created_by_id"`
	UpdatedBy   *User       `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:updated_by_id"`
	DeletedBy   *User       `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:deleted_by_id"`
}

ModelExOrg

type OperationPrivileges

type OperationPrivileges struct {
	ModelExOrg `displayName:"角色操作权限"`
	ExtendField
	RoleID   uint   `name:"角色ID"`
	MenuCode string `name:"菜单编码"`
}

OperationPrivileges

type Org

type Org struct {
	// 引用Model将无法Preload,故复制字段
	ID          uint       `gorm:"primary_key" rest:"*" displayName:"用户"`
	CreatedAt   time.Time  `name:"创建时间,ISO字符串(默认字段)"`
	UpdatedAt   time.Time  `name:"修改时间,ISO字符串(默认字段)"`
	DeletedAt   *time.Time `name:"删除时间,ISO字符串(默认字段)" sql:"index"`
	Dr          int64      `name:"删除标记(0表示未删除,非0表示删除时间戳)" gorm:"DEFAULT:0;UNIQUE_INDEX:kuu_unique"`
	OrgID       uint       `name:"所属组织ID(默认字段)"`
	CreatedByID uint       `name:"创建人ID(默认字段)"`
	UpdatedByID uint       `name:"修改人ID(默认字段)"`
	DeletedByID uint       `name:"删除人ID(默认字段)"`
	Remark      string     `name:"备注" gorm:"text"`
	Ts          time.Time  `name:"时间戳"`
	Org         *Org       `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:org_id"`
	CreatedBy   *User      `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:created_by_id"`
	UpdatedBy   *User      `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:updated_by_id"`
	DeletedBy   *User      `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:deleted_by_id"`

	ExtendField
	Code      string `name:"组织编码" gorm:"not null;UNIQUE_INDEX:kuu_unique"`
	Name      string `name:"组织名称" gorm:"not null"`
	Pid       uint   `name:"父组织ID"`
	Sort      int    `name:"排序值"`
	FullPid   string
	FullName  string
	Class     string
	IsBuiltIn null.Bool `name:"是否内置"`
}

Org

func FillOrgFullInfo

func FillOrgFullInfo(list []Org) []Org

FillOrgFullInfo

func GetActOrg

func GetActOrg(c *Context, actOrgID uint) (actOrg Org, err error)

func GetLoginableOrgs

func GetLoginableOrgs(c *Context, uid uint) ([]Org, error)

GetLoginableOrgs

func RootOrg

func RootOrg() *Org

RootOrg

func (*Org) BeforeCreate

func (o *Org) BeforeCreate()

BeforeCreate

type Param

type Param struct {
	gorm.Model `rest:"*" displayName:"参数"`
	ExtendField
	Dr        int64     `name:"删除标记(0表示未删除,非0表示删除时间戳)" gorm:"DEFAULT:0;UNIQUE_INDEX:kuu_unique"`
	Code      string    `name:"参数编码" gorm:"not null;UNIQUE_INDEX:kuu_unique"`
	Name      string    `name:"参数名称" gorm:"not null"`
	Value     string    `name:"参数值" gorm:"type:text"`
	Type      string    `name:"参数类型"`
	IsBuiltIn null.Bool `name:"是否预置"`
}

Param

func (*Param) AfterSave

func (l *Param) AfterSave(tx *gorm.DB) error

func (*Param) RepairDBTypes

func (l *Param) RepairDBTypes()

RepairDBTypes

type PrivilegesDesc

type PrivilegesDesc struct {
	UID                      uint
	OrgID                    uint
	Permissions              []string
	PermissionMap            map[string]int64
	ReadableOrgIDs           []uint
	ReadableOrgIDMap         map[uint]Org
	FullReadableOrgIDs       []uint
	FullReadableOrgIDMap     map[uint]Org
	WritableOrgIDs           []uint
	WritableOrgIDMap         map[uint]Org
	PersonalReadableOrgIDs   []uint
	PersonalReadableOrgIDMap map[uint]Org
	PersonalWritableOrgIDs   []uint
	PersonalWritableOrgIDMap map[uint]Org
	LoginableOrgIDs          []uint
	LoginableOrgIDMap        map[uint]Org
	Valid                    bool
	SignInfo                 *SignContext
	ActOrgID                 uint
	ActOrgCode               string
	ActOrgName               string
	RolesCode                []string
}

PrivilegesDesc

func GetPrivilegesDesc

func GetPrivilegesDesc(signOrContextOrUID interface{}) (desc *PrivilegesDesc)

GetPrivilegesDesc

func GetRoutinePrivilegesDesc

func GetRoutinePrivilegesDesc() *PrivilegesDesc

GetRoutinePrivilegesDesc

func (*PrivilegesDesc) HasPermission

func (desc *PrivilegesDesc) HasPermission(permission string) bool

func (*PrivilegesDesc) HasRole

func (desc *PrivilegesDesc) HasRole(code string) bool

func (*PrivilegesDesc) IsLoginableOrgID

func (desc *PrivilegesDesc) IsLoginableOrgID(orgID uint) bool

IsLoginableOrgID

func (*PrivilegesDesc) IsReadableOrgID

func (desc *PrivilegesDesc) IsReadableOrgID(orgID uint) bool

IsReadableOrgID

func (*PrivilegesDesc) IsValid

func (desc *PrivilegesDesc) IsValid() bool

IsValid

func (*PrivilegesDesc) IsWritableOrgID

func (desc *PrivilegesDesc) IsWritableOrgID(orgID uint) bool

IsWritableOrgID

func (*PrivilegesDesc) NotRootUser

func (desc *PrivilegesDesc) NotRootUser() bool

NotRootUser

type REContext

type REContext struct {
	Event   RepeatEvent
	Current int
	Max     int
	Data    map[string]interface{}
}

func (*REContext) Name

func (context *REContext) Name() string

type RepeatEvent

type RepeatEvent struct {
	Model         `rest:"*" displayName:"重试事件"`
	Name          string     `name:"任务类型"`
	EventID       string     `name:"任务ID"`
	RetryInterval string     `name:"重试间隔"`
	NextTime      *time.Time `name:"下次重试时间"`
	RetryCount    int        `name:"重试次数"`
	Status        string     `name:"状态" enum:"RepeatEventStatus"`
	Message       string     `name:"错误消息" gorm:"type:text"`
	Data          string     `name:"上下文数据" gorm:"type:text"`
}

type RepeatEventProcesser

type RepeatEventProcesser func(*REContext, map[string]interface{}) error

type RestDesc

type RestDesc struct {
	Create bool
	Delete bool
	Query  bool
	Update bool
	Import bool
}

RestDesc

func RESTful

func RESTful(r *Engine, routePrefix string, value interface{}) (desc *RestDesc)

RESTful

func (*RestDesc) IsValid

func (r *RestDesc) IsValid() bool

IsValid

type Role

type Role struct {
	// 引用Model将无法Preload,故复制字段
	ID          uint       `gorm:"primary_key" rest:"*" displayName:"角色"`
	CreatedAt   time.Time  `name:"创建时间,ISO字符串(默认字段)"`
	UpdatedAt   time.Time  `name:"修改时间,ISO字符串(默认字段)"`
	DeletedAt   *time.Time `name:"删除时间,ISO字符串(默认字段)" sql:"index"`
	Dr          int64      `name:"删除标记(0表示未删除,非0表示删除时间戳)" gorm:"DEFAULT:0;UNIQUE_INDEX:kuu_unique"`
	OrgID       uint       `name:"所属组织ID(默认字段)"`
	CreatedByID uint       `name:"创建人ID(默认字段)"`
	UpdatedByID uint       `name:"修改人ID(默认字段)"`
	DeletedByID uint       `name:"删除人ID(默认字段)"`
	Remark      string     `name:"备注" gorm:"text"`
	Ts          time.Time  `name:"时间戳"`
	Org         *Org       `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:org_id"`
	CreatedBy   *User      `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:created_by_id"`
	UpdatedBy   *User      `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:updated_by_id"`
	DeletedBy   *User      `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:deleted_by_id"`

	ExtendField
	Code                string                `name:"角色编码" gorm:"not null;UNIQUE_INDEX:kuu_unique"`
	Name                string                `name:"角色名称" gorm:"not null"`
	OperationPrivileges []OperationPrivileges `name:"角色操作权限"`
	DataPrivileges      []DataPrivileges      `name:"角色数据权限"`
	IsBuiltIn           null.Bool             `name:"是否内置"`
}

Role

func RootRole

func RootRole() *Role

RootRole

type RoleAssign

type RoleAssign struct {
	ModelExOrg `displayName:"用户角色分配"`
	ExtendField
	UserID     uint `name:"用户ID" gorm:"not null"`
	RoleID     uint `name:"角色ID" gorm:"not null"`
	Role       *Role
	ExpireUnix int64
}

RoleAssign

type RouteInfo

type RouteInfo struct {
	Method         string
	Path           string
	Name           string
	HandlerFunc    HandlerFunc
	IgnorePrefix   bool
	Description    string
	Tags           []string
	SignType       []string
	RequestParams  route.RequestParams
	ResponseParams route.ResponseParams
	IntlMessages   map[string]string
	IntlWithCode   bool
}

RouteInfo represents a request route's specification which contains method and path and its handler.

type RoutesInfo

type RoutesInfo []RouteInfo

RoutesInfo defines a RouteInfo array.

type RoutineCaches

type RoutineCaches map[string]interface{}

RoutineCaches

func GetRoutineCaches

func GetRoutineCaches() RoutineCaches

GetRoutineCaches

func (RoutineCaches) IgnoreAuth

func (v RoutineCaches) IgnoreAuth(cancel ...bool)

IgnoreAuth

type STDReply

type STDReply struct {
	HTTPAction func(code int, jsonObj interface{}) `json:"-"`
	HTTPCode   int                                 `json:"-"`
	Code       int                                 `json:"code"`
	Data       interface{}                         `json:"data,omitempty"`
	Message    string                              `json:"msg,omitempty"`
}

func AuthMiddleware

func AuthMiddleware(c *Context) *STDReply

AuthMiddleware

func (*STDReply) MarshalJSON

func (s *STDReply) MarshalJSON() ([]byte, error)

type Scope

type Scope struct {
	Value       interface{}
	Meta        *Metadata
	ReflectType reflect.Type
	Context     *Context
	DB          *gorm.DB

	QueryResult  *BizQueryResult
	UpdateCond   interface{}
	UpdateParams *BizUpdateParams
	// contains filtered or unexported fields
}

Scope

func NewBizScope

func NewBizScope(c *Context, value interface{}, db *gorm.DB) *Scope

NewBizScope

func (*Scope) CallMethod

func (scope *Scope) CallMethod(methodName string)

CallMethod

func (*Scope) Err

func (scope *Scope) Err(err error) error

Err

func (*Scope) HasError

func (scope *Scope) HasError() bool

HasError

func (*Scope) IndirectValue

func (scope *Scope) IndirectValue() reflect.Value

IndirectValue

func (*Scope) SkipLeft

func (scope *Scope) SkipLeft()

SkipLeft skip remaining callbacks

type SignContext

type SignContext struct {
	Token    string
	Type     string
	Lang     string
	UID      uint
	Username string
	SubDocID uint
	Payload  jwt.MapClaims
	Secret   *SignSecret
}

SignContext

func (*SignContext) IsValid

func (s *SignContext) IsValid() (ret bool)

IsValid

type SignHistory

type SignHistory struct {
	gorm.Model `rest:"*" displayName:"登录历史"`
	UID        uint   `name:"用户ID"`
	SecretID   uint   `name:"密钥ID"`
	SecretData string `name:"密钥"`
	Token      string `name:"令牌" gorm:"NOT NULL;INDEX:kuu_token;size:767"`
	Method     string `name:"登录/登出"`
}

SignHistory

type SignSecret

type SignSecret struct {
	gorm.Model `rest:"*" displayName:"令牌密钥"`
	UID        uint      `name:"用户ID"`
	Username   string    `name:"用户账号"`
	SubDocID   uint      `name:"扩展档案ID"`
	Desc       string    `name:"令牌描述"`
	Secret     string    `name:"令牌密钥"`
	Payload    string    `name:"令牌数据(JSON-String)" gorm:"type:text"`
	Token      string    `name:"令牌" gorm:"NOT NULL;INDEX:kuu_token;size:767"`
	Iat        int64     `name:"令牌签发时间戳"`
	Exp        int64     `name:"令牌过期时间戳"`
	Method     string    `name:"登录/登出"`
	IsAPIKey   null.Bool `name:"是否API Key"`
	Type       string    `name:"令牌类型"`
}

SignSecret

func GenToken

func GenToken(desc GenTokenDesc) (secretData *SignSecret, err error)

GenToken

func GetSignSecret

func GetSignSecret(token string, tx ...*gorm.DB) (*SignSecret, error)

type User

type User struct {
	// 引用Model将无法Preload,故复制字段
	ID          uint       `gorm:"primary_key" rest:"*" displayName:"用户"`
	CreatedAt   time.Time  `name:"创建时间,ISO字符串(默认字段)"`
	UpdatedAt   time.Time  `name:"修改时间,ISO字符串(默认字段)"`
	DeletedAt   *time.Time `name:"删除时间,ISO字符串(默认字段)" sql:"index"`
	Dr          int64      `name:"删除标记(0表示未删除,非0表示删除时间戳)" gorm:"DEFAULT:0;UNIQUE_INDEX:kuu_unique"`
	OrgID       uint       `name:"所属组织ID(默认字段)"`
	CreatedByID uint       `name:"创建人ID(默认字段)"`
	UpdatedByID uint       `name:"修改人ID(默认字段)"`
	DeletedByID uint       `name:"删除人ID(默认字段)"`
	Remark      string     `name:"备注" gorm:"text"`
	Ts          time.Time  `name:"时间戳"`
	Org         *Org       `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:org_id"`
	CreatedBy   *User      `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:created_by_id"`
	UpdatedBy   *User      `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:updated_by_id"`
	DeletedBy   *User      `gorm:"association_autoupdate:false;association_autocreate:false;foreignkey:id;association_foreignkey:deleted_by_id"`

	ExtendField
	Username               string       `name:"账号" gorm:"not null;UNIQUE_INDEX:kuu_unique"`
	Password               string       `name:"密码" gorm:"not null" json:",omitempty" kuu:"password"`
	Name                   string       `name:"姓名"`
	Avatar                 string       `name:"头像"`
	Sex                    int          `name:"性别"`
	Mobile                 string       `name:"手机号"`
	Email                  string       `name:"邮箱地址"`
	Disable                null.Bool    `name:"是否禁用"`
	RoleAssigns            []RoleAssign `name:"已分配角色"`
	IsBuiltIn              null.Bool    `name:"是否内置"`
	SubDocIDs              string       `name:"扩展档案ID映射(令牌类型为键、ID为值的JSON字符串,如{\"ADMIN\":3,\"WECHAT:COMPANY\":2})"`
	Lang                   string       `name:"最近使用语言"`
	DenyLogin              null.Bool    `name:"禁止登录"`
	ActOrgID               uint         `name:"当前组织"`
	LastChangePasswordTime *time.Time   `name:"最后修改密码时间"`
}

User

func GetUserFromCache

func GetUserFromCache(uid uint) (user User)

GetUserFromCache

func RootUser

func RootUser() *User

RootUser

func (*User) AfterDelete

func (u *User) AfterDelete()

AfterDelete

func (*User) BeforeSave

func (u *User) BeforeSave(scope *gorm.Scope) (err error)

BeforeSave

func (*User) DelSubDocID

func (u *User) DelSubDocID(signType string) error

DelSubDocID

func (*User) GetSubDocID

func (u *User) GetSubDocID(signType string) (uint, error)

GetSubDocID

func (*User) GetSubDocIDs

func (u *User) GetSubDocIDs() (v map[string]uint, err error)

GetSubDocIDs

func (*User) SetSubDocID

func (u *User) SetSubDocID(signType string, subDocID uint) error

SetSubDocIDs

func (*User) SetSubDocIDs

func (u *User) SetSubDocIDs(v map[string]uint)

SetSubDocIDs

type ValidError

type ValidError struct {
	Resource interface{}
	Column   string
	Message  string
}

ValidError is a validation error struct that hold model, column and error message

func (ValidError) Error

func (err ValidError) Error() string

ValidError show error message

func (ValidError) Label

func (err ValidError) Label() string

ValidError is a label including model type, primary key and column name

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL