优化模块租户检查逻辑,重构数据结构并简化代码

This commit is contained in:
2026-01-22 15:04:13 +08:00
committed by 张斌
parent b9f0360447
commit 8f6adbb16d
3 changed files with 51 additions and 110 deletions

View File

@@ -19,27 +19,36 @@ var (
TenantModuleAICs = ModuleAssetId["customerService"] // AI客服模块
)
// TenantModuleType 租户类型
type TenantModuleType struct {
type TenantModuleType string
const (
TenantModuleTypePlatform TenantModuleType = "platform"
TenantModuleTypePrivate TenantModuleType = "private"
TenantModuleTypeSupplier TenantModuleType = "supplier"
TenantModuleTypeSmallShop TenantModuleType = "small_shop"
)
// TenantModuleTypeKV 租户类型
type TenantModuleTypeKV struct {
Key string
Value string
}
// TenantModuleTypesAssets 资产模块租户类型
var TenantModuleTypesAssets = []TenantModuleType{
{Key: "private_cloud", Value: "私有云租户"},
{Key: "supplier", Value: "供应商"},
{Key: "small_shop", Value: "电商小店"},
var TenantModuleTypesAssets = []TenantModuleTypeKV{
{Key: string(TenantModuleTypePrivate), Value: "私租户"},
{Key: string(TenantModuleTypeSupplier), Value: "供应商"},
{Key: string(TenantModuleTypeSmallShop), Value: "电商小店"},
}
// TenantModuleTypesAd 广告模块租户类型(待定)
var TenantModuleTypesAd []TenantModuleType
var TenantModuleTypesAd []TenantModuleTypeKV
// TenantModuleTypesAICs AI客服模块租户类型待定
var TenantModuleTypesAICs []TenantModuleType
var TenantModuleTypesAICs []TenantModuleTypeKV
// GetTenantModuleTypes 获取模块的租户类型列表
func GetTenantModuleTypes(module string) []TenantModuleType {
func GetTenantModuleTypes(module string) []TenantModuleTypeKV {
switch module {
case TenantModuleAssets:
return TenantModuleTypesAssets
@@ -48,7 +57,7 @@ func GetTenantModuleTypes(module string) []TenantModuleType {
case TenantModuleAICs:
return TenantModuleTypesAICs
default:
return []TenantModuleType{}
return []TenantModuleTypeKV{}
}
}
@@ -59,21 +68,14 @@ type ModuleTenantCheckReq struct {
// ModuleTenantCheckRes 调用admin-go设置模块租户关系的响应
type ModuleTenantCheckRes struct {
Status string `json:"status"` // 开通状态activated(已开通)、expired(已到期)、not_activated(未开通)
Status bool `json:"status"`
CertificationStatus bool `json:"certificationStatus"`
Message string `json:"message"` // 状态描述
OpenStatus bool `json:"openStatus"` // 开通状态
}
// ModuleTenant 模块租户关系实体(引用自admin-go)
type ModuleTenant struct {
Id uint64 `json:"id" description:""`
CreateBy uint64 `json:"createBy" description:"创建者"`
UpdateBy uint64 `json:"updateBy" description:"更新者"`
CreatedAt *gtime.Time `json:"createdAt" description:"创建时间"`
UpdatedAt *gtime.Time `json:"updatedAt" description:"更新时间"`
ModuleKey string `json:"moduleKey" description:"模块Key"`
TenantId uint64 `json:"tenantId" description:"租户ID"`
ExpireAt *gtime.Time `json:"expireAt" description:"到期时间"`
AssetId string `json:"assetId" description:"资产ID"`
AssetSkuId string `json:"assetSkuId" description:"资产SKU ID"`
TenantModuleType TenantModuleType `json:"tenantModuleType" description:"租户模块类型"`
CertificationStatus int `json:"certificationStatus" description:"认证状态"`
}

View File

@@ -13,21 +13,15 @@ import (
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
"net/http"
"time"
)
func ModuleTenantCheck(r *ghttp.Request) {
//将 http.Header 转换为 map[string]string
headers := make(map[string]string)
for k, v := range r.Request.Header {
if len(v) > 0 {
headers[k] = v[0]
}
}
// 检查是否是超级管理员
isSuperAdmin := false
if err := nats.CallRPC(r.Context(), "userService.IsSuperAdmin", nil, &isSuperAdmin); err != nil {
SetResponseInfo(r.Context(), r, err)
SetResponseInfo(r.Context(), r, http.StatusPaymentRequired, err)
}
// 如果是超级管理员,则不进行模块租户检查
if isSuperAdmin || r.Request.RequestURI == "/asset/getAssetAndSku?assetId=696b4acd1be1c8b76c4b4c15" {
@@ -36,7 +30,7 @@ func ModuleTenantCheck(r *ghttp.Request) {
}
getUserInfo, err := utils.GetUserInfo(r.Context())
if err != nil {
SetResponseInfo(r.Context(), r, err)
SetResponseInfo(r.Context(), r, http.StatusPaymentRequired, err)
}
exit := gconv.Int64(time.Minute * 1)
getEX, err := message.GetRedisClientTest("test").GetEX(r.Context(), fmt.Sprintf("module_tenant:tenantId-%v", getUserInfo.TenantId), gredis.GetEXOption{
@@ -45,34 +39,30 @@ func ModuleTenantCheck(r *ghttp.Request) {
},
})
if err != nil {
SetResponseInfo(r.Context(), r, err)
SetResponseInfo(r.Context(), r, http.StatusPaymentRequired, err)
}
// 获取模块key
moduleKey := g.Cfg().MustGet(context.Background(), "server.name").String()
if !g.IsEmpty(getEX.String()) {
list := make([]beans.ModuleTenant, 0)
if err = json.Unmarshal([]byte(getEX.String()), &list); err != nil {
SetResponseInfo(r.Context(), r, err)
}
var expireAt *gtime.Time
for _, value := range list {
if value.ModuleKey == moduleKey {
expireAt = value.ExpireAt
break
}
list := new(beans.ModuleTenant)
if err = json.Unmarshal(getEX.Bytes(), &list); err != nil {
SetResponseInfo(r.Context(), r, http.StatusPaymentRequired, err)
}
// 缓存中有数据,检查是否过期
if !g.IsEmpty(expireAt) {
if !g.IsEmpty(list.ExpireAt) {
gt1 := gtime.New(time.Now())
gt2 := gtime.New(expireAt)
gt2 := gtime.New(list.ExpireAt)
if !gt1.Before(gt2) {
SetResponseInfo(r.Context(), r, "您访问的模块已期,请续期后再使用")
SetResponseInfo(r.Context(), r, http.StatusPaymentRequired, "功能模块已期,请续期后再使用")
} else {
if list.CertificationStatus != 2 {
SetResponseInfo(r.Context(), r, http.StatusPreconditionRequired, "功能模块未认证通过,请认证后再使用")
}
}
} else {
SetResponseInfo(r.Context(), r, "您未开通此模块,请开通后再使用")
SetResponseInfo(r.Context(), r, http.StatusPaymentRequired, "您未开通此功能模块,请开通后再使用")
}
} else {
// 缓存为空调用admin-go的Check接口检查模块开通状态
checkRes := new(beans.ModuleTenantCheckRes)
checkReq := beans.ModuleTenantCheckReq{
ModuleKey: moduleKey,
@@ -80,25 +70,27 @@ func ModuleTenantCheck(r *ghttp.Request) {
}
err = nats.CallRPC(r.Context(), "moduleService.Check", &checkReq, checkRes)
if err != nil {
SetResponseInfo(r.Context(), r, err)
SetResponseInfo(r.Context(), r, http.StatusPaymentRequired, err)
}
// 根据检查结果判断是否允许访问
if checkRes.Status == "not_activated" {
SetResponseInfo(r.Context(), r, "您未开通此模块,请开通后再使用")
} else if checkRes.Status == "expired" {
SetResponseInfo(r.Context(), r, "您访问的模块已过期,请续期后再使用")
if !checkRes.Status {
SetResponseInfo(r.Context(), r, http.StatusPaymentRequired, checkRes.Message)
} else {
if !checkRes.CertificationStatus {
SetResponseInfo(r.Context(), r, http.StatusPreconditionRequired, checkRes.Message)
}
}
}
r.Middleware.Next() // 继续执行后续中间件和路由处理
}
// SetResponseInfo 设置响应信息
func SetResponseInfo(ctx context.Context, r *ghttp.Request, message any) {
func SetResponseInfo(ctx context.Context, r *ghttp.Request, code int, message any) {
_ = ctx
r.Response.Status = 402
r.Response.WriteJsonExit(map[string]interface{}{
"success": false,
"code": 402,
"code": code,
"message": fmt.Sprintf("服务不可用:%s", message),
})
r.Exit()

View File

@@ -10,7 +10,6 @@ import (
"errors"
"fmt"
"gitee.com/red-future---jilin-g/common/log/consts"
"reflect"
"time"
"gitee.com/red-future---jilin-g/common/beans"
@@ -643,7 +642,7 @@ func (m *MongoDB) SaveOrUpdate(ctx context.Context, filter []bson.M, update []bs
return bulkResult, nil
}
func BuildUpdateFilter(ctx context.Context, req interface{}) (filter bson.M, err error) {
func BuildUpdateData(ctx context.Context, req interface{}) (filter bson.M, err error) {
_ = ctx
filter = bson.M{}
reqMap := gconv.Map(req)
@@ -654,55 +653,3 @@ func BuildUpdateFilter(ctx context.Context, req interface{}) (filter bson.M, err
}
return
}
// EntityToBson 将 *entity/entity 转换为 bson.M
func EntityToBson(entity interface{}) (bson.M, error) {
return EntityToBsonWithFilter(entity, false)
}
// EntityToBsonWithFilter 将 *entity/entity 转换为 bson.M并可选择是否过滤空值
func EntityToBsonWithFilter(entity interface{}, filterEmpty bool) (bson.M, error) {
if entity == nil {
return nil, fmt.Errorf("传入的 entity 实例为 nil")
}
bsonBytes, err := bson.Marshal(entity)
if err != nil {
return nil, fmt.Errorf("entity 序列化为 BSON 字节流失败:%w", err)
}
var bsonMap bson.M
err = bson.Unmarshal(bsonBytes, &bsonMap)
if err != nil {
return nil, fmt.Errorf("BSON 字节流反序列化为 bson.M 失败:%w", err)
}
if filterEmpty {
for key, value := range bsonMap {
if isEmptyWithZero(value) {
delete(bsonMap, key)
}
}
}
return bsonMap, nil
}
// isEmptyWithZero 判断是否为空值,但保留 int 类型的 0 值
func isEmptyWithZero(value interface{}) bool {
if value == nil {
return true
}
rv := reflect.ValueOf(value)
kind := rv.Kind()
if kind == reflect.Ptr {
if rv.IsNil() {
return true
}
kind = rv.Elem().Kind()
}
switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
return false
default:
return g.IsEmpty(value)
}
}