筋斗雲接口編程 / 函數型接口

如果不是典型的對象增刪改查操作,可以設計函數型接口,比如登錄、修改密碼、上傳文件這些。

函數型接口一般實現在文件 php/api_functions.php 中,它被主文件api.php包含。
假設有以下接口定義:

獲取登錄信息(who am i?)

whoami() -> {id}

應用邏輯
- 權限:AUTH_USER (必須用戶登錄後纔可用)

我們使用模擬數據實現接口,函數名規範爲api_{接口名}

function api_whoami()
{
    checkAuth(AUTH_USER);
    return ["id" => 100];
}

在api_functions.php中,作爲示例,其中已經定義了登錄、退出等接口,實際開發時在其基礎上修改即可。
由於登錄與權限定義密切相關,爲了瞭解原理,我們清空這個文件,重新來寫登錄、退出接口。
同時學習獲取參數、數據庫操作等常用函數。

[任務]

本節要求實現登錄、退出、取登錄信息三個接口,設計如下:

登錄接口

login(uname, pwd, _app?=user) -> {id, _isNew?}

用戶或員工登錄(通過_app參數區分),如果是用戶登錄且用戶不存在,可自動創建用戶。

參數
- _app: 前端應用名稱,用於區分登錄類型,"user"-用戶端, "emp"-員工端。

返回
- _isNew: 如果是新註冊用戶,該字段爲1,否則不返回此字段。

應用邏輯
- 權限: AUTH_GUEST
- 對於用戶登錄(_app是"user"),如果用戶不存在,則自動創建用戶。
- 密碼採用md5加密保存

取登錄信息

whoami() -> {id}

如果已登錄,則返回與登錄接口相同的信息,否則返回未登錄錯誤。
用戶端或員工端均可用。
客戶端可調用本接口測試是否可以通過重用會話,實現免登錄進入。

應用邏輯
- 權限:AUTH_USER | AUTH_EMP

退出接口

logout()

退出登錄。用戶端或員工端均可用。

應用邏輯
- 權限:AUTH_USER | AUTH_EMP

在接口定義中,一般包括接口原型,參數及返回數據說明,應用邏輯等。
對於含義清晰的參數和返回數據,也不必一一說明。
應用邏輯中應先規定該接口的權限。

權限定義

在實現接口前,我們先了解如何定義權限。

權限定義在接口應用的主文件api.php中,打開它我們能看到登錄類型和權限類型的定義:

const AUTH_GUEST = 0;
// 登陸類型
const AUTH_USER = 0x01;
const AUTH_EMP = 0x02;
const AUTH_ADMIN = 0x04;

// AUTH_LOGIN是一個特殊的權限,表示任一身份已登錄。
define("AUTH_LOGIN", AUTH_USER | AUTH_EMP | AUTH_ADMIN);

// 權限類型
const PERM_MGR = 0x08;
const PERM_TEST_MODE = 0x1000;
const PERM_MOCK_MODE = 0x2000;

$PERMS = [
    AUTH_GUEST => "guest",
    AUTH_USER => "user",
    AUTH_EMP => "employee",
    AUTH_ADMIN => "admin",

    PERM_MGR => "manager",

    PERM_TEST_MODE => "testmode",
    PERM_MOCK_MODE => "mockmode",
];

上面按二進制位數不同,定義登錄類型和各類權限,測試模式與模擬模式也可當作特殊的權限來對待。
在全局變量$PERMS中,爲每個權限指定了一個可讀的名字。

然後定義有一個重要的回調函數onGetPerms,它將根據登錄情況、session中的數據或全局變量來取出所有當前可能有的權限,
後面常用的檢查權限的函數hasPerm/checkAuth都將調用它:

function onGetPerms()
{
    $perms = 0;
    if (isset($_SESSION["uid"])) {
        $perms |= AUTH_USER;
    }
    else if (isset($_SESSION["empId"])) {
        $perms |= AUTH_EMP;
    }
    ...

    if (@$GLOBALS["TEST_MODE"]) {
        $perms |= PERM_TEST_MODE;
    }
    ...

    return $perms;
}

在登錄成功時,我們應設置相應的session變量,如用戶登錄成功設置$_SESSION["uid"],員工登錄成功設置$_SESSION["empId"],等等。

後面講對象型接口時,還會有另一個重要的回調函數onCreateAC,用於將權限與類名進行綁定。

登錄與退出

上節我們已經瞭解到,登錄與權限檢查密切相關,需要將用戶信息存入session中,登錄接口的大致實現如下:

function api_login()
{
    $type = getAppType();
    if ($type == "user") {
        ... 驗證成功 ...
        $_SESSION["uid"] = ...
    }
    else if ($type == "emp") {
        ... 驗證成功 ...
        $_SESSION["empId"] = ...
    }
    ...
}

定義一個函數型接口,函數名稱一定要符合 api_{接口名} 的規範。接口名以小寫字母開頭。
在接口實現時,一般應根據接口中的權限說明,使用checkAuth函數進行權限檢查。

// 按設計要求,用md5加密後保存密碼。
function hashPwd($pwd)
{
    return md5($pwd);
}

function api_login()
{
    $type = getAppType();
    $uname = mparam("uname");
    $pwd = mparam("pwd");

    // 用戶登錄,如不存在則自動創建新用戶
    if ($type == "user") {
        $sql = sprintf("SELECT id,pwd FROM User WHERE uname=%s", Q($uname));
        $row = queryOne($sql, PDO::FETCH_ASSOC);
        if ($row === false) {
            // 自動註冊新用戶
            $sql = sprintf("INSERT INTO User (uname, pwd) VALUES (%s, '%s')", Q($uname), hashPwd($pwd));
            $id = execOne($sql, true);
            $ret = [
                "id" => $id,
                "_isNew" => 1
            ];
        }
        else if (hashPwd($pwd) != $row["pwd"]) {
            throw new MyException(E_AUTHFAIL, "bad password", "密碼錯誤");
        }
        else {
            $ret = ["id" => $row["id"] ];
        }
        $_SESSION["uid"] = $ret["id"];
    }
    // 員工登錄
    else if ($type == "emp") {
        $sql = sprintf("SELECT id,pwd FROM Employee WHERE uname=%s", Q($uname));
        $row = queryOne($sql, PDO::FETCH_ASSOC);
        if ($row === false || hashPwd($pwd) != $row["pwd"])
            throw new MyException(E_AUTHFAIL, "bad uname or password", "用戶名或密碼錯誤");

        $ret = ["id" => $row["id"] ];
        $_SESSION["empId"] = $row["id"];
    }
    else {
        throw new MyException(E_PARAM, "Unknown type `$type`");
    }

    return $ret;
}

在api_login函數中,先使用框架函數getAppType獲取到登錄類型(也稱應用類型),再按登錄類型分別查驗身份,並最終設置$_SESSION相關變量,
這裏設置的變量與之前的權限回調函數onGetPerms中相對應。

這裏使用了很多常用函數,比如獲取必需參數使用mparam函數,數據庫查詢使用了queryOne, execOne函數,出錯返回使用MyException等,之後章節將詳細介紹。

在實現whoami接口時,返回保存在會話(session)中的變量即可,logout接口則更加簡單,直接銷燬會話:


function api_whoami()
{
    checkAuth(AUTH_USER | AUTH_EMP);
    // 也可以用AUTH_LOGIN這個特殊的權限,表示任一身份已登錄。
    // checkAuth(AUTH_LOGIN);
    if (hasPerm(AUTH_USER))
        return ["id"=> $_SESSION["uid"]];
    if (hasPerm(AUTH_EMP))
        return ["id"=> $_SESSION["empId"]];
    throw new MyException(E_SERVER);
}

function api_logout()
{
    checkAuth(AUTH_LOGIN);
    session_destroy();
}

[應用標識與應用類型]

在筋斗雲中,URL參數_app稱爲前端應用標識(app),缺省爲”user”,表示用戶端應用。

不同應用要求使用不同的應用標識,在與後端的會話中使用的cookie也會有所不同,因而不同的應用即使同時在瀏覽器中打開也不會相互干擾。

應用標識中的主幹部分稱爲應用類型(app type),例如有三個應用分別標識爲”emp”(員工端), “emp2”(經理端)和”emp-store”(商戶管理端),
它們的主幹部分(去除尾部數字,去除”-“及後面部分)是相同的,都是”emp”,即它們具有相同的應用類型”emp”。

函數getAppType就是用來根據URL參數_app取應用類型,不同的應用如果是相同的應用類型,則登錄方式相同,比如上例中都是用員工登錄。

發佈了65 篇原創文章 · 獲贊 16 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章