使用Sinch创建约会应用程序:RESTful API

在本教程中,我们将为iOS创建一个类似于Tinder的约会应用程序。 对于语音和消息传递,我们将利用Sinch平台,利用其强大的SDK。

在第一部分中,我们将专注于RESTful API的开发,以存储和检索用户信息。 在第二部分中,iOS客户端将挂接到该API中,以根据用户的当前位置查找附近的用户。

我们将把Laravel 5.0用于RESTful服务,并将涵盖基本概念,例如路由和控制器。 我们还将定义自定义模型,以类似于ActiveRecord的方式支持MongoDB集成。 让我们开始吧。

1.基本设置

我假设您已经安装了Composer和最新的Laravel安装程序 。 如果还没有,请遵循Laravel 5.0官方文档 。 安装过程不应超过几分钟。

从命令行,导航到要为RESTful服务创建应用程序的位置,然后执行以下命令:

laravel new mobilesinch

几秒钟后,该命令将告诉您mobilesinch应用程序已成功创建。 导航到新文件夹并执行以下命令:

php artisan fresh

默认情况下,任何Laravel 5.0应用程序都附带一些用于用户注册和身份验证的基本支架。 由于我们要从头开始,因此此命令将删除此命令。

在为RESTful服务编写实际代码之前,我们还需要处理另一件事。 默认情况下,Laravel 5.0附带了用于跨站点请求伪造(CSRF)保护的中间件。 但是,由于我们不是在构建网站而是在建立RESTful API,因此没有必要使用它。 同样,它可能会在使用过程中引起一些问题。

对于此应用程序,最好将其删除。 在应用程序文件夹的根目录中,导航到app / Http 。 在该文件夹中,有一个名为Kernel.php的文件。 打开它并删除以下行:

'App\Http\Middleware\VerifyCsrfToken',

您还可以删除位于app / Http / Controllers内的WelcomeController.php ,以及位于resources / views文件夹内的默认welcome.blade.php视图。 我们不会使用它们,但是您可以根据需要将其保留在此处。 只需确保将503.blade.php视图保留在适当的位置即可,这对于调试应用程序很有用。

2. Base模型

本教程旨在创建的约会应用程序具有类似Tinder的功能,您可以在其中找到当前位置附近的用户。 为此,API需要根据用户的位置执行搜索,这称为地理空间查询。 尽管我们可以使用MySQL做到这一点,但是行业标准倾向于MongoDB,我个人更喜欢它。

代替使用Laravel的DB Facade,我们将创建自己的类,应用程序的模型将扩展为在MongoDB中执行查询。

这将是一个简单的类,并且不会集成到Laravel的Eloquent模型中,尽管我们可以,但我想暂时保持简单。

步骤1:MongoDB配置

在编写用于执行MongoDB查询的类之前,我们需要设置数据库信息,就像对MySQL或PostgreSQL或任何其他数据库服务器所做的一样。

在根config文件夹中,创建一个新文件,并将其命名为mongodb.php 。 向其添加以下代码:

<?php

return [
    'host'          => 'localhost',
    'port'          => 27017,
    'user'          => '',
    'pass'          => '',
    'db'            => 'mobilesinch'
];

我们为MongoDB服务器设置主机和端口(如果有),为连接设置用户和密码,并定义我们将使用的数据库mobilesinch

由于MongoDB是一个面向文档的数据库,它是无模式的,因此我们不需要进一步的配置,迁移定义或其他任何结构表。 它只是工作。

步骤2:数据库连接

我们已经有了配置文件,现在该创建用于处理与数据库交互的实际类了。

此类将使用类似ActiveRecord的语法执行对MongoDB的查询。 在app / Http内部 文件夹,创建一个新的Models ,并在其中添加Base.php文件。 为其添加以下代码:

<?php namespace App\Http\Models;

use Illuminate\Support\Facades\Config;

class Base {
   
    private $_config        = null;
   
    private $_conn            = null;
   
    private $_db            = null;

    public function __construct() {
        $this->_config        = Config::get( 'mongodb' );
       
        $this->_connect();
    }
   
    private function _connect() {}
}

这些是我们Base模型类的Base 。 它不扩展任何内容,仅依靠Laravel的Config外观来检索我们之前创建的配置参数。

接下来,我们需要创建与数据库的连接。 将以下代码添加到private _connect方法:

$conn = 'mongodb://'.$this->_config['host'];
if( ! empty( $this->_config['port'] ) ) {
    $conn .= ":{$this->_config['port']}";
}

$options = array();
if( ! empty( $this->_config['user'] ) && ! empty( $this->_config['pass'] ) ) {
    $options['username'] = $this->_config['user'];
    $options['password'] = $this->_config['pass'];
}

