feat: add Go backend
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
// Package hgorm
|
||||
// @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 hgorm
|
||||
|
||||
// dao.
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"hotgo/utility/convert"
|
||||
"hotgo/utility/db"
|
||||
|
||||
"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/text/gstr"
|
||||
)
|
||||
|
||||
type daoInstance interface {
|
||||
Table() string
|
||||
Ctx(ctx context.Context) *gdb.Model
|
||||
}
|
||||
|
||||
// Join 关联表属性
|
||||
type Join struct {
|
||||
Dao daoInstance // 关联表dao实例
|
||||
Alias string // 别名
|
||||
fields map[string]*gdb.TableField // 表字段列表
|
||||
}
|
||||
|
||||
func LeftJoin(m *gdb.Model, masterTable, masterField, joinTable, alias, onField string) *gdb.Model {
|
||||
return m.LeftJoin(GenJoinOnRelation(masterTable, masterField, joinTable, alias, onField)...)
|
||||
}
|
||||
|
||||
// GenJoinOnRelation 生成关联表关联条件
|
||||
func GenJoinOnRelation(masterTable, masterField, joinTable, alias, onField string) []string {
|
||||
q := db.GetQuoteChar()
|
||||
relation := fmt.Sprintf("%s%s%s.%s%s%s = %s%s%s.%s%s%s", q, alias, q, q, onField, q, q, masterTable, q, q, masterField, q)
|
||||
return []string{joinTable, alias, relation}
|
||||
}
|
||||
|
||||
func JoinFields(ctx context.Context, entity interface{}, dao daoInstance, as string) (fs string) {
|
||||
entityFs, err := convert.GetEntityFieldTags(entity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(entityFs) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
fields, err := dao.Ctx(ctx).TableFields(dao.Table())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var columns []string
|
||||
for _, v := range entityFs {
|
||||
if !gstr.HasPrefix(v, as) {
|
||||
continue
|
||||
}
|
||||
|
||||
field := gstr.CaseSnakeFirstUpper(gstr.StrEx(v, as))
|
||||
if _, ok := fields[field]; ok {
|
||||
columns = append(columns, db.QuoteFieldAs(dao.Table(), field, v))
|
||||
}
|
||||
}
|
||||
|
||||
if len(columns) > 0 {
|
||||
return gstr.Implode(",", convert.UniqueSlice(columns))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GenJoinSelect 生成关联表select
|
||||
// 这里会将实体中的字段驼峰转为下划线于数据库进行匹配,意味着数据库字段必须全部是小写字母+下划线的格式
|
||||
func GenJoinSelect(ctx context.Context, entity interface{}, dao daoInstance, joins []*Join) (allFields string, err error) {
|
||||
var tmpFields []string
|
||||
if len(joins) == 0 {
|
||||
err = gerror.New("JoinFields joins len = 0")
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range joins {
|
||||
v.fields, err = v.Dao.Ctx(ctx).TableFields(v.Dao.Table())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
masterFields, err := dao.Ctx(ctx).TableFields(dao.Table())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
entityFields, err := convert.GetEntityFieldTags(entity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(entityFields) == 0 {
|
||||
return "*", nil
|
||||
}
|
||||
|
||||
// 是否为关联表字段
|
||||
getJoinAttribute := func(field string) (*Join, string) {
|
||||
for _, v := range joins {
|
||||
if gstr.HasPrefix(field, v.Alias) {
|
||||
return v, gstr.CaseSnakeFirstUpper(gstr.StrEx(field, v.Alias))
|
||||
}
|
||||
}
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
for _, field := range entityFields {
|
||||
// 关联表
|
||||
jd, joinField := getJoinAttribute(field)
|
||||
if jd != nil {
|
||||
if _, ok := jd.fields[joinField]; ok {
|
||||
tmpFields = append(tmpFields, db.QuoteFieldAs(jd.Alias, joinField, field))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 主表
|
||||
originalField := gstr.CaseSnakeFirstUpper(field)
|
||||
if _, ok := masterFields[originalField]; ok {
|
||||
tmpFields = append(tmpFields, db.QuoteField(dao.Table(), originalField))
|
||||
continue
|
||||
}
|
||||
}
|
||||
return gstr.Implode(",", convert.UniqueSlice(tmpFields)), 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 strings.ToUpper(field.Key) == "PRI" {
|
||||
return field.Name, nil
|
||||
}
|
||||
}
|
||||
return "", gerror.New("no primary key")
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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])
|
||||
}
|
||||
|
||||
exist, err := m.Exist()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exist {
|
||||
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
|
||||
}
|
||||
|
||||
// FilterKeywordsWithOr 多条件关键词OR查询
|
||||
func FilterKeywordsWithOr(m *gdb.Model, filterColumns map[string]string, keyword string) *gdb.Model {
|
||||
if filterColumns == nil || len(filterColumns) == 0 {
|
||||
return m
|
||||
}
|
||||
|
||||
conditions := make([]string, 0)
|
||||
args := make([]interface{}, 0)
|
||||
|
||||
for col, operator := range filterColumns {
|
||||
val := keyword
|
||||
var condition string
|
||||
switch operator {
|
||||
case "LIKE":
|
||||
condition = fmt.Sprintf("%s LIKE ?", col)
|
||||
val = "%" + keyword + "%"
|
||||
default:
|
||||
condition = fmt.Sprintf("%s = ?", col)
|
||||
}
|
||||
|
||||
conditions = append(conditions, condition)
|
||||
args = append(args, val)
|
||||
}
|
||||
|
||||
filter := fmt.Sprintf("(%s)", strings.Join(conditions, " OR "))
|
||||
return m.Where(filter, args...)
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Package hgorm
|
||||
// @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 hgorm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/utility/tree"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// TenantRelation 租户关系
|
||||
type TenantRelation struct {
|
||||
DeptType string // 部门类型
|
||||
TenantId int64 // 租户ID
|
||||
MerchantId int64 // 商户ID
|
||||
UserId int64 // 用户ID
|
||||
}
|
||||
|
||||
// GetTenantRelation 获取租户关系
|
||||
func GetTenantRelation(ctx context.Context, memberId int64) (tr *TenantRelation, err error) {
|
||||
|
||||
data, err := g.Model(gstr.Join([]string{dao.AdminMember.Table(), "u"}, " ")).Ctx(ctx).
|
||||
LeftJoin(gstr.Join([]string{dao.AdminDept.Table(), "d"}, " "), "u.dept_id=d.id").
|
||||
Fields("u.tree,d.type").
|
||||
Where("u.id", memberId).One()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if data.IsEmpty() {
|
||||
err = gerror.Newf("未找到用户[%v]的租户关系,该用户不存在", memberId)
|
||||
return
|
||||
}
|
||||
|
||||
ids := tree.GetIds(data["tree"].String())
|
||||
|
||||
getRelationId := func(deptType string) (int64, error) {
|
||||
id, err := g.Model(gstr.Join([]string{dao.AdminMember.Table(), "u"}, " ")).Ctx(ctx).
|
||||
LeftJoin(gstr.Join([]string{dao.AdminDept.Table(), "d"}, " "), "u.dept_id=d.id").
|
||||
Fields("u.id").
|
||||
WhereIn("u.id", ids).Where("d.type", deptType).
|
||||
OrderAsc("u.level"). // 确保是第一关系
|
||||
Limit(1).
|
||||
Value()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if id.Int64() < 1 {
|
||||
err = gerror.Newf("未找到有效的租户关系,memberId:%v,deptType:%v", memberId, deptType)
|
||||
return 0, err
|
||||
}
|
||||
return id.Int64(), nil
|
||||
}
|
||||
|
||||
tr = new(TenantRelation)
|
||||
tr.DeptType = data["type"].String()
|
||||
switch tr.DeptType {
|
||||
// 公司
|
||||
case consts.DeptTypeCompany:
|
||||
return
|
||||
// 租户
|
||||
case consts.DeptTypeTenant:
|
||||
tr.TenantId = memberId
|
||||
|
||||
// 商户
|
||||
case consts.DeptTypeMerchant:
|
||||
tr.TenantId, err = getRelationId(consts.DeptTypeTenant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr.MerchantId = memberId
|
||||
// 用户
|
||||
case consts.DeptTypeUser:
|
||||
tr.TenantId, err = getRelationId(consts.DeptTypeTenant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr.MerchantId, err = getRelationId(consts.DeptTypeMerchant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr.UserId = memberId
|
||||
default:
|
||||
err = gerror.Newf("未找到用户[%v]的租户关系,部门类型[%v] 无效", memberId, tr.DeptType)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Package hgorm
|
||||
// @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 hgorm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"hotgo/internal/model"
|
||||
"hotgo/utility/tree"
|
||||
"hotgo/utility/validate"
|
||||
)
|
||||
|
||||
// GenSubTree 生成下级关系树
|
||||
func GenSubTree(ctx context.Context, dao daoInstance, oldPid int64) (newPid int64, newLevel int, subTree string, err error) {
|
||||
if err = CheckTreeTable(ctx, dao); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if oldPid <= 0 {
|
||||
return 0, 1, "", nil
|
||||
}
|
||||
|
||||
var models *model.DefaultTree
|
||||
if err = dao.Ctx(ctx).WherePri(oldPid).Scan(&models); err != nil {
|
||||
return 0, 0, "", err
|
||||
}
|
||||
|
||||
if models == nil {
|
||||
return 0, 0, "", gerror.New("上级信息不存在")
|
||||
}
|
||||
|
||||
newPid = oldPid
|
||||
newLevel = models.Level + 1
|
||||
subTree = tree.GenLabel(models.Tree, oldPid)
|
||||
return
|
||||
}
|
||||
|
||||
// CheckTreeTable 检查树表
|
||||
func CheckTreeTable(ctx context.Context, dao daoInstance) (err error) {
|
||||
fields, err := GetFieldsToSlice(ctx, dao)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !validate.InSlice(fields, "pid") {
|
||||
return gerror.New("树表必须包含`pid`字段")
|
||||
}
|
||||
|
||||
if !validate.InSlice(fields, "level") {
|
||||
return gerror.New("树表必须包含`level`字段")
|
||||
}
|
||||
|
||||
if !validate.InSlice(fields, "tree") {
|
||||
return gerror.New("树表必须包含`tree`字段")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AutoUpdateTree 自动更新关系树
|
||||
func AutoUpdateTree(ctx context.Context, dao daoInstance, id, pid int64) (newPid int64, newLevel int, newTree string, err error) {
|
||||
if err = CheckTreeTable(ctx, dao); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if pid <= 0 {
|
||||
newPid = 0
|
||||
newLevel = 1
|
||||
newTree = ""
|
||||
} else {
|
||||
var pd *model.DefaultTree
|
||||
if err = dao.Ctx(ctx).WherePri(pid).Scan(&pd); err != nil {
|
||||
return 0, 0, "", err
|
||||
}
|
||||
|
||||
if pd == nil {
|
||||
return 0, 0, "", gerror.New("未查询到树表上级信息,请检查!")
|
||||
}
|
||||
|
||||
if id > 0 && validate.InSlice(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 *model.DefaultTree
|
||||
if err = dao.Ctx(ctx).WherePri(id).Scan(&models); err != nil {
|
||||
return 0, 0, "", err
|
||||
}
|
||||
|
||||
if models == nil {
|
||||
return 0, 0, "", gerror.New("树表信息不存在,请检查!")
|
||||
}
|
||||
|
||||
// 上级发生变化时,遍历修改其所有的下级关系树
|
||||
if models.Pid != pid {
|
||||
if err = updateChildrenTree(ctx, dao, models.Id, newLevel, newTree); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// updateChildrenTree 更新下级关系树
|
||||
func updateChildrenTree(ctx context.Context, dao daoInstance, pid int64, pLevel int, pTree string) (err error) {
|
||||
var list []*model.DefaultTree
|
||||
if err = dao.Ctx(ctx).Where("pid", pid).Scan(&list); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
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); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(updateIds) > 0 {
|
||||
update := g.Map{
|
||||
"level": newLevel,
|
||||
"tree": newTree,
|
||||
}
|
||||
_, err = dao.Ctx(ctx).WhereIn("id", updateIds).Data(update).Update()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Package hook
|
||||
// @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 hook
|
||||
|
||||
// hook.
|
||||
@@ -0,0 +1,146 @@
|
||||
// Package hook
|
||||
// @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 hook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/utility/convert"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
// MemberInfo 后台用户信息
|
||||
var MemberInfo = gdb.HookHandler{
|
||||
Select: func(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) {
|
||||
result, err = in.Next(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for i, record := range result {
|
||||
// 部门
|
||||
if !record["dept_id"].IsEmpty() {
|
||||
deptName, err := dao.AdminDept.Ctx(ctx).
|
||||
Fields(dao.AdminDept.Columns().Name).
|
||||
Where(dao.AdminDept.Columns().Id, record["dept_id"]).
|
||||
Value()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
record["deptName"] = deptName
|
||||
}
|
||||
|
||||
// 角色
|
||||
if !record["role_id"].IsEmpty() {
|
||||
roleName, err := dao.AdminRole.Ctx(ctx).
|
||||
Fields(dao.AdminRole.Columns().Name).
|
||||
Where(dao.AdminRole.Columns().Id, record["role_id"]).
|
||||
Value()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
record["roleName"] = roleName
|
||||
}
|
||||
|
||||
if !record["password_hash"].IsEmpty() {
|
||||
record["password_hash"] = gvar.New("")
|
||||
}
|
||||
|
||||
if !record["salt"].IsEmpty() {
|
||||
record["salt"] = gvar.New("")
|
||||
}
|
||||
|
||||
if !record["auth_key"].IsEmpty() {
|
||||
record["auth_key"] = gvar.New("")
|
||||
}
|
||||
|
||||
result[i] = record
|
||||
}
|
||||
return
|
||||
},
|
||||
}
|
||||
|
||||
type MemberSumma struct {
|
||||
Id int64 `json:"id" description:"管理员ID"`
|
||||
RealName string `json:"realName" description:"真实姓名"`
|
||||
Username string `json:"username" description:"帐号"`
|
||||
Avatar string `json:"avatar" description:"头像"`
|
||||
}
|
||||
|
||||
// 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["member_id"].Int64() > 0 {
|
||||
memberIds = append(memberIds, record["member_id"].Int64())
|
||||
}
|
||||
}
|
||||
|
||||
memberIds = append(memberIds, createdByIds...)
|
||||
memberIds = append(memberIds, updatedByIds...)
|
||||
memberIds = append(memberIds, deletedByIds...)
|
||||
memberIds = convert.UniqueSlice(memberIds)
|
||||
if len(memberIds) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var members []*MemberSumma
|
||||
if err = dao.AdminMember.Ctx(ctx).Fields(MemberSumma{}).WhereIn(dao.AdminMember.Columns().Id, memberIds).Scan(&members); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(members) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
findMember := func(id *gvar.Var) *MemberSumma {
|
||||
for _, v := range members {
|
||||
if v.Id == id.Int64() {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, record := range result {
|
||||
if record["created_by"].Int64() > 0 {
|
||||
record["createdBySumma"] = gvar.New(findMember(record["created_by"]))
|
||||
}
|
||||
if record["updated_by"].Int64() > 0 {
|
||||
record["updatedBySumma"] = gvar.New(findMember(record["updated_by"]))
|
||||
}
|
||||
if record["deleted_by"].Int64() > 0 {
|
||||
record["deletedBySumma"] = gvar.New(findMember(record["deleted_by"]))
|
||||
}
|
||||
if record["member_id"].Int64() > 0 {
|
||||
record["memberBySumma"] = gvar.New(findMember(record["member_id"]))
|
||||
}
|
||||
}
|
||||
return
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Package hook
|
||||
// @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 hook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"hotgo/internal/library/location"
|
||||
)
|
||||
|
||||
// CityLabel 城市地区标签
|
||||
var CityLabel = gdb.HookHandler{
|
||||
Select: func(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) {
|
||||
result, err = in.Next(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
parse := func(id int64, index int) {
|
||||
cityLabel, err := location.ParseSimpleRegion(ctx, id)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "hook.CityLabel parse err:%+v", err)
|
||||
}
|
||||
result[index]["cityLabel"] = gvar.New(cityLabel)
|
||||
}
|
||||
|
||||
for i, record := range result {
|
||||
// 优先级: 区 > 市 > 省
|
||||
|
||||
cityId, ok := record["city_id"]
|
||||
if ok && !cityId.IsEmpty() {
|
||||
parse(cityId.Int64(), i)
|
||||
continue
|
||||
}
|
||||
|
||||
provinceId, ok := record["province_id"]
|
||||
if ok && !provinceId.IsEmpty() {
|
||||
parse(cityId.Int64(), i)
|
||||
continue
|
||||
}
|
||||
|
||||
// 以下是默认关联表 省市区字段
|
||||
|
||||
sysLogCityId, ok := record["sysLogCityId"]
|
||||
if ok && !sysLogCityId.IsEmpty() {
|
||||
parse(sysLogCityId.Int64(), i)
|
||||
continue
|
||||
}
|
||||
|
||||
sysLogProvinceId, ok := record["sysLogProvinceId"]
|
||||
if ok && !sysLogProvinceId.IsEmpty() {
|
||||
parse(cityId.Int64(), i)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
// Package hook
|
||||
// @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 hook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"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/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/hgorm"
|
||||
"hotgo/utility/convert"
|
||||
)
|
||||
|
||||
// SaveTenant 自动维护更新租户关系字段
|
||||
// 根据部门类型识别当前租户、商户、用户身份
|
||||
var SaveTenant = gdb.HookHandler{
|
||||
Insert: func(ctx context.Context, in *gdb.HookInsertInput) (result sql.Result, err error) {
|
||||
h, err := newHookSaveTenant(ctx, in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.handle()
|
||||
},
|
||||
Update: func(ctx context.Context, in *gdb.HookUpdateInput) (result sql.Result, err error) {
|
||||
h, err := newHookSaveTenant(ctx, in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.handle()
|
||||
},
|
||||
}
|
||||
|
||||
type hookSaveTenant struct {
|
||||
ctx context.Context
|
||||
in any
|
||||
isNewRecord bool
|
||||
relations map[int64]*hgorm.TenantRelation
|
||||
}
|
||||
|
||||
func newHookSaveTenant(ctx context.Context, in any) (*hookSaveTenant, error) {
|
||||
h := new(hookSaveTenant)
|
||||
h.ctx = ctx
|
||||
h.in = in
|
||||
_, h.isNewRecord = in.(*gdb.HookInsertInput)
|
||||
h.relations = make(map[int64]*hgorm.TenantRelation)
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// getFields 获取表字段
|
||||
func (h *hookSaveTenant) getFields() []string {
|
||||
if h.isNewRecord {
|
||||
in := h.in.(*gdb.HookInsertInput)
|
||||
return convert.EscapeFieldsToSlice(in.Model.GetFieldsStr())
|
||||
}
|
||||
in := h.in.(*gdb.HookUpdateInput)
|
||||
return convert.EscapeFieldsToSlice(in.Model.GetFieldsStr())
|
||||
}
|
||||
|
||||
// getRelation 获取指定用户的租户关系
|
||||
func (h *hookSaveTenant) getRelation(id int64) (tr *hgorm.TenantRelation, err error) {
|
||||
v, ok := h.relations[id]
|
||||
if ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
h.relations[id], err = hgorm.GetTenantRelation(h.ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.relations[id], nil
|
||||
}
|
||||
|
||||
// getData 获取更新数据
|
||||
func (h *hookSaveTenant) getData() any {
|
||||
if h.isNewRecord {
|
||||
in := h.in.(*gdb.HookInsertInput)
|
||||
return in.Data
|
||||
}
|
||||
in := h.in.(*gdb.HookUpdateInput)
|
||||
return in.Data
|
||||
}
|
||||
|
||||
// setData 修改更新数据
|
||||
func (h *hookSaveTenant) setData(data any) {
|
||||
if h.isNewRecord {
|
||||
in := h.in.(*gdb.HookInsertInput)
|
||||
in.Data = data.(gdb.List)
|
||||
return
|
||||
}
|
||||
in := h.in.(*gdb.HookUpdateInput)
|
||||
in.Data = data
|
||||
}
|
||||
|
||||
func (h *hookSaveTenant) next() (result sql.Result, err error) {
|
||||
if h.isNewRecord {
|
||||
in := h.in.(*gdb.HookInsertInput)
|
||||
return in.Next(h.ctx)
|
||||
}
|
||||
in := h.in.(*gdb.HookUpdateInput)
|
||||
return in.Next(h.ctx)
|
||||
}
|
||||
|
||||
// checkRelationConsistent 检查关系是否一致
|
||||
func (h *hookSaveTenant) checkRelationConsistent(tid, mid, uid any) error {
|
||||
var (
|
||||
tenantId = gconv.Int64(tid)
|
||||
merchantId = gconv.Int64(mid)
|
||||
userId = gconv.Int64(uid)
|
||||
)
|
||||
|
||||
// 存在用户,优先用用户开始检查
|
||||
if userId > 0 {
|
||||
tr, err := h.getRelation(userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tenantId > 0 && tr.TenantId != tenantId {
|
||||
err = gerror.Newf("租户[%v]与用户[%v]关系不匹配", tenantId, userId)
|
||||
return err
|
||||
}
|
||||
if merchantId > 0 && tr.MerchantId != merchantId {
|
||||
err = gerror.Newf("商户[%v]与用户[%v]关系不匹配", merchantId, userId)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if merchantId > 0 {
|
||||
tr, err := h.getRelation(merchantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tenantId > 0 && tr.TenantId != tenantId {
|
||||
err = gerror.Newf("租户[%v]与商户[%v]关系不匹配", tenantId, userId)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkRelationSingle 检查单个用户关系
|
||||
func (h *hookSaveTenant) checkRelationSingle(idx any, relation, limitType string) (err error) {
|
||||
id := gconv.Int64(idx)
|
||||
if id < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
ok := false
|
||||
tr, err := h.getRelation(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tr.DeptType != limitType {
|
||||
err = gerror.Newf("用户[%v]关系验证不通过,类型身份不匹配[%v != %v]", id, tr.DeptType, limitType)
|
||||
return
|
||||
}
|
||||
|
||||
relationId := contexts.GetUserId(h.ctx)
|
||||
switch relation {
|
||||
case consts.DeptTypeTenant:
|
||||
if ok = tr.TenantId == relationId; !ok {
|
||||
err = gerror.Newf("%v的租户不是%v", id, relationId)
|
||||
return
|
||||
}
|
||||
case consts.DeptTypeMerchant:
|
||||
if ok = tr.MerchantId == relationId; !ok {
|
||||
err = gerror.Newf("%v的商户不是%v", id, relationId)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 检查用户关系是否有效
|
||||
func (h *hookSaveTenant) checkRelation(deptType string, data gdb.Map) (err error) {
|
||||
switch deptType {
|
||||
// 公司和用户,检查关系是否一致
|
||||
case consts.DeptTypeCompany, consts.DeptTypeUser:
|
||||
if err = h.checkRelationConsistent(data[consts.TenantId], data[consts.MerchantId], data[consts.UserId]); err != nil {
|
||||
return
|
||||
}
|
||||
// 租户,检查商户和用户是否属于自己
|
||||
case consts.DeptTypeTenant:
|
||||
if err = h.checkRelationSingle(data[consts.MerchantId], consts.DeptTypeTenant, consts.DeptTypeMerchant); err != nil {
|
||||
return
|
||||
}
|
||||
if err = h.checkRelationSingle(data[consts.UserId], consts.DeptTypeTenant, consts.DeptTypeUser); err != nil {
|
||||
return
|
||||
}
|
||||
// 商户,检查用户是否属于自己
|
||||
case consts.DeptTypeMerchant:
|
||||
if err = h.checkRelationSingle(data[consts.UserId], consts.DeptTypeMerchant, consts.DeptTypeUser); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h *hookSaveTenant) handle() (result sql.Result, err error) {
|
||||
var (
|
||||
update = make(g.Map)
|
||||
fields = h.getFields()
|
||||
memberId = contexts.GetUserId(h.ctx)
|
||||
deptType = contexts.GetDeptType(h.ctx)
|
||||
tr *hgorm.TenantRelation
|
||||
)
|
||||
|
||||
if memberId == 0 || len(deptType) == 0 {
|
||||
err = gerror.New("缺少用户上下文数据")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 非公司类型,加载自己的租户关系,用于重写关系
|
||||
if !contexts.IsCompanyDept(h.ctx) {
|
||||
tr, err = h.getRelation(memberId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
switch deptType {
|
||||
// 公司
|
||||
case consts.DeptTypeCompany:
|
||||
|
||||
// 租户
|
||||
case consts.DeptTypeTenant:
|
||||
if gstr.InArray(fields, consts.TenantId) {
|
||||
update[consts.TenantId] = tr.TenantId
|
||||
}
|
||||
|
||||
// 商户
|
||||
case consts.DeptTypeMerchant:
|
||||
if gstr.InArray(fields, consts.TenantId) {
|
||||
update[consts.TenantId] = tr.TenantId
|
||||
}
|
||||
if gstr.InArray(fields, consts.MerchantId) {
|
||||
update[consts.MerchantId] = tr.MerchantId
|
||||
}
|
||||
|
||||
// 用户
|
||||
case consts.DeptTypeUser:
|
||||
if gstr.InArray(fields, consts.TenantId) {
|
||||
update[consts.TenantId] = tr.TenantId
|
||||
}
|
||||
if gstr.InArray(fields, consts.MerchantId) {
|
||||
update[consts.MerchantId] = tr.MerchantId
|
||||
}
|
||||
if gstr.InArray(fields, consts.UserId) {
|
||||
update[consts.UserId] = tr.UserId
|
||||
}
|
||||
default:
|
||||
err = gerror.Newf("当前用户部门类型[%v] 找到有效的hook,请检查!", deptType)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch value := h.getData().(type) {
|
||||
case gdb.List:
|
||||
for i, data := range value {
|
||||
if err = h.checkRelation(deptType, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range update {
|
||||
data[k] = v
|
||||
}
|
||||
value[i] = data
|
||||
}
|
||||
h.setData(value)
|
||||
case gdb.Map:
|
||||
if err = h.checkRelation(deptType, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range update {
|
||||
value[k] = v
|
||||
}
|
||||
h.setData(value)
|
||||
}
|
||||
return h.next()
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Package hook
|
||||
// @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 hook_test
|
||||
|
||||
import (
|
||||
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/hgorm/hook"
|
||||
"hotgo/internal/model"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
g.DB().SetDryRun(true)
|
||||
g.DB().SetDebug(true)
|
||||
}
|
||||
|
||||
// TestSaveTenant_User 以用户身份增改购买订单数据
|
||||
func TestSaveTenant_User(t *testing.T) {
|
||||
|
||||
// 设置上下文用户身份为用户
|
||||
ctx := context.WithValue(gctx.New(), consts.ContextHTTPKey, &model.Context{
|
||||
// 为了测试只设置了hook中需要用到的数据
|
||||
User: &model.Identity{
|
||||
Id: 12,
|
||||
DeptType: consts.DeptTypeUser,
|
||||
},
|
||||
})
|
||||
|
||||
cols := dao.AddonHgexampleTenantOrder.Columns()
|
||||
orderSn := grand.Letters(32)
|
||||
|
||||
// 以用户身份插入购买订单数据,自动维护租户关系字段
|
||||
data := g.Map{
|
||||
//cols.TenantId: 8,
|
||||
//cols.MerchantId: 11,
|
||||
//cols.UserId: 12,
|
||||
cols.ProductName: "无线运动耳机",
|
||||
cols.OrderSn: orderSn,
|
||||
cols.Money: 99,
|
||||
cols.Status: consts.PayStatusWait,
|
||||
}
|
||||
|
||||
_, err := dao.AddonHgexampleTenantOrder.Ctx(ctx).Data(data).Hook(hook.SaveTenant).OmitEmptyData().Insert()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 以用户身份插入修改订单数据,自动维护租户关系字段
|
||||
update := g.Map{
|
||||
cols.Status: consts.PayStatusOk,
|
||||
}
|
||||
|
||||
_, err = dao.AddonHgexampleTenantOrder.Ctx(ctx).Where(cols.OrderSn, orderSn).Data(update).Hook(hook.SaveTenant).Update()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user