mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-11-13 04:33:44 +08:00
发布v2.15.1版本,更新内容请查看:https://github.com/bufanyun/hotgo/blob/v2.0/docs/guide-zh-CN/start-update-log.md
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"hotgo/utility/convert"
|
||||
)
|
||||
|
||||
// MemberInfo 后台用户信息
|
||||
@@ -61,3 +62,76 @@ var MemberInfo = gdb.HookHandler{
|
||||
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
|
||||
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["member_id"].Int64() > 0 {
|
||||
memberIds = append(memberIds, record["member_id"].Int64())
|
||||
}
|
||||
}
|
||||
|
||||
memberIds = append(memberIds, createdByIds...)
|
||||
memberIds = append(memberIds, updatedByIds...)
|
||||
memberIds = convert.UniqueSlice(memberIds)
|
||||
if len(memberIds) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var members []*MemberSumma
|
||||
if err = g.Model("admin_member").Ctx(ctx).WhereIn("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["member_id"].Int64() > 0 {
|
||||
record["memberBySumma"] = gvar.New(findMember(record["member_id"]))
|
||||
}
|
||||
}
|
||||
return
|
||||
},
|
||||
}
|
||||
|
||||
288
server/internal/library/hgorm/hook/tenant.go
Normal file
288
server/internal/library/hgorm/hook/tenant.go
Normal file
@@ -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()
|
||||
}
|
||||
67
server/internal/library/hgorm/hook/tenant_test.go
Normal file
67
server/internal/library/hgorm/hook/tenant_test.go
Normal file
@@ -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).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