洪柏利 / RestfulApiPHP

0 Watch 1 Star 0 Fork 0
加入码云
与超过 300 万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
简单介绍RestfulApi的用法 展开 收起

paidui_pc 最后提交于 更新readme.md
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README.md 42.56 KB

RestfulApi

简单介绍RestfulApi的用法,主要是参考慕课网的《Restful API实战》教程

1-1 restful简介及资源介绍

1. Restful的介绍

本质:基于资源的一种软件架构风格

核心:面向资源

解决的问题:

  • 降低开发的复杂性
  • 提高系统的可伸缩性

2. 设计概念和准则

网络上的所有事物都可以被抽象为资源

每一个资源都有唯一的资源标识,对资源的操作不会改变这些标识

所有的操作都是无状态的

3. 资源

所谓的资源,就是网络上的一个实体,或者说是网络上的一个具体信息

2-1 Restful中HTTP协议介绍

1. HTTP的协议

HTTP全称是HyperText Transfer Protocal,即:超文本传输协议,从1990年开始就在WWW上广泛应用,是现今在WWW上应用最多的协议。

Http是应用层协议,特点是简洁快速。

当你上网浏览网页的时候,浏览器和Web服务器之间就会通过HTTP在Internet上进行数据的发送和接收。

Http是一个基于请求/响应模式的、无状态的协议。即我们通常所说的Request/Response

比如:schema://host[:port]/path[?query-string][#anchor]

  • scheme:指定底层使用的协议(如http、https、ftp等)
  • host:服务器的IP地址或者域名
  • port:服务器端口,默认为80
  • path:访问资源的路径
  • query-string:发送给http服务器的数据
  • anthor:锚

2. HTTP协议-请求

组成格式:请求行、消息报头、请求正文

3. 请求行

请求行的格式如下:

Method Request-URL HTTP-Version CRLF

  • Method:请求方法(get、post等)
  • Request-URL:一个统一资源标识符,即请求地址
  • HTTP-Version:请求的HTTP协议版本
  • CRLF:回车和换行(除了作为结尾的CRLF外,不允许出现单独的CR或LF字符)

比如:GET / HTTP/1.1 CRLF

4. 请求方法

  • GET:请求获取Request-URL所标识的资源
  • POST:在Request-URL所标识的资源后附加新的数据
  • HEAD:请求获取由Request-URL所标识的资源的响应消息报头
  • PUT:请求服务器存储一个资源,并用Request-URL作为其标识
  • DELETE:请求服务器删除Request-URL所标识的资源
  • TRACE:请求服务器回送收到的请求信息,主要用于测试或诊断
  • OPTIONS:请求查询服务器的性能,或者查询与资源相关的选项和需求

5. HTTP协议-响应

组成格式:状态行、消息报头、响应正文

6. 状态行

状态行的格式如下:

HTTP-Version Status-Code Reason-Phrase CRLF

  • HTTP-Version:服务器HTTP协议的版本
  • Status-Code:服务器发回的响应状态代码
  • Reason-Phrase:状态代码的文本描述

比如:HTTP/1.1 200 OK

7. 常用状态码

  • 200 OK :客户端请求成功
  • 400 Bad Request :客户端请求有语法错误,不能被服务器所理解
  • 401 Unauthorized :服务器收到请求,但是拒绝提供服务
  • 404 Not Found :请求资源不存在
  • 500 Internal Server Error :服务器发生不可预期的错误
  • 503 Server Unavailable :服务器当前不能处理客户端的请求

2-2 架构区别

1. SOAP WebService架构

WebService 是一种跨编程语言和跨操作系统平台的远程调用技术。

WebService通过HTTP协议发送请求和接收结果时采用XML格式封装,并增加了一些特定的HTTP消息头,这些特定的HTTP消息头和XML内容格式就是SOAP协议

2. Restful架构和SOAP架构的区别

  • 效率和易用性

SOAP由于各种需求不断扩充其本身协议的内容,导致在SOAP处理方面的性能有所下降,同时在易用性方面以及学习成本上也有所增加,Restful由于其面向资源接口设计以及操作抽象简化了开发者的不良设计,同时也最大限度的利用了HTTP最初的应用协议设计理念

  • 安全性

Restful对于资源型服务接口来说很合适,同时特别适合对于效率要求很高,但是对于安全要求不高的场景。而SOAP的成熟性可以给需要提供给多开发语言,对于安全性要求较高的接口设计带来便利。

3-1 Restful设计要素

1. Restful的设计要素

  • 资源路径(URL)
  • HTTP动词(请求方式)
  • 过滤信息
  • 状态码
  • 错误处理
  • 返回结果

2. 资源路径

在Restful架构中,每个网址代表一种资源,所以网址中不能有动词,只能有名词。一般来说API中的名词应该使用复数。 比如:有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样:

https://api.example.com/v1/zoos

https://api.example.com/v1/animals

3. HTTP动词

对于资源的操作(CURD),由HTTP动词表示

  • GET:从服务器取出资源(一项或多项)
  • POST:在服务器新建一个资源
  • PUT:在服务器更新资源(客户端提供改变后的完整资源)
  • PATCH:在服务器更新资源(客户端提供改变的属性)
  • DELETE:从服务器删除资源

