nodeJS开发一套完整的项目(4、编写底层功能模块)

本章节我们讨论一下如何编写底层公共模块,首先我们来看看WEB文件夹下的目录结构,谈谈他们具体的作用。


目录截图
controller(C)

作用:控制器 主要连接模型和视图

middlewares

作用:项目中间件

models(M)

作用:系统模型,主要编写数据库表结构、部分逻辑处理

mongodb

作用:数据库信息

prototype

作用:编写公共组件。比如百度地图的API、表单处理、图片上传等等

util

自定义目录,目前只存放了日志文件

下面我们正式进入开发状态,各位都坐稳啦,我们要起飞啦O(∩_∩)O

创建Ids.js文件

这个文件的作用是当用户调用接口的时候,会向该表里保存数据,这个文件我们会在公共文件中使用。

数据库截图
代码截图

具体代码如下:

'use strict';
import mongoose from 'mongoose';
在创建表之前我们需要跟大家说一下mongoDB的数据类型,具体数据类型如下:

字符串 - 这是用于存储数据的最常用的数据类型。MongoDB中的字符串必须为UTF-8。
整型 - 此类型用于存储数值。 整数可以是32位或64位,具体取决于服务器。
布尔类型 - 此类型用于存储布尔值(true / false)值。
双精度浮点数 - 此类型用于存储浮点值。
最小/最大键 - 此类型用于将值与最小和最大BSON元素进行比较。
数组 - 此类型用于将数组或列表或多个值存储到一个键中。
时间戳 - ctimestamp,当文档被修改或添加时,可以方便地进行录制。
对象 - 此数据类型用于嵌入式文档。
对象 - 此数据类型用于嵌入式文档。
Null - 此类型用于存储Null值。
符号 - 该数据类型与字符串相同; 但是,通常保留用于使用特定符号类型的语言。
日期 - 此数据类型用于以UNIX时间格式存储当前日期或时间。您可以通过创建日期对象并将日,月,年的日期进行指定自己需要的日期时间。
对象ID - 此数据类型用于存储文档的ID。
二进制数据 - 此数据类型用于存储二进制数据。
代码 - 此数据类型用于将JavaScript代码存储到文档中。
正则表达式 - 此数据类型用于存储正则表达式。

//创建表(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所定义的数据模型,需要将定义好的phoneSchema转换为Model。
 可以使用mongoose.model(modelName, schema)进行转换。
 在Mongoose的设计理念中,Schema用来也只用来定义数据结构,具体对数据的增删改查操作都由Model来执行
 */

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(); //保存数据
    }
});

export default Ids;

创建公共文件

在web->prototype中创建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 {
    //构造函数是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值
    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(); //所有小写字符全部被转换为了大写字符
        resType = resType.toUpperCase();
        if(type == 'GET') {
            let dataStr = ''; //数据拼接字符串
            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数据失败:' + 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数据失败:' + 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);
                }
            })
        });
    }
}

创建统一调配组件

在web->prototype中创建addressComponent.js文件,这个文件是腾讯地图和百度地图API,也就是我们在项目里要用到地图信息。具体如下:

/**
 * Created by admin on 2017/9/28 0014.
 */
'use strict';

import BaseComponent from './baseComponent';

/*
 腾讯地图和百度地图API统一调配组件
 */

class AddressComponent extends BaseComponent {
    //扩展函数
    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('调用百度地图测距失败');
            }
        } 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;

创建statis文件

该文件的作用是记录访问接口的日志记录。我们在web文件夹下新建statis文件夹,然后在其下创建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;

创建初始中间件文件

我们在middlewares文件夹下创建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 {
    //构造函数
    constructor() {
        super(); //可以表示构造函数传递。this(a,b)表示调用另外一个构造函数
        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数据出错',err);
        }
        next()
    }
}

export default new Statistic();
statis对应的表数据

以上就是项目的核心文件,后面涉及到的接口都会用到它们。前面几个章节就是一个项目搭建的前提,当这些都做好以后,我们下面的接口写起来就方便多了。

相关章节

nodeJS开发一套完整的项目(1、基础配置)
nodeJS开发一套完整的项目(2、相关模块介绍)
nodeJS开发一套完整的项目(3、数据库链接和项目启动)
为了更好的服务大家,请加入我们的技术交流群:(511387930),同时您也可以扫描下方的二维码关注我们的公众号,每天我们都会分享经验,谢谢大家。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容