本章節(jié)我們討論一下如何編寫底層公共模塊疗疟,首先我們來看看WEB文件夾下的目錄結(jié)構(gòu)该默,談?wù)勊麄兙唧w的作用。
controller(C)
作用:控制器 主要連接模型和視圖
middlewares
作用:項目中間件
models(M)
作用:系統(tǒng)模型策彤,主要編寫數(shù)據(jù)庫表結(jié)構(gòu)栓袖、部分邏輯處理
mongodb
作用:數(shù)據(jù)庫信息
prototype
作用:編寫公共組件匣摘。比如百度地圖的API、表單處理裹刮、圖片上傳等等
util
自定義目錄音榜,目前只存放了日志文件
下面我們正式進(jìn)入開發(fā)狀態(tài),各位都坐穩(wěn)啦必指,我們要起飛啦O(∩_∩)O
創(chuàng)建Ids.js文件
這個文件的作用是當(dāng)用戶調(diào)用接口的時候囊咏,會向該表里保存數(shù)據(jù),這個文件我們會在公共文件中使用塔橡。
具體代碼如下:
'use strict';
import mongoose from 'mongoose';
在創(chuàng)建表之前我們需要跟大家說一下mongoDB的數(shù)據(jù)類型梅割,具體數(shù)據(jù)類型如下:
字符串 - 這是用于存儲數(shù)據(jù)的最常用的數(shù)據(jù)類型。MongoDB中的字符串必須為UTF-8葛家。
整型 - 此類型用于存儲數(shù)值户辞。 整數(shù)可以是32位或64位,具體取決于服務(wù)器癞谒。
布爾類型 - 此類型用于存儲布爾值(true / false)值底燎。
雙精度浮點數(shù) - 此類型用于存儲浮點值。
最小/最大鍵 - 此類型用于將值與最小和最大BSON元素進(jìn)行比較弹砚。
數(shù)組 - 此類型用于將數(shù)組或列表或多個值存儲到一個鍵中双仍。
時間戳 - ctimestamp,當(dāng)文檔被修改或添加時桌吃,可以方便地進(jìn)行錄制朱沃。
對象 - 此數(shù)據(jù)類型用于嵌入式文檔。
對象 - 此數(shù)據(jù)類型用于嵌入式文檔茅诱。
Null - 此類型用于存儲Null值逗物。
符號 - 該數(shù)據(jù)類型與字符串相同; 但是,通常保留用于使用特定符號類型的語言瑟俭。
日期 - 此數(shù)據(jù)類型用于以UNIX時間格式存儲當(dāng)前日期或時間翎卓。您可以通過創(chuàng)建日期對象并將日,月摆寄,年的日期進(jìn)行指定自己需要的日期時間失暴。
對象ID - 此數(shù)據(jù)類型用于存儲文檔的ID。
二進(jìn)制數(shù)據(jù) - 此數(shù)據(jù)類型用于存儲二進(jìn)制數(shù)據(jù)微饥。
代碼 - 此數(shù)據(jù)類型用于將JavaScript代碼存儲到文檔中锐帜。
正則表達(dá)式 - 此數(shù)據(jù)類型用于存儲正則表達(dá)式。
//創(chuàng)建表(Ids)
const idsSchema = new mongoose.Schema({
restaurant_id: Number,
food_id: Number,
order_id: Number,
user_id: Number,
address_id: Number,
cart_id: Number,
img_id: Number,
category_id: Number,
item_id: Number,
sku_id: Number,
admin_id: Number,
statis_id: Number
});
/**
* 下一步在代碼中使用Schema所定義的數(shù)據(jù)模型畜号,需要將定義好的phoneSchema轉(zhuǎn)換為Model缴阎。
可以使用mongoose.model(modelName, schema)進(jìn)行轉(zhuǎn)換。
在Mongoose的設(shè)計理念中简软,Schema用來也只用來定義數(shù)據(jù)結(jié)構(gòu)蛮拔,具體對數(shù)據(jù)的增刪改查操作都由Model來執(zhí)行
*/
const Ids = mongoose.model('Ids',idsSchema);
Ids.findOne((err,data) => {
if(!data) {
const newIds = new Ids({
restaurant_id: 0,
food_id: 0,
order_id: 0,
user_id: 0,
address_id: 0,
cart_id: 0,
img_id: 0,
category_id: 0,
item_id: 0,
sku_id: 0,
admin_id: 0,
statis_id: 0
});
newIds.save(); //保存數(shù)據(jù)
}
});
export default Ids;
創(chuàng)建公共文件
在web->prototype中創(chuàng)建baseComponent.js文件述暂,具體如下:
/**
* Created by admin on 2017/9/28 0001.
*/
import fetch from 'node-fetch';
import formidable from 'formidable'; //表單處理
import path from 'path';
import fs from 'fs';
import Ids from '../models/ids';
import qiniu from 'qiniu'; //七牛云
qiniu.conf.ACCESS_KEY = 'Ep714TDrVhrhZzV2VJJxDYgGHBAX-KmU1xV1SQdS';
qiniu.conf.SECRET_KEY = 'XNIW2dNffPBdaAhvm9dadBlJ-H6yyCTIJLxNM_N6';
export default class BaseComponent {
//構(gòu)造函數(shù)是一種特殊的方法。主要用來在創(chuàng)建對象時初始化對象建炫, 即為對象成員變量賦初始值
constructor() {
this.idList = ['restaurant_id', 'food_id', 'order_id', 'user_id', 'address_id', 'cart_id', 'img_id', 'category_id', 'item_id', 'sku_id', 'admin_id', 'statis_id'];
this.imgTypeList = ['shop','food','avatar','default'];
this.uploadImg = this.uploadImg.bind(this);
this.qiniu = this.qiniu.bind(this);
}
//async是異步處理
async fetch(url = '',data = {}, type = 'GET', resType = 'JSON') {
type = type.toUpperCase(); //所有小寫字符全部被轉(zhuǎn)換為了大寫字符
resType = resType.toUpperCase();
if(type == 'GET') {
let dataStr = ''; //數(shù)據(jù)拼接字符串
Object.keys(data).forEach(key => {
dataStr += key + '=' + data[key] + '&';
});
if(dataStr !== '') {
dataStr = dataStr.substr(0, dataStr.lastIndexOf('&'));
url = url + '?' + dataStr; //拼接URL地址
}
}
let requestConfig = {
method: type,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
};
if(type =='POST') {
Object.defineProperty(requestConfig,'body', {value: JSON.stringify(data)})
}
let responseJson;
try{
const response = await fetch(url, requestConfig);
if(resType === 'TEXT') {
responseJson = await response.text();
} else {
responseJson = await response.json();
}
} catch(err) {
console.log('獲取http數(shù)據(jù)失敗:' + err);
throw new Error(err);
}
return responseJson;
}
//獲取ID列表
async getId(type) {
if(!this.idList.includes(type)) {
console.log('id類型錯誤');
throw new Error('id類型錯誤');
return ;
}
try {
const idData = await Ids.findOne();
idData[type] ++;
await idData.save();
return idData[type];
} catch(err) {
console.log('獲取ID數(shù)據(jù)失敗:' + err);
}
}
//圖片上傳
async uploadImg(req,res,next) {
const type = req.params.type;
try{
const image_path = await this.qiniu(req,type);
res.send({
status: 1,
image_path,
});
} catch(err) {
console.log('圖片上傳失敗:' + err);
res.send({
status: 0,
type:'ERROR_UPLOAD_IMG',
message: '圖片上傳失敗'
});
}
}
async qiniu(req,type = 'default') {
return new Promise((resolve, reject) => {
const form = formidable.IncomingForm();
form.uploadDir = './public/img/' + type;
form.parse(req, async(err, fields, files) =>{
let img_id;
try{
img_id = await this.getId('img_id');
} catch(err) {
console.log('獲取圖片ID失敗');
fs.unlink(files.file.path);
reject('獲取圖片ID失敗');
}
const imgName = (new Date().getTime() + Math.ceil(Math.random()*10000)).toString(16); + img_id;
const extname = path.extname(files.file.name);
const repath = './public/img/' + type + '/' + imgName + extname;
try {
const key = imgName + extname;
await fs.rename(files.file.path,repath);
const token = this.uptoken('node-element', key);
const qiniuImg = await this.uploadFile(token.toString(), key,repath);
fs.unlink(repath);
resolve(qiniuImg);
} catch(err) {
console.log('保存至七牛失敗' + err);
fs.unlink(files.file.path);
reject('保存至七牛失敗');
}
})
})
}
//獲取TOKEN
uptoken(bucket, key) {
var putPolicy = new qiniu.rs.PutPolicy(bucket + ":" + key);
return putPolicy.token();
}
//上傳圖片
uploadFile(uptoken, key, localFile) {
return new Promise((resolve,reject) => {
var extra = new qiniu.io.PutExtra();
qiniu.io.putFile(uptoken, key, localFile, extra, function(err, ret) {
if(!err) {
resolve(ret.key);
} else {
console.log('圖片上傳至七牛失敗' + err);
reject(err);
}
})
});
}
}
創(chuàng)建統(tǒng)一調(diào)配組件
在web->prototype中創(chuàng)建addressComponent.js文件畦韭,這個文件是騰訊地圖和百度地圖API,也就是我們在項目里要用到地圖信息肛跌。具體如下:
/**
* Created by admin on 2017/9/28 0014.
*/
'use strict';
import BaseComponent from './baseComponent';
/*
騰訊地圖和百度地圖API統(tǒng)一調(diào)配組件
*/
class AddressComponent extends BaseComponent {
//擴(kuò)展函數(shù)
constructor() {
super();
this.tencentkey = 'RLHBZ-WMPRP-Q3JDS-V2IQA-JNRFH-EJBHL';
this.tencentkey2 = 'RRXBZ-WC6KF-ZQSJT-N2QU7-T5QIT-6KF5X';
this.baidukey = 'fjke3YUipM9N64GdOIh1DNeK2APO2WcT';
this.baidukey2 = 'fjke3YUipM9N64GdOIh1DNeK2APO2WcT';
}
//獲取定位地址
async guessPosition(req) {
return new Promise(async (resolve, reject) => {
let ip = req.headers['x-forwarded-for'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress;
const ipArr = ip.split(':'); //分割
ip = ipArr[ipArr.length - 1];
if(process.env.NODE_ENV == 'development') {
ip = '116.226.184.83';
}
try {
let result;
result = await this.fetch('http://apis.map.qq.com/ws/location/v1/ip',{
ip,
key: this.tencentkey
});
if(result.status !== 0) {
result = await this.fetch('http://apis.map.qq.com/ws/location/v1/ip',{
ip,
key: this.tencentkey2
});
}
if(result.status == 0) {
const cityInfo = {
lat: result.result.location.lat,
lng: result.result.location.lng,
city: result.result.ad_info.city,
};
cityInfo.city = cityInfo.city.replace(/市$/, '');
resolve(cityInfo);
} else {
console.log('定位失敗',result);
reject('定位失敗');
}
} catch(err) {
reject(err);
}
});
}
//搜索地址
async searchPlace(keyword, cityName, type ='search') {
try{
const resObj = await this.fetch('http://apis.map.qq.com/ws/place/v1/search', {
key: this.tencentkey,
keyword: encodeURIComponent(keyword),
boundary:'region(' + encodeURIComponent(cityName) + ',0)',
page_size:10
});
if(resObj.status == 0) {
return resObj;
} else {
console.log('搜索位置信息失敗');
}
} catch(err) {
throw new Error(err);
}
};
//測量距離
async getDistance(from, to ,type) {
try {
let res;
res = await this.fetch('http://api.map.baidu.com/routematrix/v2/driving',{
ak: this.baidukey,
output: 'json',
origins: from,
destinations: to
});
if(res.status != 0) {
res = await this.fetch('http://api.map.baidu.com/routematrix/v2/driving', {
ak: this.baidukey2,
output: 'json',
origins: from,
destinations: to
});
}
if(res.status == 0) {
const positionArr = [];
let timevalue;
res.result.forEach(item => {
timevalue = parseInt(item.duration.value) + 1200;
let durationtime = Math.ceil(timevalue%3600/60) + '分鐘';
if(Math.floor(timevalue/3600)) {
durationtime = Math.floor(timevalue/3600) + '小時' + durationtime;
}
positionArr.push({
distance: item.distance.text,
order_lead_time: durationtime
})
});
if(type == 'timevalue') {
return timevalue;
} else {
return positionArr;
}
} else {
console.log('調(diào)用百度地圖測距失敗');
}
} catch(err) {
console.log('獲取位置距離失敗');
throw new Error(err);
}
};
//通過ip地址獲取精確位置
async geocoder(req) {
try{
const address = await this.guessPosition(req);
const res = await this.fetch('http://apis.map.qq.com/ws/geocoder/v1/',{
key: this.tencentkey,
location: address.lat + ',' + address.lng
});
if(res.status == 0) {
return res;
} else {
console.log('獲取具體位置信息失敗');
}
} catch(err) {
console.log('geocoder獲取定位失敗');
throw new Error(err);
}
};
//通過geohash獲取精確位置
async getpois(lat,lng) {
try{
const res = await this.fetch('http://apis.map.qq.com/ws/geocoder/v1/',{
key: this.tencentkey,
location: lat + ',' + lng,
});
if(res.status == 0) {
return res;
} else {
console.log('通過獲geohash取具體位置失敗');
}
} catch(err) {
console.log('getpois獲取定位失敗');
throw new Error(err);
}
};
}
export default AddressComponent;
創(chuàng)建statis文件
該文件的作用是記錄訪問接口的日志記錄艺配。我們在web文件夾下新建statis文件夾,然后在其下創(chuàng)建statis.js文件衍慎。代碼如下:
/**
* Created by admin on 2017/9/28 0014.
*/
'use strict';
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
const statisSchema = new Schema({
date: String,
origin: String,
id: Number
});
statisSchema.index({id: 1});
const Statis = mongoose.model('Statis',statisSchema);
export default Statis;
創(chuàng)建初始中間件文件
我們在middlewares文件夾下創(chuàng)建statistic.js转唉。代碼如下:
/**
* Created by admin on 2017/9/28 0001.
*/
'use strict';
import dtime from 'time-formater'; //日期格式化
import BaseComponent from '../prototype/baseComponent';
import StatisModel from '../models/statis/statis';
class Statistic extends BaseComponent {
//構(gòu)造函數(shù)
constructor() {
super(); //可以表示構(gòu)造函數(shù)傳遞。this(a,b)表示調(diào)用另外一個構(gòu)造函數(shù)
this.apiRecord = this.apiRecord.bind(this);
}
async apiRecord(req, res, next) {
try{
const statis_id = await this.getId('statis_id');
const apiInfo = {
date: dtime().format('YYYY-MM-DD'), //日期格式化
origin: req.headers.origin,
id: statis_id
};
StatisModel.create(apiInfo);
} catch(err) {
console.log('API數(shù)據(jù)出錯',err);
}
next()
}
}
export default new Statistic();
以上就是項目的核心文件稳捆,后面涉及到的接口都會用到它們赠法。前面幾個章節(jié)就是一個項目搭建的前提,當(dāng)這些都做好以后乔夯,我們下面的接口寫起來就方便多了砖织。
相關(guān)章節(jié)
nodeJS開發(fā)一套完整的項目(1、基礎(chǔ)配置)
nodeJS開發(fā)一套完整的項目(2末荐、相關(guān)模塊介紹)
nodeJS開發(fā)一套完整的項目(3侧纯、數(shù)據(jù)庫鏈接和項目啟動)
為了更好的服務(wù)大家,請加入我們的技術(shù)交流群:(511387930)甲脏,同時您也可以掃描下方的二維碼關(guān)注我們的公眾號眶熬,每天我們都會分享經(jīng)驗,謝謝大家剃幌。