chore: 并入 iotlcm-openapi 历史到 openapi/(原提交 3b05d5a)
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
// package xgorm
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <xxx@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package xgorm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/utility"
|
||||
"slices"
|
||||
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
var (
|
||||
descTags = []string{"description", "dc", "json"} // 实体描述标签
|
||||
fieldTags = []string{"json"} // 实体字段名称映射
|
||||
)
|
||||
|
||||
// GetFieldsToSlice 获取dao实例中的所有字段
|
||||
func GetFieldsToSlice(ctx context.Context, dao daoInstance) ([]string, error) {
|
||||
fields, err := dao.Ctx(ctx).TableFields(dao.Table())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(fields) == 0 {
|
||||
return nil, gerror.New("field not found")
|
||||
}
|
||||
|
||||
var keys []string
|
||||
for _, field := range fields {
|
||||
keys = append(keys, field.Name)
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// GetPkField 获取dao实例中的主键名称
|
||||
func GetPkField(ctx context.Context, dao daoInstance) (string, error) {
|
||||
fields, err := dao.Ctx(ctx).TableFields(dao.Table())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(fields) == 0 {
|
||||
return "", gerror.New("field not found")
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
if field.Key == "PRI" {
|
||||
return field.Name, nil
|
||||
}
|
||||
}
|
||||
return "", gerror.New("no primary key")
|
||||
}
|
||||
|
||||
// IsUnique 是否唯一
|
||||
func IsUnique(ctx context.Context, dao daoInstance, where g.Map, message string, pkId ...interface{}) error {
|
||||
if len(where) == 0 {
|
||||
return gerror.New("where condition cannot be empty")
|
||||
}
|
||||
|
||||
m := dao.Ctx(ctx).Where(where)
|
||||
if len(pkId) > 0 {
|
||||
field, err := GetPkField(ctx, dao)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m = m.WhereNot(field, pkId[0])
|
||||
}
|
||||
|
||||
count, err := m.Count(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
if message == "" {
|
||||
for k := range where {
|
||||
message = fmt.Sprintf("in the table:%s, %v not uniqued", dao.Table(), where[k])
|
||||
break
|
||||
}
|
||||
}
|
||||
return gerror.New(message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func JoinFields(ctx context.Context, entity interface{}, dao daoInstance, as string) (fs string) {
|
||||
entityFs, err := GetEntityFieldTags(entity)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "JoinFields err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(entityFs) == 0 {
|
||||
g.Log().Errorf(ctx, "JoinFields entityFs is empty")
|
||||
return ""
|
||||
}
|
||||
|
||||
fields, err := dao.Ctx(ctx).TableFields(dao.Table())
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "JoinFields err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var columns []string
|
||||
for _, v := range entityFs {
|
||||
if !gstr.HasPrefix(v, as) {
|
||||
g.Log().Infof(ctx, "JoinFields gstr.HasPrefix: %+v, %+v", v, as)
|
||||
continue
|
||||
}
|
||||
g.Log().Infof(ctx, "JoinFields gstr.HasPrefix2: %+v, %+v", v, as)
|
||||
field := gstr.CaseSnakeFirstUpper(gstr.StrEx(v, as))
|
||||
if _, ok := fields[field]; ok {
|
||||
columns = append(columns, fmt.Sprintf("`%s`.`%s` as `%s`", dao.Table(), field, v))
|
||||
}
|
||||
g.Log().Infof(ctx, "JoinFields field: %+v fields: %+v", field, fields)
|
||||
}
|
||||
|
||||
if len(columns) > 0 {
|
||||
g.Log().Infof(ctx, "JoinFields columns: %+v", columns)
|
||||
return gstr.Implode(",", utility.UniqueSlice(columns))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetEntityFieldTags 获取实体中的字段名称
|
||||
func GetEntityFieldTags(entity interface{}) (tags []string, err error) {
|
||||
var formRef = reflect.TypeOf(entity)
|
||||
for i := 0; i < formRef.NumField(); i++ {
|
||||
field := formRef.Field(i)
|
||||
if field.Type.Kind() == reflect.Struct {
|
||||
addTags, err := reflectTag(field.Type, fieldTags, []string{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tags = append(tags, addTags...)
|
||||
continue
|
||||
}
|
||||
tags = append(tags, reflectTagName(field, fieldTags, true))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// reflectTag 层级递增解析tag
|
||||
func reflectTag(reflectType reflect.Type, filterTags []string, tags []string) ([]string, error) {
|
||||
if reflectType.Kind() == reflect.Ptr {
|
||||
return nil, gerror.Newf("reflect type do not support reflect.Ptr yet, reflectType:%+v", reflectType)
|
||||
}
|
||||
if reflectType.Kind() != reflect.Struct {
|
||||
return nil, nil
|
||||
}
|
||||
for i := 0; i < reflectType.NumField(); i++ {
|
||||
tag := reflectTagName(reflectType.Field(i), filterTags, false)
|
||||
if tag == "" {
|
||||
addTags, err := reflectTag(reflectType.Field(i).Type, filterTags, tags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tags = append(tags, addTags...)
|
||||
continue
|
||||
}
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// reflectTagName 解析实体中的描述标签,优先级:description > dc > json > Name
|
||||
func reflectTagName(field reflect.StructField, filterTags []string, isDef bool) string {
|
||||
if slices.Contains(filterTags, "description") {
|
||||
if description, ok := field.Tag.Lookup("description"); ok && description != "" {
|
||||
return description
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(filterTags, "dc") {
|
||||
if dc, ok := field.Tag.Lookup("dc"); ok && dc != "" {
|
||||
return dc
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(filterTags, "json") {
|
||||
if jsonName, ok := field.Tag.Lookup("json"); ok && jsonName != "" {
|
||||
return jsonName
|
||||
}
|
||||
}
|
||||
|
||||
if !isDef {
|
||||
return ""
|
||||
}
|
||||
return field.Name
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
// package xgorm
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <xxx@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package xgorm
|
||||
|
||||
import (
|
||||
"context"
|
||||
gormmodel "git.5iot.com/iotlcm-public/iotlcm-openapi/internal/library/xgorm/gorm_model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/utility/tree"
|
||||
"slices"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
const (
|
||||
idDefaulField = "id"
|
||||
pidDefaulField = "pid"
|
||||
levelDefaulField = "level"
|
||||
treeDefaulField = "tree"
|
||||
)
|
||||
|
||||
type TreeFiledOption struct {
|
||||
IdField string
|
||||
PidField string
|
||||
LevelField string
|
||||
TreeField string
|
||||
}
|
||||
|
||||
type daoInstance interface {
|
||||
Table() string
|
||||
Ctx(ctx context.Context) *gdb.Model
|
||||
}
|
||||
|
||||
func CustomTreeFiledToDefault(old map[string]interface{}, option *TreeFiledOption) *gormmodel.DefaultTree {
|
||||
if option == nil {
|
||||
option = &TreeFiledOption{
|
||||
IdField: idDefaulField,
|
||||
PidField: pidDefaulField,
|
||||
LevelField: levelDefaulField,
|
||||
TreeField: treeDefaulField,
|
||||
}
|
||||
}
|
||||
pd := &gormmodel.DefaultTree{}
|
||||
if id, ok := old[option.IdField]; ok {
|
||||
pd.Id = gconv.Int64(id)
|
||||
}
|
||||
if pid, ok := old[option.PidField]; ok {
|
||||
pd.Pid = gconv.Int64(pid)
|
||||
}
|
||||
if level, ok := old[option.LevelField]; ok {
|
||||
pd.Level = gconv.Int(level)
|
||||
}
|
||||
if tree, ok := old[option.TreeField]; ok {
|
||||
pd.Tree = gconv.String(tree)
|
||||
}
|
||||
return pd
|
||||
}
|
||||
|
||||
// AutoUpdateTree 自动更新关系树
|
||||
func AutoUpdateTree(ctx context.Context, dao daoInstance, id, pid int64, option *TreeFiledOption) (newPid int64, newLevel int, newTree string, err error) {
|
||||
if option == nil {
|
||||
option = &TreeFiledOption{
|
||||
IdField: idDefaulField,
|
||||
PidField: pidDefaulField,
|
||||
LevelField: levelDefaulField,
|
||||
TreeField: treeDefaulField,
|
||||
}
|
||||
}
|
||||
if err = CheckTreeTable(ctx, dao, option); err != nil {
|
||||
return
|
||||
}
|
||||
if pid <= 0 {
|
||||
newPid = 0
|
||||
newLevel = 1
|
||||
newTree = ""
|
||||
} else {
|
||||
var pdMap map[string]interface{}
|
||||
row, err := dao.Ctx(ctx).Fields(option.IdField, option.PidField, option.LevelField, option.TreeField).WherePri(pid).One()
|
||||
if err != nil {
|
||||
return 0, 0, "", err
|
||||
}
|
||||
if row != nil {
|
||||
pdMap = row.Map()
|
||||
}
|
||||
|
||||
if len(pdMap) == 0 {
|
||||
return 0, 0, "", gerror.New("未查询到树表上级信息,请检查!")
|
||||
}
|
||||
pd := CustomTreeFiledToDefault(pdMap, option)
|
||||
|
||||
if id > 0 && slices.Contains(tree.GetIds(pd.Tree), id) {
|
||||
return 0, 0, "", gerror.New("上级不能设为自己当前的子级!")
|
||||
}
|
||||
|
||||
newPid = pid
|
||||
newLevel = pd.Level + 1
|
||||
newTree = tree.GenLabel(pd.Tree, pid)
|
||||
}
|
||||
|
||||
if id > 0 {
|
||||
if pid == id {
|
||||
return 0, 0, "", gerror.New("上级不能是自己!")
|
||||
}
|
||||
|
||||
var models *gormmodel.DefaultTree
|
||||
if err = dao.Ctx(ctx).WherePri(id).Scan(&models); err != nil {
|
||||
return 0, 0, "", err
|
||||
}
|
||||
|
||||
if models == nil {
|
||||
return 0, 0, "", gerror.New("树表信息不存在,请检查!")
|
||||
}
|
||||
// g.Log().Infof(ctx, "models: %+v, pid: %v, newPid: %v, newLevel: %v, newTree: %v", models, pid, newPid, newLevel, newTree)
|
||||
// 上级发生变化时,遍历修改其所有的下级关系树
|
||||
if models.Pid != pid {
|
||||
if err = updateChildrenTree(ctx, dao, models.Id, newLevel, newTree, option); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CheckTreeTable 检查树表
|
||||
func CheckTreeTable(ctx context.Context, dao daoInstance, option *TreeFiledOption) (err error) {
|
||||
fields, err := GetFieldsToSlice(ctx, dao)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !slices.Contains(fields, option.PidField) {
|
||||
return gerror.New("树表必须包含`pid`字段")
|
||||
}
|
||||
|
||||
if !slices.Contains(fields, option.LevelField) {
|
||||
return gerror.New("树表必须包含`level`字段")
|
||||
}
|
||||
|
||||
if !slices.Contains(fields, option.TreeField) {
|
||||
return gerror.New("树表必须包含`tree`字段")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// updateChildrenTree 更新下级关系树
|
||||
func updateChildrenTree(ctx context.Context, dao daoInstance, pid int64, pLevel int, pTree string, option *TreeFiledOption) (err error) {
|
||||
var list []*gormmodel.DefaultTree
|
||||
// var pdMaps []map[string]interface{}
|
||||
pdMaps, err := dao.Ctx(ctx).Fields(option.IdField, option.PidField, option.LevelField, option.TreeField).Where(option.PidField, pid).All()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range pdMaps {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
pb := CustomTreeFiledToDefault(v.Map(), option)
|
||||
if pb.Id == 0 {
|
||||
g.Log().Warningf(ctx, "updateChildrenTree pb.Id is 0, pb: %+v, option: %+v, v: %+v, pdMaps: %+v", pb, option, v, pdMaps)
|
||||
continue
|
||||
}
|
||||
// g.Log().Debugf(ctx, "updateChildrenTree pb: %+v", pb)
|
||||
list = append(list, pb)
|
||||
}
|
||||
|
||||
if len(list) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
newLevel := pLevel + 1
|
||||
newTree := tree.GenLabel(pTree, pid)
|
||||
|
||||
var updateIds []int64
|
||||
for _, v := range list {
|
||||
updateIds = append(updateIds, v.Id)
|
||||
if err = updateChildrenTree(ctx, dao, v.Id, newLevel, newTree, option); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(updateIds) > 0 {
|
||||
update := g.Map{
|
||||
option.LevelField: newLevel,
|
||||
option.TreeField: newTree,
|
||||
}
|
||||
_, err = dao.Ctx(ctx).WhereIn(option.IdField, updateIds).Data(update).Update()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// package gormmodel
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <xxx@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package gormmodel
|
||||
|
||||
// DefaultTree 默认树表字段
|
||||
type DefaultTree struct {
|
||||
Id int64 `json:"id" description:"ID"`
|
||||
Pid int64 `json:"pid" description:"父ID"`
|
||||
Level int `json:"level" description:"关系树等级"`
|
||||
Tree string `json:"tree" description:"关系树"`
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// package handler
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <xxx@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/utility/convert"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// FilterAuth 过滤数据权限
|
||||
// 通过上下文中的用户角色权限和表中是否含有需要过滤的字段附加查询条件
|
||||
func FilterAuth(m *gdb.Model) *gdb.Model {
|
||||
var (
|
||||
ctx = m.GetCtx()
|
||||
fields = convert.EscapeFieldsToSlice(m.GetFieldsStr())
|
||||
)
|
||||
|
||||
// 超管拥有全部权限
|
||||
if contexts.IsSuperAdmin(ctx) {
|
||||
return m
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
user := contexts.GetUser(ctx)
|
||||
if user == nil {
|
||||
return m
|
||||
}
|
||||
if len(user.Roles) == 0 {
|
||||
return m
|
||||
}
|
||||
|
||||
deptIds := make([]int64, 0)
|
||||
userIds := make([]int64, 0)
|
||||
// 是否进入了非「全部数据」的受限范围;解析结果为空时必须拒绝,不能漏过滤
|
||||
hasRestrictedScope := false
|
||||
currentRoleId := ""
|
||||
currentRoleDataScope := ""
|
||||
currentRoleIdIf := contexts.GetDataValue(ctx, "currentRoleId")
|
||||
if currentRoleIdIf != nil {
|
||||
currentRoleId = currentRoleIdIf.(string)
|
||||
}
|
||||
currentRoleDataScopeIf := contexts.GetDataValue(ctx, "currentRoleDataScope")
|
||||
if currentRoleDataScopeIf != nil {
|
||||
currentRoleDataScope = currentRoleDataScopeIf.(string)
|
||||
}
|
||||
if len(currentRoleDataScope) > 0 && len(currentRoleId) > 0 {
|
||||
dataScope := currentRoleDataScope
|
||||
roleId := gconv.Int64(currentRoleId)
|
||||
g.Log().Infof(ctx, "uid:%d, currentRoleId: %d, currentRoleDataScope: %s", user.ID, roleId, dataScope)
|
||||
if dataScope == consts.SysRoleDataScopeAll { // 角色数据范围: 1全部数据权限
|
||||
return m
|
||||
}
|
||||
hasRestrictedScope = true
|
||||
if dataScope == consts.SysRoleDataScopeCustom { // 角色数据范围: 2自定数据权限
|
||||
var roleDepts []*entity.SysRoleDept
|
||||
err := g.Model("sys_role_dept").Where("tenant_id", contexts.GetTenantId(ctx)).Where("role_id", roleId).Scan(&roleDepts)
|
||||
if err != nil {
|
||||
g.Log().Panicf(ctx, "failed to get role dept data err:%+v", err)
|
||||
}
|
||||
for _, roleDept := range roleDepts {
|
||||
deptIds = append(deptIds, roleDept.DeptId)
|
||||
}
|
||||
} else if dataScope == consts.SysRoleDataScopeDept { // 角色数据范围: 3本部门数据权限
|
||||
deptIds = append(deptIds, user.DeptId)
|
||||
} else if dataScope == consts.SysRoleDataScopeDeptAndBelow { // 角色数据范围: 4本部门及以下数据权限
|
||||
deptIds = append(deptIds, GetDeptAndSub(ctx, user.DeptId)...)
|
||||
} else if dataScope == consts.SysRoleDataScopePersonal { // 角色数据范围: 5仅本人数据权限
|
||||
userIds = append(userIds, user.ID)
|
||||
} else if dataScope == consts.SysRoleDataScopeDeptAndBelowOrPersonal { // 角色数据范围: 6本部门及以下或本人数据权限
|
||||
deptIds = append(deptIds, GetDeptAndSub(ctx, user.DeptId)...)
|
||||
userIds = append(userIds, user.ID)
|
||||
} else if dataScope == consts.SysRoleDataScopeOrgAndNextLevel { // 角色数据范围: 7本组织及本组织下一级数据权限
|
||||
deptIds = append(deptIds, GetOrgAndNextLevelDeptIds(ctx, user.DeptId)...)
|
||||
} else if dataScope == consts.SysRoleDataScopeOrgNextLevel { // 角色数据范围: 8本组织下一级数据权限
|
||||
deptIds = append(deptIds, GetOrgNextLevelDeptIds(ctx, user.DeptId)...)
|
||||
}
|
||||
} else {
|
||||
for _, role := range user.Roles {
|
||||
dataScope := role.DataScope
|
||||
roleId := role.RoleId
|
||||
if dataScope == consts.SysRoleDataScopeAll { // 角色数据范围: 1全部数据权限
|
||||
return m
|
||||
} else if dataScope == consts.SysRoleDataScopeCustom { // 角色数据范围: 2自定数据权限
|
||||
hasRestrictedScope = true
|
||||
var roleDepts []*entity.SysRoleDept
|
||||
err := g.Model("sys_role_dept").Where("tenant_id", contexts.GetTenantId(ctx)).Where("role_id", roleId).Scan(&roleDepts)
|
||||
if err != nil {
|
||||
g.Log().Panicf(ctx, "failed to get role dept data err:%+v", err)
|
||||
}
|
||||
for _, roleDept := range roleDepts {
|
||||
deptIds = append(deptIds, roleDept.DeptId)
|
||||
}
|
||||
} else if dataScope == consts.SysRoleDataScopeDept { // 角色数据范围: 3本部门数据权限
|
||||
hasRestrictedScope = true
|
||||
deptIds = append(deptIds, user.DeptId)
|
||||
} else if dataScope == consts.SysRoleDataScopeDeptAndBelow { // 角色数据范围: 4本部门及以下数据权限
|
||||
hasRestrictedScope = true
|
||||
deptIds = append(deptIds, GetDeptAndSub(ctx, user.DeptId)...)
|
||||
} else if dataScope == consts.SysRoleDataScopePersonal { // 角色数据范围: 5仅本人数据权限
|
||||
hasRestrictedScope = true
|
||||
userIds = append(userIds, user.ID)
|
||||
} else if dataScope == consts.SysRoleDataScopeDeptAndBelowOrPersonal { // 角色数据范围: 6本部门及以下或本人数据权限
|
||||
hasRestrictedScope = true
|
||||
deptIds = append(deptIds, GetDeptAndSub(ctx, user.DeptId)...)
|
||||
userIds = append(userIds, user.ID)
|
||||
} else if dataScope == consts.SysRoleDataScopeOrgAndNextLevel { // 角色数据范围: 7本组织及本组织下一级数据权限
|
||||
hasRestrictedScope = true
|
||||
deptIds = append(deptIds, GetOrgAndNextLevelDeptIds(ctx, user.DeptId)...)
|
||||
} else if dataScope == consts.SysRoleDataScopeOrgNextLevel { // 角色数据范围: 8本组织下一级数据权限
|
||||
hasRestrictedScope = true
|
||||
deptIds = append(deptIds, GetOrgNextLevelDeptIds(ctx, user.DeptId)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deptFilterField := ""
|
||||
if gstr.InArray(fields, "dept_id") {
|
||||
deptFilterField = "dept_id"
|
||||
} else if gstr.InArray(fields, "created_dept") {
|
||||
deptFilterField = "created_dept"
|
||||
}
|
||||
userFilterField := ""
|
||||
if gstr.InArray(fields, "user_id") {
|
||||
userFilterField = "user_id"
|
||||
} else if gstr.InArray(fields, "created_by") {
|
||||
userFilterField = "created_by"
|
||||
}
|
||||
|
||||
if len(deptIds) > 0 && len(userIds) > 0 {
|
||||
if deptFilterField != "" && userFilterField != "" {
|
||||
m = m.Where(deptFilterField+" IN (?) OR "+userFilterField+" IN (?)", deptIds, userIds)
|
||||
} else if deptFilterField != "" {
|
||||
m = m.WhereIn(deptFilterField, deptIds)
|
||||
} else if userFilterField != "" {
|
||||
m = m.WhereIn(userFilterField, userIds)
|
||||
}
|
||||
} else if len(deptIds) > 0 && deptFilterField != "" {
|
||||
m = m.WhereIn(deptFilterField, deptIds)
|
||||
} else if len(userIds) > 0 && userFilterField != "" {
|
||||
m = m.WhereIn(userFilterField, userIds)
|
||||
} else if hasRestrictedScope && (deptFilterField != "" || userFilterField != "") {
|
||||
// 例:范围8无下级部门、范围2未配自定义部门 → 可见集合为空,禁止放行全表
|
||||
g.Log().Warningf(ctx, "FilterAuth empty scope result, deny all data. uid:%d, deptIds:%v, userIds:%v", user.ID, deptIds, userIds)
|
||||
m = m.Where("1 = 0")
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// GetParentCompanyDeptId 获取用户所属上级公司组织ID
|
||||
func GetParentCompanyDeptId(ctx context.Context, deptId int64) int64 {
|
||||
if deptId <= 0 {
|
||||
return deptId
|
||||
}
|
||||
var dept entity.SysDept
|
||||
err := g.Model("sys_dept").Where("dept_id", deptId).Scan(&dept)
|
||||
if err != nil || dept.DeptId == 0 {
|
||||
return deptId
|
||||
}
|
||||
if dept.ParentId == 0 || dept.DeptType == 1 {
|
||||
return dept.DeptId
|
||||
}
|
||||
return GetParentCompanyDeptId(ctx, dept.ParentId)
|
||||
}
|
||||
|
||||
// GetDeptDirectSub 获取指定部门的直接下级部门ID
|
||||
func GetDeptDirectSub(ctx context.Context, deptId int64) (ids []int64) {
|
||||
array, err := g.Model("sys_dept").
|
||||
Where("parent_id", deptId).
|
||||
Fields("dept_id").
|
||||
Array()
|
||||
if err != nil {
|
||||
g.Log().Panicf(ctx, "GetDeptDirectSub err:%+v", err)
|
||||
return
|
||||
}
|
||||
for _, v := range array {
|
||||
ids = append(ids, v.Int64())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetOrgAndNextLevelDeptIds 获取本组织及本组织下一级数据权限范围:所属公司组织及直接下级
|
||||
func GetOrgAndNextLevelDeptIds(ctx context.Context, deptId int64) (ids []int64) {
|
||||
orgDeptId := GetParentCompanyDeptId(ctx, deptId)
|
||||
ids = append(ids, orgDeptId)
|
||||
ids = append(ids, GetDeptDirectSub(ctx, orgDeptId)...)
|
||||
return
|
||||
}
|
||||
|
||||
// GetOrgNextLevelDeptIds 获取本组织下一级数据权限范围:所属公司组织的直接下级
|
||||
func GetOrgNextLevelDeptIds(ctx context.Context, deptId int64) (ids []int64) {
|
||||
orgDeptId := GetParentCompanyDeptId(ctx, deptId)
|
||||
return GetDeptDirectSub(ctx, orgDeptId)
|
||||
}
|
||||
|
||||
// GetDeptAndSub 获取指定部门的所有下级,含本部门
|
||||
func GetDeptAndSub(ctx context.Context, deptId int64) (ids []int64) {
|
||||
array, err := g.Model("sys_dept").
|
||||
WhereLike("ancestors", "%,"+fmt.Sprintf("%d", deptId)+",%").
|
||||
Fields("dept_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
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// package handler
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <xxx@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/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,40 @@
|
||||
// package handler
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <xxx@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package 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,
|
||||
FilterTenant: 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,27 @@
|
||||
// package handler
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <xxx@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package handler
|
||||
|
||||
import (
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/utility/convert"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// FilterTenant 过滤多租户数据权限
|
||||
func FilterTenant(m *gdb.Model) *gdb.Model {
|
||||
var (
|
||||
ctx = m.GetCtx()
|
||||
fields = convert.EscapeFieldsToSlice(m.GetFieldsStr())
|
||||
)
|
||||
// 如果包含tenant_id字段,则过滤多租户数据权限
|
||||
if gstr.InArray(fields, "tenant_id") {
|
||||
m = m.Where("tenant_id", contexts.GetTenantId(ctx))
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// package hook
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <xxx@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package hook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/utility"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type MemberSumma struct {
|
||||
UserId int64 `json:"userId" orm:"user_id" description:"用户ID"`
|
||||
UserName string `json:"userName" orm:"user_name" description:"用户账号"`
|
||||
NickName string `json:"nickName" orm:"nick_name" description:"用户昵称"`
|
||||
Avatar string `json:"avatar" orm:"avatar" description:"头像地址"`
|
||||
TenantId string `json:"tenantId" orm:"tenant_id" description:"租户编号"`
|
||||
DeptId int64 `json:"deptId" orm:"dept_id" description:"部门ID"`
|
||||
}
|
||||
|
||||
// MemberSummary 操作人摘要信息
|
||||
var MemberSummary = gdb.HookHandler{
|
||||
Select: func(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) {
|
||||
result, err = in.Next(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
createdByIds []int64
|
||||
updatedByIds []int64
|
||||
deletedByIds []int64
|
||||
memberIds []int64
|
||||
)
|
||||
|
||||
for _, record := range result {
|
||||
if record["created_by"].Int64() > 0 {
|
||||
createdByIds = append(createdByIds, record["created_by"].Int64())
|
||||
}
|
||||
if record["updated_by"].Int64() > 0 {
|
||||
updatedByIds = append(updatedByIds, record["updated_by"].Int64())
|
||||
}
|
||||
if record["deleted_by"].Int64() > 0 {
|
||||
deletedByIds = append(deletedByIds, record["deleted_by"].Int64())
|
||||
}
|
||||
if record["user_id"].Int64() > 0 {
|
||||
memberIds = append(memberIds, record["user_id"].Int64())
|
||||
}
|
||||
}
|
||||
|
||||
memberIds = append(memberIds, createdByIds...)
|
||||
memberIds = append(memberIds, updatedByIds...)
|
||||
memberIds = append(memberIds, deletedByIds...)
|
||||
memberIds = utility.UniqueSlice(memberIds)
|
||||
if len(memberIds) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var members []*MemberSumma
|
||||
if err = g.Model("sys_user").Ctx(ctx).WhereIn("user_id", memberIds).Scan(&members); err != nil {
|
||||
g.Log().Errorf(ctx, "[MemberSummary] 查询用户信息失败: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(members) == 0 {
|
||||
g.Log().Warningf(ctx, "[MemberSummary] 没有找到任何用户信息,用户IDs: %v", memberIds)
|
||||
return
|
||||
}
|
||||
|
||||
findMember := func(id *gvar.Var) *MemberSumma {
|
||||
userId := id.Int64()
|
||||
for _, v := range members {
|
||||
if v.UserId == userId {
|
||||
return v
|
||||
}
|
||||
}
|
||||
// 只在找不到用户时记录警告(避免日志过多)
|
||||
g.Log().Warningf(ctx, "[MemberSummary] 未找到用户ID: %d", userId)
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, record := range result {
|
||||
if record["created_by"].Int64() > 0 {
|
||||
member := findMember(record["created_by"])
|
||||
if member != nil {
|
||||
record["createdBySumma"] = gvar.New(member)
|
||||
}
|
||||
}
|
||||
|
||||
if record["updated_by"].Int64() > 0 {
|
||||
member := findMember(record["updated_by"])
|
||||
if member != nil {
|
||||
record["updatedBySumma"] = gvar.New(member)
|
||||
}
|
||||
}
|
||||
|
||||
if record["deleted_by"].Int64() > 0 {
|
||||
member := findMember(record["deleted_by"])
|
||||
if member != nil {
|
||||
record["deletedBySumma"] = gvar.New(member)
|
||||
}
|
||||
}
|
||||
|
||||
if record["user_id"].Int64() > 0 {
|
||||
member := findMember(record["user_id"])
|
||||
if member != nil {
|
||||
record["memberBySumma"] = gvar.New(member)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user