v0.9.33 修复表格会话数据BUG & 安全更新
This commit is contained in:
parent
d68accee47
commit
6d2cdaaf1b
13 changed files with 373 additions and 50 deletions
|
|
@ -20,5 +20,5 @@ func check(t *testing.T) {
|
|||
for key := range gou.Flows {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
assert.Equal(t, 18, len(keys))
|
||||
assert.Equal(t, 19, len(keys))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,15 +118,20 @@ func (api API) MergeDefaultQueryParam(param gou.QueryParam, i int, sid string) g
|
|||
|
||||
// GetQueryParam 解析参数
|
||||
func GetQueryParam(v interface{}, sid string) gou.QueryParam {
|
||||
fmt.Println("\n==== GetQueryParam ===== SID:", sid)
|
||||
data := map[string]interface{}{}
|
||||
if sid != "" {
|
||||
var err error
|
||||
ss := session.Global().ID(sid)
|
||||
data, err = ss.Dump()
|
||||
utils.Dump(data)
|
||||
if err != nil {
|
||||
xlog.Printf("读取会话信息出错 %s", err.Error())
|
||||
}
|
||||
}
|
||||
fmt.Println("==== GetQueryParam========================")
|
||||
fmt.Println("")
|
||||
|
||||
v = share.Bind(v, maps.Of(data).Dot())
|
||||
param, ok := gou.AnyToQueryParam(v)
|
||||
if !ok {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package share
|
||||
|
||||
// VERSION 版本号
|
||||
const VERSION = "0.9.32"
|
||||
const VERSION = "0.9.33"
|
||||
|
||||
// DOMAIN 许可域(废弃)
|
||||
const DOMAIN = "*.iqka.com"
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ func apiDefaultWhere(model *gou.Model, withs map[string]gou.With, name string, p
|
|||
}
|
||||
|
||||
// apiDefaultSetting 数据表格配置默认值
|
||||
func apiDefaultSetting(table *Table) share.API {
|
||||
func apiDefaultSetting() share.API {
|
||||
return share.API{
|
||||
Name: "setting",
|
||||
Guard: "bearer-jwt",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package table
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/yaoapp/gou"
|
||||
"github.com/yaoapp/kun/any"
|
||||
"github.com/yaoapp/kun/maps"
|
||||
"github.com/yaoapp/kun/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -39,11 +41,17 @@ func ProcessSearch(process *gou.Process) interface{} {
|
|||
}
|
||||
|
||||
// Before Hook
|
||||
process.Args = table.Before(table.Hooks.BeforeSearch, process.Args)
|
||||
process.Args = table.Before(table.Hooks.BeforeSearch, process.Args, process.Sid)
|
||||
|
||||
// 参数表
|
||||
process.ValidateArgNums(4)
|
||||
param := api.MergeDefaultQueryParam(process.ArgsQueryParams(1), 0, process.Sid)
|
||||
|
||||
fmt.Println("\n==== ProcessSearch ============= SID:", process.Sid)
|
||||
utils.Dump(param)
|
||||
fmt.Println("==== END ProcessSearch ===============================================")
|
||||
fmt.Println("")
|
||||
|
||||
page := process.ArgsInt(2, api.DefaultInt(1))
|
||||
pagesize := process.ArgsInt(3, api.DefaultInt(2))
|
||||
|
||||
|
|
@ -51,7 +59,7 @@ func ProcessSearch(process *gou.Process) interface{} {
|
|||
response := gou.NewProcess(api.Process, param, page, pagesize).Run()
|
||||
|
||||
// After Hook
|
||||
return table.After(table.Hooks.AfterSearch, response)
|
||||
return table.After(table.Hooks.AfterSearch, response, process.Sid)
|
||||
}
|
||||
|
||||
// ProcessFind xiang.table.Find
|
||||
|
|
@ -67,7 +75,7 @@ func ProcessFind(process *gou.Process) interface{} {
|
|||
}
|
||||
|
||||
// Before Hook
|
||||
process.Args = table.Before(table.Hooks.BeforeFind, process.Args)
|
||||
process.Args = table.Before(table.Hooks.BeforeFind, process.Args, process.Sid)
|
||||
|
||||
// 参数表
|
||||
process.ValidateArgNums(2)
|
||||
|
|
@ -78,7 +86,7 @@ func ProcessFind(process *gou.Process) interface{} {
|
|||
response := gou.NewProcess(api.Process, id, param).Run()
|
||||
|
||||
// After Hook
|
||||
return table.After(table.Hooks.AfterFind, response)
|
||||
return table.After(table.Hooks.AfterFind, response, process.Sid)
|
||||
}
|
||||
|
||||
// ProcessSave xiang.table.Save
|
||||
|
|
@ -95,7 +103,7 @@ func ProcessSave(process *gou.Process) interface{} {
|
|||
}
|
||||
|
||||
// Before Hook
|
||||
process.Args = table.Before(table.Hooks.BeforeSave, process.Args)
|
||||
process.Args = table.Before(table.Hooks.BeforeSave, process.Args, process.Sid)
|
||||
|
||||
// 参数处理
|
||||
process.ValidateArgNums(2)
|
||||
|
|
@ -104,7 +112,7 @@ func ProcessSave(process *gou.Process) interface{} {
|
|||
response := gou.NewProcess(api.Process, process.Args[1]).Run()
|
||||
|
||||
// After Hook
|
||||
return table.After(table.Hooks.AfterSave, response)
|
||||
return table.After(table.Hooks.AfterSave, response, process.Sid)
|
||||
}
|
||||
|
||||
// ProcessDelete xiang.table.Delete
|
||||
|
|
@ -315,10 +323,10 @@ func ProcessSelect(process *gou.Process) interface{} {
|
|||
}
|
||||
|
||||
// Before Hook
|
||||
process.Args = table.Before(table.Hooks.BeforeSelect, process.Args)
|
||||
process.Args = table.Before(table.Hooks.BeforeSelect, process.Args, process.Sid)
|
||||
|
||||
response := gou.NewProcess(api.Process, process.Args[1:]...).Run()
|
||||
|
||||
// After Hook
|
||||
return table.After(table.Hooks.AfterSelect, response)
|
||||
return table.After(table.Hooks.AfterSelect, response, process.Sid)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ func (table *Table) loadAPIs() {
|
|||
return
|
||||
}
|
||||
defaults := getDefaultAPIs(table.Bind)
|
||||
defaults["setting"] = apiDefaultSetting(table)
|
||||
defaults["setting"] = apiDefaultSetting()
|
||||
|
||||
for name := range table.APIs {
|
||||
if _, has := defaults[name]; !has {
|
||||
|
|
@ -150,7 +150,7 @@ func getDefaultAPIs(bind Bind) map[string]share.API {
|
|||
}
|
||||
|
||||
// Before 运行 Before hook
|
||||
func (table *Table) Before(process string, processArgs []interface{}) []interface{} {
|
||||
func (table *Table) Before(process string, processArgs []interface{}, sid string) []interface{} {
|
||||
if process == "" {
|
||||
return processArgs
|
||||
}
|
||||
|
|
@ -161,7 +161,7 @@ func (table *Table) Before(process string, processArgs []interface{}) []interfac
|
|||
res = append(res, processArgs[0])
|
||||
}
|
||||
|
||||
response, err := gou.NewProcess(process, args...).Exec()
|
||||
response, err := gou.NewProcess(process, args...).WithSID(sid).Exec()
|
||||
if err != nil {
|
||||
xlog.Println("Hook执行失败: ", err.Error(), maps.StrAny{"process": process, "args": args})
|
||||
return processArgs
|
||||
|
|
@ -177,11 +177,11 @@ func (table *Table) Before(process string, processArgs []interface{}) []interfac
|
|||
}
|
||||
|
||||
// After 运行 After hook
|
||||
func (table *Table) After(process string, data interface{}) interface{} {
|
||||
func (table *Table) After(process string, data interface{}, sid string) interface{} {
|
||||
if process == "" {
|
||||
return data
|
||||
}
|
||||
response, err := gou.NewProcess(process, data).Exec()
|
||||
response, err := gou.NewProcess(process, data).WithSID(sid).Exec()
|
||||
if err != nil {
|
||||
xlog.Println("Hook执行失败: ", err.Error(), maps.StrAny{"process": process, "data": data})
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -48,5 +48,5 @@ func check(t *testing.T) {
|
|||
for key := range Tables {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
assert.Equal(t, 8, len(keys))
|
||||
assert.Equal(t, 9, len(keys))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
"name": "象传应用",
|
||||
"short": "象传",
|
||||
"description": "象传应用后台",
|
||||
"option": {
|
||||
"index": "/table/service"
|
||||
},
|
||||
"storage": {
|
||||
"default": "oss",
|
||||
"oss": {
|
||||
|
|
@ -14,6 +17,5 @@
|
|||
},
|
||||
"cos": {},
|
||||
"s3": {}
|
||||
},
|
||||
"option": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
17
tests/flows/hooks/session.flow.json
Normal file
17
tests/flows/hooks/session.flow.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"label": "会话测试",
|
||||
"version": "1.0.0",
|
||||
"description": "Before:Select",
|
||||
"nodes": [
|
||||
{
|
||||
"name": "会话数据",
|
||||
"process": "session.Dump"
|
||||
},
|
||||
{
|
||||
"name": "打印数据",
|
||||
"process": "xiang.helper.Print",
|
||||
"args": ["会话数据:", "{{$res.会话数据}}"]
|
||||
}
|
||||
],
|
||||
"output": "{{$in}}"
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@
|
|||
"select": { "guard": "-" },
|
||||
"search": {
|
||||
"process": "models.service.Paginate",
|
||||
"guard": "-",
|
||||
"default": [null, null, 15]
|
||||
},
|
||||
"find": {
|
||||
|
|
|
|||
305
tests/tables/session.json
Normal file
305
tests/tables/session.json
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
{
|
||||
"name": "会话调试",
|
||||
"version": "1.0.0",
|
||||
"decription": "会话调试",
|
||||
"bind": {
|
||||
"model": "service",
|
||||
"withs": {
|
||||
"manu": {
|
||||
"query": {
|
||||
"select": ["id", "name", "short_name", "status"]
|
||||
}
|
||||
},
|
||||
"kind": {
|
||||
"query": {
|
||||
"select": ["id", "name"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"before:search": "flows.hooks.session",
|
||||
"after:search": "flows.hooks.session"
|
||||
},
|
||||
"apis": {
|
||||
"search": {
|
||||
"default": [
|
||||
{
|
||||
"wheres": [{ "column": "status", "value": "{{user_id}}" }],
|
||||
"withs": { "manu": {}, "kind": {} }
|
||||
},
|
||||
null,
|
||||
10
|
||||
]
|
||||
}
|
||||
},
|
||||
"columns": {
|
||||
"服务名称": {
|
||||
"label": "服务名称",
|
||||
"view": {
|
||||
"type": "label",
|
||||
"props": {
|
||||
"value": ":name"
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"type": "input",
|
||||
"props": {
|
||||
"value": ":name"
|
||||
}
|
||||
}
|
||||
},
|
||||
"所属厂商": {
|
||||
"label": "所属厂商",
|
||||
"view": {
|
||||
"name": "label",
|
||||
"props": {
|
||||
"value": ":manu.short_name"
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"type": "select",
|
||||
"props": {
|
||||
"value": ":manu.id",
|
||||
"searchable": true,
|
||||
"remote": {
|
||||
"api": "/api/manu/get",
|
||||
"query": {
|
||||
"select": ["id", "name"],
|
||||
"keyword": "where_name_like"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"服务类型": {
|
||||
"label": "服务类型",
|
||||
"view": {
|
||||
"name": "label",
|
||||
"props": {
|
||||
"value": ":kind.name"
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"type": "select",
|
||||
"props": {
|
||||
"value": ":kind.id",
|
||||
"tree": true,
|
||||
"searchable": true,
|
||||
"remote": {
|
||||
"api": "/api/kind/tree",
|
||||
"query": {
|
||||
"select": ["id", "name"],
|
||||
"keyword": "where_name_like"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"服务领域": {
|
||||
"label": "服务领域",
|
||||
"view": {
|
||||
"name": "tags",
|
||||
"props": {
|
||||
"value": ":fields"
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"type": "select",
|
||||
"props": {
|
||||
"value": ":fields",
|
||||
"searchable": true,
|
||||
"inputable": true,
|
||||
"multiple": true,
|
||||
"options": [
|
||||
{ "label": "视频会议", "value": "视频会议" },
|
||||
{ "label": "即时通信", "value": "即时通信" },
|
||||
{ "label": "客服管理", "value": "客服管理" },
|
||||
{ "label": "财务管理", "value": "财务管理" },
|
||||
{ "label": "客户关系管理", "value": "客户关系管理" },
|
||||
{ "label": "营销管理", "value": "营销管理" },
|
||||
{ "label": "办公自动化", "value": "办公自动化" },
|
||||
{ "label": "ERP", "value": "ERP" },
|
||||
{ "label": "人力", "value": "人力" },
|
||||
{ "label": "采购", "value": "采购" },
|
||||
{ "label": "供应链", "value": "供应链" },
|
||||
{ "label": "企业网盘", "value": "企业网盘" },
|
||||
{ "label": "邮件服务", "value": "邮件服务" },
|
||||
{ "label": "电子合同", "value": "电子合同" },
|
||||
{ "label": "安全工具", "value": "安全工具" },
|
||||
{ "label": "舆情分析", "value": "舆情分析" },
|
||||
{ "label": "行业应用", "value": "行业应用" },
|
||||
{ "label": "其他", "value": "其他" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"计费方式C": {
|
||||
"label": "计费方式",
|
||||
"view": {
|
||||
"name": "xxxxx",
|
||||
"components": { "label": {}, "image": {} }
|
||||
}
|
||||
},
|
||||
"计费方式": {
|
||||
"label": "计费方式",
|
||||
"view": {
|
||||
"name": "tags",
|
||||
"props": {
|
||||
"value": ":fields",
|
||||
"format": [".jpg", "..."]
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"type": "select",
|
||||
"props": {
|
||||
"value": ":price_options",
|
||||
"multiple": true,
|
||||
"options": [
|
||||
{ "label": "按年订阅", "value": "按年订阅" },
|
||||
{ "label": "按月订阅", "value": "按月订阅" },
|
||||
{ "label": "按量计费", "value": "按量计费" },
|
||||
{ "label": "私有化部署", "value": "私有化部署" },
|
||||
{ "label": "其他", "value": "其他" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"行业覆盖": {
|
||||
"label": "行业覆盖",
|
||||
"view": {
|
||||
"name": "tags",
|
||||
"props": {
|
||||
"value": ":industries"
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"type": "select",
|
||||
"props": {
|
||||
"value": ":industries",
|
||||
"inputable": true,
|
||||
"multiple": true,
|
||||
"options": [
|
||||
{ "label": "房地产", "value": "房地产" },
|
||||
{ "label": "旅游", "value": "旅游" },
|
||||
{ "label": "教育", "value": "教育" },
|
||||
{ "label": "互联网", "value": "互联网" },
|
||||
{ "label": "其他", "value": "其他" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"状态": {
|
||||
"label": "状态",
|
||||
"view": {
|
||||
"name": "tags",
|
||||
"props": {
|
||||
"value": ":status",
|
||||
"options": [
|
||||
{ "label": "开启", "value": "enabled", "color": "primary" },
|
||||
{ "label": "关闭", "value": "disabled", "color": "danger" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"type": "select",
|
||||
"props": {
|
||||
"value": ":status",
|
||||
"options": [
|
||||
{ "label": "开启", "value": "enabled" },
|
||||
{ "label": "关闭", "value": "disabled" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"创建时间": {
|
||||
"label": "创建时间",
|
||||
"view": {
|
||||
"name": "label",
|
||||
"props": {
|
||||
"value": ":created_at",
|
||||
"datetime-format": "YYYY年MM月DD日 @hh:mm:ss"
|
||||
}
|
||||
}
|
||||
},
|
||||
"更新时间": {
|
||||
"label": "更新时间",
|
||||
"view": {
|
||||
"name": "label",
|
||||
"props": {
|
||||
"value": ":update_at",
|
||||
"datetime-format": "YYYY年MM月DD日 @hh:mm:ss"
|
||||
}
|
||||
}
|
||||
},
|
||||
"查询排序": {
|
||||
"label": "查询排序",
|
||||
"view": {
|
||||
"type": "label",
|
||||
"props": {
|
||||
"value": ":rank"
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"type": "input",
|
||||
"props": {
|
||||
"value": ":rank",
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"filters": {},
|
||||
"list": {
|
||||
"primary": "id",
|
||||
"layout": {
|
||||
"columns": [
|
||||
{ "name": "服务名称", "width": 6 },
|
||||
{ "name": "所属厂商", "width": 6 },
|
||||
{ "name": "服务类型", "width": 4 },
|
||||
{ "name": "状态", "width": 4 },
|
||||
{ "name": "服务领域", "width": 4 },
|
||||
{ "name": "计费方式", "width": 4 },
|
||||
{ "name": "创建时间", "width": 6 },
|
||||
{ "name": "更新时间", "width": 6 }
|
||||
],
|
||||
"filters": []
|
||||
},
|
||||
"actions": {}
|
||||
},
|
||||
"edit": {
|
||||
"primary": "id",
|
||||
"layout": {
|
||||
"fieldset": [
|
||||
{
|
||||
"title": "基础信息",
|
||||
"description": "",
|
||||
"columns": [
|
||||
{ "name": "服务名称", "width": 6 },
|
||||
{ "name": "所属厂商", "width": 6 },
|
||||
{ "name": "服务类型", "width": 6 },
|
||||
{ "name": "状态", "width": 6 },
|
||||
{ "name": "rank", "width": 6 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "数据标签",
|
||||
"description": "",
|
||||
"columns": ["服务领域", "计费方式", "行业覆盖"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"actions": {
|
||||
"cancel": {},
|
||||
"save": {
|
||||
"type": "button",
|
||||
"props": {
|
||||
"label": "保存"
|
||||
}
|
||||
},
|
||||
"delete": {}
|
||||
}
|
||||
},
|
||||
"insert": {},
|
||||
"view": {}
|
||||
}
|
||||
45
user/user.go
45
user/user.go
|
|
@ -1,14 +1,15 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/yaoapp/gou"
|
||||
"github.com/yaoapp/gou/session"
|
||||
"github.com/yaoapp/kun/any"
|
||||
"github.com/yaoapp/kun/exception"
|
||||
"github.com/yaoapp/kun/maps"
|
||||
"github.com/yaoapp/xiang/config"
|
||||
"github.com/yaoapp/xiang/helper"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
|
|
@ -52,6 +53,7 @@ func Auth(field string, value string, password string) maps.Map {
|
|||
|
||||
row := rows[0]
|
||||
passwordHash := row.Get("password").(string)
|
||||
row.Del("password")
|
||||
|
||||
err = bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(password))
|
||||
if err != nil {
|
||||
|
|
@ -59,38 +61,23 @@ func Auth(field string, value string, password string) maps.Map {
|
|||
}
|
||||
|
||||
expiresAt := time.Now().Unix() + 3600
|
||||
token := MakeToken(row, expiresAt)
|
||||
row.Del("password")
|
||||
|
||||
// token := MakeToken(row, expiresAt)
|
||||
sid := session.ID()
|
||||
id := any.Of(row.Get("id")).CInt()
|
||||
token := helper.JwtMake(id, map[string]interface{}{}, map[string]interface{}{
|
||||
"expires_at": expiresAt,
|
||||
"sid": sid,
|
||||
})
|
||||
session.Global().Expire(time.Duration(token.ExpiresAt)*time.Second).ID(sid).Set("user_id", id)
|
||||
session.Global().ID(sid).Set("user", row)
|
||||
|
||||
// 读取菜单
|
||||
menus := gou.NewProcess("flows.xiang.menu").Run()
|
||||
return maps.Map{
|
||||
"expires_at": expiresAt,
|
||||
"token": token,
|
||||
"expires_at": token.ExpiresAt,
|
||||
"token": token.Token,
|
||||
"user": row,
|
||||
"menus": menus,
|
||||
}
|
||||
}
|
||||
|
||||
// MakeToken 生成 JWT Token
|
||||
func MakeToken(row maps.Map, ExpiresAt int64) string {
|
||||
claims := &JwtClaims{
|
||||
ID: int(row.Get("id").(int64)),
|
||||
Type: row.Get("type").(string),
|
||||
Name: row.Get("name").(string),
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
Id: fmt.Sprintf("%s_%d", row.Get("type"), row.Get("id")),
|
||||
Subject: fmt.Sprintf("%d", row.Get("id")),
|
||||
ExpiresAt: ExpiresAt,
|
||||
Issuer: fmt.Sprintf("%d", row.Get("id")),
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString([]byte(config.Conf.JWT.Secret))
|
||||
if err != nil {
|
||||
exception.New("生成登录口令失败 %s", 500, err).Throw()
|
||||
}
|
||||
|
||||
return tokenString
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"version": "1.0.0",
|
||||
"description": "数据表格接口API",
|
||||
"group": "xiang/table",
|
||||
"guard": "in-process",
|
||||
"guard": "bearer-jwt",
|
||||
"paths": [
|
||||
{
|
||||
"path": "/:name/search",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue