permission

command
v0.0.0-...-9c76002 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 24, 2021 License: GPL-3.0 Imports: 14 Imported by: 0

README

前言

随着移动应用的普及,不管应用是否免费,只要应用内部包含购买功能的,货多多少都需要对应用的功能做一些限制,好能更好的区分付费用户和非付费用户的区别,或者由于国家出台的一系列政策,对于青少年在使用应用方面也会提出一些要求,因此功能限制也算是基础的功能了,今天将以围绕数值系统的方式来构建功能限制(功能权限).

流程

比如某些功能只能对大于12岁的人开放,那么这个12其实是一个数值,然后要访问这个功能的时候,需要通过网络的方式访问服务端,服务端通过会话或者令牌获取该用户的数值数据,从数值数据中获取年龄,如果年龄小于等于12则返回错误响应数据给客户端,客户端收到响应数据之后弹出对应的提示语告知用户.

数值类型

这里用一个数值枚举来存储用所有的数值类型, 定义如下:

package valuetype

type Value int

const (
	Age        Value = 1 // Age is 年龄
)

用户数值模型

由于数值类型会随着应用的开发而增加,为了防止字段的增加而导致频繁修改结构,因此我们可以用map[valuetype.Value]int64来存储,结构如下:

type UserValue struct {
	ID     string
	Values map[valuetype.Value]int64
}

条件

根据以上的业务需求,需要存储数值类型\数量以及运算符,如果条件不满足的情况下需要下发数据给客户端,为了让数据格式更灵活,那么可以用map[string]interface{},其次就是定义哪些路由会触发条件判断,因此模型如下:

package relationaloperator

type Value int

const (
	EQ Value = 0 // EQ is 等于
	GE Value = 1 // GE is 大于等于
	GT Value = 2 // GT is 大于
	LE Value = 3 // LE is 小于等于
	LT Value = 4 // LT is 小于
)

// 数值条件
type ValueCondition struct {
	Count     int64
	Op        relationaloperator.Value
	ValueType valuetype.Value
}

// 路由权限
type RoutePermission struct {
	Conditions []ValueCondition
	ID         string
	Response   map[string]interface{}
	Routes     []string
}

Conditions是以and方式运算的,如果要满足or则需要同一个路由定义多个RoutePermission即可

数据

有了以上接口,就可以定义业务需要的数据,数据如下:

RoutePermission{
	Conditions: []ValueCondition{
		{
			Count:     12,
			Op:        relationaloperator.LT,
			ValueType: valuetype.Age,
		},
	},
	ID:     "id",
	Routes: []string{"路由"},
	Response: map[string]interface{}{
		"err": 1,
		"msg": "小于12岁禁止进入",
	},
}

用户数值接口

由于需要通过数值类型获取用户的数值,如果用户数值数据不存在或者error则应该返回默认值(0),否则则返回具体的值,如果直接在使用的时候获取用户数据那么需要频繁判断,由于条件判断也需要用到用户的数值,因此可以将判断条件的逻辑也放在该接口内,代码如下:

// 接口
type IUserValueService interface {
	MustCheckConditions([]ValueCondition) bool
	MustGetCount(valueType valuetype.Value) int64
}

// 实现
type valueService struct {
	dbFactory IDbFactory
	rows      []UserValue
	uid       string
}

func (m *valueService) MustCheckConditions(conditions []ValueCondition) bool {
	return underscore.Chain(conditions).Any(func(r ValueCondition, _ int) bool {
		count := m.MustGetCount(r.ValueType)
		ok := (r.Op == relationaloperator.EQ && count == r.Count) ||
			(r.Op == relationaloperator.LE && count <= r.Count) ||
			(r.Op == relationaloperator.LT && count < r.Count) ||
			(r.Op == relationaloperator.GE && count >= r.Count) ||
			(r.Op == relationaloperator.GT && count > r.Count)
		return !ok
	})
}

func (m *valueService) MustGetCount(valueType valuetype.Value) int64 {
	entry, err := m.getEntry()
	if err != nil || entry == nil {
		return 0
	}

	if v, ok := entry.Values[valueType]; ok {
		return v
	}

	return 0
}

func (m *valueService) getEntry() (*UserValue, error) {
	// 延迟加载
	if m.rows == nil {
		err := m.dbFactory.Db(UserValue{}).Query().Where(bson.M{
			"_id": m.uid,
		}).ToArray(&(m.rows))
		if err != nil {
			return nil, err
		}
	}

	if len(m.rows) > 0 {
		return &m.rows[0], nil
	}

	return nil, nil
}

func NewValueService(
	dbFactory IDbFactory,
	uid string,
) contract.IUserValueService {
	return &valueService{
		dbFactory: dbFactory,
		uid:       uid,
	}
}

触发

接下来只需要在请求的时候,根据路由获取权限,如果权限数据存在则判断,只要其中一条权限不满足则下发Response,否则则调用api,并下发api结果.这里以gin为例,代码如下:

app.POST("/:endpoint/:api", func(ctx *gin.Context) {
	// 其他略
	
	uid := ctx.GetHeader("uid")
	if uid == "" {
		err = errors.New("未认证")
		return
	}
	
	var routePermissions []RoutePermission
	err = dbFactory.Db(RoutePermission{}).Query().Where(bson.M{
		"Routes": ctx.Request.URL.String(),
	}).ToArray(&routePermissions)
	if err != nil {
		return
	}
	
	if len(routePermissions) > 0 {
		userValueService := NewValueService(dbFactory, uid)
		for _, r := range routePermissions {
			if ok := userValueService.MustCheckConditions(r.Conditions); !ok {
				err = fmt.Errorf("%+v", r.Response)
				return
			}
		}
	}
})

示例

以下提供2个示例:

  1. 国家政策要求一定年龄的小孩只能玩一定时间
  2. vip到达一定等级之后才能使用快捷功能
// 年龄小于等于18岁只能在线8小时
RoutePermission{
	Conditions: []ValueCondition{
		{
			Count:     18,
			Op:        relationaloperator.LE,
			ValueType: valuetype.Age,
		},
		{
			Count:     60 * 60 * 8,
			Op:        relationaloperator.GE,
			ValueType: valuetype.OnlineTime,
		},
	},
	ID:     "id-online-time",
	Routes: []string{"路由"},
	Response: map[string]interface{}{
		"err": 1,
		"msg": "已超过累积游戏时间",
	},
}

// vip等级大于2才能使用快捷功能
RoutePermission{
	Conditions: []ValueCondition{
		{
			Count:     2,
			Op:        relationaloperator.LT,
			ValueType: valuetype.Vip,
		},
	},
	ID:     "id-quick-play",
	Routes: []string{"路由"},
	Response: map[string]interface{}{
		"err": 1,
		"msg": "vip大于2级才能使用快捷功能",
	},
}

结尾

那么今天的文章到这里就结束了,数值模块的实现可以参考IDbFactory则可以参考.

如果文章有任何问题或者对以上内容有任何疑问,请留言告诉我,谢谢.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
api
Package contract is a generated GoMock package.
Package contract is a generated GoMock package.
model
service

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL