如何寫出安全又可靠的PHP指令碼

發表於2021-09-22

前言

咔咔目前所做的專案是一個saas系統,在開發新功能之後,需要為使用者角色新增相應的許可權,這時整個系統的所有使用者都需要新增相應的許可權。

因為以前系統的缺陷現在只能用指令碼來處理這些工作,所以接下來咔咔將向你介紹如何編寫安全可靠的PHP指令碼,以及如何事先設計好這個功能,踩過一個坑直接把它埋起來

一、如何寫一份安全又可靠的PHP指令碼

1-1 設定合理的記憶體

PHP中使用memory_limit為每個程式設定記憶體,跑指令碼的記憶體給256M或512M。

通過設定記憶體來防止指令碼執行死迴圈佔用大量的記憶體,導致系統崩潰。

在檔案開頭寫入ini_set('memory_limit', '512M');即可。

1-2 可接收命令列傳參

在PHP中接命令列的引數為$argv,下標0是檔名,下標1為你傳入的引數。

簡單來說就是先建立一個 index.php檔案,然後列印出$argv

執行index.php檔案php index.php kaka,列印出來的資料與上面介紹的陣列形式完全一樣。第一個值是執行的檔名,第二個引數就是攜帶給指令碼的引數。

之前寫了一篇laravel中給命令列攜帶引數不瞭解一下嗎?可以看看,當時是rabbitmq路由模式遇到問題才知道的這個引數,如今在寫指令碼就可以直接用了。

1-3 使用while死迴圈執行

$id = !empty($argv[1]) ? $argv[1] : 0;
while(true){
    $sql = "select * from user id > $id  order by id asc limit 10000";
    $res = 執行$sql語句;
    if(empty($res)){
        break;
    }
    foreach ($res as $k => $v) {
     // 這個id儲存每次執行的值
        $id = $v['id'];
        $checkDataSql = "檢測資料是否已存在";
    // 不存在時在進行新增資料
        if(!$checkSql){
            $sql = "insert into user ...";
            $res = "執行新增操作";
            // 返回最後執行的主鍵ID
            echo $res."\n";
        }
    }
    // 刪除此次的變數,防止記憶體溢位
    unset($res);
}
echo 'ok';

簡單的解釋一下這份程式碼

  • 接受命令引數,此引數用於防止指令碼執行一半掛掉,可以輸入引數繼續執行。
  • 使用while執行死迴圈邏輯
  • 獲取需要新增許可權的資料,每次查詢10000行。
  • 死迴圈退出條件是查詢不到需要新增許可權的資料。
  • 迴圈處理每次查詢的10000行資料。
  • 將查詢出來的資料ID迴圈一次就賦值給while外層的ID,防止指令碼掛掉知道從哪條資料繼續執行。
  • 做一步檢測操作,判斷此條資料是否已經存在將要給新增的許可權。不存在時再進行新增操作。
  • 最後一步也是最重要的一步將新增完成的主鍵ID返回到終端,指令碼掛掉直接用這個ID作為引數繼續執行。
  • 刪除或者10000行資料的變數,防止記憶體溢位。
  • 最後一步也是最重要的資料處理完成後需要返回一個標識,知道此次指令碼已經執行完成了。

這裡舉的例子的是新增,若你的資料量大的情況下可以進行批量新增、修改。

這份指令碼還不是很完善,如你有更好的想法那我們們評論區見。

二、如何提前設計好類似此種情況

1-1 使用指令碼刷資料的缺點

  • 新員工對業務不熟悉,資料刷錯後恢復資料工作量更大
  • 前期使用者基數少沒感覺,使用者量起來後每次執行指令碼會花費很長時間
  • 使用者量大之後MySQL深度分頁查詢非常慢,會人為造成慢查詢影響正常使用者使用
  • 在你刷資料的同時會有新使用者註冊進來,非常容易造成資料庫死鎖。

為什麼會產生死鎖?

例如你目前的資料資料是這樣的,id、authority_info、name

insert into t values(1,5,5),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);

指令碼執行語句為update role set authority_info=authority_info+1 where id = 7

此條語句為等值查詢會退化為間隙鎖,加鎖範圍為(5,10),若此時來了一個使用者執行語句為insert into role (8,8,8)很遺憾是執行不成功的。

update將(5,10)的資料加了間隙鎖,在沒有釋放鎖的情況下,這個範圍的所有資料是無法新增的。

當然這種情況僅在無主鍵索引的情況下會產生,因為主鍵索引的等值查詢會退化為行鎖。

總之使用指令碼刷資料是百害而無一利的,接下來看看如何處理這種情況。

1-2 應用場景

每一家公司註冊後,預設有幾個角色,如超管、子管、財務、行政、人事等,所有許可權都繫結在相應的角色下,使用單條記錄方式。

既然需要新增一個CUI功能模組,就需要新增此模組對應的許可權,預設情況下為所有租戶相應的所有角色新增這個許可權。

每一個租戶對應角色的許可權都儲存在角色表中,所有許可權資訊是在一個json串中儲存,並切維護了一個更新時間。

那麼我們就以獲取許可權為切入點,主系統單獨維護一個許可權表,來看看這個過程是怎樣的。

1-2 方案落地

那麼如何在這個設計上進行優化,確保後期不再使用指令碼進行刷資料,而是讓使用者自主觸發。

role表結構資料如下

系統表

系統新增新功能需要設定許可權時,將許可權名以json儲存至系統表的auth_info中,每次更新許可權都新增一條資料。

使用者登入後獲取role表的更新時間,去系統表中做判斷將大於role時間的資料拿出來,將查詢出來的許可權合併到role表中,並更新這條記錄的更新時間。

並將新增的許可權維護至應用初始化中,新註冊的使用者需將所有的許可權給新增上,這點根據自身系統看是否需要維護。

1-4 方案弊端

此方案雖然解決了每次新增新許可權需要執行指令碼,但不可避免使用者每次登入獲取許可權時都需要使用自身的許可權時間跟系統許可權時間做比對。

無形中會多進行一次查詢,這也是咔咔目前能想到的方案,你要是有更好的方案可以探討一下。

三、總結

本文圍繞實際業務進行討論了指令碼如何寫、設計缺陷如何後期彌補,在指令碼中一定要使用正序(防止新使用者註冊進來)這點相信大家都清楚。

方案在我看來還不是最優的,你要是有更好的思路,可以在評論區擴充套件一下。

堅持學習、堅持寫作、堅持分享是咔咔從業以來所秉持的信念。願文章在偌大的網際網路上能給你帶來一點幫助,我是咔咔,下期見。

相關文章