一,为api项目搭建数据库
设计数据库的小技巧
- 一个对象,一张表
- 一张表,一个主键
- 表名用
数据库名
做前缀 - 字段名用
表名
做前缀 - 前缀后面加
缩写
(容易看懂的前提下) - 数据表的关系处理
二,为api项目写接口文档
用户登录
举例
# 判断数据库中是否有此用户
post
www.test.com/apiapi.test.com
参数 | 必选 | 类型 | 说明 |
---|---|---|---|
time | true | int | 时间戳 (用于确定接口的访问时间) |
token | true | string |
确定访问者身份 (MD5(USER_MD5(time)_USER) ) |
username | true | string | 只接受邮箱
|
password | true | string | 用户密码 |
{
"ret": 200, // 返回结果状态。200:接口正常请求并返回/40*:服务端的数据有误/500:服务器运行错误
"data": {
"user_id": "27", // 用户id
"user_tag": "1" // 用户身份
},
"msg": "" // 401:用户名不存在!/402:手机号不存在!/403:密码不正确!
}
三,配置URL
需求分析
· api.test.com
===> www.test.com/index.php/api
为api配置二级域名
- 打开
httpd-vhosts.conf
添加ServerAlias api.test.com
- 配置
host
文件127.0.0.1 api.test.com
使用tp5路由进行URL解析
- 新建
User.php
文件,路径G:tp5/application/api/controller/User.php
<?php
namespace app\api\controller;
class User{
public function index($id){
echo '<br/>';
echo $id;
}
}
- 修改
config.php
(开启路由)
// 是否开启路由'url_route_on' => true,
// 域名部署'url_domain_deploy' => true,
- 配置路由规则
route.php
<?php
use think\Route;
// api.test.com ===> www.test.com/index.php/api
Route::domain('api','api');
四,接口安全
常见的安全问题
- 接口被大规模调用消耗系统资源,影响系统的正常访问,甚至系统瘫痪
解决方案: 获取 timestamp (时间戳), 设置接口失效时间
- 接口数据被黑客篡改(伪造请求)
解决方案: 对参数加密, 生成 token , 判断 token 是否正确
- 数据被黑客截取
解决方案: 使用 https , 用证书对数据进行加密, 即使数据被截取, 对黑客也没有意义
黑客可以获取数据, 但是无法获取数据的加密方法
api项目的安全设计
- time
时间戳, 用于判断请求是否超时, 设置为30秒
- token
其他参数加密而来, 保证数据不被篡改
- 敏感信息加密传输
接收加密过的用户密码, 用户密码永不返回
最好使用 https, 所有信息都会被加密
五,开发前准备工作(参数过滤)
使用 common.php
统一处理参数过滤 G:tp5/application/api/controller/Common.php
<?php
namespace app\api\controller;
use think\Controller;
use think\Request;
use think\Validate;
class Common extends Controller {
protected $request; // 用来处理参数
protected $validater; // 用来验证数据/参数
protected $params; // 过滤后符合要求的参数
protected $rules = array(
'User'=>array(......);
protected function _initialize() {
parent::_initialize();
$this->request = Request::instance();
$this->check_time($this->request->only(['time']));
$this->check_token($this->request->param());
$this->params=$this->check_params($this->request->except(['time','token']));
}
自定义返回信息函数 G:tp5/application/api/controller/Common.php
/**
* api 数据返回
* @param [int] $code [结果码 200:正常/4**数据问题/5**服务器问题]
* @param [string] $msg [接口要返回的提示信息]
* @param [array] $data [接口要返回的数据]
* @return [string] [最终的json数据]
*/
public function return_msg($code, $msg = '', $data = []) {
/*********** 组合数据 ***********/
$return_data['code'] = $code;
$return_data['msg'] = $msg;
$return_data['data'] = $data;
/*********** 返回信息并终止脚本 ***********/
echo json_encode($return_data);die;
}
验证 time
G:tp5/application/api/controller/Common.php
/**
* 验证请求是否超时
* @param [array] $arr [包含时间戳的参数数组]
* @return [json] [检测结果]
*/
public function check_time($arr) {
if (!isset($arr['time']) || intval($arr['time']) <= 1) {
$this->return_msg(400, '时间戳不正确!');
}
if (time() - intval($arr['time']) > 60) {
$this->return_msg(400, '请求超时!');
}
}
验证token G:tp5/application/api/controller/Common.php
/**
* 验证token(防止篡改数据)
* @param [array] $arr [全部请求参数]
* @return [json] [token验证结果]
*/
public function check_token($arr) {
/*********** api传过来的token ***********/
if (!isset($arr['token']) || empty($arr['token'])) {
$this->return_msg(400, 'token不能为空!');
}
$app_token = $arr['token']; // api传过来的token
/*********** 服务器端生成token ***********/
unset($arr['token']);
$service_token = '';
foreach ($arr as $key => $value) {
$service_token .= md5($value);
}
$service_token = md5('api_' . $service_token . '_api'); // 服务器端即时生成的token
/*********** 对比token,返回结果 ***********/
if ($app_token !== $service_token) {
$this->return_msg(400, 'token值不正确!');
}
}
为每个接口配置验证规则 G:tp5/application/api/controller/Common.php
如
protected $rules = array(
'User' => array(
'login' => array(
'user_name' => ['require', 'chsDash', 'max' => 20],
'user_pwd' => 'require|length:32',
),
),
);
验证参数 G:tp5/application/api/controller/Common.php
/**
* 验证参数 参数过滤
* @param [array] $arr [除time和token外的所有参数]
* @return [return] [合格的参数数组]
*/
public function check_params($arr) {
/*********** 获取参数的验证规则 ***********/
$rule = $this->rules[$this->request->controller()][$this->request->action()];
/*********** 验证参数并返回错误 ***********/
$this->validater = new Validate($rule);
if (!$this->validater->check($arr)) {
$this->return_msg(400, $this->validater->getError());
}
/*********** 如果正常,通过验证 ***********/
return $arr;
}
五,获取验证码
验证码原理
- 生成及发送
点击获取验证码
发送邮箱号到后台
后台生成邮箱验证码
用session保存验证码及邮箱
发送邮件
- 验证
获取用户输入的验证码及邮箱
取出session保存的内容
对比验证
返回信息, 结束
配置路由 G:tp5/application/route.php
注意: get方式没有参数名, 所以要注意参数的顺序, 对号入座.
// 获取验证码
Route::get('code/:time/:token/:username/:is_exist','code/get_code');
参数过滤 G:tp5/application/api/controller/Common.php
在common.php里简单过滤, 具体验证放在code.php里
'Code' => array(
'get_code' => array(
'username' => 'require',
'is_exist' => 'require|number|length:1',
),
),
检测用户名 G:tp5/application/api/controller/Code.php
namespace app\api\controller;
use phpmailer\phpmailer;
use submail\messagexsend;
class Code extends Common {
public function get_code() {
$username = $this->params['username'];
$exist = $this->params['is_exist'];
$username_type = $this->check_username($username); // 检查用户名, 决定用下面哪那个函数
switch ($username_type) {
case 'phone':
$this->get_code_by_username($username, 'phone', $exist); // 通过手机获取验证码
break;
case 'email':
$this->get_code_by_username($username, 'email', $exist); // 通过邮箱获取验证码
break;
}
}
}
在 Common.php
中写一个判断用户名类型的函数
public function check_username($username) {
/*********** 判断是否为邮箱 ***********/
$is_email = Validate::is($username, 'email') ? 1 : 0;
/*********** 判断是否为手机 ***********/
$is_phone = preg_match('/^1[34578]\d{9}$/', $username) ? 4 : 2;
/*********** 最终结果 ***********/
$flag = $is_email + $is_phone;
switch ($flag) {
/*********** not phone not email ***********/
case 2:
$this->return_msg(400, '邮箱或手机号不正确!');
break;
/*********** is email not phone ***********/
case 3:
return 'email';
break;
/*********** is phone not email ***********/
case 4:
return 'phone';
break;
}
}
通过用户名(手机/邮箱)获取验证码 G:tp5/application/api/controller/Code.php
public function get_code_by_username($username, $type, $exist) {
if ($type == 'phone') {
$type_name = '手机';
} else {
$type_name = '邮箱';
}
/*********** 检测手机号/邮箱是否存在 ***********/
$this->check_exist($username, $type, $exist);
/*********** 检查验证码请求频率 30秒一次 ***********/
if (session("?" . $username . '_last_send_time')) {
if (time() - session($username . '_last_send_time') < 30) {
$this->return_msg(400, $type_name . '验证码,每30秒只能发送一次!');
}
}
/*********** 生成验证码 ***********/
$code = $this->make_code(6);
/*********** 使用session存储验证码, 方便比对, md5加密 ***********/
$md5_code = md5($username . '_' . md5($code));
session($username . '_code', $md5_code);
/*********** 使用session存储验证码的发送时间 ***********/
session($username . '_last_send_time', time());
/*********** 发送验证码 ***********/
if ($type == 'phone') {
$this->send_code_to_phone($username, $code);
} else {
$this->send_code_to_email($username, $code);
}
}
判断用户名(手机/邮箱)在数据库中是否应该存在(因为要分两种情况1,注册 2,修改)
当我们注册的时候数据库里不能有,当我们修改的时候数据库里必须有G:tp5/application/api/controller/Common.php
public function check_exist($value, $type, $exist) {
$type_num = $type == "phone" ? 2 : 4;
$flag = $type_num + $exist;
$phone_res = db('user')->where('user_phone', $value)->find();
$email_res = db('user')->where('user_email', $value)->find();
switch ($flag) {
/*********** 2+0 phone need no exist ***********/
case 2:
if ($phone_res) {
$this->return_msg(400, '此手机号已被占用!');
}
break;
/*********** 2+1 phone need exist ***********/
case 3:
if (!$phone_res) {
$this->return_msg(400, '此手机号不存在!');
}
break;
/*********** 4+0 email need no exist ***********/
case 4:
if ($email_res) {
$this->return_msg(400, '此邮箱已被占用!');
}
break;
/*********** 4+1 email need exist ***********/
case 5:
if (!$email_res) {
$this->return_msg(400, '此邮箱不存在!');
}
break;
}
}
在 Code.php
中生成验证码
public function make_code($num) {
$max = pow(10, $num) - 1;
$min = pow(10, $num - 1);
return rand($min, $max);
}
只介绍邮箱发送验证码
开启邮箱smtp
php 开启php_opensslG:tp5/application/api/controller/Code.php
public function send_code_to_email($email, $code) {
$toemail = $email;
$mail = new PHPMailer();
$mail->isSMTP();
$mail->CharSet = 'utf8'; // 设置字符集
$mail->Host = 'smtp.qq.com'; // smtp服务器
$mail->SMTPAuth = true;
$mail->Username = "123456789@qq.com";
$mail->Password = "asd1151sad51dsa"; // 自己设置的smtp密码, 与登录密码无关
$mail->SMTPSecure = 'ssl';
$mail->Port = 465;
$mail->setFrom('123456789@qq.com', '接口测试');
$mail->addAddress($toemail, 'test');
$mail->addReplyTo('123456789@qq.com', 'lee');
$mail->Subject = "您有新的验证码!"; // 邮件标题
$mail->Body = "这是一个测试邮件,您的验证码是$code,验证码的有效期为1分钟,本邮件请勿回复!"; // 邮件内容
if (!$mail->send()) {
$this->return_msg(400, $mail->ErrorInfo);
} else {
$this->return_msg(200, '验证码已经发送成功,请注意查收!');
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。