im 管理后台
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
// Package handler
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/utility/convert"
|
||||
"hotgo/utility/tree"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// FilterAuth 过滤数据权限
|
||||
// 通过上下文中的用户角色权限和表中是否含有需要过滤的字段附加查询条件
|
||||
func FilterAuth(m *gdb.Model) *gdb.Model {
|
||||
var (
|
||||
needAuth bool
|
||||
filterField string
|
||||
fields = convert.EscapeFieldsToSlice(m.GetFieldsStr())
|
||||
)
|
||||
|
||||
// 优先级:created_by > member_id
|
||||
if gstr.InArray(fields, "created_by") {
|
||||
needAuth = true
|
||||
filterField = "created_by"
|
||||
}
|
||||
|
||||
if !needAuth && gstr.InArray(fields, "member_id") {
|
||||
needAuth = true
|
||||
filterField = "member_id"
|
||||
}
|
||||
|
||||
if !needAuth {
|
||||
return m
|
||||
}
|
||||
return m.Handler(FilterAuthWithField(filterField))
|
||||
}
|
||||
|
||||
// FilterAuthWithField 过滤数据权限,设置指定字段
|
||||
func FilterAuthWithField(filterField string) func(m *gdb.Model) *gdb.Model {
|
||||
return func(m *gdb.Model) *gdb.Model {
|
||||
var (
|
||||
role *entity.AdminRole
|
||||
ctx = m.GetCtx()
|
||||
co = contexts.Get(ctx)
|
||||
)
|
||||
|
||||
if co == nil || co.User == nil {
|
||||
return m
|
||||
}
|
||||
|
||||
err := dao.AdminRole.Ctx(ctx).Where(dao.AdminRole.Columns().Id, co.User.RoleId).Scan(&role)
|
||||
if err != nil {
|
||||
g.Log().Panicf(ctx, "failed to role information err:%+v", err)
|
||||
}
|
||||
|
||||
if role == nil {
|
||||
g.Log().Panic(ctx, "failed to role information roleModel == nil")
|
||||
}
|
||||
|
||||
// 超管拥有全部权限
|
||||
if role.Key == consts.SuperRoleKey {
|
||||
return m
|
||||
}
|
||||
|
||||
getDeptIds := func(in interface{}) []gdb.Value {
|
||||
ds, err := dao.AdminMember.Ctx(ctx).Fields(dao.AdminMember.Columns().Id).Where(dao.AdminMember.Columns().DeptId, in).Array()
|
||||
if err != nil {
|
||||
g.Log().Panic(ctx, "failed to get member dept data")
|
||||
}
|
||||
return ds
|
||||
}
|
||||
|
||||
switch role.DataScope {
|
||||
case consts.RoleDataAll: // 全部权限
|
||||
// ...
|
||||
case consts.RoleDataNowDept: // 当前部门
|
||||
m = m.WhereIn(filterField, getDeptIds(co.User.DeptId))
|
||||
case consts.RoleDataDeptAndSub: // 当前部门及以下部门ds
|
||||
m = m.WhereIn(filterField, getDeptIds(GetDeptAndSub(ctx, co.User.DeptId)))
|
||||
case consts.RoleDataDeptCustom: // 自定义部门
|
||||
m = m.WhereIn(filterField, getDeptIds(role.CustomDept.Var().Ints()))
|
||||
case consts.RoleDataSelf: // 仅自己
|
||||
m = m.Where(filterField, co.User.Id)
|
||||
case consts.RoleDataSelfAndSub: // 自己和直属下级
|
||||
m = m.WhereIn(filterField, GetSelfAndSub(ctx, co.User.Id))
|
||||
case consts.RoleDataSelfAndAllSub: // 自己和全部下级
|
||||
m = m.WhereIn(filterField, GetSelfAndAllSub(ctx, co.User.Id))
|
||||
default:
|
||||
g.Log().Panic(ctx, "dataScope is not registered")
|
||||
}
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
||||
// GetDeptAndSub 获取指定部门的所有下级,含本部门
|
||||
func GetDeptAndSub(ctx context.Context, deptId int64) (ids []int64) {
|
||||
array, err := dao.AdminDept.Ctx(ctx).
|
||||
WhereLike(dao.AdminDept.Columns().Tree, "%"+tree.GetIdLabel(deptId)+"%").
|
||||
Fields(dao.AdminDept.Columns().Id).
|
||||
Array()
|
||||
if err != nil {
|
||||
g.Log().Panicf(ctx, "GetDeptAndSub err:%+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range array {
|
||||
ids = append(ids, v.Int64())
|
||||
}
|
||||
|
||||
ids = append(ids, deptId)
|
||||
return
|
||||
}
|
||||
|
||||
// GetSelfAndSub 获取直属下级,包含自己
|
||||
func GetSelfAndSub(ctx context.Context, memberId int64) (ids []int64) {
|
||||
array, err := dao.AdminMember.Ctx(ctx).
|
||||
Where(dao.AdminMember.Columns().Pid, memberId).
|
||||
Fields(dao.AdminMember.Columns().Id).
|
||||
Array()
|
||||
if err != nil {
|
||||
g.Log().Panicf(ctx, "GetSelfAndSub err:%+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range array {
|
||||
ids = append(ids, v.Int64())
|
||||
}
|
||||
|
||||
ids = append(ids, memberId)
|
||||
return
|
||||
}
|
||||
|
||||
// GetSelfAndAllSub 获取全部下级,包含自己
|
||||
func GetSelfAndAllSub(ctx context.Context, memberId int64) (ids []int64) {
|
||||
array, err := dao.AdminMember.Ctx(ctx).
|
||||
WhereLike(dao.AdminMember.Columns().Tree, "%"+tree.GetIdLabel(memberId)+"%").
|
||||
Fields(dao.AdminMember.Columns().Id).
|
||||
Array()
|
||||
if err != nil {
|
||||
g.Log().Panicf(ctx, "GetSelfAndAllSub err:%+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range array {
|
||||
ids = append(ids, v.Int64())
|
||||
}
|
||||
|
||||
ids = append(ids, memberId)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Package handler
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
// ForceCache 强制缓存
|
||||
func ForceCache(m *gdb.Model) *gdb.Model {
|
||||
return m.Cache(gdb.CacheOption{Duration: 0, Force: true})
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Package handler
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package handler
|
||||
|
||||
// handler.
|
||||
import (
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
// Option 预处理选项
|
||||
type Option struct {
|
||||
FilterAuth bool // 过滤权限
|
||||
ForceCache bool // 强制缓存
|
||||
FilterTenant bool // 过滤多租户权限
|
||||
}
|
||||
|
||||
// DefaultOption 默认预处理选项
|
||||
var DefaultOption = &Option{
|
||||
FilterAuth: true,
|
||||
}
|
||||
|
||||
func Model(m *gdb.Model, opt ...*Option) *gdb.Model {
|
||||
var option *Option
|
||||
if len(opt) > 0 {
|
||||
option = opt[0]
|
||||
} else {
|
||||
option = DefaultOption
|
||||
}
|
||||
if option.FilterAuth {
|
||||
m = m.Handler(FilterAuth)
|
||||
}
|
||||
if option.ForceCache {
|
||||
m = m.Handler(ForceCache)
|
||||
}
|
||||
if option.FilterTenant {
|
||||
m = m.Handler(FilterTenant)
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
// Package handler
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package handler
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/model/input/form"
|
||||
"hotgo/utility/convert"
|
||||
"hotgo/utility/db"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// ISorter 排序器接口,实现该接口即可使用Handler匹配排序,支持多字段排序
|
||||
type ISorter interface {
|
||||
GetSorters() []*form.Sorter
|
||||
}
|
||||
|
||||
// Sorter 排序器
|
||||
func Sorter(in ISorter) func(m *gdb.Model) *gdb.Model {
|
||||
return func(m *gdb.Model) *gdb.Model {
|
||||
masterTable, ts := convert.GetModelTable(m)
|
||||
fields, err := m.TableFields(masterTable)
|
||||
if err != nil {
|
||||
g.Log().Panicf(m.GetCtx(), "failed to sorter TableFields err:%+v", err)
|
||||
}
|
||||
|
||||
sorters := in.GetSorters()
|
||||
aliases := extractTableAliases(ts)
|
||||
if len(aliases) > 0 {
|
||||
var newSorters []*form.Sorter
|
||||
var removeIndex []int
|
||||
for as, table := range aliases {
|
||||
// 关联表
|
||||
fds, err := m.TableFields(table)
|
||||
if err != nil {
|
||||
g.Log().Panicf(m.GetCtx(), "failed to sorter TableFields err2:%+v", err)
|
||||
}
|
||||
|
||||
var sorter2 []*form.Sorter
|
||||
for k, sorter := range sorters {
|
||||
as2 := as
|
||||
if gstr.Contains(sorter.ColumnKey, `.`) {
|
||||
as2 += "."
|
||||
}
|
||||
if gstr.HasPrefix(sorter.ColumnKey, as2) {
|
||||
sorter2 = append(sorter2, &form.Sorter{
|
||||
ColumnKey: gstr.Replace(sorter.ColumnKey, as2, ""),
|
||||
Order: sorter.Order,
|
||||
})
|
||||
removeIndex = append(removeIndex, k)
|
||||
}
|
||||
}
|
||||
|
||||
if len(sorter2) > 0 {
|
||||
sorter2 = mappingAndFilterToTableFields(fds, sorter2)
|
||||
for _, v := range sorter2 {
|
||||
v.ColumnKey = db.QuoteField(as, v.ColumnKey)
|
||||
}
|
||||
newSorters = append(newSorters, sorter2...)
|
||||
}
|
||||
}
|
||||
|
||||
// 移除关联表字段
|
||||
sorters = mappingAndFilterToTableFields(fields, removeSorterIndexes(sorters, removeIndex))
|
||||
for _, v := range sorters {
|
||||
v.ColumnKey = db.QuoteField(masterTable, v.ColumnKey)
|
||||
}
|
||||
|
||||
sorters = append(newSorters, sorters...)
|
||||
} else {
|
||||
// 单表
|
||||
sorters = mappingAndFilterToTableFields(fields, sorters)
|
||||
for _, v := range sorters {
|
||||
v.ColumnKey = db.QuoteField(masterTable, v.ColumnKey)
|
||||
}
|
||||
}
|
||||
|
||||
hasSort := false
|
||||
for _, sorter := range sorters {
|
||||
if len(sorter.ColumnKey) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch sorter.Order {
|
||||
case "descend": // 降序
|
||||
hasSort = true
|
||||
m = m.OrderDesc(sorter.ColumnKey)
|
||||
case "ascend": // 升序
|
||||
hasSort = true
|
||||
m = m.OrderAsc(sorter.ColumnKey)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if hasSort {
|
||||
return m
|
||||
}
|
||||
|
||||
// 不存在排序条件,默认使用主表主键做降序排序
|
||||
var pk string
|
||||
for name, field := range fields {
|
||||
if gstr.ContainsI(gstr.ToUpper(field.Key), gstr.ToUpper(consts.GenCodesIndexPK)) {
|
||||
pk = name
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 没有主键
|
||||
if len(pk) == 0 {
|
||||
return m
|
||||
}
|
||||
|
||||
// 存在别名,优先匹配别名
|
||||
if len(aliases) > 0 {
|
||||
for as, table := range aliases {
|
||||
if table == masterTable {
|
||||
return m.OrderDesc(db.QuoteField(as, pk))
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.OrderDesc(db.QuoteField(masterTable, pk))
|
||||
}
|
||||
}
|
||||
|
||||
// extractTableAliases 解析关联条件中的关联表别名
|
||||
func extractTableAliases(ts string) map[string]string {
|
||||
re := regexp.MustCompile("`?([^`\\s]+)`?\\s+AS\\s+`?([^`\\s]+)`?\\s")
|
||||
matches := re.FindAllStringSubmatch(ts, -1)
|
||||
|
||||
result := make(map[string]string)
|
||||
for _, match := range matches {
|
||||
result[match[2]] = match[1]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// removeSorterIndexes 移除指定索引的排序器
|
||||
func removeSorterIndexes(slice []*form.Sorter, indexes []int) []*form.Sorter {
|
||||
removed := make([]*form.Sorter, 0)
|
||||
indexMap := make(map[int]bool)
|
||||
|
||||
for _, index := range indexes {
|
||||
indexMap[index] = true
|
||||
}
|
||||
|
||||
for i, value := range slice {
|
||||
if !indexMap[i] {
|
||||
removed = append(removed, value)
|
||||
}
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
// mappingAndFilterToTableFields 将排序字段映射为实际的表字段
|
||||
func mappingAndFilterToTableFields(fieldsMap map[string]*gdb.TableField, sorters []*form.Sorter) (ser []*form.Sorter) {
|
||||
if len(fieldsMap) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var fields []string
|
||||
for _, v := range sorters {
|
||||
fields = append(fields, v.ColumnKey)
|
||||
}
|
||||
|
||||
fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
|
||||
for k := range fieldsMap {
|
||||
fieldsKeyMap[k] = nil
|
||||
}
|
||||
|
||||
var inputFieldsArray = gstr.SplitAndTrim(gstr.Join(fields, ","), ",")
|
||||
for _, field := range inputFieldsArray {
|
||||
if _, ok := fieldsKeyMap[field]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if !gregex.IsMatchString(`^[\w\-]+$`, field) {
|
||||
continue
|
||||
}
|
||||
|
||||
if foundKey, _ := gutil.MapPossibleItemByKey(fieldsKeyMap, field); foundKey != "" {
|
||||
for _, v := range sorters {
|
||||
if v.ColumnKey == field {
|
||||
v.ColumnKey = foundKey
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sorters
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Package handler
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/hgorm"
|
||||
"hotgo/internal/library/hgorm/handler"
|
||||
"hotgo/internal/model/input/form"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type SorterInput struct {
|
||||
form.Sorters
|
||||
}
|
||||
|
||||
// TestSorterDefault 默认排序
|
||||
func TestSorterDefault(t *testing.T) {
|
||||
in := &SorterInput{} // 不存在排序条件,默认使用主表主键降序排序
|
||||
|
||||
_, err := dao.SysGenCurdDemo.Ctx(gctx.New()).Handler(handler.Sorter(in)).All()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TestSorter 多字段排序
|
||||
func TestSorter(t *testing.T) {
|
||||
in := &SorterInput{
|
||||
Sorters: form.Sorters{
|
||||
Sorters: []*form.Sorter{
|
||||
{
|
||||
ColumnKey: "id",
|
||||
Order: "descend", // 降序
|
||||
},
|
||||
{
|
||||
ColumnKey: "categoryId", // 自动转换为下划线。categoryId -> category_id
|
||||
Order: false, // 不参与排序
|
||||
},
|
||||
{
|
||||
ColumnKey: "created_at",
|
||||
Order: "descend", // 降序
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := dao.SysGenCurdDemo.Ctx(gctx.New()).Handler(handler.Sorter(in)).All()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TestSorterJoinTable 关联表多字段排序
|
||||
func TestSorterJoinTable(t *testing.T) {
|
||||
in := &SorterInput{
|
||||
Sorters: form.Sorters{
|
||||
Sorters: []*form.Sorter{
|
||||
{
|
||||
ColumnKey: "id",
|
||||
Order: "descend", // 降序
|
||||
},
|
||||
{
|
||||
ColumnKey: "categoryId", // 自动转换为下划线。categoryId -> category_id
|
||||
Order: false, // 不参与排序
|
||||
},
|
||||
{
|
||||
ColumnKey: "created_at",
|
||||
Order: "descend", // 降序
|
||||
},
|
||||
{
|
||||
ColumnKey: "testCategoryName", // 自动识别关联表别名。 testCategoryName -> testCategory.name
|
||||
Order: "ascend", // 升序
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := dao.SysGenCurdDemo.Ctx(gctx.New()).
|
||||
LeftJoin(hgorm.GenJoinOnRelation(
|
||||
dao.SysGenCurdDemo.Table(), dao.SysGenCurdDemo.Columns().CategoryId, // 主表表名,关联字段
|
||||
dao.TestCategory.Table(), "testCategory", dao.TestCategory.Columns().Id, // 关联表表名,别名,关联字段
|
||||
)...).
|
||||
Handler(handler.Sorter(in)).All()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Package handler
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/utility/convert"
|
||||
)
|
||||
|
||||
// FilterTenant 过滤多租户数据权限
|
||||
// 根据部门类型识别当前租户、商户、用户身份,过滤只属于自己的数据
|
||||
func FilterTenant(m *gdb.Model) *gdb.Model {
|
||||
var (
|
||||
needAuth bool
|
||||
filterField string
|
||||
fields = convert.EscapeFieldsToSlice(m.GetFieldsStr())
|
||||
ctx = m.GetCtx()
|
||||
)
|
||||
|
||||
// 租户
|
||||
if contexts.IsTenantDept(ctx) && gstr.InArray(fields, "tenant_id") {
|
||||
needAuth = true
|
||||
filterField = "tenant_id"
|
||||
}
|
||||
|
||||
// 商户
|
||||
if contexts.IsMerchantDept(ctx) && gstr.InArray(fields, "merchant_id") {
|
||||
needAuth = true
|
||||
filterField = "merchant_id"
|
||||
}
|
||||
|
||||
// 用户
|
||||
if contexts.IsUserDept(ctx) && gstr.InArray(fields, "user_id") {
|
||||
needAuth = true
|
||||
filterField = "user_id"
|
||||
}
|
||||
|
||||
if !needAuth {
|
||||
return m
|
||||
}
|
||||
|
||||
m = m.Where(filterField, contexts.GetUserId(ctx))
|
||||
return m
|
||||
}
|
||||
Reference in New Issue
Block a user