函式型介面

天笑發表於2017-03-01

函式型介面

如果不是典型的物件增刪改查操作,可以設計函式型介面,比如登入、修改密碼、上傳檔案這些。

函式型介面一般實現在檔案 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取應用型別,不同的應用如果是相同的應用型別,則登入方式相同,比如上例中都是用員工登入。

相關文章