tp5 接口

一,为api项目搭建数据库


设计数据库的小技巧

  1. 一个对象,一张表
  2. 一张表,一个主键
  3. 表名用 数据库名 作前缀
  4. 字段名用 表名 作前缀
  5. 前缀后面加 缩写 (容易看懂的前提下)
  6. 数据表的关系处理

二,为api项目写接口文档


用户登陆举例javascript

# 判断数据库中是否有此用户
post www.test.com/api api.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/apiphp

为api配置二级域名
  1. 打开 httpd-vhosts.conf 添加 ServerAlias api.test.com
  2. 配置 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');

四,接口安全


常见的安全问题

  1. 接口被大规模调用消耗系统资源,影响系统的正常访问,甚至系统瘫痪
解决方案: 获取 timestamp (时间戳), 设置接口失效时间
  1. 接口数据被黑客篡改(伪造请求)
解决方案: 对参数加密, 生成 token , 判断 token 是否正确
  1. 数据被黑客截取
解决方案: 使用 https , 用证书对数据进行加密, 即便数据被截取, 对黑客也没有意义
黑客能够获取数据, 可是没法获取数据的加密方法
api项目的安全设计
  1. time
时间戳, 用于判断请求是否超时, 设置为30秒
  1. token
其余参数加密而来, 保证数据不被篡改
  1. 敏感信息加密传输
接收加密过的用户密码, 用户密码永不返回
最好使用 https, 全部信息都会被加密

五,开发前准备工做(参数过滤)

使用 common.php 统一处理参数过滤 G:tp5/application/api/controller/Common.phpjava

<?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.phpjson

/**
* 验证请求是否超时
 * @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.phpapi

/**
* 验证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.phpsession

注意: 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_openssl
G: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, '验证码已经发送成功,请注意查收!');
    }
}
相关文章
相关标签/搜索