筋斗云接口编程 / 函数型接口

如果不是典型的对象增删改查操作,可以设计函数型接口,比如登录、修改密码、上传文件这些。

函数型接口一般实现在文件 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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章