比如:

  • POST /zoos:新建一个动物园
  • GET /zoos/ID:获取某个指定动物园的信息
  • PUT /zoos/ID:更新某个指定动物园的信息
  • DELETE /zoos/ID:删除某个动物园

4. 过滤信息

如果记录数量很多,服务器不可能都将它们返回给用户,API应该提供参数,过滤返回结果

比如:

  • ?offset=10:指定返回记录的开始位置
  • ?page=2&per_page=100:指定第几页,以及每页的记录数
  • ?sortby=name&order=asc:指定返回结果排序,以及排序顺序
  • ?animal_type_id=1:指定筛选条件

5. 状态码

服务器向用户返回的状态码和提示信息,使用标准HTTP状态码

比如:

  • 200 OK
  • 201 CREATED(新建或修改成功)
  • 204 NO CONTENT(删除数据成功)
  • 400 BAD REQUEST(请求有错误)
  • 401 Unauthorized(用户没有验证)
  • 403 Forbidden(用户访问被禁止)
  • 422 Unprocesable Entity(创建对象时,验证错误)
  • 500 Internal Server Error(服务器内部错误)

6. 错误处理

如果状态码是4XX或者是5XX,就应该向用户返回出错信息。

一把来说,返回的信息中将error作为键名,出错信息作为键值即可:

{
    "error" : "参数错误"
}

7. 返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范:

  • GET/ collections:返回资源对象列表(数组)
  • GET/ collection/identity:返回单个资源对象
  • POS/ collections:返回新生成的资源对象
  • PUT/collections/identity:返回完整的资源对象
  • PATCH/collections/identity:返回被修改的属性
  • DELETE/collections/identity:返回一个空文档

3-2 DHC Client

我这里是使用postman来代替

3-3 本地开发环境搭建

1. 开发环境搭建

  • 下载UPUPW.NET集成环境
  • 添加虚拟主机以及取消跨站目录限制
  • 添加虚拟主机的本地hosts解析

3-4 确认设计要素

1. 项目需求

  • 用户登录、注册
  • 文章发表、编辑、管理、列表

2. 确认设计要素

  • 资源路径: /users、/articles
  • HTTP动词:GET、POST、DELETE、PUT
  • 过滤信息:文章的分页筛选
  • 状态码:200、404、422、403
  • 错误处理:输出JSON格式错误信息
  • 返回结果:输出JSON数组或JSON对象

3-5 数据库设计

1. 数据库设计

  • 用户表:ID、用户名、密码、注册时间
  • 文章表:文章ID、标题、内容、发表时间、用户ID

两个表的创建sql为:

