uber-zap_如何构建自己的Uber-for-X应用程序

uber-zap

Featured in Mybridge’s Top Ten NodeJS articles from October 2016 and Top Ten NodeJS articles of the year (v.2017)

2016年10月的 Mybridge十大 NodeJS 文章本年度的十大NodeJS文章中精选(v.2017)



Update: Check-out the latest version on my tech blog! This article is now a few years old - and due to JavaScript's rapidly changing ecosystem, the article has become slightly outdated. Click on the above link for the updated version of this article and the project.

更新:在我的技术博客出最新版本! 本文已有数年历史了-由于JavaScript的快速变化的生态系统,本文已略有过时。 单击上面的链接,获取本文和项目的更新版本。



Uber (if you haven’t heard of it) is a handy app that allows you to catch a cab without walking around to look for one. And most importantly, it solves the problems of demand and supply that exists among cab drivers and cab seekers.

Uber(如果您还没有听说过的话)是一款方便的应用程序,可让您骑出租车而无需四处寻找。 最重要的是,它解决了出租车司机和出租车司机之间存在的供需问题。

Today, there are a variety of startups focused around Uber-for-X apps. The thinking goes that, what Uber did for cabs, they can surely do for other supply/demand problems.

如今,有许多针对Uber-for-X应用程序的创业公司。 人们认为,Uber在出租车上所做的事情肯定可以解决其他供需问题。

So during a hackathon, me and my friend decided to build a citizen-cop app. We figured it would be cool to build something that can help your friends in times of trouble!

因此在一次黑客马拉松中,我和我的朋友决定构建一个公民警察应用程序。 我们认为,构建可以在遇到麻烦时帮助您的朋友的东西会很酷!

After some thinking, these were the following features that we agreed upon:

经过一番思考,我们同意了以下这些功能:

  1. Civilians will be able to request the nearest police officer in their neighborhood at the press of a button. It’ll raise a ‘distress signal’ and alert nearby cops.

    按下按钮,平民将可以请求附近的最近警察。 它将发出“遇险信号”并警告附近的警察。
  2. Any police in the vicinity will immediately receive the user’s location and can choose to accept the request and solve the issue.

    附近的任何警察都会立即收到用户的位置,并可以选择接受请求并解决问题。
  3. A rating system

    评分系统
  4. Data collected from locations, crime cases solved, etc. can be visualized on a map, or graphed with some other cool user interface widgets

    从位置,解决的犯罪案件等收集的数据可以在地图上可视化,或与其他一些炫酷的用户界面小部件图形化

In this tutorial, I’ll walk you through how we built it step-by-step, so that you’ll be able to build your own Uber-for-X app.

在本教程中,我将逐步向您介绍如何构建它,以便您能够构建自己的Uber-for-X应用程序。

Before you begin, it would help to keep the following points in mind —

在开始之前,请记住以下几点-

  • This tutorial will not focus on how to build the app for scale. Or for performance. It’s basically designed so that you can have fun while building it, and how you can create something that mimics Uber. Think of this as though building a Minimum Viable Product to demonstrate your idea or startup, for a proof-of-concept.

    本教程将不会重点介绍如何构建可扩展应用程序。 还是为了表现。 它的基本设计目的是让您在构建它的同时可以享受乐趣,以及如何创建模仿Uber的东西。 可以将其视为构建概念的最小可行产品来证明您的想法或创业。
  • Since I’ve not worked on Android or iPhone apps much, I’ll be building this to work inside a browser.

    由于我在Android或iPhone应用程序上的工作量不大,因此我将其构建为可在浏览器中使用。

Now, every app that you build has few important pieces:

现在,您构建的每个应用程序都有一些重要的部分:

  • a client-facing app (that you see in a browser or on your phone screens)

    面向客户端的应用程序(您在浏览器或手机屏幕上看到的)
  • on the back end, a web-server to handle incoming requests from the client and to route information

    在后端,一个Web服务器处理来自客户端的传入请求并路由信息
  • and a database to store and query information.

    还有一个用于存储和查询信息的数据库。

On the back end, you’ll use MongoDB as your database. it’s easier to learn, and offers a lot of querying techniques to handle geospatial information, which you’ll need for your app.

在后端,您将使用MongoDB作为数据库。 它更易于学习,并提供了许多查询技术来处理地理空间信息,这是应用程序所需的。

You’ll use NodeJS for your back end logic. Because it’s the same language for both front-end and back-end you wouldn’t have to worry about learning a new language or syntax.

您将使用NodeJS作为后端逻辑。 因为前端和后端都使用相同的语言,所以您不必担心学习新的语言或语法。

On the front end, you’ll use HTML5, CSS3, JavaScript, and also the Google Maps and Places APIs.

在前端,您将使用HTML5,CSS3,JavaScript以及Google Maps and Places API。

I’m assuming that you already have a working knowledge of JavaScript, and that you have at least a theoretical understanding of how NodeJS and MongoDB work.

我假设您已经掌握JavaScript的使用知识,并且至少对NodeJS和MongoDB的工作原理有理论上的了解。

Here are the contents of this tutorial :

以下是本教程的内容:

Part 1 (what you’re reading right now):

第1部分(您现在正在阅读的内容)

  • MongoDB Schema design

    MongoDB模式设计
  • Using the Mongo Shell to query information

    使用Mongo Shell查询信息
  • Connecting your database with your Node-Express server and writing RESTful APIs

    将数据库与Node-Express服务器连接并编写RESTful API

Part 2:

第2部分

  • Using Socket.IO to enable the cop and civilian devices talk to each other

    使用Socket.IO启用cop和民用设备相互通信
  • Using Google Maps API to show civilians and cops on a map

    使用Google Maps API在地图上显示平民和警察

让我们开始吧! (Let’s get started!)

Developers have used MongoDB to build applications for quite some time now. It has a shallow learning curve, and its versatility allows developers to rapidly build applications with ease.

开发人员已经使用MongoDB构建应用程序已有相当一段时间了。 它的学习曲线很浅,其多功能性使开发人员可以轻松快速地构建应用程序。

I personally like MongoDB because it allows me to quickly build prototypes for an idea to demonstrate proof-of-concept.

我个人喜欢MongoDB,因为它使我能够快速构建原型以展示概念验证。

Before you begin, do make sure that you have MongoDB and NodeJS installed. At the time of writing this article, the current version of MongoDB is 3.2.

在开始之前,请确保已安装MongoDB和NodeJS。 在撰写本文时,MongoDB的当前版本是3.2

设计架构 (Designing the Schema)

Since you’re using MongoDB, everything that you save in it is a collection of documents.

由于您使用的是MongoDB,因此您保存在其中的所有内容都是文档的集合。

Let’s create a collection called citizensData for storing citizen information, and another collection called policeData for storing cops info. So go ahead, open up your terminal and type mongo to fire up the mongo shell. Once it opens up, you can show existing databases in MongoDB by typing:

让我们创建一个名为citizensData用于存储公民信息的收集,并呼吁policeData存储警察信息的另一个集合。 所以,继续,打开您的终端并输入mongo来启动mongo shell。 打开后,您可以输入以下内容显示MongoDB中的现有数据库:

show dbs

You need a new database to store your app data. Let’s call it myUberApp. To create a new database, you can type:

您需要一个新的数据库来存储您的应用程序数据。 我们称之为myUberApp。 要创建一个新的数据库,您可以输入:

use myUberApp

The use command has the effect of creating a new database if it doesn’t exist. If it does, it tells Mongo to apply all following commands to this database.

如果不存在,则use命令具有创建新数据库的作用。 如果是这样,它将告诉Mongo将以下所有命令应用于此数据库。

Mongo stores documents in collections. Collections are like tables. To see existing collections, type:

Mongo将文档存储在集合中 。 集合就像表。 要查看现有集合,请键入:

show collections

For the cop, the username could be the badge-id too. You might add in a field for email address and one for password too (which won’t be revealed) for authentication purposes.

对于警察,用户名也可以是徽章ID。 您可能还添加了一个用于电子邮件地址的字段和一个用于密码的字段(不会显示)以进行身份​​验证。

Go to this link, and save the JSON data-set for cop related information.

转到此链接 ,并保存JSON数据集以获取cop相关信息。

To import data from this JSON file, type this in your terminal :

要从此JSON文件导入数据,请在终端中输入以下内容:

mongoimport --db myUberApp --collection policeData --drop --file ./path/to/jsonfile.json

Now, before you start querying your database, you need to learn a little on how indexes in MongoDB (or any database for that matter) work.

现在,在开始查询数据库之前,您需要了解一些有关MongoDB(或与此相关的任何数据库)中的索引如何工作的知识。

An index is a special arrangement of data or data structure that allows you to query for information very efficiently. That way you can quickly retrieve results without having to scan across the entire database.

索引是数据或数据结构的一种特殊安排,可让您非常有效地查询信息。 这样,您可以快速检索结果,而不必扫描整个数据库。

For example — let’s say you stored student related information in ascending order of their name in a book, which means that you have an index on the name field. That way, if you had to fetch information of a person named Tyrion, you can quickly locate his information without going through the rest of the students first.

例如,假设您将与学生相关的信息按其姓名的升序存储在书中,这意味着您在名称字段上有一个索引。 这样,如果您必须获取名为Tyrion的人的信息,则可以快速找到他的信息,而无需先经过其余学生。

But if you saved the same information in ascending order of their height, then querying information for a person using their name would become difficult. It could take lot of time, because now the students are not saved in order of their names, so you might have to scan and search across multiple rows.

但是,如果您按相同的信息以其身高升序保存,那么使用其姓名查询人的信息将变得很困难。 这可能会花费大量时间,因为现在没有按姓名顺序保存学生,因此您可能必须扫描和搜索多行。

But other kind of queries become possible. For example, fetch information of students whose heights lie between 4 and 5 feet. In which case Tyrion’s info could be retrieved quickly, because:

但是其他类型的查询也成为可能。 例如,获取高度在4到5英尺之间的学生的信息。 在这种情况下,可以快速检索Tyrion的信息,因为:

Different databases support different types of indexes. You could read on the complete list of indexes that supports MongoDB here.

不同的数据库支持不同类型的索引。 您可以在此处阅读支持MongoDB的完整索引列表。

So, now if you type this command:

因此,现在,如果您键入以下命令:

db.policeData.find().pretty()

which will return you all the documents that exist inside the policeData collection — which is the entire list of cops. (The pretty function makes the output easier to read).

它将返回您存在于PoliceData集合中的所有文档-这是整个警察名单。 ( 漂亮的功能使输出更易于阅读)。

If you want to fetch information about a particular cop whose userId is 01, you can type out db.policeData.find({userId: “01”}).pretty()

如果要获取有关userId01的特定警察的信息,则可以键入db.policeData.find({userId: “01”}).pretty()

{
    "_id" : ObjectId("57e75af5eb1b8edc94406943"),
    "userId" : "01",
    "displayName" : "Cop 1",
    "phone" : "01",
    "email" : "cop01@gmail.com",
    "earnedRatings" : 21,
    "totalRatings" : 25,
    "location" : {
        "type" : "Point",
        "address" : "Kalyan Nagar, Bengaluru, Karnataka 560043, India",
        "coordinates" : [
            77.63997110000003,
            13.0280047
        ]
    }
}
使用MongoDB地理空间索引 (Using MongoDB geospatial indexes)

Geospatial indexes allow you to store GeoJSON objects within documents.

地理空间索引使您可以在文档中存储GeoJSON对象。

GeoJSON objects can be of different types, such as Point, LineString and Polygon.

GeoJSON对象可以具有不同的类型 ,例如Point,LineStringPolygon。

If you observe the output of your .find() command, you’ll notice that every location is an object which has the type field and the coordinates field within it. This is important, because if you store your GeoJSON object as a Point type, you can use the $near command to query for points within certain proximity for a given longitude and latitude.

如果观察.find()命令的输出,您会注意到每个位置都是一个对象,其中包含类型字段和坐标字段。 这很重要,因为如果将GeoJSON对象存储为Point类型,则可以使用$ near命令来查询给定经度和纬度在一定距离内的点。

To use this, you need to create a 2dsphere index (which is a geospatial index) on the location field, and have a type field within it. The 2dsphere index supports queries that calculate geometries on an earth-like sphere. This includes MongoDB geospatial queries: queries for inclusion, intersection and proximity.

要使用此功能,您需要在位置字段上创建2dsphere索引(这是地理空间索引),并在其中包含类型字段。 2dsphere索引支持查询,该查询可计算类似地球的球体上的几何形状。 这包括MongoDB地理空间查询:包含,相交和邻近度查询。

So type this in your mongo shell:

因此,在您的mongo shell中键入以下内容:

db.policeData.createIndex({"location": "2dsphere"})

Now, to fetch documents from nearest to furthest from a given pair of co-ordinates, you need to issue a command with this syntax :

现在,要从给定的一对坐标中获取最近到最远的文档,您需要使用以下语法发出命令:

db.<collectionName>.find({
    <fieldName>: {
        $near: {
            $geometry: {
                type: "Point",
                coordinates: [<longitude>, <latitude>]
            },
            $minDistance: <distance in metres>,
            $maxDistance: <distance in metres>
        }
    }
}).pretty()

$minDistance and $maxDistance are optional fields. Now, to get all cops that are located within 2 kilometers from latitude 12.9718915 and longitude 77.64115449999997, run this :

$ minDistance和$ maxDistance是可选字段。 现在,要获取位于距纬度12.9718915经度77.64115449999997 2公里以内的所有警察,请运行以下命令:

db.policeData.find({
    location: {
        $near: {
            $geometry: {
                type: "Point",
                coordinates: [77.64115449999997, 12.9718915]
            },
            $maxDistance: 2000
        }
    }
}).pretty()

And that’s it — you’ll find a list of documents returned in the output!

就是这样-您将在输出中找到返回的文档列表!

Perfect! Now let’s try doing the same with a web server. Download this package.json file and save it in the root of your project folder (make sure you named it package.json), and then in your terminal, cd to the directory that contains the file and run

完善! 现在,让我们尝试对Web服务器执行相同的操作。 下载此package.json文件,并将其保存在项目文件夹的根目录中(确保将其命名为package.json ),然后在终端中, cd到包含该文件的目录并运行

sudo npm install

A brief explanation about some of the packages that you’re going to use :

有关将要使用的某些软件包的简要说明:

  • Express is a web framework for NodeJS. It has lots of APIs, utilities and middlewares in its ecosystem to help you build your application.

    Express是NodeJS的Web框架。 它在其生态系统中具有许多API,实用程序和中间件,可帮助您构建应用程序。

  • body-parser parses incoming request bodies in a middleware before your handlers, available under the req.body property. You need this so you can handle POST requests.

    body-parser在您的处理程序(可在req.body属性下使用)之前解析中间件中的传入请求正文。 您需要这样做,以便您可以处理POST请求。

  • underscore makes writing JavaScript simpler. Feel free to use another library if you prefer.

    下划线使编写JavaScript更简单。 如果愿意,可以随意使用其他库。

  • socket.io lets you use web sockets within your Node application.

    socket.io使您可以在Node应用程序中使用Web套接字。

  • mongodb is the official NodeJS driver for MongoDB. It helps your Node app talk to your database.

    mongodb是MongoDB的官方NodeJS驱动程序。 它可以帮助您的Node应用程序与数据库对话。

The package.json file contains other modules as well. You’ll need them while building a complete app, but I’ll focus on how to use the mongodb driver in your express app to execute queries. Here’s what some of the other modules do :

package.json文件还包含其他模块。 构建完整的应用程序时将需要它们,但我将重点介绍如何在快速应用程序中使用mongodb驱动程序来执行查询。 这是其他一些模块的功能:

  • async is a utility for dealing with asynchronous code in NodeJS. It helps you avoid callback hell.

    async是用于处理NodeJS中异步代码的实用程序。 它可以帮助您避免回调地狱。

  • debug is a debugging library. This handy tool helps debug your programs without the use of ugly console.log statement outputs.

    debug是一个调试库。 这个方便的工具可帮助您调试程序,而无需使用丑陋的console.log语句输出。

  • redis is similar to the mongodb driver. It lets your NodeJS app talk to your Redis database.

    redismongodb驱动类似。 它使您的NodeJS应用程序可以与Redis数据库对话。

  • connect-redis is a session store that uses Redis to manage sessions. You’ll need this later when you decide to have user accounts.

    connect-redis是使用Redis管理会话的会话存储。 以后决定拥有用户帐户时将需要此功能。

Before you write code, it’ll be helpful to organize it first. For now, you can use two files:

在编写代码之前,先对其进行组织将很有帮助。 现在,您可以使用两个文件:

  • A file for writing your API endpoints

    用于编写API端点的文件
  • A file that uses database drivers for database related operations. The route-handler would decide which function to call from the database file. Once a query is performed, the results are returned back to your route-handler with the help of a callback function.

    使用数据库驱动程序进行数据库相关操作的文件。 路由处理程序将决定从数据库文件中调用哪个函数。 执行查询后,结果将在回调函数的帮助下返回到您的路由处理程序。

Let’s see how this looks like when you write your code:

让我们看看编写代码时的样子:

var http = require("http");
var express = require("express");
var consolidate = require("consolidate");//1
var _ = require("underscore");
var bodyParser = require('body-parser');

var routes = require('./routes'); //File that contains our endpoints
var mongoClient = require("mongodb").MongoClient;

var app = express();
app.use(bodyParser.urlencoded({
   extended: true,
}));
             
app.use(bodyParser.json({limit: '5mb'}));

app.set('views', 'views'); //Set the folder-name from where you serve the html page. 
app.use(express.static('./public')); //setting the folder name (public) where all the static files like css, js, images etc are made available

app.set('view engine','html');
app.engine('html',consolidate.underscore);
var portNumber = 8000; //for locahost:8000

http.createServer(app).listen(portNumber, function(){ //creating the server which is listening to the port number:8000, and calls a function within in which calls the initialize(app) function in the router module
	console.log('Server listening at port '+ portNumber);
	
	var url = 'mongodb://localhost:27017/myUberApp';
	mongoClient.connect(url, function(err, db) { //a connection with the mongodb is established here.
		console.log("Connected to Database");
		routes.initialize(app, db); //function defined in routes.js which is exported to be accessed by other modules
	});
});

/* 1. Not all the template engines work uniformly with express, hence this library in js, (consolidate), is used to make the template engines work uniformly. Altough it doesn't have any 
modules of its own and any template engine to be used should be seprately installed!*/

In this example, you create a new instance of the MongoClient object from the mongodb module. Once the web server begins, you connect to your MongoDB database using the connect function that’s exposed by your MongoClient instance. After it initializes the connection, it returns a Db instance in the callback.

在此示例中,您将从mongodb模块创建MongoClient对象的新实例。 Web服务器启动后,您将使用MongoClient实例公开的connect函数连接到MongoDB数据库。 初始化连接后,它将在回调中返回一个Db实例。

You can now pass both the app and db instances to the initialize function of your routes.js file.

现在,您可以将appdb实例都传递给route.js文件的initialize函数。

Next, you need to create a new file called routes.js, and add this code:

接下来,您需要创建一个名为routes.js的新文件,并添加以下代码:

function initialize(app, db) { 
    //A GET request to /cops should return back the nearest cops in the vicinity.
    app.get('/cops', function(req, res){
    /*extract the latitude and longitude info from the request. Then, fetch the nearest cops using MongoDB's geospatial queries and return it back to the client.
    */
    });
}
exports.initialize = initialize;

For this to work, you’ll have to pass the coordinates as query strings in your request. You’ll also write your database operations in another file. So go ahead and create a new file db-operations.js, and write this:

为此,您必须在请求中将坐标作为查询字符串传递。 您还将在另一个文件中编写数据库操作。 因此,继续创建一个新文件db-operations.js,并编写以下代码:

function fetchNearestCops(db, coordinates, callback) {
    db.collection('policeData').createIndex({
        "location": "2dsphere"
    }, function() {
        db.collection("policeData").find({
            location: {
                $near: {
                    $geometry: {
                        type: "Point",
                        coordinates: coordinates
                    },
                    $maxDistance: 2000
                }
            }
        }).toArray(function(err, results) {
            if(err) {
                console.log(err)
            }else {
                callback(results);
            }
        });
    });
}
exports.fetchNearestCops = fetchNearestCops;

This function accepts three arguments: an instance of db, an array that contains co-ordinates in the order [<longitude>,<latitude>], and a callback function, to which it returns the results of your query.

此函数接受三个参数: db的实例,包含按[<longitude>,<latitude>]顺序排列的坐标的数组和一个回调函数,该函数将查询结果返回到该函数。

The createIndex ensures that an index is created on the specified field if it doesn’t exist, so you may want to skip that if you have already created an index prior to querying.

createIndex确保在指定字段上创建索引(如果该索引不存在),因此如果在查询之前已经创建了索引,则可能要跳过该索引。

Now, all that’s left to do is to call this function inside your handler. So modify your routes.js code to this:

现在,剩下要做的就是在处理程序中调用此函数。 因此,将您的routes.js代码修改为:

var dbOperations = require('./db-operations');
function initialize(app, db) {
    // '/cops?lat=12.9718915&&lng=77.64115449999997'
    app.get('/cops', function(req, res){
        //Convert the query strings into Numbers
        var latitude = Number(req.query.lat);
        var longitude = Number(req.query.lng);
        dbOperations.fetchNearestCops(db, [longitude,latitude], function(results){
        //return the results back to the client in the form of JSON
            res.json({
                cops: results
            });
        });  
    });
}
exports.initialize = initialize;

And that’s it! Run

就是这样! 跑

node app.js

from your terminal, then open your browser and hit http://localhost:8000/cops?lat=12.9718915&&lng=77.64115449999997

在您的终端上,然后打开浏览器并点击http:// localhost:8000 / cops?lat = 12.9718915 && lng = 77.64115449999997

Depending on the query strings that you passed, you should either get a JSON response containing an empty array or an array containing cop data!

根据您传递的查询字符串,您应该获得一个包含空数组或包含cop数据的JSON响应!

This is the end of Part 1. In Part 2, you’ll take it up a notch and try to send a distress signal to nearby cops. Then you’ll figure out how a cop could respond back to the signal using socket.io. You’ll also see how to display the location of the citizen on a map.

这是第1 部分的结束。在第2部分中 ,您将遇到一个缺口,并尝试向附近的警察发送求救信号。 然后,您将了解到警察如何使用socket.io响应信号。 您还将看到如何在地图上显示市民的位置。

In the meantime do have a look at the source code on Github!

同时,请查看Github上的源代码

If you liked this article, please consider supporting me on Patreon.

如果您喜欢本文,请考虑在Patreon上支持我。

Become a Patron!

成为赞助人!

You should totally subscribe. I won't waste your time.

您应该完全订阅 。 我不会浪费你的时间。

Many thanks to Quincy Larson for helping me make this article better.

非常感谢Quincy Larson帮助我使本文变得更好。

You can read this article and subsequent parts in my blog too!

您也可以在我的博客中阅读本文和后续部分!

Featured in Mybridge’s Top Ten NodeJS articles from October 2016 and Top Ten NodeJS articles of the year (v.2017)

2016年10月的 Mybridge十大 NodeJS 文章本年度的十大NodeJS文章中精选(v.2017)

翻译自: https://www.freecodecamp.org/news/how-to-build-your-own-uber-for-x-app-33237955e253/

uber-zap

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值