nodeJS開發(fā)一套完整的項目(4新翎、編寫底層功能模塊)

本章節(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ù),這個文件我們會在公共文件中使用塔橡。

數(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();
statis對應(yīng)的表數(shù)據(jù)

以上就是項目的核心文件稳捆,后面涉及到的接口都會用到它們赠法。前面幾個章節(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)驗,謝謝大家剃幌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市晾浴,隨后出現(xiàn)的幾起案子负乡,更是在濱河造成了極大的恐慌,老刑警劉巖脊凰,帶你破解...
    沈念sama閱讀 212,686評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抖棘,死亡現(xiàn)場離奇詭異,居然都是意外死亡狸涌,警方通過查閱死者的電腦和手機(jī)切省,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來帕胆,“玉大人朝捆,你說我怎么就攤上這事±帘” “怎么了芙盘?”我有些...
    開封第一講書人閱讀 158,160評論 0 348
  • 文/不壞的土叔 我叫張陵驯用,是天一觀的道長。 經(jīng)常有香客問我儒老,道長蝴乔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,736評論 1 284
  • 正文 為了忘掉前任驮樊,我火速辦了婚禮薇正,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘囚衔。我一直安慰自己挖腰,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,847評論 6 386
  • 文/花漫 我一把揭開白布佳魔。 她就那樣靜靜地躺著曙聂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鞠鲜。 梳的紋絲不亂的頭發(fā)上宁脊,一...
    開封第一講書人閱讀 50,043評論 1 291
  • 那天,我揣著相機(jī)與錄音贤姆,去河邊找鬼榆苞。 笑死,一個胖子當(dāng)著我的面吹牛霞捡,可吹牛的內(nèi)容都是我干的坐漏。 我是一名探鬼主播,決...
    沈念sama閱讀 39,129評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼碧信,長吁一口氣:“原來是場噩夢啊……” “哼赊琳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起砰碴,我...
    開封第一講書人閱讀 37,872評論 0 268
  • 序言:老撾萬榮一對情侶失蹤躏筏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后呈枉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趁尼,經(jīng)...
    沈念sama閱讀 44,318評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,645評論 2 327
  • 正文 我和宋清朗相戀三年猖辫,在試婚紗的時候發(fā)現(xiàn)自己被綠了酥泞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,777評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡啃憎,死狀恐怖芝囤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤凡人,帶...
    沈念sama閱讀 34,470評論 4 333
  • 正文 年R本政府宣布名党,位于F島的核電站馏段,受9級特大地震影響卦停,放射性物質(zhì)發(fā)生泄漏铛纬。R本人自食惡果不足惜澈驼,卻給世界環(huán)境...
    茶點故事閱讀 40,126評論 3 317
  • 文/蒙蒙 一袄友、第九天 我趴在偏房一處隱蔽的房頂上張望怔檩。 院中可真熱鬧鼓择,春花似錦兑宇、人聲如沸启上。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冈在。三九已至倒慧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間包券,已是汗流浹背纫谅。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留溅固,地道東北人付秕。 一個月前我還...
    沈念sama閱讀 46,589評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像侍郭,于是被迫代替她去往敵國和親询吴。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,687評論 2 351

推薦閱讀更多精彩內(nèi)容