CREATE TABLE `articles` (
  `article_id` int(30) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(40) NOT NULL,
  `content` text NOT NULL,
  `user_id` int(30) unsigned NOT NULL,
  `created_at` datetime NOT NULL,
  PRIMARY KEY (`article_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `users` (
  `user_id` int(32) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(20) NOT NULL,
  `password` char(32) NOT NULL,
  `created_at` datetime NOT NULL,
  PRIMARY KEY (`user_id`),
  KEY `created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3-6 用户业务逻辑

1. 目录结构

目录结构如下:

Alt1

2. 具体代码

具体的代码如下

入口文件index.php:

<?php

// 采用自动载入类,不用手动去require所需的类文件
spl_autoload_register('autoload');

function autoload($class)
{
    require __DIR__ . '/lib/' . $class . '.php';
}

$pdo = require __DIR__ . '/lib/db.php';
$user = new User($pdo);

// 注册
// var_dump($user->register('admin2', '123456'));

// 登录
var_dump($user->login('admin1', '123456'));

数据库配置文件lib/db.php:

<?php
/**
 * 连接数据库
 */

$pdo = new PDO('mysql:host=localhost;dbname=restful_api', 'root', '123456');

// 使用utf8编码,防止写入中文乱码
$pdo->exec("SET NAMES 'UTF8'");

// 默认pdo查询出来的所有字段的类型都是string类型的,如果想要实现数据库的类型是怎样,pdo查出来的类型就是怎样的话那么可以做下面的设置
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

return $pdo;

错误定义码文件lib/ErrorCode.php:

<?php

class ErrorCode
{
    // 用户名不能为空
    const USERNAME_CANNOT_EMPTY = 1;

    // 密码不能为空
    const PASSWORD_CANNOT_EMPTY = 2;

    // 用户名已存在
    const USERNAME_EXISTS = 3;

    // 注册失败
    const REGISTER_FAILED = 4;

    // 用户名或密码错误
    const USERNAME_OR_PASSWORD_INVALID = 5;
}

用户登录注册处理文件 /lib/User.php:

<?php
/**
 * Class User
 */
class User
{
    /**
     * 数据库连接句柄
     * @var
     */
    private $_db;
    public function __construct($db)
    {
        $this->_db = $db;
    }
    /**
     * 用户登录
     * @param $username
     * @param $password
     * @return array
     * @throws Exception
     */
    public function login($username, $password)
    {
        if (empty($username)) {
            throw new Exception('用户名不能为空', ErrorCode::USERNAME_CANNOT_EMPTY);
        }
        if (empty($password)) {
            throw new Exception('密码不能为空', ErrorCode::PASSWORD_CANNOT_EMPTY);
        }
        $sql = 'SELECT `user_id`, `username`, `created_at` FROM `users` WHERE `username` = :username AND `password` = :password';
        $password = $this->_md5($password);
        $stmt = $this->_db->prepare($sql);
        $stmt->bindParam(':username', $username, PDO::PARAM_STR);
        $stmt->bindParam(':password', $password, PDO::PARAM_STR);
        if (!$stmt->execute()) {
            throw new Exception('服务器内部错误', ErrorCode::SERVER_INTERNAL_ERROR);
        }
        $user = $stmt->fetch(PDO::FETCH_ASSOC);
        if (empty($user)) {
            throw new Exception('用户名或密码错误', ErrorCode::USERNAME_OR_PASSWORD_INVALID);
        }
        return $user;
    }
    /**
     * 用户注册
     * @param $username
     * @param $password
     * @return array
     * @throws Exception
     */
    public function register($username, $password)
    {
        if (empty($username)) {
            throw new Exception('用户名不能为空', ErrorCode::USERNAME_CANNOT_EMPTY);
        }
        if (empty($password)) {
            throw new Exception('密码不能为空', ErrorCode::PASSWORD_CANNOT_EMPTY);
        }
        if ($this->_isUsernameExists($username)) {
            throw new Exception('用户名已存在', ErrorCode::USERNAME_EXISTS);
        }
        // 写入数据库
        $sql = 'INSERT INTO `users`(`username`, `password`, `created_at`) VALUES (:username, :password, :created_at)';
        $stmt = $this->_db->prepare($sql);
        $password = $this->_md5($password);
        $created_at = date('Y-m-d H:i:s', time());
        $stmt->bindParam(':username', $username, PDO::PARAM_STR);
        $stmt->bindParam(':password', $password, PDO::PARAM_STR);
        $stmt->bindParam(':created_at', $created_at);
        if (!$stmt->execute()) {
            throw new Exception('注册失败', ErrorCode::REGISTER_FAILED);
        }
        return [
            'user_id' => $this->_db->lastInsertId(),
            'username' => $username,
            'password' => $password
        ];
    }
    /**
     * md5加密
     * @param $string
     * @param $key
     * @return string
     */
    private function _md5($string, $key = 'password')
    {
        return md5($string . $key);
    }
    /**
     * 判断用户名是否存在
     * @param $username
     * @return bool
     */
    private function _isUsernameExists($username)
    {
        $exists = false;
        $sql = 'SELECT * FROM `users` WHERE `username` = :username';
        $stmt = $this->_db->prepare($sql);
        $stmt->bindParam(':username', $username, PDO::PARAM_STR);
        $stmt->execute();
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!empty($result)) {
            $exists = true;
        }
        return $exists;
    }
}

测试的话,直接运行index.php即可实现注册和登录

3-7 文章业务逻辑

1. 具体代码

入口文件index.php:

<?php

// 采用自动载入类,不用手动去require所需的类文件
spl_autoload_register('autoload');

function autoload($class)
{
    require_once __DIR__ . '/lib/' . $class . '.php';
}

$pdo = require_once __DIR__ . '/lib/db.php';
$user = new User($pdo);
$article = new Article($pdo);

// 注册
// var_dump($user->register('admin2', '123456'));

// 登录
// var_dump($user->login('admin1', '123456'));

// 发表文章
// var_dump($article->create('这个是标题', '这个是内容', 6));

// 查看一篇文章详情
// var_dump($article->view(2));

// 修改一篇文章
// var_dump($article->edit(2, '新的标题2', '新的内容2', 6));

// 删除一篇文章
// var_dump($article->delete(6, 6));

// 获取文章列表
var_dump($article->getList(6, 2, 3));

错误定义码文件lib/ErrorCode.php:

<?php
class ErrorCode
{
    // 用户名不能为空
    const USERNAME_CANNOT_EMPTY = 1;
    // 密码不能为空
    const PASSWORD_CANNOT_EMPTY = 2;
    // 用户名已存在
    const USERNAME_EXISTS = 3;
    // 注册失败
    const REGISTER_FAILED = 4;
    // 用户名或密码错误
    const USERNAME_OR_PASSWORD_INVALID = 5;
    // 文章标题不能为空
    const ARTICLE_TITLE_CANNOT_EMPTY = 6;
    // 文章内容不能为空
    const ARTICLE_CONTENT_CANNOT_EMPTY = 7;
    // 发表文章失败
    const ARTICLE_CREATE_FAILED = 8;
    // 文章ID不能为空
    const ARTICLE_ID_CANNOT_EMPTY = 9;
    // 文章不存在
    const ARTICLE_NOT_FOUND = 10;
    // 无权限操作
    const PERMISSION_DENIED = 11;
    // 文章编辑失败
    const ARTICLE_EDIT_FAILED = 12;
    // 文章删除失败
    const ARTICLE_DELETE_FAILED = 14;
    // 分页大小太大
    const PAGE_SIZE_TOO_BIG = 15;
    // 服务器内部错误
    const SERVER_INTERNAL_ERROR = 16;
}

文章处理文件:lib/Article.php:

<?php
/**
 * Class Article
 */
class Article
{
    private $_db;
    public function __construct($db)
    {
        $this->_db = $db;
    }
    /**
     * 创建文章
     * @param $title
     * @param $content
     * @param $user_id
     * @return array
     * @throws Exception
     */
    public function create($title, $content, $user_id)
    {
        if (empty($title)) {
            throw new Exception('文章标题不能为空', ErrorCode::ARTICLE_TITLE_CANNOT_EMPTY);
        }
        if (empty($content)) {
            throw new Exception('文章内容不能为空', ErrorCode::ARTICLE_CONTENT_CANNOT_EMPTY);
        }
        // 写入数据库
        $sql = 'INSERT INTO `articles`(`title`, `content`, `user_id`, `created_at`) VALUES(:title, :content, :user_id, :created_at)';
        $created_at = date('Y-m-d H:i:s', time());
        $stmt = $this->_db->prepare($sql);
        $stmt->bindParam(':title', $title, PDO::PARAM_STR);
        $stmt->bindParam(':content', $content, PDO::PARAM_STR);
        $stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
        $stmt->bindParam(':created_at', $created_at);
        if (!$stmt->execute()) {
            throw new Exception('发表文章失败', ErrorCode::ARTICLE_CREATE_FAILED);
        }
        return [
            'article_id' => $this->_db->lastInsertId(),
            'title' => $title,
            'content' => $content
        ];
    }
    /**
     * 获取文章详情
     * @param $article_id
     * @return mixed
     * @throws Exception
     */
    public function view($article_id)
    {
        if (empty($article_id)) {
            throw new Exception('文章ID不能为空', ErrorCode::ARTICLE_ID_CANNOT_EMPTY);
        }
        $sql = 'SELECT * FROM `articles` WHERE `article_id` = :article_id';
        $stmt = $this->_db->prepare($sql);
        $stmt->bindParam(':article_id', $article_id, PDO::PARAM_INT);
        $stmt->execute();
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        if (empty($result)) {
            throw new Exception('文章不存在', ErrorCode::ARTICLE_NOT_FOUND);
        }
        return $result;
    }
    /**
     * 修改文章
     * @param $article_id
     * @param $title
     * @param $content
     * @param $user_id
     * @return array|mixed
     * @throws Exception
     */
    public function edit($article_id, $title, $content, $user_id)
    {
        // 获取文章详情
        $article = $this->view($article_id);
        if ($article['user_id'] !== $user_id) {
            throw new Exception('您无权编辑该文章', ErrorCode::PERMISSION_DENIED);
        }
        $title = empty($title) ? $article['title'] : $title;
        $content = empty($content) ? $article['content'] : $content;
        if ($title === $article['title'] && $content === $article['content']) {
            return $article;
        }
        $sql = 'UPDATE `articles` SET `title` = :title, `content` = :content WHERE `article_id` = :article_id';
        $stmt = $this->_db->prepare($sql);
        $stmt->bindParam(':title', $title, PDO::PARAM_STR);
        $stmt->bindParam(':content', $content, PDO::PARAM_STR);
        $stmt->bindParam(':article_id', $article_id, PDO::PARAM_INT);
        if (!$stmt->execute()) {
            throw new Exception('文章编辑失败', ErrorCode::ARTICLE_EDIT_FAILED);
        }
        return [
            'article_id' => $article_id,
            'title' => $title,
            'content' => $content,
            'created_at' => $article['created_at']
        ];
    }
    /**
     * 删除文章
     * @param $article_id
     * @param $user_id
     * @return array|bool
     * @throws Exception
     */
    public function delete($article_id, $user_id)
    {
        // 获取文章详情
        $article = $this->view($article_id);
        if ($article['user_id'] !== $user_id) {
            throw new Exception('您无权删除该文章', ErrorCode::PERMISSION_DENIED);
        }
        $sql = 'DELETE FROM `articles` WHERE `article_id` = :article_id AND `user_id` = :user_id';
        $stmt = $this->_db->prepare($sql);
        $stmt->bindParam(':article_id', $article_id, PDO::PARAM_INT);
        $stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
        if (!$stmt->execute()) {
            throw new Exception('文章删除失败', ErrorCode::ARTICLE_DELETE_FAILED);
        }
        return true;
    }
    /**
     * 读取文章列表
     * @param $user_id
     * @param int $page 第几页 默认第一页
     * @param int $size 每一页的数量 默认10
     * @return array
     * @throws Exception
     */
    public function getList($user_id, $page = 1, $size = 10)
    {
        if ($size > 100) {
            throw new Exception('分页大小最大为100', ErrorCode::PAGE_SIZE_TOO_BIG);
        }
        $sql = 'SELECT * FROM `articles` WHERE `user_id` = :user_id LIMIT :limit, :offset';
        $limit = ($page - 1) * $size;
        $limit = $limit < 0 ? 0 : $limit;
        $stmt = $this->_db->prepare($sql);
        $stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
        $stmt->bindParam(':limit', $limit, PDO::PARAM_INT);
        $stmt->bindParam(':offset', $size, PDO::PARAM_INT);
        $stmt->execute();
        $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
        return $result;
    }
}

测试的话,直接运行index.php即可实现注册和登录

3-8 RestfulApi设计要素

1. 设计要素

  • 资源路径:如:users,articles
  • HTTP动词:如:get,post,put,delete
  • 过滤信息:如:limit分页处理
  • 状态码:如:200,403
  • 错误处理:如:抛出异常,返回提示信息
  • 返回结果:如:返回的格式和状态码

2. 状态码

状态码的设计如下:

/** 用户注册 */
请求方法不被允许 405
用户名不能为空 USERNAME_CANNOT_EMPTY 400
密码不能为空 PASSWORD_CANNOT_EMPTY 400
用户名已存在 USERNAME_EXIST 400
注册失败 REGISTER_FAILED 500

/** 创建文章 */
用户名不能为空 USERNAME_CANNOT_EMPTY 401
密码不能为空 PASSWORD_CANNOT_EMPTY 401
用户名或密码错误 USERNAME_OR_PASSWORD_INVALID 401
服务器内部错误 SERVER_INTERNAL_ERROR 500
文章标题不能为空 ARTICLE_TITLE_CANNOT_EMPTY 400
文章内容不能为空 ARTICLE_CONTENT_CANNOT_EMPTY 400
发表文章失败 ARTICLE_CREATE_FAILED 500

/** 修改文章 */
用户名不能为空 USERNAME_CANNOT_EMPTY 401
密码不能为空 PASSWORD_CANNOT_EMPTY 401
用户名或密码错误 USERNAME_OR_PASSWORD_INVALID 401
服务器内部错误 SERVER_INTERNAL_ERROR 500
文章ID不能为空 ARTICLE_ID_CANNOT_EMPTY 400
您无权编辑该文章 PERMISSION_DENIED 403
文章不存在 ARTICLE_NOT_FOUND 404
文章编辑失败 ARTICLE_EDIT_FAILED 500

/** 删除文章 */
用户名不能为空 USERNAME_CANNOT_EMPTY 401
密码不能为空 PASSWORD_CANNOT_EMPTY 401
用户名或密码错误 USERNAME_OR_PASSWORD_INVALID 401
服务器内部错误 SERVER_INTERNAL_ERROR 500
文章ID不能为空 ARTICLE_ID_CANNOT_EMPTY 400
文章不存在 ARTICLE_NOT_FOUND 404
您无权删除该文章 PERMISSION_DENIED 403
文章删除失败 ARTICLE_DELETE_FAILED 500

/** 获取文章列表 */
用户名不能为空 USERNAME_CANNOT_EMPTY 401
密码不能为空 PASSWORD_CANNOT_EMPTY 401
用户名或密码错误 USERNAME_OR_PASSWORD_INVALID 401
服务器内部错误 SERVER_INTERNAL_ERROR 500
分页大小最大为100 PAGE_SIZE_TOO_BIG 400

/** 获取文章详情 */
文章ID不能为空 ARTICLE_ID_CANNOT_EMPTY 400
文章不存在 ARTICLE_NOT_FOUND 404

具体的状态码可以参考:

【HTTP状态码】HTTP状态码详解【转载】

3-9 初始化运行参数

1. 设计结构

由于restful是根据资源和请求方法来执行具体的方法的,比如根据域名后/articles或者是/users来定位资源,根据请求方法来定位具体的方法,所以设计的时候需要定义一个新的入口,可以新建个目录比如命名为restful,目录下新建个index.php,然后请求域名为 域名/restful/index.php/资源/其他参数

2. 设计模式

采用适配器模式,在lib下新建个Restful.php类,根据资源和请求方法来对Article类和User类进行再封装

3. 目录结构

目录结构如下:

Alt2

2. 入口文件

入口文件代码为restful/index.php:

<?php
// 采用自动载入类,不用手动去require所需的类文件
spl_autoload_register('autoload');
function autoload($class)
{
    require_once '../lib/' . $class . '.php';
}
$pdo = require_once '../lib/db.php';
$user = new User($pdo);
$article = new Article($pdo);
$restful = new Restful($user, $article);
$restful->run();

3. 适配器文件

适配器文件代码为

lib/Restful.php:

<?php
class Restful
{
    // 用户资源
    private $_user;

    // 文章资源
    private $_article;

    // 请求方法
    private $_requestMethod;

    // 请求资源名称
    private $_resourceName;

    // 请求的资源id
    private $_id;

    // 允许请求的HTTP方法
    private $_allowRequestMethods = ['GET', 'POST', 'PUT', 'DELETE'];

    // 允许请求的资源列表
    private $_allowResources = ['users', 'articles'];

    // 常用状态码
    private $_statusCode = [
        200 => 'OK',
        204 => 'No Content',
        400 => 'Bad request',
        401 => 'Unauthorized',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        500 => 'Server Internal Error'
    ];

    public function __construct(User $_user, Article $_article)
    {
        $this->_user = $_user;
        $this->_article = $_article;
    }

    public function run()
    {
        try {
            // 初始化请求方法
            $this->_setupRequestMethod();

            // 初始化请求资源(用户资源还是文章资源)
            $this->_setupResource();

            // 根据请求资源分别处理(用户资源还是文章资源)
            if ($this->_resourceName == 'users') {
                $this->_json($this->_handleUser());
            } elseif ($this->_resourceName == 'articles') {
                $this->_json($this->_handleArticle());
            }
        } catch (Exception $e) {
            $this->_json(['error' => $e->getMessage(), 'code' => $e->getCode()]);
        }
    }

    /**
     * 初始化请求方法
     */
    private function _setupRequestMethod()
    {
        $this->_requestMethod = $_SERVER['REQUEST_METHOD'];
        if (!in_array($this->_requestMethod, $this->_allowRequestMethods)) {
            throw new Exception('请求方法不被允许', 405);
        }
    }

    /**
     * 初始化请求资源(用户资源还是文章资源)
     */
    private function _setupResource()
    {
        $path = $_SERVER['QUERY_STRING'];
        $params = explode('type=', $path);
        $resource = $params[1];
        if (strstr($resource, '&')) {
            $resource = explode('&', $params[1])[0];
        }
        if (!in_array($resource, $this->_allowResources)) {
            throw new Exception('请求资源不被允许', 400);
        }
        $this->_resourceName = $resource;

        // 初始化资源标识符(id)
        if (strstr($path, 'id=')) {
            $params = explode('id=', $path);
            $resource = $params[1];
            if (strstr($resource, '&')) {
                $resource = explode('&', $params[1])[0];
            }
            $this->_id = $resource;
        }
    }

    /**
     * 输出json
     * @param $array
     * @internal param $array
     */
    private function _json($array)
    {
        if ($array === null) {
            $array['code'] = 204;
        }
        if (isset($array['code']) && $array['code'] > 0 && $array['code'] != 200 && $array['code'] != 204) {
            header('HTTP/1.1 ' . $array['code'] . ' ' . $this->_statusCode[$array['code']]);
        }
        header('Content-Type:application/json;charset=utf-8');
        echo json_encode($array, JSON_UNESCAPED_UNICODE);
        exit();
    }

    /**
     * 请求用户资源(注册)
     */
    private function _handleUser(){}

    /**
     * 请求文章资源(增删改查)
     */
    private function _handleArticle()
    {
        switch ($this->_requestMethod) {
            case 'POST':
                // 创建文章
                return $this->_handleArticleCreate();
            case 'PUT':
                // 修改文章
                return $this->_handleArticleEdit();
            case 'DELETE':
                // 删除文章
                return $this->_handleArticleDelete();
            case 'GET':
                // 获取文章列表
                if (empty($this->_id)) {
                    return $this->_handleArticleList();
                }
                // 获取文章详情
                return $this->_handleArticleView();
            default:
                throw new Exception('请求方法不被允许', 405);
        }
    }

    /**
     * 获取请求参数
     * @return mixed
     * @throws Exception
     */
    private function _getBodyParams()
    {
        $raw = file_get_contents('php://input');
        if (empty($raw)) {
            throw new Exception('请求参数错误', 400);
        }
        return json_decode($raw, true);
    }

    /**
     * 创建文章(需要用户授权登录)
     */
    private function _handleArticleCreate(){}

    /**
     * 编辑文章(需要用户授权登录)
     */
    private function _handleArticleEdit() {}

    /**
     * 删除文章(需要用户授权登录)
     */
    private function _handleArticleDelete(){}

    /**
     * 读取文章列表(需要用户授权登录)
     */
    private function _handleArticleList(){}

    /**
     * 查看一篇文章详情(不需要用户授权登录)
     */
    private function _handleArticleView(){}
}

3-10 完善用户API & 3-11 完善文章API

1. 代码

lib/Restful.php

<?php
class Restful
{
    // 用户资源
    private $_user;
    // 文章资源
    private $_article;
    // 请求方法
    private $_requestMethod;
    // 请求资源名称
    private $_resourceName;
    // 请求的资源id
    private $_id;
    // 允许请求的HTTP方法
    private $_allowRequestMethods = ['GET', 'POST', 'PUT', 'DELETE'];
    // 允许请求的资源列表
    private $_allowResources = ['users', 'articles'];
    // 常用状态码
    private $_statusCode = [
        200 => 'OK',
        204 => 'No Content',
        400 => 'Bad request',
        401 => 'Unauthorized',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        500 => 'Server Internal Error'
    ];
    public function __construct(User $_user, Article $_article)
    {
        $this->_user = $_user;
        $this->_article = $_article;
    }
    public function run()
    {
        try {
            // 初始化请求方法
            $this->_setupRequestMethod();
            // 初始化请求资源(用户资源还是文章资源)
            $this->_setupResource();
            // 根据请求资源分别处理(用户资源还是文章资源)
            if ($this->_resourceName == 'users') {
                $this->_json($this->_handleUser());
            } elseif ($this->_resourceName == 'articles') {
                $this->_json($this->_handleArticle());
            }
        } catch (Exception $e) {
            $this->_json(['error' => $e->getMessage(), 'code' => $e->getCode()]);
        }
    }
    /**
     * 初始化请求方法
     */
    private function _setupRequestMethod()
    {
        $this->_requestMethod = $_SERVER['REQUEST_METHOD'];
        if (!in_array($this->_requestMethod, $this->_allowRequestMethods)) {
            throw new Exception('请求方法不被允许', 405);
        }
    }
    /**
     * 初始化请求资源(用户资源还是文章资源)
     */
    private function _setupResource()
    {
        $path = $_SERVER['QUERY_STRING'];
        $params = explode('type=', $path);
        $resource = $params[1];
        if (strstr($resource, '&')) {
            $resource = explode('&', $params[1])[0];
        }
        if (!in_array($resource, $this->_allowResources)) {
            throw new Exception('请求资源不被允许', 400);
        }
        $this->_resourceName = $resource;
        // 初始化资源标识符(id)
        if (strstr($path, 'id=')) {
            $params = explode('id=', $path);
            $resource = $params[1];
            if (strstr($resource, '&')) {
                $resource = explode('&', $params[1])[0];
            }
            $this->_id = $resource;
        }
    }
    /**
     * 输出json
     * @param $array
     * @internal param $array
     */
    private function _json($array)
    {
        if ($array === null) {
            $array['code'] = 204;
        }
        if (isset($array['code']) && $array['code'] > 0 && $array['code'] != 200 && $array['code'] != 204) {
            header('HTTP/1.1 ' . $array['code'] . ' ' . $this->_statusCode[$array['code']]);
        }
        header('Content-Type:application/json;charset=utf-8');
        echo json_encode($array, JSON_UNESCAPED_UNICODE);
        exit();
    }
    /**
     * 请求用户资源(注册)
     * @return array
     * @throws Exception
     */
    private function _handleUser()
    {
        if ($this->_requestMethod != 'POST') {
            throw new Exception('请求方法不被允许', 405);
        }
        try {
            // 获取请求参数
            $body = $this->_getBodyParams();
            // 用户注册
            return $this->_user->register($body['username'], $body['password']);
        } catch (Exception $e) {
            // 用户操作错误的编码数组
            $UserErrorCode = [
                ErrorCode::USERNAME_CANNOT_EMPTY,
                ErrorCode::PASSWORD_CANNOT_EMPTY,
                ErrorCode::USERNAME_EXISTS
            ];
            // 用户操作错误归为400错误,其他的则为500错误
            if (in_array($e->getCode(), $UserErrorCode)) {
                throw new Exception($e->getMessage(), 400);
            }
            throw new Exception($e->getMessage(), 500);
        }
    }
    /**
     * 请求文章资源(增删改查)
     * @return array|bool|mixed
     * @throws Exception
     */
    private function _handleArticle()
    {
        switch ($this->_requestMethod) {
            case 'POST':
                // 创建文章
                return $this->_handleArticleCreate();
            case 'PUT':
                // 修改文章
                return $this->_handleArticleEdit();
            case 'DELETE':
                // 删除文章
                return $this->_handleArticleDelete();
            case 'GET':
                // 获取文章列表
                if (empty($this->_id)) {
                    return $this->_handleArticleList();
                }
                // 获取文章详情
                return $this->_handleArticleView();
            default:
                throw new Exception('请求方法不被允许', 405);
        }
    }
    /**
     * 获取请求参数
     * @return mixed
     * @throws Exception
     */
    private function _getBodyParams()
    {
        $raw = file_get_contents('php://input');
        if (empty($raw)) {
            throw new Exception('请求参数错误', 400);
        }
        return json_decode($raw, true);
    }
    /**
     * 创建文章(需要用户授权登录)
     * @return array
     * @throws Exception
     */
    private function _handleArticleCreate()
    {
        // 用户登录
        $user = $this->_userLogin();
        try {
            // 获取请求参数
            $body = $this->_getBodyParams();
            // 创建文章
            $article = $this->_article->create($body['title'], $body['content'], $user['user_id']);
            return $article;
        } catch (Exception $e) {
            // 文章操作错误的编码数组
            $ArticleErrorCode = [
                ErrorCode::ARTICLE_TITLE_CANNOT_EMPTY,
                ErrorCode::ARTICLE_CONTENT_CANNOT_EMPTY,
            ];
            // 文章操作归为400错误
            if (in_array($e->getCode(), $ArticleErrorCode)) {
                throw new Exception($e->getMessage(), 400);
            }
            // 其他的则为500错误
            throw new Exception($e->getMessage(), 500);
        }
    }
    /**
     * 编辑文章(需要用户授权登录)
     * @return array
     * @throws Exception
     */
    private function _handleArticleEdit()
    {
        // 用户登录
        $user = $this->_userLogin();
        try {
            // 获取请求参数
            $body = $this->_getBodyParams();
            return $this->_article->edit($this->_id, $body['title'], $body['content'], $user['user_id']);
        } catch (Exception $e) {
            switch ($e->getCode()) {
                // 文章ID不能为空
                case ErrorCode::ARTICLE_ID_CANNOT_EMPTY:
                    throw new Exception($e->getMessage(), 400);
                // 文章不存在
                case ErrorCode::ARTICLE_NOT_FOUND:
                    throw new Exception($e->getMessage(), 404);
                // 当前用户无权限编辑该文章
                case ErrorCode::PERMISSION_DENIED:
                    throw new Exception($e->getMessage(), 403);
                // 其他错误
                default:
                    throw new Exception($e->getMessage(), 500);
            }
        }
    }
    /**
     * 删除文章(需要用户授权登录)
     * @return bool
     * @throws Exception
     */
    private function _handleArticleDelete()
    {
        // 用户登录
        $user = $this->_userLogin();
        try {
            return $this->_article->delete($this->_id, $user['user_id']);
        } catch (Exception $e) {
            switch ($e->getCode()) {
                // 文章ID不能为空
                case ErrorCode::ARTICLE_ID_CANNOT_EMPTY:
                    throw new Exception($e->getMessage(), 400);
                // 文章不存在
                case ErrorCode::ARTICLE_NOT_FOUND:
                    throw new Exception($e->getMessage(), 404);
                // 当前用户无权限删除该文章
                case ErrorCode::PERMISSION_DENIED:
                    throw new Exception($e->getMessage(), 403);
                // 其他错误
                default:
                    throw new Exception($e->getMessage(), 500);
            }
        }
    }
    /**
     * 读取文章列表(需要用户授权登录)
     * @return array
     * @throws Exception
     */
    private function _handleArticleList()
    {
        // 用户登录
        $user = $this->_userLogin();
        try {
            // 第几页
            $page = isset($_GET['page']) ? $_GET['page'] : 1;
            // 每一页的数量
            $size = isset($_GET['size']) ? $_GET['size'] : 10;
            return $this->_article->getList($user['user_id'], $page, $size);
        } catch (Exception $e) {
            if ($e->getCode() == ErrorCode::PAGE_SIZE_TOO_BIG) {
                throw new Exception($e->getMessage(), 400);
            }
            throw new Exception($e->getMessage(), 500);
        }
    }
    /**
     * 查看一篇文章详情(不需要用户授权登录)
     * @return mixed
     * @throws Exception
     */
    private function _handleArticleView()
    {
        try {
            return $this->_article->view($this->_id);
        } catch (Exception $e) {
            switch ($e->getCode()) {
                // 文章ID不能为空
                case ErrorCode::ARTICLE_ID_CANNOT_EMPTY:
                    throw new Exception($e->getMessage(), 400);
                // 文章不存在
                case ErrorCode::ARTICLE_NOT_FOUND:
                    throw new Exception($e->getMessage(), 404);
                // 其他错误
                default:
                    throw new Exception($e->getMessage(), 500);
            }
        }
    }
    /**
     * 用户授权登录(基于HTTP请求头的身份认证)
     * @return array
     * @throws Exception
     */
    private function _userLogin()
    {
        try {
            // 用户名
            $PHP_AUTH_USER = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : '';
            // 用户密码
            $PHP_AUTH_PW = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';
            return $this->_user->login($PHP_AUTH_USER, $PHP_AUTH_PW);
        } catch (Exception $e) {
            // 用户登录操作错误的编码数组
            $UserLoginErrorCode = [
                ErrorCode::USERNAME_CANNOT_EMPTY,
                ErrorCode::PASSWORD_CANNOT_EMPTY,
                ErrorCode::USERNAME_OR_PASSWORD_INVALID
            ];
            // 用户登录操作错误归为401错误
            if (in_array($e->getCode(), $UserLoginErrorCode)) {
                throw new Exception($e->getMessage(), 401);
            }
            // 其他的则为500错误
            throw new Exception($e->getMessage(), 500);
        }
    }
}

2. 测试

a. 用户资源

用户资源只开放注册的功能,而用户登录的功能是后面的文章资源需要的,所以这里只测试用户注册即可

域名:http://域名/RestfulApi/restful/index.php?type=users

方法:post

Headers参数:

Content-Type:application/json

Body参数:

{
 "username": "admintest5",
 "password": "123456"
}

如图:

Alt3

由于数据库中已经有admintest5这个用户,所以返回400,并且返回错误结果

b. 创建文章

由于创建文章,是需要用户授权登录的,所以Headers中是需要加上账号密码的信息的

域名:http://域名/RestfulApi/restful/index.php?type=articles

方法:post

Headers参数:

Content-Type:application/json
Authorization:Basic YWRtaW50ZXN0NToxMjM0NTY=

如图:

Alt4

其中Authorization:Basic 后面的编码是 admintest4:123456 经过base64编码得到的

Body参数:

{
 "title": "这个是标题",
 "content": "123123"
}

结果为:

{
    "article_id": "20",
    "title": "这个是标题",
    "content": "123123"
}
c. 编辑文章

域名:http://域名/RestfulApi/restful/index.php?type=articles&id=12

方法:put

Header参数:同上

Body参数:

{
 "title": "这个是修改标题2",
 "content": "这个是修改过的内容"
}
d. 删除文章

域名:http://域名/RestfulApi/restful/index.php?type=articles&id=12

方法:delete

Header参数:同上

Body参数:无

e. 获取文章列表

域名:http://域名/RestfulApi/restful/index.php?type=articles

方法:get

Header参数:同上

Body参数:无

f. 获取文章详情

域名:http://域名/RestfulApi/restful/index.php?type=articles&id=13

方法:get

Header参数:

Content-Type:application/json

Body参数:无

项目点评 ( 0 )

你可以在登录后,发表评论

搜索帮助

12_float_left_people 12_float_left_close