简单介绍RestfulApi的用法,主要是参考慕课网的《Restful API实战》教程
本质:基于资源的一种软件架构风格
核心:面向资源
解决的问题:
网络上的所有事物都可以被抽象为资源
每一个资源都有唯一的资源标识,对资源的操作不会改变这些标识
所有的操作都是无状态的
所谓的资源,就是网络上的一个实体,或者说是网络上的一个具体信息
HTTP全称是HyperText Transfer Protocal,即:超文本传输协议,从1990年开始就在WWW上广泛应用,是现今在WWW上应用最多的协议。
Http是应用层协议,特点是简洁快速。
当你上网浏览网页的时候,浏览器和Web服务器之间就会通过HTTP在Internet上进行数据的发送和接收。
Http是一个基于请求/响应模式的、无状态的协议。即我们通常所说的Request/Response
比如:schema://host[:port]/path[?query-string][#anchor]
组成格式:请求行、消息报头、请求正文
请求行的格式如下:
Method Request-URL HTTP-Version CRLF
比如:GET / HTTP/1.1 CRLF
组成格式:状态行、消息报头、响应正文
状态行的格式如下:
HTTP-Version Status-Code Reason-Phrase CRLF
比如:HTTP/1.1 200 OK
WebService 是一种跨编程语言和跨操作系统平台的远程调用技术。
WebService通过HTTP协议发送请求和接收结果时采用XML格式封装,并增加了一些特定的HTTP消息头,这些特定的HTTP消息头和XML内容格式就是SOAP协议
SOAP由于各种需求不断扩充其本身协议的内容,导致在SOAP处理方面的性能有所下降,同时在易用性方面以及学习成本上也有所增加,Restful由于其面向资源接口设计以及操作抽象简化了开发者的不良设计,同时也最大限度的利用了HTTP最初的应用协议设计理念
Restful对于资源型服务接口来说很合适,同时特别适合对于效率要求很高,但是对于安全要求不高的场景。而SOAP的成熟性可以给需要提供给多开发语言,对于安全性要求较高的接口设计带来便利。
在Restful架构中,每个网址代表一种资源,所以网址中不能有动词,只能有名词。一般来说API中的名词应该使用复数。 比如:有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样:
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
对于资源的操作(CURD),由HTTP动词表示
比如:
如果记录数量很多,服务器不可能都将它们返回给用户,API应该提供参数,过滤返回结果
比如:
服务器向用户返回的状态码和提示信息,使用标准HTTP状态码
比如:
如果状态码是4XX或者是5XX,就应该向用户返回出错信息。
一把来说,返回的信息中将error作为键名,出错信息作为键值即可:
{
"error" : "参数错误"
}
针对不同操作,服务器向用户返回的结果应该符合以下规范:
我这里是使用postman来代替
两个表的创建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;
目录结构如下:
具体的代码如下
入口文件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即可实现注册和登录
入口文件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即可实现注册和登录
状态码的设计如下:
/** 用户注册 */
请求方法不被允许 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
具体的状态码可以参考:
由于restful是根据资源和请求方法来执行具体的方法的,比如根据域名后/articles或者是/users来定位资源,根据请求方法来定位具体的方法,所以设计的时候需要定义一个新的入口,可以新建个目录比如命名为restful,目录下新建个index.php,然后请求域名为 域名/restful/index.php/资源/其他参数
采用适配器模式,在lib下新建个Restful.php类,根据资源和请求方法来对Article类和User类进行再封装
目录结构如下:
入口文件代码为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();
适配器文件代码为
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(){}
}
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);
}
}
}
用户资源只开放注册的功能,而用户登录的功能是后面的文章资源需要的,所以这里只测试用户注册即可
域名:http://域名/RestfulApi/restful/index.php?type=users
方法:post
Headers参数:
Content-Type:application/json
Body参数:
{
"username": "admintest5",
"password": "123456"
}
如图:
由于数据库中已经有admintest5这个用户,所以返回400,并且返回错误结果
由于创建文章,是需要用户授权登录的,所以Headers中是需要加上账号密码的信息的
域名:http://域名/RestfulApi/restful/index.php?type=articles
方法:post
Headers参数:
Content-Type:application/json
Authorization:Basic YWRtaW50ZXN0NToxMjM0NTY=
如图:
其中Authorization:Basic 后面的编码是 admintest4:123456 经过base64编码得到的
Body参数:
{
"title": "这个是标题",
"content": "123123"
}
结果为:
{
"article_id": "20",
"title": "这个是标题",
"content": "123123"
}
域名:http://域名/RestfulApi/restful/index.php?type=articles&id=12
方法:put
Header参数:同上
Body参数:
{
"title": "这个是修改标题2",
"content": "这个是修改过的内容"
}
域名:http://域名/RestfulApi/restful/index.php?type=articles&id=12
方法:delete
Header参数:同上
Body参数:无
域名:http://域名/RestfulApi/restful/index.php?type=articles
方法:get
Header参数:同上
Body参数:无
域名:http://域名/RestfulApi/restful/index.php?type=articles&id=13
方法:get
Header参数:
Content-Type:application/json
Body参数:无
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。