try {
    $this->_conn    = new \MongoClient( $conn, $options );

    $this->_db      = $this->_conn->{$this->_config['db']};
    return true;
} catch( \MongoConnectionException $e ) {
    $this->_conn    = null;
    return false;
}

在这种方法中,我们创建一个连接字符串,并设置用户名和密码(如果有的话)。 然后,我们使用PHP的MongoDB驱动程序创建一个连接,并将数据库设置为配置文件中指定的数据库。

如果您从命令行熟悉MongoDB语法,则此方法等效于进入mongo控制台并键入use mobilesinch 。 有关更多信息,请参考PHP MongoDB官方文档

步骤3:辅助方法

在继续数据库CRUD操作之前,我们的Base类必须实现一些方法。 这些用于设置过滤器,选择语句和其他用于执行数据库操作的查询变量。 让我们从添加必要的成员变量开始。 在类构造函数上方,添加以下代码:

private $_ws            = array();

private $_sls           = array();

private $_lmt           = 99999;

private $_ost           = 0;

这些是数据库查询的whereselectlimitoffset的持有者。 要设置这些成员变量,请创建以下setter方法:

protected function _limit( $limit, $offset = null ) {}

protected function _select( $select = "" ) {}

protected function _where( $key, $value = null ) {}

_limit方法对于分页READ操作的结果很有用。 用户可以设置limit参数来指定要检索的记录数,还可以设置offset参数来指定要读取的页面。 将以下代码添加到_limit方法中:

if ( $limit !== NULL && is_numeric( $limit ) && $limit >= 1 ) {
    $this->_lmt = $limit;
}
if ( $offset !== NULL && is_numeric( $offset ) && $offset >= 1 ) {
    $this->_ost = $offset;
}

_select方法将用于确定READ查询必须返回记录的哪些字段。 select语句必须以逗号分隔的字符串形式提供。

$fields = explode( ',', $select );
foreach ( $fields as $field ) {
    $this->_sls[trim( $field )] = true;
}

最后, _where方法将用于过滤查询结果,可以是数组或键/值对。

if ( is_array( $key ) ) {
    foreach( $key as $k => $v ) {
        $this->_ws[$k] = $v;
    }
} else {
    $this->_ws[$key] = $value;
}

现在,我们已经支持限制和过滤查询,但是我们必须添加其他一些辅助方法。 第一个将用于在发出查询之前将任何where语句集与查询的where参数组合在一起。

稍后,当我们编写CRUD方法时,这会更有意义。 在类的底部,添加以下私有方法:

private function _set_where( $where = null ) {
    if ( is_array( $where ) ) {
        $where  = array_merge( $where, $this->_ws );
        foreach ( $where as $k => $v ) {
            if ( $k == "_id" && ( gettype( $v ) == "string" ) ) {
                $this->_ws[$k]  = new \MongoId( $v );
            } else {
                $this->_ws[$k]  = $v;
            }
        }
    } else if( is_string( $where ) ) {
        $wheres = explode( ',', $where );
        foreach ( $wheres as $wr ) {
            $pair = explode( '=', trim( $wr ) );
            if ( $pair[0] == "_id" ) {
                $this->_ws[trim( $pair[0] )] = new \MongoId( trim( $pair[1] ) );
            } else {
                $this->_ws[trim( $pair[0] )] = trim( $pair[1] );
            }
        }
    }
}

看起来有些吓人,但实际上非常简单。 它首先检查where参数是否为数组。 如果是这样,它将使用_where helper方法将给定值与现有值合并。

但是,此方法还支持用于设置READ操作返回值的字符串。 该字符串应具有以下格式:

name=John,last_name=Smith

本示例将运行查询,并返回将name字段设置为John并将last_name字段设置为Smith字段。

但是请注意,对于数组或字符串,我们都会检查_id字段是否存在。 如果是这种情况,它是一个字符串,我们将根据它创建一个新的MongoId对象。 id是MongoDB中的对象,将它们与字符串进行比较将返回false ,这就是为什么必须进行此转换的原因。

我们要做的另一件事是,一旦执行了一个操作,就重置所有查询参数,这样它们就不会影响后续查询。 _flush方法将解决此问题。

private function _flush() {
    $this->_ws      = array();
    $this->_sls     = array();
    $this->_lmt     = 99999;
    $this->_ost     = 0;
}

步骤4:CRUD操作

现在,我们具有所有必需的功能,可以过滤和限制查询结果。 现在该进行实际的数据库操作了,这将取决于PHP的MongoDB驱动程序。 如果不确定某事,请参阅文档

创建操作

我们要支持的第一个操作是在系统中创建记录的操作。 将以下代码添加到Base类:

protected function _insert( $collection, $data ) {
    if ( is_object( $data ) ) {
        $data   = ( array ) $data;
    }

    $result = false;
    try {
        if ( $this->_db->{$collection}->insert( $data ) ) {
            $data['_id']    = ( string ) $data['_id'];
            $result         = ( object ) $data;
        }
    } catch( \MongoCursorException $e ) {
        $result         = new \stdClass();
        $result->error  = $e->getMessage();
    }
    $this->_flush();

    return $result;
}

即使PHP驱动程序期望插入的数据是一个数组,我们的类也将同时支持数组和对象。 我们首先验证传递给我们的内容,然后进行相应的投射。 然后,我们尝试将记录插入数据库中,并将插入的记录作为对象返回,包括_id

读操作

我们将实现两种读取方法,一种将用于检索单个记录,而另一种将用于获取记录列表。 让我们从前者开始。

protected function _findOne( $collection, $where = array() ) {
    $this->_set_where( $where );

    $row    = $this->_db->{$collection}->findOne( $this->_ws, $this->_sls );
    $this->_flush();
    return ( object ) $row;
}

我们为查询定义where子句,并使用PHP的MongoDB驱动程序执行findOne操作。 然后,我们刷新查询参数并将记录作为对象返回。

PHP的MongoDB驱动程序以数组形式返回结果,而我个人更喜欢对象。 那是演员阵容的真正原因。

接下来,我们实现_find方法以获取记录列表。

protected function _find( $collection, $where = array() ) {
    $this->_set_where( $where );
   
    $docs = $this->_db->{$collection}
        ->find( $this->_ws, $this->_sls )
        ->limit( $this->_lmt )
        ->skip( $this->_ost );
    $this->_flush();

    $result = array();
    foreach( $docs as $row ) {
        $result[] = ( object ) $row;
    }
    return $result;
}

_find方法中,我们使用驱动程序的find方法,设置查询limitskip参数以支持分页。

但是,此方法返回一个MongoCursor对象,然后我们对其进行迭代以获取实际记录。 和以前一样,我们将每个记录都转换为对象,并将其追加到结果数组。

UPDATE操作

我们已经支持从数据库创建和读取记录。 现在,我们需要能够编辑这些记录并添加或修改记录的数据。 创建一个新方法_update并实现它,如下所示:

protected function _update( $collection, $data, $where = array() ) {
    if ( is_object( $data ) ) {
        $data   = ( array ) $data;
    }
    $this->_set_where( $where );

    if ( array_key_exists( '$set', $data ) ) {
        $newdoc     = $data;
    } else {
        $newdoc     = array( '$set' => $data );
    }

    $result         = false;
    try {
        if( $this->_db->{$collection}->update( $this->_ws, $newdoc ) ) {
            $result = ( object ) $data;
        }
    } catch( \MongoCursorException $e ) {
        $result         = new \stdClass();
        $result->error  = $e->getMessage();
    }
    $this->_flush();

    return $result;
}

与CREATE操作一样,我们支持数组和对象,这意味着我们将进行相应的检查和转换。 我们使用辅助方法将传递给该方法的where子句组合在一起。 其余与已经创建的_insert方法没有什么不同。

不过,有一件特别的事情要注意。 当我们更新MongoDB文档并将数据传递给_update方法时,该文档将被替换。 如果我们仅更新一个字段并传递该字段的数据,则文档将成为该字段。 这就是为什么我们需要使用$set键和添加的信息创建一个数组。 结果是我们的记录不会被新信息代替。

删除操作

最后,驱动程序必须支持DELETE操作才能从数据库中删除文档。

protected function _remove( $collection, $where = array() ) {
    $this->_set_where( $where );

    $result = false;
    try {
        if ( $this->_db->{$collection}->remove( $this->_ws ) ) {
            $result = true;
        }
    } catch( \MongoCursorException $e ) {
        $result         = new \stdClass();
        $result->error  = $e->getMessage();
    }
    $this->_flush();

    return $result;
}

和以前一样,我们为删除操作设置where子句,并依靠PHP的MongoDB驱动程序对数据库执行remove操作。

这就是我们的Base模型。 它有很多代码,但是我们现在可以在MongoDB中对继承自Base类的模型执行操作。

3. Session模型

Session模型将负责在数据库中创建,删除和查找会话。 在应用程序的Models文件夹中创建一个新文件,将其命名为Session.php ,并向其中添加以下代码:

<?php namespace App\Http\Models;

class Session extends Base {

    private $_col   = "sessions";

    public function create( $user ) {
        $this->_where( 'user_id', ( string ) $user->_id );
        $existing   = $this->_findOne( $this->_col );

        if ( !empty( ( array ) $existing ) ) {
            $this->_where( 'user_id', ( string ) $user->_id );
            $this->_remove( $this->_col );
        }

        $session            = new \stdClass();
        $session->user_id   = ( string ) $user->_id;
        $session->user_name = $user->name;
        $session            = $this->_insert( $this->_col, $session );

        return $session;
    }

    public function find( $token ) {
        $this->_where( '_id', $token );
        return $this->_findOne( $this->_col );
    }

    public function remove( $token ) {
        $this->_where( '_id', $token );
        return $this->_remove( $this->_col );
    }
}

该模型是我们先前创建的支持MongoDB操作的Base类的扩展。 它还设置了用于sessions的集合。

create方法用于创建用户会话记录。 在尝试创建它之前,该方法将验证用户是否已经具有活动会话。 如果是这样,它将从数据库中删除它,并使用传入的用户信息创建新记录。

find方法用于使用会话令牌从数据库中检索会话记录。 请注意,它只是为查询设置了where子句,并将查找记录的任务委托给Base类的_findOne方法。

为了结束用户会话,我们实现了remove方法。 使用会话令牌,它将繁重的工作委托给Base类的_remove方法。 请注意,该模型不检查传入的会话令牌。这应由控制器处理。 该模型唯一需要关注的是数据操作。

4. User模型

REST API需要的另一种模型是处理与用户相关的交互的模型。 在应用程序的Models文件夹中,创建一个新的User.php文件,并向其中添加以下代码:

<?php namespace App\Http\Models;

use App\Http\Models\Base as Model;

class User extends Model {

    private $_col   = "users";

    private $_error = null;

    public function get( $where ) {}

    public function get_error() {}

    public function create( $user ) {}

    public function remove( $id ) {}

    public function retrieve( $id, $distance, $limit = 9999, $page = 1 ) {}

    public function update( $id, $data ) {}
}

User模型稍微复杂一些。 让我们从检索用户的方法开始。 get方法将负责使用用户的ID检索单个记录。 将以下代码添加到get方法:

if ( is_array( $where ) ) {
    return $this->_findOne( $this->_col, $where );
} else {
    $this->_where( '_id', $where );
    return $this->_findOne( $this->_col );
}

我们假设在where参数不是数组的情况下,它是用户的ID。 然后, get方法将查找记录的任务委托给Base类的_findOne方法。

get_error方法是一个辅助方法,它将为控制器提供有关模型故障的更多信息。

return $this->_error;

User模型中的最后一个读取操作是针对retrieve方法的操作。 这将获取用户列表。 将以下代码添加到retrieve方法中:

if ( !empty( $id ) && !empty( $distance ) ) {
    $this->_where( '_id', $id );
    $this->_select( 'location' );
    $user   = $this->_findOne( $this->_col );

    if ( empty( ( array ) $user ) ) {
        $this->_error   = "ERROR_INVALID_USER";
        return false;
    }

    $this->_where( '$and', array(
            array(
                '_id'       => array( '$ne' => new \MongoId( $id ) )
            ),
            array(
                'location'  => array(
                    '$nearSphere'       => array(
                        '$geometry'     => array(
                            'type'          => "Point",
                            'coordinates'   => $user->location['coordinates']
                        ),
                        '$maxDistance'  => ( float ) $distance
                    )
                )
            )
        ) );
}

$this->_limit( $limit, ( $limit * --$page ) );
return $this->_find( $this->_col );

此方法支持分页和地理空间查询。 如果传入了iddistance参数,它将尝试根据用户的位置搜索附近的用户。

如果id与任何记录都不匹配,则返回false 。 如果用户确实存在,它将使用MongoDB 2dsphere索引准备地理空间查询。

请注意,我们还将查询设置为不返回与执行搜索的用户的_id相匹配的用户。 最后,它设置查询限制和偏移量参数,从而将任务委派给Base类的_find方法。

要删除用户,我们需要实现remove方法。 将以下代码添加到remove方法:

$this->_where( '_id', $id );
$user   = $this->_findOne( $this->_col );

if ( empty( ( array ) $user ) ) {
    $this->_error       = "ERROR_INVALID_ID";
    return false;
} else {
    $this->_where( '_id', $id );
    if ( !$this->_remove( $this->_col ) ) {
        $this->_error   = "ERROR_REMOVING_USER";
        return false;
    }
}

return $user;

我们检查给定的_id对应于现有用户,并尝试使用Base类的_remove方法将其删除。 如果出现问题,我们将设置模型的_error属性并返回false

我们的模型应支持的另一种操作是创建用户记录。 将以下代码添加到create方法:

if ( is_array( $user ) ) {
    $user   = ( object ) $user;
}
$this->_where( '$or', array(
        array(
            "email"     => $user->email
        ),
        array(
            "mobile"    => $user->mobile
        )
    )
);
$existing   = $this->_findOne( $this->_col );

if ( empty( ( array ) $existing ) ) {
    $user   = $this->_insert( $this->_col, $user );
} else {
    $user   = $existing;
}

$user->_id  = ( string ) $user->_id;

return $user;

在这种方法中,我们确保没有用户与给定的emailmobile相关联。 如果是这样,我们将返回相应的用户。 如果不是,我们将创建用户的任务委派给Base类的_insert方法。

在返回用户记录之前,我们将_id转换为字符串。 这是为什么? 返回给我们的对象将_id字段定义为MongoId对象。 但是,客户端应用程序不需要此对象。

User模型还需要支持更新用户记录。 将以下代码添加到update方法:

if ( is_array( $data ) ) {
    $data   = ( object ) $data;
}
if ( isset( $data->email ) || isset( $data->mobile ) ) {
        $this->_where( '$and', array(
            array(
                '_id'       => array( '$ne' => new \MongoId( $id ) )
            ),
            array(
                '$or'       => array(
                    array(
                        'email'     => ( isset( $data->email ) ) ? $data->email : ""
                    ),
                    array(
                        'mobile'    => ( isset( $data->mobile ) ) ? $data->mobile : ""
                    )
                )
            )
        )
    );
    $existing   = $this->_findOne( $this->_col );
    if ( !empty( ( array ) $existing ) && $existing->_id != $id ) {
        $this->_error   = "ERROR_EXISTING_USER";
        return false;
    }
}

$this->_where( '_id', $id );
return $this->_update( $this->_col, ( array ) $data );

与在Base类中一样, update方法接受数组和对象作为用户的数据。 这使方法更加灵活。

在更新用户记录之前,请确保其他用户尚未使用该用户的emailmobile 。 如果是这种情况,我们将错误设置为EXISTING_USER并返回false 。 否则,我们将更新操作委托给Base类。

5. BaseController

就像应用程序的模型继承自Base类一样,控制器也继承自Laravel的Controller类之外的公共父类。 但是,这个BaseController类不会接近Base模型的复杂性。

该类仅用于处理一些简单的任务。 要创建类,我们使用Laravel的artisan命令。 在命令行中,导航到应用程序的根目录并执行以下命令:

php artisan make:controller BaseController --plain

这将在app / Http文件夹中的应用程序的Controllers文件夹内创建一个名为BaseController.php的文件。 由于我们正在使用 --plain标志,控制器将没有任何方法,这就是我们想要的。

该控制器将不会使用Request类,因此可以继续删除以下行:

use Illuminate\Http\Request;

因为我们需要访问Session模型,所以BaseController添加到BaseController类的声明中:

use App\Http\Models\Session as SessionModel;

现在,我们准备实现BaseController类的方法。 首先在类声明中添加以下方法:

protected function _check_session( $token = "", $id = "" ) {
    $result = false;
    if ( !empty( $token ) ) {
        $SessionModel   = new SessionModel();
        $session        = $SessionModel->find( $token );

        if ( !empty( ( array ) $session ) ) {
            if ( !empty( $id ) ) {
                if ( $session->user_id == $id ) {
                    $result = $session;
                }
            } else {
                $result     = $session;
            }
        }
    }

    return $result;
}

protected function _response( $result ) {
    if ( is_object( $result ) && property_exists( $result, "status" ) ) {
        return response()->json( $result, $result->status );
    } else {
        return response()->json( $result );
    }
}

_check_session方法用于验证会话令牌,该令牌作为第一个参数传递。 应用程序中的某些任务要求用户登录。例如,在更新用户记录时,与活动会话相对应的用户需要匹配需要更新的记录的_id

实现非常简单。 我们获取会话令牌的会话,如果与该会话相对应的用户的ID与作为第二个参数传递的ID匹配,则返回该会话。 否则,我们返回false

另一个帮助程序方法负责将结果发送回使用该API的客户端。 目前,我们仅支持JSON。 如果要返回的结果是一个对象并且具有状态参数,则可以使用Laravel的response帮助器方法进行设置。 否则,我们仅返回结果。

6. SessionController

我们将实现的下一个控制器是处理对Sessions资源的请求的控制器。 在命令行中,导航到应用程序的根目录并执行以下命令:

php artisan make:controller SessionController --plain

这将在app / Http文件夹中的应用程序的Controllers文件夹内创建一个名为SessionController.php的新文件。 在实施此类之前,我们需要注意一些事项。

SessionController类当前继承自Laravel的Controller类。 我们需要设置它以使用我们的BaseController类。 这意味着我们需要更换

use App\Http\Controllers\Controller;

use App\Http\Controllers\BaseController;

我们还需要更改该类的extends子句。 而不是从Controller进行扩展,请确保您的类在扩展BaseController类。 我们还需要包括控制器中使用的模型。 在最后一个声明下方,添加以下行:

use App\Http\Models\Session as SessionModel;
use App\Http\Models\User as UserModel;

通常,我们只使用SessionModel ,但是您很快就会看到为什么还要使用UserModel原因。 至于控制器类本身,请添加以下代码:

private $_model = null;

public function __construct() {
    $this->_model   = new SessionModel();
}

public function create( Request $request ) {}

public function destroy( $token ) {}

我们在构造函数中设置控制器的model对象,并声明几个方法,这是Sessions资源支持的操作。

第1步:会话删除

为了删除用户会话,我们仅使用会话令牌,该令牌在资源URL中作为参数给出。 我们将在稍后的申请路线中对此进行声明。 在destroy方法内,添加以下代码:

$result = new \stdClass();
if ( !$this->_model->remove( $token ) ) {
    $result->error  = "ERROR_REMOVING_SESSION";
    $result->status = 403;
}

return $this->_response( $result );

该方法使用SessionModelremove方法,并使用BaseController类的_response方法返回结果。 如果删除会话成功,我们将返回一个空对象。 如果发生错误,我们将返回带有403状态代码的错误。

步骤2:建立工作阶段

创建会话的方法稍微复杂一些。 请注意,在方法声明中,我们使用的是Laravel的Request对象。 我们使用该对象访问请求的POST参数。 在create方法内,添加以下代码:

$email      = $request->get( 'email' );
$mobile     = $request->get( 'mobile' );
$fbId       = $request->get( 'fbId' );

$result     = new \stdClass();
if ( ( empty( $email ) && empty( $mobile ) ) || empty( $fbId ) ) {
    $result->error  = "ERROR_INVALID_PARAMETERS";
    $result->status = 403;
} else {}

return $this->_response( $result );

我们尚未创建会话对象,因为首先需要讨论一些内容。 该应用程序将仅使用Facebook登录名。 在执行登录操作时,我们将从Facebook SDK中获取用户信息。 在API的Session资源POST处理程序中,我们需要支持两件事:

  • 为用户启动会话
  • 在不存在时创建用户,然后开始会话

这也是在控制器中包含UserModel的原因。 在上面的空else子句中,添加以下代码:

$UserModel  = new UserModel();
$where      = ( !empty( $email ) ) ? array( 'email' => $email ) : array( 'mobile' => $mobile );
$user       = $UserModel->get( $where );

if ( empty( ( array ) $user ) ) {
   
} else {
    if ( $fbId != $user->fbId ) {
        $result->error  = "ERROR_INVALID_CREDENTIALS";
        $result->status = 403;
    }
}

if ( !property_exists( $result, "error" ) ) {
    $result         = $this->_model->create( $user );
    $result->token  = $result->_id;
    unset( $result->_id );
}

我们首先使用传入的emailmobile检查现有用户。 如果用户存在,我们将验证给定的Facebook ID与用户记录的Facebook ID匹配。 在这种情况下,我们将创建会话对象。 如果不是,则该方法返回带有403状态代码的INVALID_CREDENTIALS错误。

现在开始会话。 请注意,这并不是特别安全。 但是,就本教程而言,它可以正常工作。

对于没有用户与传入emailmobile关联的情况,我们要创建一个新记录。 在上面的空if子句中,添加以下代码:

name           = $request->get( 'name' );
$gender         = $request->get( 'gender' );
$location       = $request->get( 'location' );

if ( empty( $name ) || empty( ( array ) $location ) || empty( $gender ) ) {
    $result->error  = "ERROR_INVALID_PARAMETERS";
    $result->status = 403;
} else {
    if ( gettype( $location ) == "string" ) {
        $location   = json_decode( $location );
    }
    $locObj                 = new \stdClass();
    $locObj->type           = "Point";
    $locObj->coordinates    = array( $location->lon, $location->lat );

    $user->name     = $name;
    $user->fbId     = $fbId;
    $user->email    = $email;
    $user->mobile   = $mobile;
    $user->gender   = $gender;
    $user->location = $locObj;

    $user           = $UserModel->create( $user );
}

我们首先从请求中检索其余的必需参数,然后检查location参数是作为JSON对象还是编码的JSON对象(字符串)给出。 该方法期望此参数采用以下格式:

{
    "lat"    : 37.427208696456866,
    "lon"    : -122.17097282409668
}

然后,我们将此位置转换为MongoDB 2dSphere位置。 要执行地理空间查询,此字段必须具有以下格式:

{
    "type"            : "Point"
    "coordinates"    : [ -122.17097282409668, 37.427208696456866 ]
}

我们可以要求客户以这种格式发送位置信息。 但是,最好不要给客户端重新设置用户位置格式的负担,因为这是我们的实现所特有的。

设置位置对象后,我们检查用户所需的参数是否存在,如果是这种情况,我们使用UserModel类的create方法创建一个新的用户对象。

而已。 即使我们可以仅通过发送emailfbId参数或mobilefbId参数来启动会话,但是如果给出了其余的用户信息,我们的处理程序将在必要时负责创建新用户并启动会话。

7. UserController

应用程序需要的最后一个控制器是负责处理Users资源的控制器。 再一次,我们使用Laravel的工匠命令。 在命令行中,导航到应用程序的根目录并执行以下命令:

php artisan make:controller UserController --plain

这将在app / Http文件夹中的应用程序的Controllers文件夹内创建一个UserController.php文件。 与SessionController类一样,请确保UserController类继承自BaseController并包括UserModel类。 在类声明内,添加以下代码:

private $_model = null;

public function __construct() {
    $this->_model   = new UserModel();
}

public function create( Request $request ) {}

public function get( Request $request, $id ) {}

public function remove( Request $request, $id ) {}

public function retrieve( Request $request ) {}

public function update( Request $request, $id ) {}

SessionController类一样,我们初始化模型对象并声明Users资源将支持的方法。 让我们从GET操作开始。 在get方法中,添加以下代码:

$token  = $request->get( 'token' );

$result = new \stdClass();
if ( !$this->_check_session( $token ) ) {
    $result->error  = "PERMISSION_DENIED";
    $result->status = 403;
} else {
    $result = $this->_model->get( $id );
}

return $this->_response( $result );

要从系统中检索记录,我们要求用户具有活动会话。 它不必与检索到的用户的ID匹配。 如果用户没有有效的会话,我们将返回PERMISSION_DENIED错误和403状态代码。 否则,我们将用户记录作为JSON对象返回。

接下来,对于用户列表,我们需要实现retrieve方法。 将以下代码添加到retrieve方法中:

$token      = $request->get( 'token' );
$distance   = $request->get( 'distance' );

$session    = $this->_check_session( $token );
$result     = $this->_model->retrieve( ( isset( $session->user_id ) ? $session->user_id : "" ), $distance, $request->get( 'limit' ), $request->get( 'page' ) );
if ( !is_array( $result ) && !$result ) {
    $result         = new \stdClass();
    $result->error  = $this->_model->get_error();
    $result->status = 403;
}

return $this->_response( $result );

我们首先获取请求参数,尤其是用户的会话令牌和距离参数。 但是,此方法不需要活动的会话。 如果会话有效,则将用户ID传递到UserModel类的retrieve方法。

如果传入了distance参数,则将执行地理空间查询。 如果不是,则执行常规find查询。 发生错误时,我们会从模型中检索错误,并使用403状态代码将其返回给用户。 否则,我们将返回一个包含找到的用户的数组。

用户创建将映射到Users资源的POST操作。 将以下代码添加到create方法:

$email      = $request->get( 'email' );
$fbId       = $request->get( 'fbId' );
$gender     = $request->get( 'gender' );
$location   = $request->get( 'location' );
$mobile     = $request->get( 'mobile' );
$name       = $request->get( 'name' );

if ( gettype( $location ) == "string" ) {
    $location   = json_decode( $location );
}
$locObj                 = new \stdClass();
$locObj->type           = "Point";
$locObj->coordinates    = array( $location->lon, $location->lat );

$result     = new \stdClass();
if ( empty( $name ) || empty( ( array ) $location ) || empty( $fbId ) || empty( $gender ) || ( empty( $email ) && empty( $mobile ) ) ) {
    $result->error  = "ERROR_INVALID_PARAMETERS";
    $result->status = 403;
} else {
    $user       = array(
        "email"     => $email,
        "fbId"      => $fbId,
        "gender"    => $gender,
        "location"  => $locObj,
        "mobile"    => $mobile,
        "name"      => $name
    );
    $result     = $this->_model->create( $user );
}

return $this->_response( $result );

我们首先为用户检索必要的信息,然后像在会话创建处理程序中一样,将用户的位置转换为适当的格式。

此后,我们检查是否传递了所需的信息。请注意,即使emailmobile字段都是可选的,也必须至少显示一个。

这些检查之后,我们调用UserModel类的create方法将新用户插入数据库。 最后,我们返回新用户或错误。

要删除用户,我们需要实现remove方法。 将以下代码添加到remove方法:

$token  = $request->get( 'token' );
$result = new \stdClass();

if ( !$this->_check_session( $token, $id ) ) {
    $result->error  = "PERMISSION_DENIED";
    $result->status = 403;
} else {
    $result = $this->_model->remove( $id );
    if ( !$result ) {
        $result         = new \stdClass();
        $result->error  = $this->_model->get_error();
        $result->status = 403;
    }
}

return $this->_response( $result );

这是在我们希望这些方法之一_id要删除的用户的匹配_id与活动会话的用户。 这是我们验证的第一件事。 在这种情况下,我们委托给模型的remove方法。 否则,我们将错误设置为PERMISSION_DENIED并将结果发送回用户。

最后,让我们实现用户的更新操作。 在update方法内,添加以下代码:

$token      = $request->get( 'token' );
$data       = new \stdClass();
if ( !empty( $email = $request->get( 'email' ) ) ) {
    $data->email    = $email;
}
if ( !empty( $fbId = $request->get( 'fbId' ) ) ) {
    $data->fbId     = $fbId;
}
if ( !empty( $gender = $request->get( 'gender' ) ) ) {
    $data->gender   = $gender;
}
if ( !empty( $location = $request->get( 'location' ) ) ) {
    if ( gettype( $location ) == "string" ) {
        $location   = json_decode( $location );
    }
    $locObj                 = new \stdClass();
    $locObj->type           = "Point";
    $locObj->coordinates    = array( $location->lon, $location->lat );
       
    $data->location = $locObj;
}
if ( !empty( $mobile = $request->get( 'mobile' ) ) ) {
    $data->mobile   = $mobile;
}
if ( !empty( $name = $request->get( 'name' ) ) ) {
    $data->name     = $name;
}

$result     = new \stdClass();
if ( !$this->_check_session( $token, $id ) ) {
    $result->error  = "PERMISSION_DENIED";
    $result->status = 403;
} else {
    $result = $this->_model->update( $id, $data );
    if ( !$result ) {
        $result         = new \stdClass();
        $result->error  = $this->_model->get_error();
        $result->status = 403;
    }
}

return $this->_response( $result );

我们验证传入的数据并为要更新的内容设置适当的对象。 对于location参数,我们首先将其重新格式化。

同样,只有活动会话与其自己的_id相对应的用户才可以访问此方法。 这意味着我们首先检查情况是否如此。

然后,我们调用UserModel类的update方法,并将结果返回给客户端。

8.应用路由器

有了最后的代码,我们的API就完成了。 我们有适当的控制器和模型。 我们要做的最后一件事是将传入请求映射到适当的端点。

为此,我们需要编辑应用程序的route.php文件。 它位于app / Http文件夹中。 如果打开它,应该会看到类似以下内容的内容:

Route::get( '/', 'WelcomeController@index' );

当应用程序收到未指定任何资源的GET请求时, WelcomeController类的index方法应处理该WelcomeController 。 但是,您可能已经在本教程开始时删除了WelcomeController 。 如果尝试在浏览器中导航到此端点,则会收到错误消息。 让我们用以下代码替换最后一行:

Route::post( 'sessions', 'SessionController@create' );

Route::delete( 'sessions/{token}', 'SessionController@destroy' );

Route::delete( 'users/{id}', 'UserController@remove' );

Route::get( 'users', 'UserController@retrieve' );

Route::get( 'users/{id}', 'UserController@get' );

Route::post( 'users', 'UserController@create' );

Route::put( 'users/{id}', 'UserController@update' );

我们将API请求映射到先前添加到控制器中的方法。 例如,以下调用

[ DELETE ] - http://YOUR_API_URL/sessions/abc

转换为给定URL的DELETE请求。 这意味着在SessionController   delete方法将以abc的令牌调用。

结论

使用Laravel 5.0的RESTful API就是这样。 我们支持用户和会话管理,这正是实现iOS客户端所需要的。

在本教程的下一部分中,Jordan将向您展示如何将该API集成到iOS应用程序中。 他还将向您展示如何集成Sinch SDK进行消息传递和语音呼叫。

翻译自: https://code.tutsplus.com/tutorials/creating-a-dating-application-with-sinch-restful-api--cms-23709

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值