美文网首页Nodejs
nodeJS开发一套完整的项目(4、编写底层功能模块)

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

作者: 陈楠酒肆 | 来源:发表于2017-09-28 13:29 被阅读186次

    本章节我们讨论一下如何编写底层公共模块,首先我们来看看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),同时您也可以扫描下方的二维码关注我们的公众号,每天我们都会分享经验,谢谢大家。

    相关文章

      网友评论

      • 静皂蓝本:请问还有本专题还有后续吗 可否分享一个github项目地址

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

      本文链接:https://www.haomeiwen.com/subject/uwkpextx.html