RBAC基於角色的權限訪問控制


RBAC是什么,能解決什么難題?

RBAC是Role-Based Access Control的首字母,譯成中文即基於角色的權限訪問控制,說白了也就是用戶通過角色與權限進行關聯[其架構靈感來源於操作系統的GBAC(GROUP-Based Access Control)的權限管理控制]。簡單的來說,一個用戶可以擁有若干角色,每一個角色擁有若干權限。這樣,就構造成“用戶-角色-權限”的授權模型。在這種模型中,用戶與角色之間,角色與權限之間,一般者是多對多的關系。其對應關系如下:
image
在許多的實際應用中,系統不只是需要用戶完成簡單的注冊,還需要對不同級別的用戶對不同資源的訪問具有不同的操作權限。且在企業開發中,權限管理系統也成了重復開發效率最高的一個模塊之一。而在多套系統中,對應的權限管理只能滿足自身系統的管理需要,無論是在數據庫設計、權限訪問和權限管理機制方式上都可能不同,這種不致性也就存在如下的憋端:

  • 維護多套系統,重復造輪子,時間沒用在刀刃上
  • 用戶管理、組織機制等數據重復維護,數據的完整性、一致性很難得到保障
  • 權限系統設計不同,概念理解不同,及相應技術差異,系統之間集成存在問題,單點登錄難度大,也復雜的企業系統帶來困難

RBAC是基於不斷實踐之后,提出的一個比較成熟的訪問控制方案。實踐表明,采用基於RBAC模型的權限管理系統具有以下優勢:

  • 由於角色、權限之間的變化比角色、用戶關系之間的變化相對要慢得多,減小了授權管理的復雜性,降低管理開銷;
  • 而且能夠靈活地支持應用系統的安全策略,並對應用系統的變化有很大的伸縮性;
  • 在操作上,權限分配直觀、容易理解,便於使用;分級權限適合分層的用戶級形式;
  • 重用性強。

ThinkPHP中RBAC實現體系

ThinkPHP中RBAC基於Java的Spring的Acegi安全系統作為參考原型,並做了相應的簡化處理,以適應當前的ThinkPHP結構,提供一個多層、可定制的安全體系來為應用開發提供安全控制。安全體系中主要有以下幾部分:

  • 安全攔截器
  • 認證管理器
  • 決策訪問管理器
  • 運行身份管理器

安全攔截器

安全攔截器就好比一道道門,在系統的安全防護系統中可能存在很多不同的安全控制環節,一旦某個環節你未通過安全體系認證,那么安全攔截器就會實施攔截。

認證管理器

防護體系的第一道門就是認證管理器,認證管理器負責決定你是誰,一般它通過驗證你的主體(通常是一個用戶名)和你的憑證(通常是一個密碼),或者更多的資料來做到。更簡單的說,認證管理器驗證你的身份是否在安全防護體系授權范圍之內。

訪問決策管理

雖然通過了認證管理器的身份驗證,但是並不代表你可以在系統里面肆意妄為,因為你還需要通過訪問決策管理這道門。訪問決策管理器對用戶進行授權,通過考慮你的身份認證信息和與受保護資源關聯的安全屬性決定是是否可以進入系統的某個模塊,和進行某項操作。例如,安全規則規定只有主管才允許訪問某個模塊,而你並沒有被授予主管權限,那么安全攔截器會攔截你的訪問操作。
決策訪問管理器不能單獨運行,必須首先依賴認證管理器進行身份確認,因此,在加載訪問決策過濾器的時候已經包含了認證管理器和決策訪問管理器。
為了滿足應用的不同需要,ThinkPHP 在進行訪問決策管理的時候采用兩種模式:登錄模式和即時模式。登錄模式,系統在用戶登錄的時候讀取改用戶所具備的授權信息到 Session,下次不再重新獲取授權信息。也就是說即使管理員對該用戶進行了權限修改,用戶也必須在下次登錄后才能生效。即時模式就是為了解決上面的問題,在每次訪問系統的模塊或者操作時候,進行即使驗證該用戶是否具有該模塊和操作的授權,從更高程度上保障了系統的安全。

運行身份管理器

運行身份管理器的用處在大多數應用系統中是有限的,例如某個操作和模塊需要多個身份的安全需求,運行身份管理器可以用另一個身份替換你目前的身份,從而允許你訪問應用系統內部更深處的受保護對象。這一層安全體系目前的 RBAC 中尚未實現。

ThinkPHP中RBAC認證流程

對應上面的安全體系,ThinkPHP 的 RBAC 認證的過程大致如下:

  1. 判斷當前模塊的當前操作是否需要認證
  2. 如果需要認證並且尚未登錄,跳到認證網關,如果已經登錄 執行5
  3. 通過委托認證進行用戶身份認證
  4. 獲取用戶的決策訪問列表
  5. 判斷當前用戶是否具有訪問權限

權限管理的具體實現過程

RBAC相關的數據庫介紹

在ThinkPHP完整包,包含了RBAC處理類RBAC.class.php文件,位於Extend/Library/ORG/Util。打開該文件,其中就包含了使用RBAC必備的4張表,SQL語句如下(復制后請替換表前綴):

// 配置文件增加設置
// ADMIN_AUTH_KEY 管理員的認證鍵名
// USER_AUTH_ON 是否需要認證
// USER_AUTH_TYPE 認證類型 1 登錄認證 2 實時認證 見AccessDecision函數。
// USER_AUTH_KEY 認證識別號
// REQUIRE_AUTH_MODULE  需要認證模塊
// NOT_AUTH_MODULE 無需認證模塊
// USER_AUTH_GATEWAY 認證網關
// RBAC_DB_DSN  數據庫連接DSN
// RBAC_ROLE_TABLE 角色表名稱
// RBAC_USER_TABLE 用戶表名稱
// RBAC_ACCESS_TABLE 權限表名稱
// RBAC_NODE_TABLE 節點表名稱
CREATE TABLE IF NOT EXISTS `think_access` (
  `role_id` smallint(6) unsigned NOT NULL,
  `node_id` smallint(6) unsigned NOT NULL,
  `level` tinyint(1) NOT NULL,
  `module` varchar(50) DEFAULT NULL,
  KEY `groupId` (`role_id`),
  KEY `nodeId` (`node_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `think_node` (
  `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `title` varchar(50) DEFAULT NULL,
  `status` tinyint(1) DEFAULT '0',
  `remark` varchar(255) DEFAULT NULL,
  `sort` smallint(6) unsigned DEFAULT NULL,
  `pid` smallint(6) unsigned NOT NULL,
  `level` tinyint(1) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `level` (`level`),
  KEY `pid` (`pid`),
  KEY `status` (`status`),
  KEY `name` (`name`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `think_role` (
  `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `pid` smallint(6) DEFAULT NULL,
  `status` tinyint(1) unsigned DEFAULT NULL,
  `remark` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `pid` (`pid`),
  KEY `status` (`status`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 ;

CREATE TABLE IF NOT EXISTS `think_role_user` (
  `role_id` mediumint(9) unsigned DEFAULT NULL,
  `user_id` char(32) DEFAULT NULL,
  KEY `group_id` (`role_id`),
  KEY `user_id` (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

user用戶表,這個根據業務自己定義

字段名 字段類型 作用
id INT 用戶ID(唯一識別號)
username VARCHAR(16) 用戶名
password VARCHAR(32) 密碼
email VARCHAR(100) 用戶郵箱
create_time TIMESTAMP 創建時間(時間戳)
logintime TIMESTAMP 最近一次登錄時間(時間戳)
loginip VARCHAR(15) 最近登錄的IP地址
status TINYINT(1) 啟用狀態:0:表示禁用;1:表示啟用
remark VARCHAR(255) 備注信息

role角色表

字段名 字段類型 作用
id INT 角色ID
name VARCHAR(20) 角色名稱
pid SMALLINT(6) 父角色對應ID
status TINYINT(1) 啟用狀態(同上)
remark VARCHAR(255) 備注信息

node節點表(功能模塊節點)

字段名 字段類型 作用
id SMALLINT(6) 節點ID
name VARCHAR(20) 節點名稱(英文名,對應應用控制器、應用、方法名)
title VARCHAR(50) 節點中文名(方便看懂)
status TINYINT(1) 啟用狀態(同上)
remark VARCHAR(255) 備注信息
sort SMALLINT(6) 排序值(默認值為50)
pid SMALLINT(6) 父節點ID(如:方法pid對應相應的控制器)
level TINYINT(1) 節點類型:1:表示應用(模塊);2:表示控制器;3:表示方法

role_user用戶角色關系表

字段名 字段類型 作用
user_id INT 用戶ID
role_id SMALLINT(6) 角色ID

access權限表

字段名 字段類型 作用
role_id SMALLINT(6) 角色ID
node_id SMALLINT(6) 節點ID
level TINYINT(1) 冗余節點表界別?
module VARCHAR(50) 模塊說明?

ThinkPHP的RBAC處理類

  1 class Rbac {
  2     // 認證方法,$map參數根據用戶表自定義,只要能校驗用戶名密碼就行。
  3     static public function authenticate($map,$model='') {
  4         if(empty($model)) $model =  C('USER_AUTH_MODEL');
  5         //使用給定的Map進行認證
  6         return M($model)->where($map)->find();
  7     }
  8  
  9     //用於檢測用戶權限的方法,並保存到Session中
 10     static function saveAccessList($authId=null) {
 11         if(null===$authId)   $authId = $_SESSION[C('USER_AUTH_KEY')];
 12         // 如果使用普通權限模式,保存當前用戶的訪問權限列表
 13         // 對管理員開發所有權限
 14         if(C('USER_AUTH_TYPE') !=2 && !$_SESSION[C('ADMIN_AUTH_KEY')] )
 15             $_SESSION['_ACCESS_LIST']    =    self::getAccessList($authId);
 16         return ;
 17     }
 18  
 19     // 取得模塊的所屬記錄訪問權限列表 返回有權限的記錄ID數組
 20     static function getRecordAccessList($authId=null,$module='') {
 21         if(null===$authId)   $authId = $_SESSION[C('USER_AUTH_KEY')];
 22         if(empty($module))  $module    =    CONTROLLER_NAME;
 23         //獲取權限訪問列表
 24         $accessList = self::getModuleAccessList($authId,$module);
 25         return $accessList;
 26     }
 27  
 28     //檢查當前操作是否需要認證
 29     static function checkAccess() {
 30         //如果項目要求認證,並且當前模塊需要認證,則進行權限認證
 31         if( C('USER_AUTH_ON') ){
 32             $_module    =    array();
 33             $_action    =    array();
 34             if("" != C('REQUIRE_AUTH_MODULE')) {
 35                 //需要認證的模塊
 36                 $_module['yes'] = explode(',',strtoupper(C('REQUIRE_AUTH_MODULE')));
 37             }else {
 38                 //無需認證的模塊
 39                 $_module['no'] = explode(',',strtoupper(C('NOT_AUTH_MODULE')));
 40             }
 41             //檢查當前模塊是否需要認證
 42             if((!empty($_module['no']) && !in_array(strtoupper(CONTROLLER_NAME),$_module['no'])) || (!empty($_module['yes']) && in_array(strtoupper(CONTROLLER_NAME),$_module['yes']))) {
 43                 if("" != C('REQUIRE_AUTH_ACTION')) {
 44                     //需要認證的操作
 45                     $_action['yes'] = explode(',',strtoupper(C('REQUIRE_AUTH_ACTION')));
 46                 }else {
 47                     //無需認證的操作
 48                     $_action['no'] = explode(',',strtoupper(C('NOT_AUTH_ACTION')));
 49                 }
 50                 //檢查當前操作是否需要認證
 51                 if((!empty($_action['no']) && !in_array(strtoupper(ACTION_NAME),$_action['no'])) || (!empty($_action['yes']) && in_array(strtoupper(ACTION_NAME),$_action['yes']))) {
 52                     return true;
 53                 }else {
 54                     return false;
 55                 }
 56             }else {
 57                 return false;
 58             }
 59         }
 60         return false;
 61     }
 62  
 63     // 登錄檢查
 64     static public function checkLogin() {
 65         //檢查當前操作是否需要認證
 66         if(self::checkAccess()) {
 67             //檢查認證識別號
 68             if(!$_SESSION[C('USER_AUTH_KEY')]) {
 69                 if(C('GUEST_AUTH_ON')) {
 70                     // 開啟游客授權訪問
 71                     if(!isset($_SESSION['_ACCESS_LIST']))
 72                         // 保存游客權限
 73                         self::saveAccessList(C('GUEST_AUTH_ID'));
 74                 }else{
 75                     // 禁止游客訪問跳轉到認證網關
 76                     redirect(PHP_FILE.C('USER_AUTH_GATEWAY'));
 77                 }
 78             }
 79         }
 80         return true;
 81     }
 82  
 83     //權限認證的過濾器方法
 84     static public function AccessDecision($appName=MODULE_NAME) {
 85         //檢查是否需要認證
 86         if(self::checkAccess()) {
 87             //存在認證識別號,則進行進一步的訪問決策
 88             $accessGuid   =   md5($appName.CONTROLLER_NAME.ACTION_NAME);
 89             if(empty($_SESSION[C('ADMIN_AUTH_KEY')])) {
 90                 if(C('USER_AUTH_TYPE')==2) {
 91                     //加強驗證和即時驗證模式 更加安全 后台權限修改可以即時生效
 92                     //通過數據庫進行訪問檢查
 93                     $accessList = self::getAccessList($_SESSION[C('USER_AUTH_KEY')]);
 94                 }else {
 95                     // 如果是管理員或者當前操作已經認證過,無需再次認證
 96                     if( $_SESSION[$accessGuid]) {
 97                         return true;
 98                     }
 99                     //登錄驗證模式,比較登錄后保存的權限訪問列表
100                     $accessList = $_SESSION['_ACCESS_LIST'];
101                 }
102                 //判斷是否為組件化模式,如果是,驗證其全模塊名
103                 if(!isset($accessList[strtoupper($appName)][strtoupper(CONTROLLER_NAME)][strtoupper(ACTION_NAME)])) {
104                     $_SESSION[$accessGuid]  =   false;
105                     return false;
106                 }
107                 else {
108                     $_SESSION[$accessGuid]    =    true;
109                 }
110             }else{
111                 //管理員無需認證
112                 return true;
113             }
114         }
115         return true;
116     }
117  
118     /**
119      +----------------------------------------------------------
120      * 取得當前認證號的所有權限列表,看起來有點暈,仔細閱讀。
121      +----------------------------------------------------------
122      * @param integer $authId 用戶ID
123      +----------------------------------------------------------
124      * @access public
125      +----------------------------------------------------------
126      */
127     static public function getAccessList($authId) {
128         // Db方式權限數據
129         $db     =   Db::getInstance(C('RBAC_DB_DSN'));
130         $table = array('role'=>C('RBAC_ROLE_TABLE'),'user'=>C('RBAC_USER_TABLE'),'access'=>C('RBAC_ACCESS_TABLE'),'node'=>C('RBAC_NODE_TABLE'));
131         $sql    =   "select node.id,node.name from ".
132                     $table['role']." as role,".
133                     $table['user']." as user,".
134                     $table['access']." as access ,".
135                     $table['node']." as node ".
136                     "where user.user_id='{$authId}' and user.role_id=role.id and ( access.role_id=role.id  or (access.role_id=role.pid and role.pid!=0 ) ) and role.status=1 and access.node_id=node.id and node.level=1 and node.status=1";
137         $apps =   $db->query($sql);
138         $access =  array();
139         foreach($apps as $key=>$app) {
140             $appId    =    $app['id'];
141             $appName     =     $app['name'];
142             // 讀取項目的模塊權限
143             $access[strtoupper($appName)]   =  array();
144             $sql    =   "select node.id,node.name from ".
145                     $table['role']." as role,".
146                     $table['user']." as user,".
147                     $table['access']." as access ,".
148                     $table['node']." as node ".
149                     "where user.user_id='{$authId}' and user.role_id=role.id and ( access.role_id=role.id  or (access.role_id=role.pid and role.pid!=0 ) ) and role.status=1 and access.node_id=node.id and node.level=2 and node.pid={$appId} and node.status=1";
150             $modules =   $db->query($sql);
151             // 判斷是否存在公共模塊的權限
152             $publicAction  = array();
153             foreach($modules as $key=>$module) {
154                 $moduleId     =     $module['id'];
155                 $moduleName = $module['name'];
156                 if('PUBLIC'== strtoupper($moduleName)) {
157                 $sql    =   "select node.id,node.name from ".
158                     $table['role']." as role,".
159                     $table['user']." as user,".
160                     $table['access']." as access ,".
161                     $table['node']." as node ".
162                     "where user.user_id='{$authId}' and user.role_id=role.id and ( access.role_id=role.id  or (access.role_id=role.pid and role.pid!=0 ) ) and role.status=1 and access.node_id=node.id and node.level=3 and node.pid={$moduleId} and node.status=1";
163                     $rs =   $db->query($sql);
164                     foreach ($rs as $a){
165                         $publicAction[$a['name']]     =     $a['id'];
166                     }
167                     unset($modules[$key]);
168                     break;
169                 }
170             }
171             // 依次讀取模塊的操作權限
172             foreach($modules as $key=>$module) {
173                 $moduleId     =     $module['id'];
174                 $moduleName = $module['name'];
175                 $sql    =   "select node.id,node.name from ".
176                     $table['role']." as role,".
177                     $table['user']." as user,".
178                     $table['access']." as access ,".
179                     $table['node']." as node ".
180                     "where user.user_id='{$authId}' and user.role_id=role.id and ( access.role_id=role.id  or (access.role_id=role.pid and role.pid!=0 ) ) and role.status=1 and access.node_id=node.id and node.level=3 and node.pid={$moduleId} and node.status=1";
181                 $rs =   $db->query($sql);
182                 $action = array();
183                 foreach ($rs as $a){
184                     $action[$a['name']]     =     $a['id'];
185                 }
186                 // 和公共模塊的操作權限合並
187                 $action += $publicAction;
188                 $access[strtoupper($appName)][strtoupper($moduleName)]   =  array_change_key_case($action,CASE_UPPER);
189             }
190         }
191         return $access;
192     }
193  
194     // 讀取模塊所屬的記錄訪問權限
195     static public function getModuleAccessList($authId,$module) {
196         // Db方式
197         $db     =   Db::getInstance(C('RBAC_DB_DSN'));
198         $table = array('role'=>C('RBAC_ROLE_TABLE'),'user'=>C('RBAC_USER_TABLE'),'access'=>C('RBAC_ACCESS_TABLE'));
199         $sql    =   "select access.node_id from ".
200                     $table['role']." as role,".
201                     $table['user']." as user,".
202                     $table['access']." as access ".
203                     "where user.user_id='{$authId}' and user.role_id=role.id and ( access.role_id=role.id  or (access.role_id=role.pid and role.pid!=0 ) ) and role.status=1 and  access.module='{$module}' and access.status=1";
204         $rs =   $db->query($sql);
205         $access    =    array();
206         foreach ($rs as $node){
207             $access[]    =    $node['node_id'];
208         }
209         return $access;
210     }
211 }

實際使用

登錄校驗

    // 用戶登錄頁面,如果已經登陸過,直接跳轉主頁
    public function login() {
        //RBAC類里面也包含了checkLogin函數,用哪個隨意。
        if(!isset($_SESSION[C('USER_AUTH_KEY')])) {
            $this->display();
        }else{
            $this->redirect('Index/index');
        }
    }

    // 用戶登出,清空相關session參數
    public function logout() {
        if(isset($_SESSION[C('USER_AUTH_KEY')])) {
            unset($_SESSION[C('USER_AUTH_KEY')]);
            unset($_SESSION);
            session_destroy();
            $this->success('登出成功!',__URL__.'/login/');
        }else {
            $this->error('已經登出!');
        }
    }

    // 檢查用戶是否登錄
    protected function checkUser() {
        //RBAC類里面也包含了checkLogin函數,用哪個隨意。
        if(!isset($_SESSION[C('USER_AUTH_KEY')])) {
            $this->error('沒有登錄','Public/login');
        }
    }

    // 登錄檢測
    public function checkLogin() {
        if(empty($_POST['account'])) {
            $this->error('帳號錯誤!');
        }elseif (empty($_POST['password'])){
            $this->error('密碼必須!');
        }elseif (empty($_POST['verify'])){
            $this->error('驗證碼必須!');
        }
        //生成認證條件
        $map            =   array();
        // 首先使用賬號密碼校驗是否正確,這里的account等參數和數據庫用戶表設計相關。
        $map['account']    = $_POST['account'];
        $map["status"]    =    array('gt',0);
        //先檢測驗證碼
        if(session('verify') != md5($_POST['verify'])) {
            $this->error('驗證碼錯誤!');
        }
        //開始使用RBAC校驗。
        import ( '@.ORG.Util.RBAC' );
        //這里會使用配置文件USER_AUTH_MODEL設置的model來校驗,去用戶表查看用戶密碼是否正確。
        $authInfo = RBAC::authenticate($map);
        //使用用戶名、密碼和狀態的方式進行認證
        if(false === $authInfo) {
            $this->error('帳號不存在或已禁用!');
        }else {
            //這里的密碼比對根據自己的密碼加鹽算法進行修改,默認是用MD5
            if($authInfo['password'] != md5($_POST['password'])) {
                $this->error('密碼錯誤!');
            }
            //校驗沒問題,設置好session會話參數,類似authInfo的參數和用戶表的設計有關。
            $_SESSION[C('USER_AUTH_KEY')]    =    $authInfo['id'];
            $_SESSION['email']    =    $authInfo['email'];
            $_SESSION['loginUserName']        =    $authInfo['nickname'];
            $_SESSION['lastLoginTime']        =    $authInfo['last_login_time'];
            $_SESSION['login_count']    =    $authInfo['login_count'];
            //不一定是admin就是管理員,這個也看系統設計,見配置文件ADMIN_AUTH_KEY
            if($authInfo['account']=='admin') {
                $_SESSION['administrator']        =    true;
            }
            //保存這一次的登錄信息
            $User    =    M('User');
            $ip        =    get_client_ip();
            $time    =    time();
            $data = array();
            $data['id']    =    $authInfo['id'];
            $data['last_login_time']    =    $time;
            $data['login_count']    =    array('exp','login_count+1');
            $data['last_login_ip']    =    $ip;
            $User->save($data);
 
            // 用於檢測用戶權限的方法,並保存到Session中
            RBAC::saveAccessList();
            $this->success('登錄成功!',__APP__.'/Index/index');
 
        }
    }

 

自動校驗權限狀態

在Controller或者Action中初始化函數校驗是否登錄,然后繼承即可

 1     function _initialize() {
 2         import('@.ORG.Util.Cookie');
 3         // 用戶權限檢查,只檢查需要校驗的模塊
 4         if (C('USER_AUTH_ON') && !in_array(MODULE_NAME, explode(',', C('NOT_AUTH_MODULE')))) {
 5             import('@.ORG.Util.RBAC');
 6             //判斷是否有權限,見源碼。
 7             if (!RBAC::AccessDecision()) {
 8                 //檢查認證識別號
 9                 if (!$_SESSION [C('USER_AUTH_KEY')]) {
10                     //跳轉到認證網關
11                     redirect(PHP_FILE . C('USER_AUTH_GATEWAY'));
12                 }
13                 // 沒有權限 拋出錯誤
14                 if (C('RBAC_ERROR_PAGE')) {
15                     // 定義權限錯誤頁面
16                     redirect(C('RBAC_ERROR_PAGE'));
17                 } else {
18                     //開啟游客驗證,跳轉登錄界面
19                     if (C('GUEST_AUTH_ON')) {
20                         $this->assign('jumpUrl', PHP_FILE . C('USER_AUTH_GATEWAY'));
21                     }
22                     // 提示錯誤信息
23                     $this->error(L('_VALID_ACCESS_'));
24                 }
25             }
26         }
27     }

 

總的來說,讀完源碼,再實際使用一遍,絕對搞定~

 

thinkphp demo下載地址

鏈接:http://pan.baidu.com/s/1ge5pkll 密碼:qds8

參考

http://www.lyblog.net/detail/552.html
http://www.thinkphp.cn/extend/235.html





附件列表

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM