本篇文章给大家分享一个实战,介绍一下在node中构建一个位置分析报告api的方法,在本教程结束时,你也会对node.js中的错误处理和良好的文件结构有更好的理解,希望对大家有所帮助!
由经纬度定义的位置,可以与其他数据结合使用,为企业产生洞察力,这就是所谓的位置分析。
在全球范围内经营的企业在整个价值链中使用位置分析,例如,用于定位用户、提供服务和运行目标广告。随着社交媒体和移动设备的兴起,位置分析的使用在全球范围内有所增加。
在本教程中,我们将学习如何在node.js中构建一个轻量级的位置分析报告服务api。在本教程结束时,你将能够为自己的项目构建这种类型的api。你也会对node.js中的错误处理和良好的文件结构有更好的理解
让我们开始吧!
前提条件要继续学习本教程,你需要具备以下条件。
熟悉node.js、express和gitvisual studio代码编辑器heroku账户postman账户设置文件结构首先,我们需要设置我们的文件结构。打开你的终端,创建一个新的目录,你将在其中存储项目的所有文件。在你的终端,键入以下命令,后面跟着文件夹的名称,lars 。
mkdir lars
在vs代码编辑器中打开lars 工作目录。
code .
你会看到你的vs code窗口打开。
visual studio code 窗口
通过在visual studio中打开你的终端并运行npm init -y ,初始化工作目录。
如果你想在vs code之外的操作系统的终端中运行这个命令,请导航到lars 目录并运行下面的命令。
npm init -y
上面的代码自动生成了package.json 文件。
vs code显示创建的package.json文件
在本教程中,我们将使用express作为一个依赖项。通过运行下面的命令来安装express。
npm install express --save
将express作为依赖关系安装的命令
安装完express后,你会注意到一个node_modules 文件夹被创建了。为了确认你已经安装了express,请检查你的package.json 文件,你会看到express作为一个依赖项被安装。
node_modules文件夹被创建,express被添加到package.json中。
我们需要将express导入我们的应用程序,因为它是一个npm模块。在与你的package.json 文件相同的目录下创建一个名为app.js 的新文件。
vs代码窗口的截图显示app.js已经创建。
在你的app.js 文件中,通过运行下面的代码requireexpress。
const express = require('express');
导入express
现在,调用express来创建你的应用、路由和你的应用要运行的端口。
const app = express();
node.js实现了模块化,这意味着它将你的应用分成模块,或各种文件,并导出每个文件。我们将使用export 关键字导出app 。
module.exports = app;
app.js文件
接下来,在与app.js 文件相同的目录下创建另一个名为server.js 的文件。require 将app.js 文件导入server.js 文件。
const app = require('./app');
在与server.js 相同的目录中创建一个名为config.env 的文件。config.env 文件将包含我们的应用程序需要的所有 [process.env](https://nodejs.org/dist/latest-v8.x/docs/api/process.html)我们的应用程序需要的所有密钥。在config.env 文件中,创建一个port 变量,并将port 设置为监听端口8000 。
port=8000
导入应用程序后,在server.js 文件中创建一个名为port 的常量。将其设置为你刚刚创建的port 变量和一个默认的端口3000 。
const port = process.env.port || 3000;
最后,我们将用.listen() 方法设置应用程序在该端口上监听。
app.listen(port, () => { console.log(`app listening on ${port}`)});
构建路由每当你访问一个网页或一个在网络上运行的应用程序时,你都在发出一个http请求。服务器用来自后台或数据库的数据进行响应,这就是所谓的http响应。
当你在一个网络应用程序上创建一个资源时,你正在调用post 请求。同样地,如果你试图删除或更新一个web应用上的资源,你正在调用delete 、patch 、或update 请求。让我们建立路由来处理这些请求。
在你的工作目录中创建一个名为routes 的文件夹,并在其中创建一个名为analyticsroute.js 的文件。require 在analyticsroute.js 文件中表达,以设置api的路由。
const express = require('express');
我们还需要从app.js 文件中require 我们的应用程序模块。
const app = require('../app');
然后,我们创建我们的路由。
const router = express.router();
最后,我们要导出路由器。
module.exports = router;
建立控制器我们需要为控制器创建文件,将其导入我们的analyticsroutes 文件。首先,在你的工作目录中创建一个名为controllers 的文件夹。
我们的api将使用用户提供的ip地址和坐标来计算距离和位置。我们的请求需要接受这些信息和来自用户的请求。
我们将使用一个post 请求,因为用户在req.body 。为了保存这些信息,我们需要在控制器中require 一个fs 模块(文件系统)。
处理post 的请求在controllers 文件夹中创建一个名为storecontroller.js 的文件。在storecontroller.js 文件中,我们需要导入fs 模块和fspromises.readfile() 方法来处理返回的promise ,也就是用户的ip地址和坐标。
要安装fs 模块,在你的工作目录中打开你的终端,运行以下命令。
npm i fs --save
在你的文件顶部输入以下代码。
const fsp = require('fs').promises;const fs = require('fs');
接下来,我们将创建一个控制器,处理我们的post 请求的路由。我们将使用exports 关键字并创建一个接受三个参数的异步中间件函数。
req: 代表请求对象res: 代表响应对象next: 函数在中间件输出后立即被调用。postanalytics = async(req, res, next) => {}
现在,我们将把req.body 中的数据对象的属性保存到reportanalytics 数组中。我们将设置一个date() 对象,将任何数据的创建日期保存在一个createdat 关键中。
reportanalytics.push({...req.body, createdat: new date()});
我们将创建一个名为storeanalytics.json 的文件,使用json.stringify() ,将我们的reportanalytics 数组的内容保存为一个字符串。
await fsp.writefile(`${__dirname}/storeanalytics.json`, json.stringify(reportanalytics));
当用户提出post 要求时,我们需要检查storeanalytics.json 文件是否存在。如果该文件存在,我们需要读取该文件并保存输出。
输出包含一个名为reportfile 的常量,它存储了被读取的文件内容。在reportfile ,使用json.parse ,将文件的内容转换为一个javascript对象。
// checks if file existsif (fs.existssync(`${__dirname}/storeanalytics.json`)) {// if the file exists, reads the file const reportfile = await fsp.readfile(`${__dirname}/storeanalytics.json`, 'utf-8')// converts the file to javascript objectreportanalytics = json.parse(reportfile)} else { // if file does not exist return ('file does not exist');}
该 [fs.existssync()](https://www.geeksforgeeks.org/node-js-fs-existssync-method/)方法同步地检查文件是否存在。它接受${__dirname}/storeanalytics.json 路径作为其单一参数,并指向我们要检查的文件的位置。
我们将await 关键字与reportfile ,以等待用fsp.readfile() 方法读取文件的结果。接下来,我们用(${__dirname}/storeanalytics.json 来指定我们要读取的文件的路径。我们将编码格式设置为utf-8 ,这将把从文件中读取的内容转换为一个字符串。
json.parse() 将reportfile 转换为javascript对象,并将其存储在reportanalytics 数组中。else 语句块中的代码只有在文件不存在时才会运行。最后,我们使用了return 语句,因为我们想在代码运行后停止函数的执行。
如果文件被成功读取、创建并保存在storeanalytics.json ,我们需要发送一个响应。我们将使用响应对象(res) ,它是我们的异步postanalytics 函数的第二个参数。
res.status(201).json({ status: 'success', data: { message: 'ip and coordinates successfully taken' } })
我们将用一个状态success 和数据信息ip and coordinates successfully taken 来响应。
你的storecontroller.js 文件应该看起来像下面的屏幕截图。
处理get 的请求我们需要创建另一个控制器文件来处理我们的get 请求。当用户向api发出get 请求时,我们将根据他们的ip地址和坐标来计算他们的位置。
在controllers 文件夹中创建一个名为fetchcontroller.js 的文件。fs 在storecontroller.js 文件中,我们需要require 模块和fspromises.readfile() 方法来处理返回的promise 。
const fsp = require('fs').promises;const fs = require('fs');
让我们创建控制器来处理我们对get 请求的路由。我们将使用类似的中间件函数和参数来处理上面的post 请求。
exports.getanalytics = async(req, res, next) => {}
在getanalytics 中间件中,输入以下代码,从请求的查询中获得ip地址。
const { ip } = req.query;
现在,创建一个空数组,用来存储req.body 的内容。
let reportanalytics = [];
正如我们之前所做的,我们需要检查storeanalytics.json 文件是否存在。如果文件存在,我们将在reportfile 上使用json.parse ,将文件内容转换为一个javascript对象。
if (fs.existssync(`${__dirname}/storeanalytics.json`)) { const reportfile = await fsp.readfile(`${__dirname}/storeanalytics.json`, 'utf-8') reportanalytics = json.parse(reportfile) } else { return ('file does not exist'); }
现在,我们可以在storeanalytics.json 文件中保存用户的ip地址和坐标。任何时候用户请求根据提供的坐标计算地理位置,ip地址将以查询的形式包含在请求中。
现在我们已经从req.query 对象中得到了ip地址,我们可以编写代码来检查req.query 对象中提供的ip地址是否与存储在storeanalytics.json 文件中的ip地址相同。
for (let i=0; i<reportanalytics.length; i++) { if (reportanalytics[i].ip !== ip) { return ('no coordinates found with that ip'); }; }
在上面的代码中,我们使用forloop 来循环浏览reportanalytics 数组。我们将变量i ,代表当前元素在reportanalytics 数组中的索引,初始化为0 。如果i小于reportanalytics 数组的长度,我们将其递增。
接下来,我们检查reportanalytics 数组的ip地址属性是否等于req.query 中提供的ip地址。
让我们计算一下只在最后一小时内存储的ip地址的位置。
const hourago = new date(); hourago.sethours(hourago.gethours()-1); const getreport = reportanalytics.filter(el => el.ip === ip && new date(el.createdat) > hourago )
在上面的代码块中,我们创建了一个名为hourago 的常量,并将其设置为一个date 对象。我们使用sethours() 方法将hourago 设置为最后一个小时的gethours()-1 。
当reportanalytics 文件中的当前ip地址等同于或等于req.query 中传递的ip地址时,意味着数据是在最后一小时内创建的,getreport 创建一个常量,设置为一个新的数组。
创建一个名为coordinatesarray 的常量,它将只存储已经保存在getreport 数组中的坐标。
const coordinatesarray = getreport.map(element => element.coordinates)
接下来,我们需要用坐标计算出位置。我们需要遍历coordinatesarray ,通过传入保存为坐标的两个值来计算位置。
let totallength = 0; for (let i=0; i<coordinatesarray.length; i++) { if (i == coordinatesarray.length - 1) { break; } let distance = calculatedistance(coordinatesarray[i], coordina tesarray[i+1]); totallength += distance; }
在上面的代码中,totallength 代表从两个坐标计算出来的总距离。为了遍历coordinatesarray ,我们需要初始化我们的计算结果。将totallength 设置为零,初始化总距离。
第二行包含我们使用的迭代代码forloop 。我们用let i=0 来初始化i 变量。i 变量代表当前元素在coordinatesarray 的索引。
i<coordinatesarray.length 设置迭代的条件,只有当当前元素的索引小于coordinatesarray 的长度时才运行。接下来,我们在迭代中增加当前元素的索引,以移动到下一个元素,i++ 。
接下来,我们将检查当前元素的索引是否等于数组中最后一个元素的编号。然后,我们暂停迭代代码的执行,用break 关键字移动到下一个。
最后,我们创建一个名为calculatedistance 的函数,接受两个参数,即第一和第二坐标值(经度和纬度)。我们将在另一个模块中创建calculatedistance ,并将其导出到fetchcontroller.js 文件中,然后我们将最终结果保存在我们初始化的totallength 变量中。
注意,每个请求都需要一个响应。我们将用一个200 的statuscode 和一个包含我们将计算的距离值的json来响应。只有在代码成功的情况下才会显示响应。
res.status(200).json({distance: totallength})
你的fetchcontroller.js 文件应该看起来像下面两个代码块。
fetchcontroller.js文件
fetchcontroller.js文件的续篇
建立calculatedistance 函数在你的工作目录中,创建一个名为utilities 的新文件夹,在里面创建一个名为calculatedistance.js 的文件。打开calculatedistance.js 文件,添加以下函数。
const calculatedistance = (coordinate1, coordinate2) => { const distance = math.sqrt(math.pow(number(coordinate1.x) - number(coordinate2.x), 2) + math.pow(number(coordinate1.y) - number(coordinate2.y), 2)); return distance;} module.exports = calculatedistance;
在第一行,我们创建一个名为calculatedistance 的函数,它接受两个参数:coordinate1 和coordinate2 。它使用下面的方程式。
math.sqrt: 数学中的平方根math.pow :将一个数字提高到一个幂值number(): 将一个值转换为一个数字coordinate1.x :第一个坐标(经度)的第二个值coordinate2.x :第一个坐标的第一个值(经度)。coordinate1.y :第二个坐标的第二个值(纬度)。coordinate2.y :第二个坐标的第一个值(纬度)。现在我们已经创建了calculatedistance 函数,我们需要将该函数require 到我们fetchcontroller.js 文件的代码中。在fs 模块之后添加下面的代码。
const calculatedistance = require('../utilities/calculatedistance');
实现错误处理实现错误处理是很重要的,以防止我们的代码失败或某个特定的实现没有按照设计的方式工作。我们将在开发和生产中添加错误处理。
打开你的config.env 文件,运行node_env=development ,将环境设置为开发。
在你的controllers 文件夹中,创建一个名为errorcontroller.js 的新文件。下面的代码片断创建了一个名为senderrordev 的函数,以处理在开发环境中遇到的错误。
const senderrordev = (err, res) => { res.status(err.statuscode).json({ status: err.status, error: err, message: err.message, stack: err.stack, });}
我们将创建一个名为senderrordev 的函数,它接受两个参数,err 表示错误,res 表示响应。response.status 接收错误的statuscode ,并以json数据进行响应。
此外,我们将创建一个名为senderrorprod 的函数,它将处理api在生产环境中遇到的错误。
const senderrorprod = (err, res) => { if(err.isoperational) { res.status(err.statuscode).json({ status: err.status, message: err.message }); } else { console.error('error', err); res.status(500).json({ status: 'error', message: 'something went wrong' }) }}
在你的utilities 文件夹中,创建一个名为apperror.js 的文件,并输入以下代码。
class apperror extends error { constructor(message, statuscode) { super(message); this.statuscode = statuscode; this.status = `${statuscode}`.startswith('4') ? 'fail' : 'error'; this.isoperational = true; error.capturestacktrace(this, this.constructor); }}module.exports = apperror;
我们将创建一个名为apperror 的类,它扩展了error 对象。
然后,我们将创建一个构造函数,它将初始化该类的对象。它接受两个参数,叫做message 和statuscode 。super 方法用一个参数调用构造函数,将其传入message ,并获得对构造函数的属性和方法的访问。
接下来,我们将构造函数的statuscode 属性设置为statuscode 。我们将构造函数的status 属性设置为任何以4 开始的statuscode ,例如,将404 statuscode 设置为fail 或error 。
创建另一个名为catchasync.js 的文件,并在其中添加以下代码。
module.exports = fn => { return (req, res, next) => { fn(req, res, next).catch(next); }}
在控制器文件中添加错误处理require apperror.js 文件和catchasync.js 文件在你的storecontroller.js 和fetchcontroller.js 文件中。将这两条导入语句放在两个文件中的代码顶部。
const catchasync = require('../utilities/catchasync');const apperror = require('../utilities/apperror');
在storecontroller.js 和fetchcontroller.js 文件中,用catchasync() 方法包装你的函数,如下所示。
// for storecontroller.js fileexports.postanalytics = catchasync(async(req, res, next) => {...} // for fetchcontroller.js fileexports.getanalytics = catchasync(async(req, res, next) => {...}
接下来,在你的fetchcontroller.js 文件中,运行apperror 类。
for (let i=0; i<reportanalytics.length; i++) { if (reportanalytics[i].ip !== ip) { return next(new apperror('no coordinates found with that ip', 404)); }; }
接下来,在你的storecontroller.js 文件中运行apperror 类。
if (fs.existssync(`${__dirname}/storeanalytics.json`)) { const reportfile = await fsp.readfile(`${__dirname}/storeanalytics.json`, 'utf-8') reportanalytics = json.parse(reportfile) } else { return next(new apperror('file does not exist', 404)); }
你的storecontroller.js 和fetchcontroller.js 文件中的代码应该看起来像下面的截图。
storecontroller.js文件的屏幕截图
fetchcontroller.js文件第1-32行
fetchcontroller.js文件第33-37行
设置验证我们需要验证在req.body ,其中包括ip地址和坐标的数据,是正确的,而且格式正确。坐标应该至少有两个值,代表经度和纬度。
在utilities 文件夹中,创建一个名为validation 的新文件夹。在validation 文件夹中,创建一个名为schema.js 的文件。schema.js 文件将包含req.body 中提供的任何数据的所需格式。我们将使用 [joi](https://www.npmjs.com/package/joi)验证器。
npm install joi
在schema.js 文件中输入以下代码。
const joi = require('joi');const schema = joi.object().keys({ ip: joi.string().ip().required(), coordinates: joi.object({ x: joi.number().required(), y: joi.number().required() }).required()})module.exports = schema;
joi 在上面的代码块中,我们require 验证器,用它来创建我们的模式。然后,我们将ip地址设置为总是一个字符串,并通过在请求体中要求它来验证ip地址。
我们将坐标设置为object 。我们将代表经度和纬度值的x 和y 值都设置为数字,并将其require ,以便我们的代码运行。最后,我们导出了模式。
在验证器文件夹中,创建另一个名为validateip.js 的文件。在里面,我们将编写代码来验证ip地址,使用 [is-ip](https://www.npmjs.com/package/is-ip)npm包。让我们把这个包导出到我们的代码中。
在validateip.js 文件中,添加以下代码。
const isip = require('is-ip');const fsp = require('fs').promises;const fs = require('fs');exports.validateip = (req, res, next) => { if(isip(req.query.ip) !== true) { return res.status(404).json({ status: 'fail', data: { message: 'invalid ip, not found.' } }) } next();}
运行以下命令,为我们的api安装必要的依赖项。
npm install body-parser cors dotenv express fs is-ip joi morgan ndb nodemon
你的app.js 文件应该看起来像下面的屏幕截图。
app.js文件
在你的package.json 文件中的scripts 部分下,添加以下代码片段。
"start:dev": "node server.js", "debug": "ndb server.js"
你的package.json 文件应该看起来像下面的截图。
package.json文件
用以下代码更新你的analyticsroute.js 文件。
const express = require('express');const app = require('../app');const router = express.router();const validateip = require('../utilities/validation/validateip');const storecontroller = require('../controllers/storecontroller');const fetchcontroller = require('../controllers/fetchcontroller');router.route('/analytics').post(storecontroller.postanalytics).get(validateip.validateip, fetchcontroller.getanalytics);module.exports = router;
现在,我们已经完成了我们的位置分析api的构建!现在,让我们测试一下我们的代码,以确保它的工作。
测试api我们将使用postman来测试我们的api。让我们启动我们的api以确保它在我们的终端中运行。
node server.js
你会在你的终端看到以下输出。
终端
我们的api托管在heroku上,它的最终输出应该看起来像下面的输出。
你可以自己在托管的文档中测试这个api。
https://documenter.getpostman.com/view/13856921/tzxumexs
结论位置分析是企业的一个伟大工具。位置信息可以让公司更好地服务于潜在客户和现有客户。
在本教程中,我们学会了建立一个工具,以ip地址和坐标的形式获取位置信息并计算出距离。我们在node.js中设置了我们的文件结构,建立了处理get 和post 请求的路由,添加了错误处理,最后测试了我们的应用程序。
你可以使用本教程中所学到的信息来建立你自己的位置报告api,你可以根据自己的业务需求进行定制。
the postbuild a location analytics reporting api in node.jsappeared first onlogrocket blog.
更多node相关知识,请访问:nodejs 教程!
以上就是一文详解如何在node中构建一个轻量级的位置分析报告服务api的详细内容。