如果不是典型的對象增刪改查操作,可以設計函數型接口,比如登錄、修改密碼、上傳文件這些。
函數型接口一般實現在文件 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
取應用類型,不同的應用如果是相同的應用類型,則登錄方式相同,比如上例中都是用員工登錄。