the-thought-of-yii2-rbac

YII2 的 rbac 思路

最近在做一个基于 yii2 的项目,需要用到权限控制的功能,于是考察了 yii2 原生的权限解决方案。

一般来说,权限系统有简单做,也有复杂做。我在做的时候,主要有 3 个点考虑:资源、用户、关系。资源有粒度的概念,比如某种资源、特定资源、部分资源等。用户也有粒度的概念,单个用户、角色、用户组等。而关系,就是描述用户与资源之间,用户是否能够执行某个动作,典型如增删改查。

总体来看,yii2 的原生解决方案实现得比较简单,不过思路比较轻巧。

特性

因为我考察的是基于数据库的解决方案,所以下面主要围绕这个实现来说。这个权限控制的特点:

  • 基于角色模型,role base access control
  • 支持角色(Role)、权限(Permission)、规则(Rule)三种控制层次
  • 角色可以继承
  • 一个用户可以拥有多个角色
  • 不支持直接赋予权限给用户,即用户只能有角色,不能直接拥有某个权限
  • 规则只能添加到权限上

API 使用

// 获取yii的权限管理对象
$auth = Yii::$app->authManager;
// 添加 "createPost" 权限
$createPost = $auth->createPermission('createPost');
$createPost->description = 'Create a post';
$auth->add($createPost);
// 添加 "updatePost" 权限
$updatePost = $auth->createPermission('updatePost');
$updatePost->description = 'Update post';
$auth->add($updatePost);
// 添加"author"角色,并赋予"createPost"权限
$author = $auth->createRole('author');
$auth->add($author);
$auth->addChild($author, $createPost);
// 添加"admin"角色,并赋予"updatePost"权限
// 同时让"author"角色继承"admin"
$admin = $auth->createRole('admin');
$auth->add($admin);
$auth->addChild($admin, $updatePost);
$auth->addChild($admin, $author);
// 把特定的角色赋给用户
$auth->assign($author, 2);
$auth->assign($admin, 1);

在这里,我们完成了权限的初始化。目前系统的权限是下面这样的(yii2 官网直接拿过来):

那程序在校验的时候,流程具体是怎么样的呢?

先看下 api:

if (\Yii::$app->user->can('updatePost', ['post' => $post])) {
    // update post
}

在这里,假设用户想操作一个 post,我们直接调用 user 类的 can 方法,然后去查表。我们这里先讲流程。大致的思路是。查找用户所有的角色,然后把这个角色下面的权限找出来,做一个并集,然后再问这个权限有没在用户的权限集里面。

数据结构

基于上述的考虑,数据库实现的权限控制方案,采用了下面 4 张表:

  • rule 表
    Rule 表对应” 规则”,主要用来存储规则信息。
  • auth_item 表
    这个表比较特殊,yii2 的实现是把 Role 和 Permission 两个信息也存在一起。
  • auth_item_child 表
    这个表用来存储父子关系,包括 Role 之间的继承关系,Role 和 Permission 之间的包含关系。
  • auth_assignment
    最后这张表,存储的是角色和用户之间的多对多关系了,一个用户可以拥有多个角色。

具体的 sql 语句:

create table `auth_rule`
(
   `name`                 varchar(64) not null,
   `data`                 blob,
   `created_at`           integer,
   `updated_at`           integer,
    primary key (`name`)
) engine InnoDB;
create table `auth_item`
(
   `name`                 varchar(64) not null,
   `type`                 smallint not null,
   `description`          text,
   `rule_name`            varchar(64),
   `data`                 blob,
   `created_at`           integer,
   `updated_at`           integer,
   primary key (`name`),
   foreign key (`rule_name`) references `auth_rule` (`name`) on delete set null on update cascade,
   key `type` (`type`)
) engine InnoDB;
create table `auth_item_child`
(
   `parent`               varchar(64) not null,
   `child`                varchar(64) not null,
   primary key (`parent`, `child`),
   foreign key (`parent`) references `auth_item` (`name`) on delete cascade on update cascade,
   foreign key (`child`) references `auth_item` (`name`) on delete cascade on update cascade
) engine InnoDB;
create table `auth_assignment`
(
   `item_name`            varchar(64) not null,
   `user_id`              varchar(64) not null,
   `created_at`           integer,
   primary key (`item_name`, `user_id`),
   foreign key (`item_name`) references `auth_item` (`name`) on delete cascade on update cascade
) engine InnoDB;

代码层面(算法实现)

相关的代码,在框架的路径下面,yii2/framework/rbac,具体的文件有如下几个,我们逐一来看看:

├── Assignment.php  // 对应auth_assignment表,描述用户与角色的关系
├── BaseManager.php  // 忽略
├── CheckAccessInterface.php // 权限校验接口
├── DbManager.php  // 基于数据库实现的权限校验类
├── Item.php  // Role和Permission的基类
├── ManagerInterface.php // 忽略
├── Permission.php // 权限类
├── PhpManager.php // 忽略
├── Role.php // 角色类
├── Rule.php // 规则类

整个代码的思路,比较简单,框架先提出了 3 个对象,分别是 Role,Permission,Rule,这映射到我们之前提到过的 RBAC 概念,分别对应角色、权限、规则。

主要的权限校验,其实是在 DbManager.php 实现,而关键的方法,就是下面这个。


  /**
   * Performs access check for the specified user.
   * This method is internally called by [[checkAccess()]].
   * @param string|int $user the user ID. This should can be either an integer or a string representing
   * the unique identifier of a user. See [[\yii\web\User::id]].
   * @param string $itemName the name of the operation that need access check
   * @param array $params name-value pairs that would be passed to rules associated
   * with the tasks and roles assigned to the user. A param with name 'user' is added to this array,
   * which holds the value of `$userId`.
   * @param Assignment[] $assignments the assignments to the specified user
   * @return bool whether the operations can be performed by the user.
   */
  protected function checkAccessRecursive($user, $itemName, $params, $assignments)
  {
// 参数分析:user是用户类,itemName是具体要查询的权限名(或者角色名),ssignments是用户的角色集
      // 获取这个itemName对应的数据,可能是role,也可能是permission
      if (($item = $this->getItem($itemName)) === null) {
          return false;
      }
      Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
// 暂时先忽略对rule的考虑
      if (!$this->executeRule($user, $item, $params)) {
          return false;
      }
// 角色名是否在用户的assignment里面,或者在系统的默认角色列表里面
      if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
          return true;
      }
// 构造查询函数对象
      $query = new Query;
      $parents = $query->select(['parent'])
          ->from($this->itemChildTable)
          ->where(['child' => $itemName])
          ->column($this->db);
// 递归查询
      foreach ($parents as $parent) {
          if ($this->checkAccessRecursive($user, $parent, $params, $assignments)) {
              return true;
          }
      }
      return false;
  }

整个函数关键点,其实理解这个递归查找本身。我们在代码层面,比如更新某个 post,可能会写如下代码:

if (\Yii::$app->user->can('updatePost', ['post' => $post])) {
    // update post
}

这个时候,系统会跑到这个函数里面,调用这个递归查找,但代码并不是一开始就查找 updatePost 这个 Permission 本身是否匹配用户的 assignments 列表的,而是查找这个 Permission 的父级,把它归属的那些 Role 找出来,然后去匹配 assignments 列表。可能一级找不到,那就多级查找。

那基于 Rule 的情况,又如何理解呢?

你可以理解为,在 Permission 上面,写入自己的逻辑校验,实现粒度更小的控制。

我们看下官方给的代码:

$auth = Yii::$app->authManager;
// add the rule
$rule = new \app\rbac\AuthorRule;
$auth->add($rule);
// add the "updateOwnPost" permission and associate the rule with it.
$updateOwnPost = $auth->createPermission('updateOwnPost');
$updateOwnPost->description = 'Update own post';
$updateOwnPost->ruleName = $rule->name;
$auth->add($updateOwnPost);
// "updateOwnPost" will be used from "updatePost"
$auth->addChild($updateOwnPost, $updatePost);
// allow "author" to update their own posts
$auth->addChild($author, $updateOwnPost);

图片示意:

具体的实现,可以看看上面的 checkAccessRecursive 函数。

最后

写得比较仓促,读者还可以参考以下链接:

reading note of mosquitto

背景

之前在游戏公司的时候,需要自己搭建一套推送服务,顺道研究了下一些开源实现。mosquito 的代码库,代码量少,而且也写得比较好懂,对推送协议的实现也是比较 ok 的,所以就撸了一番。虽然后来没用上,但是还是把当时的一些想法记录下来。

缺点

- 基于poll的事件模型,没有epoll的性能好
- 内存分配策略简单,来一个生成一个,存在优化空间
- 比如改用初始2倍,到达一定数量后,以后每次改用增加额定数量的算法
- 数据结构过于简单
- 常用结构为单向链表,查找耗时为O(n),存在性能问题
- 内存占用过大
- 没有使用数据库,数据都堆在内存里面,可能无法应付大数据量
继续阅读

notes of learning erlang

背景

之前因为要想实现一个基于 DHT 网络的站,就找了一些代码实现,发现一个前辈写的 erlang 实现,好奇也看了起来,顺便学了下 erlang。

感悟

  • erlang 太有趣啦,通过模拟进程创建,看到内存和 CPU 资源是怎么被吃掉的,給人很直觀的感覺哇~而且 erlang 同时支持事务和热代码替换,这两项特性碉堡了啊!!!
  • erlang 的并发编程模式,其实我觉得写起来还是读起来都非常的通俗,上手也不难,但有些约定有点反人类,或者语法有点多,约定俗成的东西还要靠看书来回忆…
继续阅读

sharding of mongodb

blog 教程

分片概念

集合可以被分片,一个片可以包含多个区间,一个区间可以包含多个块,一个块可以定大小。所有这些概念都是逻辑上的,真实的物理存储并不存在这样的一一对应关系。如下图:

分片1----
          |
          |
      +++++++++++
      +  |------ 块1   +     区间1
      +  |------ 块2   +
      +++++++++++
          |
          |
      +++++++++++
      +  |------ 块3   +     区间2
      +  |------ 块4   +
      +++++++++++
          |
          |
      +++++++++++
      +  |------ 块5   +     区间3
      +  |------ 块6   +
      +++++++++++
     逻辑概念与物理概念上并不一致

片键选择问题

Geo location

建立索引

源码阅读进度

  • 看了下整个架构
    • 基于 c++ 的一个项目,采用的库有 boost……
    • 多线程模型,通过锁机制来完成任务的调度
    • 事件循环采用最传统 select 机制
      • 每个客户端一个 threading,更优化的可能地方在内部的连接线程池,可以加快响应速度
    • [参考][http://blog.csdn.net/daizhj/article/category/803114]

经验记录

  • 传统的 master、slave 模式已经被弃用,mongo 推荐采用副本集,即 replica set。
    • 它的使用规则还是非常的简易。
      • 主从可以互相切换,当然也支持不切换的。
      • 第三方调用默认读写都走主,但可以调配副本提供读。这样就能有效分流读写的压力。
      • 副本集的提出主要是为了数据备份。
    • mongos 的提出是为了 sharding 的,它也是 mongodb 里面的节点类型之一。(PS:mongodb 有许多节点类型,单个数据库实例算一种,提供查询的 config 也是一种等等)
      • sharding 是为了把大量的数据从单台机分离出来,切分数据到不同的机子上去。避免内存被撑爆了。
      • 未完待续

notes of zhifubao pay

blog 参考资料

接口参考

代码参考

出问题可以参考的资料

开发流程

  • 生成 rsa 公钥、私钥
  • 上传我们的公钥到阿里云服务器(帮助
  • 下载阿里给的对应支付宝公钥,保存起来

spider related

怎么部署

  • scrapyd + supervisord + crontab + redis

可以用的一些 lib

分布式

参考的 blog

入门

结合

行业资料

行业需求

  • [lagou]

其他

比如如何防止被 ban 掉

Here are some tips to keep in mind when dealing with these kinds of sites:
- rotate your user agent from a pool of well-known ones from browsers (google around to get a list of them)
- disable cookies (see COOKIES_ENABLED) as some sites may use cookies to spot bot behaviour
- use download delays (2 or higher). See DOWNLOAD_DELAY setting.- if possible, use Google cache to fetch pages, instead of hitting the sites directly
- use a pool of rotating IPs. For example, the free Tor project or paid services like ProxyMesh
- use a highly distributed downloader that circumvents bans internally, so you can just focus on parsing clean pages. One example of such downloaders is Crawlera

reading note of the totor library for python

背景

在 ziz 的时候,cto 的技术选型不合理,对使用技术缺乏必要的了解,采用了旧和大而笨重的中间件。因为 tcelery 的核心开发者不再维护代码,所以需要重新选择更加合适的技术方案。

阅读

原版的 tcelery 有几点问题:

- 对于redis的backend,存在race condition的情况,具体要参考下对应的git仓库的issue
- 对于超时的处理,如果worker跑这个任务运行的运行时间超过规定时间,客户端的连接就会卡死
- 代码时间有点旧了,作者mher也没有继续维护和做codereview的工作

后来认识了一个提交 pull request 的童鞋,因为作者没有维护采纳修改的缘故,他做了一版新的工具,totoro。现在的项目就是基于这个来开发的。

ps: segmentfault 有专门一个问题来讨论这个 bug 的,也可以去看看解决方案(其实是我自问自答啦)。

《文案训练手册》阅读笔记

背景

《文案训练手册》是一本教导写作的书籍,偏向实用性,很早之前看的一本书。

公理

  • 公理 1
    文案写作是一段精神旅程。成功的文案写作,会综合反映出你全部的经经历、你的专业知识、你对这些信息进行精神加工并以卖出产品或服务为目的,将它们形成文字的能力。
  • 公理 2
    一个广告里的所有元素首先都是为了一个目的而存在:使读者阅读这篇文案的第一句话 —— 仅此而已。
  • 公理 3
    广告中第一句话的唯一目的就是为了让读者阅读第二句话。
  • 公理 4
    广告的版面设计和广告的头几个段落必须创造出一种购买环境,这非常有利于销售你的产品或服务。
  • 公理 5
    让你的读者说 “是”,让他们在阅读你的文案时,因你真诚实在的陈述而产生共鸣。
  • 公理 6
    你的读者应该是情不自禁地阅读你的文案,他们根本无法停止阅读,直到他们阅读完所有的文案,就像从滑梯上面滑下来一样。
  • 公理 7
    当你试图解决问题的时候,打破那些思维定式。
  • 公理 8
    通过好奇心的力量,使文案趣味横生,使读者兴趣盎然。
  • 公理 9
    永远不要推荐一种产品或服务,而是推销一种概念。
  • 公理 10
    酝酿过程就是你的潜意识运用你所有的知识和经理来解决一个具体问题,其效率是由时间、创意倾向、环境和自尊心所决定的。
  • 公理 11
    文案应该倡导足以引导读者按照你的要求去做。
  • 公理 12
    每一次沟通都应该是一次个人化的沟通,从作者到受众,无论使用哪种媒介。
  • 公理 13
    你在文案中提出的创意需要以一种有条理的方式贯通起来,预测用户的问题,然后回答它们,就如同这些问题是面对面问的一样。
  • 公理 14
    在编辑的过程中,你要精练你的文案,用最少的文字精确地表达出你想要表达的东西。
  • 公理 15
    销售一种治愈性产品要比销售一种预防性产品容易得多,除非这种预防性产品被看作一种治愈性产品,或者这种预防性产品的治疗作用被着重强调了。

情感原则

在广告中关于情感有以下 3 点需要记住:
情感原则 1:每一个词语都蕴涵着情感,每一个词语都讲述了一个故事。
情感原则 2:每一个好广告都是词语、感受和印象的情感流露。
情感原则 3:以情感来卖出产品,以理性来诠释购买。

平面元素

以下是设计一个邮购广告时要考虑的 10 个平面元素。
1、标题
2、副标题
3、照片或图画
4、图片说明
5、文案
6、段落标题
7、商标
8、价格
9、反馈方式
10、整体设计

功能强大的文案元素

以下是在撰写广告文案时必须考虑的 23 个文案元素。
1、字体
2、第一句话
3、第二句话
4、段落标题
5、产品说明
6、新特性
7、技术说明
8、预测异议
9、解决异议
10、性别
11、清晰性
12、陈词滥调
13、节奏
14、服务
15、物理性质
16、试用期
17、价格比较
18、代言
19、价格
20、提供总结
21、避免拖泥带水
22、订购的便利性
23、请求订购

心理诱因

1、参与或者拥有的感觉
2、诚实
3、正直
4、信用
5、价值及其证明
6、使购买合理
7、贪婪
8、建立权威性
9、满意度保证
10、产品的本质
11、客服的本质
12、当前的时尚潮流
13、时机
14、建立联系
15、一致性
16、符合客户需求
17、归属感的渴望
18、收藏冲动
19、好奇心
20、紧迫感
21、恐惧
22、瞬间满足
23、独有、珍贵或者特别
24、简单
25、人际关系
26、讲故事
27、精神投入
28、内疚感
29、具体
30、熟悉
31、希望

文案顺序流程图

文案顺序的流程图只是走向一个方向 —— 向下。

兴趣激情 -> 独特情 -> 为什么不同 -> 怎样玩 -> 特性 -> 使购买合理化 -> 永久游戏价值 -> 售后服务 -> 下订单