途牛原创|大话权限中心的PHP架构之道

488 查看

权限管理是无线运营系统中的核心模块,通过访问控制策略的配置,来约定人与资源的访问关系。

本文着重讲解如何通过PHP来构建一个灵活、通用、安全的权限管理系统。

关于权限

首先我们来聊聊权限。

权限系统一直以来是我们应用系统不可缺少的一个部分,若每个应用系统都重新对系统的权限进行设计,以满足不同系统用户的需求,将会浪费我们不少宝贵时间,所以花时间来设计一个相对通用的权限系统是很有意义的。

系统目标:对应用系统的所有对象资源和数据资源进行权限控制,比如 应用系统的功能菜单、各个界面的按钮、数据显示的列以及各种行级数据 进行权限的操控。

权限模型

设计初期,我们学习了Amazon的 IAM ,经过对比分析,最终我们选用 RBAC3模型 来指导系统的设计工作。

RBAC认为权限授权实际上是Who、What、How的问题。在RBAC模型中,who、what、how构成了访问权限三元组,也就是“Who对What(Which)进行How的操作”。

  • Who:权限的拥用者或主体(如Principal、User、Group、Role、Actor等等)

  • What:权限针对的对象或资源(Resource、Class)。

  • How:具体的权限(Privilege,正向授权与负向授权)。

  • Operator:操作。表明对What的How操作。也就是Privilege+Resource

  • Role:角色,一定数量的权限的集合。权限分配的单位与载体,目的是隔离User与Privilege的逻辑关系.

PHP架构之道

「如何用PHP构建我们的权限中心」

接下来我们将从 编码规范、依赖管理、数据源架构、数据处理、单元测试 等方面来体验一把PHP的神奇之旅。

编码规范

好的编码规范可以改善软件的可读性,可以促进团队成长,可以减少Bug,可以降低维护成本,可以。。。(这么X,我们必须要推广)

PHP社区一直百花齐放,拥有大量的函数库、框架和组件,因而PHP代码遵循或尽量接近同一个代码风格就非常重要。

框架互操作组(即PHP标准组)发布了一系列推荐风格。

权限中心的目录结构:


-- /tuniu/rbac
|-- src
|   |-- App //应用建模层
|   |   |-- City.php
|   |   |-- Cms.php
|   |   |-- Menu.php
|   |-- App.php
|   |-- Auth.php
|   |-- Orm //ActiveRecord层
|   |   |-- App
|   |   |   |-- Resource
|   |   |   |   |-- Map.php
|   |   |   |-- Resource.php
|   |   |   |-- User.php
|   |   |-- App.php
|   |   |-- Log.php
|   |   |-- Role
|   |   |   |-- User.php
|   |   |-- Role.php
|   |   |-- Rule.php
|   |   |-- User.php
|   |-- Orm.php
|   |-- Utils.php
|-- tests //测试
|   |-- bootstrap.php
|   |-- fixtures
|   |   |-- null.yml
|   |   |-- rbac
|   |   |   |-- app.yml
|   |   |   |-- auth.yml
|   |   |   |-- role.yml
|   |-- rbac
|   |   |-- appTest.php
|   |   |-- authTest.php
|   |   |-- roleTest.php
|-- vendor
|-- composer.json
|-- composer.lock
|-- phpunit.xml
|-- README.md

PSR-2,权限应用资源类:


namespace Tuniu\Rbac\Orm\App;

use Tuniu\Rbac\Orm;
use Tuniu\Rbac\Orm\App;
use Tuniu\Rbac\Orm\App\Resource\Map;
use Tuniu\Rbac\Orm\Rule;
use Tuniu\Rbac\Orm\User;

class Resource extends Orm {}

PSR-4,命名空间的约定:

\<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>

类名 文件路径
\Tuniu\Rbac\Orm\App\Resource /tuniu/rbac/src/Orm/App/Resource.php
\Tuniu\Rbac\App\Cms /tuniu/rbac/src/App/Cms.php

依赖管理

Composer 是PHP中用来管理依赖(dependency)关系的工具。你可以在自己的项目中声明所依赖的外部工具库(libraries),Composer会帮你安装这些依赖的库文件。

权限中心的依赖声明:


{
    "name": "tuniu/rbac",
    "require": { //声明依赖关系
        "php": ">=5.3.0",
        "squizlabs/php_codesniffer": "2.*", //PHP_CodeSniffer检查代码规范
        "php-activerecord/php-activerecord": "dev-master" //ActiveRecord
    },
    "require-dev": { //声明开发依赖
        "phpunit/phpunit": "~4.6",
        "phpunit/dbunit": ">=1.2"
    },
    "autoload": {
        "psr-4": {
            "Tuniu\\Rbac\\": "src/" //命名空间
        }
    }
}

检查代码规范,执行单元测试。


$./vendor/bin/phpcs --config-set default_standard PSR2
$./vendor/bin/phpcs src
$./vendor/bin/phunit

^^^^^^ 眼涩,眼酸,眼疲劳,怎么办。。。

骚年,如果舒服了,就使劲往下滑动吧。。。

争渡,争渡,惊起一滩鸥鹭。

数据源架构

基于权限中心各表的关系(各种关联,各种回调),采用传统的模式,必将把精力耗在无尽的循环中。

于是乎,开始寻觅一种数据源的架构模式,Active Record很靠谱的出现了。

Active Record(中文名:活动记录)是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。

PHP ActiveRecord 是一个基于ActiveRecord设计模式开发的开源PHP/ORM库。它旨在大大简化与数据库的交互和减少手写SQL语句。它不同于其他的ORM,你不需要使用任何的代码生成器,也不费劲去手写、维护模型层的表映射文件。这个库的灵感来自Ruby on Rails,因此它也借鉴Ruby on Rails的想法和实现。(谁用谁知道)

下面介绍下这个小伙伴给编程带来的快乐:

  • Validation(数据验证)

    • validates_presence_of

    • validates_inclusion_of

场景(角色表数据约定)


/**
 * 1:约定角色类型的范围
 * 2:约定角色状态的范围
 */
public static $validates_inclusion_of = array(
    array('f_type', 'in' => array('role', 'group', 'department', 'member')),
    array('f_status', 'in' => array(1,2))
);

/**
 * 设定角色名称、角色状态、角色类型、角色描述不能为空
 */
public static $validates_presence_of = array(
    array('f_name'),
    array('f_status'),
    array('f_type'),
    array('f_desc')
);

//录入数据不满足约定条件,就无法保存,再也不用担心那些脏脏的数据。
  • Callback(回调)

    • before_save

    • before_create

    • before_update

    • before_destroy

    • after_save

    • after_create

    • after_update

    • after_destroy

场景1(角色表操作记录)


//定义回调函数
public static $before_save = array('setMisc');

//每当角色表保存之前,都默默的把数据格式好,好开心。。。
public function setMisc()
{
    //创建时间,创建人
    $this->is_new_record() && ($this->f_create_at = date("Y-m-d H:i:s"));
    $this->is_new_record() && ($this->f_create_by = $this->op);
    //更新时间,更新人
    $this->f_update_by = $this->op; //操作人
    $this->f_update_at = date("Y-m-d H:i:s"); //操作时间
}

场景2(新增资源,推送资源映射表):


//定义回调函数
public static $after_save = array('syncRelations');

//每当资源保存之后,自动把资源数据中的映射字段值,推送给资源映射表。
public function syncRelations()
{
    //获取资源的映射字段
    $mapFiledValue = $this->getMapFiledValue();

    $mapFiledValue
        ? $this->addMap($mapFiledValue)
        : Map::removeAllByResourceId($this->id);
}

场景3(角色删除):


//定义回调函数
public static $after_destroy = array('deleteRelations');

//每当角色删除时,自动把系统中角色的成员和角色的资源规则清空,而且是事务的。
public function deleteRelations()
{
    UserRelations::removeAllByRoleId($this->id);
    RuleRelatinos::removeAllByRoleId($this->id);
}
  • Association(关联)

    • has_many

场景(新增角色用户)


//定义角色表和角色用户表的关系
public static $has_many = array(
    array(
        'relations',
        'foreign_key' => 'f_role_id',
        'class_name' => "\\Tuniu\\Rbac\\Orm\\Role\\User",
    )
);

//新增角色用户,默默的把role_id传递给了角色用户表,此处如果用SQL,简直不忍直视。
Role::first()->create_relation(
    array(
        'f_user_id' => $uid
    )
);
  • 事务(一致性与安全性)

权限系统中数据一致性和数据安全性的重要性是不言而喻,不用事务会被BS的。

在此我们郑重承诺,权限系统中每一次数据增删改请求,都是事务处理的。

比如角色保存:


self::transaction(
    function () use ($params, &$role) {

        $role->f_name   = $params['name'];
        $role->f_status = $params['status'];
        $role->f_type   = $params['type'];
        $role->f_desc   = $params['desc'];

        if ($role->is_invalid()) {
            throw new \Exception('角色相关操作失败', '900202');
        }

        $role->save();
    }
);

篇幅有限,这个小伙伴的战斗力场景远甚于此。

坦白的说,用AR是一种编程享受。

单元测试(持续交付)

一切都如此的完美,没有测试,又如何可以证明这件事情的完美,又如何可以保障交付的质量。

PHPUnit 是一个轻量级的PHP测试框架。它是在PHP5下面对JUnit3系列版本的完整移植,是xUnit测试框架家族的一员(它们都基于模式先锋Kent Beck的设计)。

单元测试是一种提高软件质量非常有效的方法,但很重要的是我们要去实践和体会。

简单的介绍下权限管理中的角色行为测试用例:

角色行为测试

  • 数据集(YAML)


t_rbac_user:
    -
        f_id: 1
        f_name: "zhaoyang2"
        f_create_at: "2013-10-10 17:04:05"
t_rbac_role_user:
    -
        f_id: 1
        f_user_id: 1
        f_role_id: 1
t_rbac_role:
    -
        f_id: 1
        f_name: "运营研发部-1"
        f_status: 1,
        f_type: "department"
        f_desc: "我们是运营研发部-1"
        f_create_at: "2013-10-10 17:04:05"
        f_create_by: "zhaoyang2"
        f_update_by: "zhaoyang2"
  • 测试代码:


//预设数据集
public function getDataSet()
{
    return new \PHPUnit_Extensions_Database_DataSet_YamlDataSet(
        fixture('rbac/role.yml')
    );
}

/**
 * 权限操作-异常验证
 * @expectedException Exception
 * @expectedExceptionMessage 您无权运营当前数据
 */
public function testRoleDeleteException()
{
    Role::first()->remove();
}

/**
 * 权限操作-删除验证
 */
public function testRoleDelete()
{
    Role::first()->remove("zhaoyang2");

    //验证数据行
    $this->assertEquals(1, $this->getConnection()->getRowCount(Role::$table_name));
    $this->assertEquals(1, $this->getConnection()->getRowCount(UserRelation::$table_name));
}

鉴权用例和应用管理用例,远比这个复杂,感兴趣的同学可以去 Fork 一把,了解下PHPUNIT的魅力。

PHPUNIT很强大,想合理运用的话,没有任何捷径,开始写测试用例吧。。

结束语

其实说架构算上下,就是和大家分享下权限中心的PHP之道。

高效便捷的使用PHP服务我们的工作。

多交流,多分享,书写更好的PHP代码,享受编程和技术所带来的快乐。