NodeJS+Angular+Mongodb Web开发(4)

来源:互联网 发布:服务器带宽测试软件 编辑:程序博客网 时间:2024/05/01 12:23

1. 项目简介

本项目实例完成时限选项卡视图、天气服务视图、可拖动的组件和交互表格数据的过程,这些组件使用不同类型的组件交互。

  • 选项卡视图允许你轻松地集成隐藏多个选项卡视图,但仍然为用户提供了用显而易见的方法来快速访问视图的能力。

  • 天气空间挂接到后端远程天气服务,以获取天气信息,这使你可以看到如何实现支持前端视图的后端服务。

  • 可拖动视图使你可以拖动并重新定位的具有图像和文本元素的一个基本页面,它演示了超越标准Web元素的组件交互的概念。

  • 表格视图存在各种控制,可以对结果进行排序、过滤和分页浏览,而无需重新加载页面,表格视图显示了表格数据可以如何互动。

2. 用到的库

  1. express:作为项目的主web服务器

  2. body-parser:为post请求提供JSON正文支持

  3. ejs:用于呈现HTML模板

  4. mongodb:用于访问MongoDB数据库

  5. mongoose:用于提供结构化的数据模型

  6. AngularJs库


3. 项目的目录结构


4. 定义项目模型、创建应用程序服务器和实现路由

4.1 定义项目模型

代码很简单,如下:

//word_model.jsvar mongoose=require('mongoose');var Schema=mongoose.Schema;var WordSchema=new Schema({    word:{type:String,index:1,required:true,unique:true},    first:{type:String,index:1},    last:String,    size:Number,    letters:[String],    stats:{        vowels:Number,//元音        consonants:Number,//辅音    },    charsets:[{type:String,chars:[String]}]});mongoose.model('Word',WordSchema);

4.2 创建应用程序服务器

//rich_ui_server.jsvar express=require('express');var bodyParser=require('body-parser');var mongoose=require('mongoose');var bluebird = require('bluebird');var db=mongoose.connect('mongodb://localhost:27017/words');// 将mongoose已经不被建议的Promise方法替换为bluebird,//在weather.controller.js中进行查询回调使用mongoose.Promise=bluebird;require('./models/word_model.js');require('./models/weather_cache_model.js');var app=express();app.engine('.html',require('ejs').__express);app.set('views',__dirname+'/views');app.set('view engine','html');app.use(bodyParser.json());app.use(bodyParser.urlencoded({extended:false}));require('./rich_ui_routes')(app);app.listen(3000);console.log('App running...');

4.3 实现路由

./rich_ui_routes.js 文件作为Express服务器的一部分配置加载,提供了加载选项卡视图、访问单词数据库、访问后端天气服务、获得必要的静态文件所需的路由。

/weather路由通过weather_controller.js 中的 getWeather() 处理程序获取数据,/words路由通过word_controller.js 控制器提供与MongoDB数据库的交互。

//rich_ui_routes.jsvar express=require('express');module.exports=function(app){    //加载控制器    var weather=require('./controllers/weather_controller');    var words=require('./controllers/words_controller');    //配置中间件    app.use('/static',express.static('./static'));    app.use('/images',express.static('./images'));    app.use('/lib',express.static('./lib'));    //配置路由    //主页路由    app.get('/',function(req,res){        res.render('rich_ui');    });    //天气服务路由    app.get('/weather',weather.getWeather);    //单词word路由    app.get('/words',words.getWords);}

5. 实现选项卡视图和天气服务视图

5.1 实现选项卡视图

(1)定义卡片模板视图

卡片模板作为窗格中元素的容器,用来支持选项卡视图中的多个窗格,需要支持定义多个窗格中的元素。
注意,ng-transclude 所在的元素将被新元素转置

代码示例如下:

//rich_tabs.html<div class="tabbable">    <!-- 一共存在三个选项卡 -->  <ul class="tabs nav nav-tabs" role='tablist'>    <li class="tab" role="presentation" ng-repeat="pane in panes"         ng-class="{activeTab:pane.selected}"        ng-click="select(pane)">        <a href="#weather-tab" role='tab' data-toggle='tab'>{{pane.title}}</a>    </li>  </ul>  <!-- 选项卡的内容,只有ng-show='selected' 为true才会显示 -->  <div class='tabContentDiv'>    <div class="tabcontent" ng-transclude></div>  </div></div>

(2)实现选项卡视图

注意,body 中的DOM元素,其实是AngularJS自定义指令的实现,使用了驼峰命名法,必须在后台进行相应的Angular自定义指令,才能实现。

//rich_ui.html<!doctype html><html ng-app="richApp"><head>  <title>NMA</title>  <link rel="stylesheet" type="text/css" href="/static/css/bootstrap.min.css">  <link rel="stylesheet" type="text/css"       href="/static/css/draggable_styles.css" />  <link rel="stylesheet" type="text/css"       href="/static/css/weather_styles.css" />  <link rel="stylesheet" type="text/css" href="/static/css/table_styles.css" />  <link rel="stylesheet" type="text/css" href="/static/css/rich_ui_styles.css" /></head><body>  <rich_tabs>    <rich_pane title="Weather">      <div ng-include="'/static/weather.html'"></div>    </rich_pane>    <rich_pane title="Draggable">      <div ng-include="'/static/draggable.html'"></div>    </rich_pane>    <rich_pane title="Tables">      <div ng-include="'/static/tables.html'"></div>    </rich_pane>  </rich_tabs>  <script src="/static/js/angular.min.js"></script>  <script src="/static/js/rich_ui_app.js"></script>  <script src="https://code.jquery.com/jquery.min.js"></script>  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script></script></body></html>

(3)实现Angular自定义指令

想要让richTabsrichPane 指令模板生效,必须使用自定义指令来支持。

richTabs 指令定义了控制器函数,其中包含用来填充选项卡的窗格数组,其中select() 函数把数组中所有窗格的select 值都设置为false ,然后把当前窗格的select 设置为true ,这样就隐藏了除被选择的一个窗格外所有的窗格。

addPane() 函数从richPane 指令调用,并把窗格添加到richTabs 中的窗格列表,注意templateUrl 指向上面定义的rich_tabs.html 模板。

richPane 定义了一个指令,并制定rich_pane.html 文件作为模板源,它定义了作用域中的标题(title),使得其显示在选项卡中。
注意,需要richTabs 才能通过tabsCtrl 从该指令访问,然后在link函数中,可执行对addPane() 的调用而将面板添加到面板数组。

//rich_ui_app.js - richTabs/richPanevar app = angular.module('richApp', []);...//自定义指令app.directive('richTabs', function() {  return { restrict: 'E', transclude: true,    scope: {},    controller: function($scope) {      var panes = $scope.panes = [];      $scope.select = function(pane) {        angular.forEach(panes, function(pane) {          pane.selected = false;        });        pane.selected = true;      };      this.addPane = function(pane) {        if (panes.length == 0) {          $scope.select(pane);        }        panes.push(pane);      };    },    templateUrl: '/static/rich_tabs.html'  };});app.directive('richPane', function() {  return { require: '^richTabs', restrict: 'E',    templateUrl: '/static/rich_pane.html',     transclude: true, scope: { title: '@' },    link: function(scope, element, attrs, tabsCtrl) {      tabsCtrl.addPane(scope);    }  };});

5.2 实现天气服务视图

本实例的天气视图部分显示了使用一个后端服务连接到openweathermap.com网站并检索特定城市的天气。

如果浏览器带有跨域问题,需要在把远程数据发送到客户端之前,对它进行格式化等情况,那么使用后台服务器访问远程站点可以很有用。

(1)创建后端天气服务

此服务区导出了getWeather() 路由处理程序来处理Express中的/weather 路由,当这段代码收到请求时,它从req,query.city 提取城市名,并用它执行服务器上的远程Web请求,parserWeather() 函数处理该请求。

//weather_controller.jsvar http=require('http');var mongoose=require('mongoose');var WeatherCache=mongoose.model('WeatherCache');//注意,因为这里是需要把Mongoose查询结果放入函数返回值,所以必须使用promise方法,例如bluebird//否则函数将接收不到值function getWeatherCache(cityname){    var getResult;    //这里使用了bluebird,在rich_ui_server.js 中声明    getResult=WeatherCache.findOne({'city.name':cityname})        .exec()        .then(function(promiseResult){            //返回promise对象            return promiseResult;        })        .error(function(error){            return error;            // ...        });    return getResult;}//如果天气缓存数据过期,则进行清除function removeWeatherCache(cityname){    var removeWeather=WeatherCache.remove({'city.name':cityname})    .exec()    .then(function(removeResult){        console.log('removeResult:'+removeResult);        if(removeResult){            return true;        }else{            return false;        }    })    .error(function(error){        return false;    });    return removeWeather;}//将获取的新数据插入到数据库function insertWeatherCache(newWeatherData){    var newWeatherDataObj=JSON.parse(newWeatherData);    newWeatherDataObj.city.name=newWeatherDataObj.city.name.replace(/\s/g, "");    var addResult='';    // var newWeatherDB=new WeatherCache(newWeatherData);    var newWeatherDB=new WeatherCache(newWeatherDataObj);    newWeatherDB.save(function(err,results){        if(err){            throw err;        }else{            addResult='ok';        }    });    return addResult;}//开氏温度转摄氏度function toCelsius(Kelvin){    return Math.round(Kelvin-273.15);}function parseWeather(req,res,weatherResponse){    var weatherData='';    var wObj;    weatherResponse.on('data',function(chunk){        weatherData+=chunk;    });    weatherResponse.on('end',function(){        if(weatherData){            wObj=JSON.parse(weatherData);            insertWeatherCache(weatherData);        }else{            wObj={name:'Not Found'}        }        res.status(200).json(wObj);    })}exports.getWeather=function(req,res){    var city=req.query.city;    //首先根据city查询数据库,看看是否有缓存    var weatherCache;    //这里因为是使用了promise,所以使用then接收    getWeatherCache(city)    .then(function(promiseData){        weatherCache= promiseData;        //如果存在,则直接从数据库获取,无需请求api        if(weatherCache){            var nowTime = new Date();            var currentTime=nowTime.toString().replace(/.+\d{4}\s(\d+).+/,"$1");            if((weatherCache.list[0].dt_txt.toString().replace(/.+\s(\d+).+/,"$1")-9)<currentTime){                res.status(200).json(weatherCache);                return;            }else{                removeWeatherCache(city)                .then(function(removeResult){                    if(!removeResult){                        res.json.status(500).json({mag:'Server error'});                    }                })            }        }        var wData='';        var options = {            hostname: 'api.openweathermap.org',            // path: '/data/2.5/forecast/city?id=524901&APPID=YOURKEYID'        //根据城市id查询            path: '/data/2.5/forecast/city?q='+city+'&APPID=YOURKEYID'      //根据城市名查询(如果是中文地名,必须用拼音代替,这里的YOURKEYID就是你从openweathermap.org申请到的key)        };        http.request(options, (response) => {          response.setEncoding('utf8');          parseWeather(req,res,response);        }).end();    });};

(2)定义天气AngularJS控制器

在NodeJs服务器端定义了天气服务和路由之后,就可以实现访问路由以获取天气数据的控制器。

本控制器实现了ANgularJS应用程序的Module对象中的weatherController ,作用域包括城市列表和获取天气数据时使用的位置或城市名。

getWeather() 函数执行一个$http GET 请求,传递来自$scope.location 的城市参数,并设置将被绑定到视图的$scope.weather 的值。

addCity() 函数使用`locationIn来把新的城市添加到城市数组,如果城市已经存在,那么就不会添加,位置被设置为新的城市,而getWeather()` 获取天气数据。

//rich_ui_app.js - weatherController//定义天气控制器app.controller('weatherController', function($scope, $http) {  $scope.cities = ['Beijing','London', 'Paris', 'New York', 'Rome'];  $scope.location = $scope.cities[0];  $scope.locationIn = '';  $scope.currentCity='Beijing';  //开氏温度转摄氏度  function toCelsius(Kelvin){    return Math.round(Kelvin-273.15);  }  $scope.getWeather = function(){    //去除所有空格    var param=$scope.location.replace(/\s/g, "");    //将第一个字母大写    param=param.charAt(0).toUpperCase()+param.slice(1);    $http({url: '/weather', method: "GET", params:{city:param}})        .then(function successCallback(data){          var wObj=data.data;          var wDataAll=[];          var wData=[];          var wDataSecond=[];          var wDataThird=[];          var wDataFourth=[];          var wDataFifth=[];          var wDatai;          var point;          var currentHours=new Date().getHours();//获取当前本地时间中的小时          if(currentHours-7<0){            //0:17>2  1:18>1 2:19>1 3:20>1 4:21>0             point=Math.ceil((7-currentHours)/3)-1;          }else{            point=Math.ceil((24-(currentHours-7))/3)-1;          }           var len=wObj.list.length;          for(var i=0;i<len;i++){            wDatai={             name: wObj.city.name,//城市名             country:wObj.city.country,//所属国家             temp: toCelsius(wObj.list[i].main.temp),//实时温度             tempMin: toCelsius(wObj.list[i].main.temp_min),//当前最低温             tempMax: toCelsius(wObj.list[i].main.temp_max),//当前最高温             humidity: (wObj.list[i].main.temp_max).toFixed(1),//空气湿度             windSpeed: (wObj.list[i].wind.speed*2.23694).toFixed(1),//风速,mph             windDirection:(wObj.list[i].wind.deg).toFixed(1),//风向             clouds: wObj.list[i].clouds.all,//云量             description: wObj.list[i].weather[0].description,//天气状况,晴、多云、小雨等             icon: wObj.list[i].weather[0].icon,//天气图标             time: wObj.list[i].dt_txt//预报的时间            };            wDataAll.push(wDatai);          }          wData=wDataAll.slice(0,point);          wDataSecond=wDataAll.slice(point,point+8);          wDataThird=wDataAll.slice(point+8,point+16);          wDataFourth=wDataAll.slice(point+16,point+24);          wDataFifth=wDataAll.slice(point+24);          //规定哪一天的天气要显示在页面上,默认为当前日期          var showData=[wData,wDataSecond,wDataThird,wDataFourth,wDataFifth];          $scope.paginations=[            {'nav':['0',wData[0].time.match(/\b\d{2}-\d+\b/).toString()]},            {'nav':['1',wDataSecond[0].time.match(/\b\d{2}-\d+\b/).toString()]},            {'nav':['2',wDataThird[0].time.match(/\b\d{2}-\d+\b/).toString()]},            {'nav':['3',wDataFourth[0].time.match(/\b\d{2}-\d+\b/).toString()]},            {'nav':['4',wDataFifth[0].time.match(/\b\d{2}-\d+\b/).toString()]}          ];            //改变要显示的日期天气          $scope.changeDate=function(num){            var numId=parseInt(num);            $scope.weather=showData[numId]          }          $scope.weather = showData[0];        },function errorCallback(data){          console.log('weather dataError:'+JSON.stringify(data));          $scope.weather = data.data;        });  };  $scope.addCity = function(){    if ($scope.cities.indexOf($scope.locationIn) != 0){      $scope.cities.push($scope.locationIn);    }    $scope.location = $scope.locationIn;    $scope.currentCity = $scope.locationIn;    $scope.getWeather();  };  $scope.setLocation = function(city){    $scope.location = city;    $scope.currentCity = city;    $scope.getWeather();  };  $scope.getWeather('Beijing');});

(3)定义天气AngularJS视图

有了控制器,就可以定义天气数据的视图。

在视图的顶部,有个文本框接受新的城市名称,并将其绑定到locationIn ,还有一个按钮。它被单击时调用控制器中的addCity()

所有的天气数据都来自$scope.weather 变量,并使用AngularJS表达式呈现(后续我使用了bootstrap布局)

//weather.html<div ng-controller="weatherController"><hr>  <br><label class="weatherInfo">City:</label>  <input class="weatherInput" type="text" ng-model="locationIn" />  <input class="weatherButton" type="button"          ng-click="addCity()" value="Add City"/><hr>  <div class="cities">      <div class="city" ng-repeat="city in cities"            ng-click="setLocation(city)">        {{city}}      </div>  </div>  <div class="weatherData">    <p class="weatherCity">{{weather.name}}</p>        <img     ng-src="http://openweathermap.org/img/w/{{weather.icon}}.png" />    <span class="weatherTemp">{{weather.temp}}&deg;F</span>    <p class="weatherDesc">{{weather.description}}</p>    <label class="weatherInfo">Clouds:</label>    <span class="weatherInfo">{{weather.clouds}}%</span>    <label class="weatherInfo">Humidity:</label>    <span class="weatherInfo">{{weather.humidity}}%</span>    <label class="weatherInfo">Wind Speed:</label>    <span class="weatherInfo">{{weather.wind}} mph</span>    <label class="weatherInfo">Min Temp:</label>    <span class="weatherInfo">{{weather.tempMin}} &deg;F</span>    <label class="weatherInfo">Max Temp:</label>    <span class="weatherInfo">{{weather.tempMax}} &deg;F</span>  </div></div>

效果如下:

这里写图片描述


6. 实现可拖动的元素

6.1 定义可拖动的自定义AngularJS指令

定义了一个richDraggable 自定义指令,该指令存储在模板编译时传入的元素的初始位置,然后把mousedown 事件处理程序注册到元素上。

当鼠标被按下时,处理程序增加了mousemovemouseup 事件, mousemove 处理程序调整top和left CSS属性,以便使元素在屏幕上移动。
mouseup 事件处理程序解除mousemovemouseup 事件处理程序的绑定,并停止拖动。

//rich_ui_app.js - richDraggableapp.directive('richDraggable', function($document, $window) {  return function(scope, element, attr) {    var startX = 0, startY = 0;    var x = Math.floor((Math.random()*500)+40);    var y = Math.floor(100+Math.random()*(550-100));    element.css({       position: 'absolute',       cursor: 'pointer',      top: y + 'px',      left: x + 'px'    });    element.on('mousedown', function(event) {      event.preventDefault();      startX = event.pageX - x;      startY = event.pageY - y;      $document.on('mousemove', mousemove);      $document.on('mouseup', mouseup);    });    function mousemove(event) {      y = event.pageY - startY;      x = event.pageX - startX;      element.css({        top: y + 'px',        left:  x + 'px'      });    }    function mouseup() {      $document.unbind('mousemove', mousemove);      $document.unbind('mouseup', mouseup);    }  };});

6.2 在AngularJS视图中实现可拖动的指令

定义了上面的自定义指令后,可以通过在HTML元素定义中包括rich-draggable 指令在你的视图中实现它,例如:

<p rich-draggable>My draggable paragraph.</p>

<!--draggable.html--><!--后续我使用了bootsrap布局--><img class="dragImage" rich-draggable src="/images/arch.jpg" /><img class="dragImage" rich-draggable src="/images/flower.jpg" /><img class="dragImage" rich-draggable src="/images/lake.jpg" /><img class="dragImage" rich-draggable src="/images/volcano.jpg" /><img class="dragImage" rich-draggable src="/images/jump.jpg" /><img class="dragImage" rich-draggable src="/images/bison.jpg" /><span class="dragLabel" rich-draggable>Lake</span><span class="dragLabel" rich-draggable>Volcano</span><span class="dragLabel" rich-draggable>Sunset</span><span class="dragLabel" rich-draggable>Bison</span><span class="dragLabel" rich-draggable>Flower</span><span class="dragLabel" rich-draggable>Arch</span>

效果如下:

这里写图片描述

7. 单词查询实现动态数据访问

若要以便于阅读的格式显示大量来自服务器的数据,使用表格是一种不错的方式。

7.1 创建/word 路由的Express路由控制器

getWords() :该路由处理程序从查询读取 limit、skip、sort 和 direction参数,并使用它们来查询MongoDB数据库。

getSortObj() :将sort字段和direction转换为在Query对象的.sort()发放中被使用的sort对象。

//words.controller.jsvar mongoose = require('mongoose'),    Word = mongoose.model('Word');exports.getWords = function(req, res) {  var sort = getSortObj(req);//例如:{"size":1} {'word':1}  var query = Word.find();  var limit=parseInt(req.query.limit,10);  var skip=parseInt(req.query.skip,10);  //判断是直接查询所有还是根据值查询  if(req.query.contains.length > 0){    query.find({'word' : new RegExp(req.query.contains, 'i')});  }  //注意,这里有个坑,limit和skip字段必须首先转成number才能正常查询  query.sort(sort)  .limit(limit)  .skip(skip)  .exec(function(err, word) {    if(err){      var errs=err;    }    if (!word){      // res.json(404, {msg: 'Word Not Found.'});      res.status(404).json({msg: errs+',Word Not Found.'+word})    } else {      // res.json(word);      res.status(200).json(word);    }  });};//判断排序的依据字段是什么function getSortObj(req){  var field = "word";  if(req.query.sort == 'Vowels'){    field = 'stats.vowels';  } else if(req.query.sort == 'Consonants'){    field = 'stats.consonants';  } else if(req.query.sort == 'Length'){    field = 'size';  }else{    field = req.query.sort.toLowerCase();  }  var sort = new Object();  //正序或者逆序查询  sort[field] = req.query.direction==='asc'?1:-1;  return sort;};

7.2 定义表格AngularJS控制器

完成了上面/word 路由,就可以实现访问列表中显示的单词列表的ANgularJS控制器了,该控制器实现了tableController

前几行定义了words 数组,其中包含表中的数据,以及用在$http GET 请求中来检索改组单词的contains limit skip direction 的值。

sortFields 数组提供用来选择进行排序字段的数据。

getWords() 函数对/words 路由发出一个$http GET 请求,并用请求中的结果来绑定到表中数据的$scope.words 数组,在此过程中,contains limit skip direction 字段随同请求被发送,以支持排序、分页和过滤。

find() 方法重新出赤化skip() 值,然后调用getWords() 来执行新的搜索。
next()prev() 调整skip() 值来对单词数据库的结果进行分页。

//rich_ui_app.js - tabelController//表格单词数据控制器//表格单词数据控制器app.controller('tableController', function($scope, $http) {  $scope.words = [];  $scope.contains = '';  $scope.limit = 5;  $scope.skip = 0;  $scope.skipEnd = 0;  //选择通过何种方式查询  $scope.sortFields = ['Word', 'First', 'Last', 'Length',                        'Vowels', 'Consonants'];  //默认通过"Word"查询  $scope.sortField ="Word";  //默认正序查询  $scope.direction = "asc";  //请求查询单词函数  $scope.getWords = function(){    $http({url: '/words', method: "GET",            params:{ limit:$scope.limit,                     skip:$scope.skip,                    sort:$scope.sortField,                     direction:$scope.direction,                    contains:$scope.contains }})    .success(function(data, status, headers, config) {      //获取查询到的所有数据,并通过Angular的双向绑定,呈现在视图表格内       $scope.words = data;       //将下次查询的单词坐标,调整到当前       $scope.skipEnd = $scope.skip + $scope.words.length;     })     .error(function(data, status, headers, config) {       $scope.words = [];       //$scope.skipEnd 呈现在视图上的单词查询区间之一       $scope.skipEnd = $scope.skip + $scope.words.length;     });  };  $scope.find = function(){    $scope.skip = 0;    $scope.getWords();  };  $scope.next = function(){    if($scope.words.length == $scope.limit){      $scope.skip += parseInt($scope.limit);      $scope.getWords();    }  };  $scope.prev = function(){    if($scope.skip > 0){      if($scope.skip >= parseInt($scope.limit)){        $scope.skip -= parseInt($scope.limit);      } else{        //如果已经到头了,查找索引就置为0        $scope.skip = 0;      }      $scope.getWords();    }  };  //刚打开页面的时候,调用函数初始化一下  $scope.getWords();});

7.3 实现表格AngularJS视图

完成了 tableController 控制器后,就可以使用AngularJS视图呈现,提供对单词的过滤、排序和分页,并进行显示(后续我使用了bootstrap进行布局)。

//table.html<div ng-controller="tableController"><hr><input class="findButton" type="button"        value="Find Words" ng-click="find()" /><div id="sortOptions">  <label class="tableLabel">Page Limit</label>  <input class="tableInput" type="text" ng-model="limit" /><br>  <label class="tableLabel">Contains</label>  <input class="tableInput" type="text" ng-model="contains" /><br>  <label class="tableLabel">Sort By</label>  <select class="tableInput" ng-model="sortField"           ng-options="field for field in sortFields"></select>  <input type="radio" ng-model="direction" value="asc"> Ascending  <input type="radio" ng-model="direction" value="desc"> Descending</div><hr><div>  <input class="pageButton" type="button" value="Prev"          ng-click="prev()" />  <input class="pageButton" type="button" value="Next"          ng-click="next()" />  <label class="tableLabel">Words {{skip+1}} to {{skipEnd}}</label>  <hr>  <div id="tableContainer">    <table>      <tr><th>Word</th><th>First</th><th>Last</th><th>Length</th>      <th>Vowels</th><th>Consonants</th></tr>      <tr ng-repeat="word in words">        <td>{{word.word}}</td>        <td>{{word.first}}</td>        <td>{{word.last}}</td>        <td>{{word.size}}</td>        <td>{{word.stats.vowels}}</td>        <td>{{word.stats.consonants}}</td>      </tr>    </table>  </div></div></div>

效果如下:

这里写图片描述

8. 初始化应用程序

主要就是为数据库中的words文档进行初始化。

//word_init.jsvar vowelArr = "aeiou";var consenantArr = "bcdfghjklmnpqrstvwxyz";//words中就是你想要插入到数据库中的单词,数量由你自己决定,在我的文件中,一共是包含了4000多个单词,这里我就不详细贴出来了。var words = "the,be,and,of,a,in,to,have,it,I,that...";var wordArr = words.split(",");var wordObjArr = new Array();for (var i=0; i<wordArr.length; i++){  try{    var word = wordArr[i].toLowerCase();    //使用正则分割掉元音,只剩下辅音字段,使用'|',是为了避免分割后的数组中存在空字符    var vowelCnt = ("|"+word+"|").split(/[aeiou]/i).length-1;    //使用正则分割出辅音,只剩下元音字段,使用'|',是为了避免分割后的数组中存在空字符    var consonantCnt =       ("|"+word+"|").split(/[bcdfghjklmnpqrstvwxyz]/i).length-1;    var letters = [];    var vowels = [];    var consonants = [];    var other = [];    for (var j=0; j<word.length; j++){      var ch = word[j];      if (letters.indexOf(ch) === -1){        letters.push(ch);      }      if (vowelArr.indexOf(ch) !== -1){        if(vowels.indexOf(ch) === -1){          vowels.push(ch);        }      }else if (consenantArr.indexOf(ch) !== -1){        if(consonants.indexOf(ch) === -1){          consonants.push(ch);        }      }else{        if(other.indexOf(ch) === -1){          other.push(ch);        }      }    }    var charsets = [];    if(consonants.length){      charsets.push({type:"consonants", chars:consonants});    }    if(vowels.length){      charsets.push({type:"vowels", chars:vowels});    }    if(other.length){      charsets.push({type:"other", chars:other});    }    var wordObj = {      word: word,      first: word[0],      last: word[word.length-1],      size: word.length,      letters: letters,      stats: { vowels: vowelCnt, consonants: consonantCnt },      charsets: charsets    };    if(other.length){      wordObj.otherChars = other;    }    wordObjArr.push(wordObj);  } catch (e){    console.log(e);    console.log(word);  }}var MongoClient = require('mongodb').MongoClient;MongoClient.connect("mongodb://localhost/27017", function(err, db) {    var myDB = db.db("words");  myDB.dropCollection("words");  myDB.createCollection("words", function(err, wordCollection){    wordCollection.insert(wordObjArr, function(err, result){      console.log(result);      db.close();    });  });});

9. 额外的工作

至此,应用程序就算是结束了,你首先启动word_init.js 文件初始化数据库后,就可以启动项目文件rich_ui_server.js 了,然后就可以看到项目的真实效果。

我这里的天气api因为调用的是openweathermap.org网站,速度很慢,所以我另外设置了缓存,将查询到的天气状况缓存到数据库,方便下次查询,并且设置了如果天气日期过期,则重新获取新的天气情况数据。

数据库模型如下:
天气状况模型中包括城市名称(name)、所属国家(country)、气温(temp)、风速(speed)、风向(deg)、时间(dt_txt)等基本天气信息,如下:

//weather_cache_model.jsvar mongoose=require('mongoose');var Schema=mongoose.Schema;var WeatherCacheSchema=new Schema({    city:{        id:Number,        name:String,        coord:{            lon:Number,            lat:Number,        },        country:String,        population:Number,        sys:{            population:Number        }    },    cod: String,    message: Number,    cnt: Number,    list:[{        "dt": Number,        "main": {            "temp": Number,            "temp_min": Number,            "temp_max": Number,            "pressure": Number,            "sea_level": Number,            "grnd_level": Number,            "humidity": Number,            "temp_kf": Number        },        "weather": [{            "id": Number,            "main": String,            "description": String,            "icon": String,        }],        "clouds": {            "all": Number        },        "wind": {            "speed": Number,            "deg": Number        },        "sys": {            "pod": String,        },        "dt_txt": String,    }]});mongoose.model('WeatherCache',WeatherCacheSchema);

10. 程序完成

到这里,一个基本的NodeJS+Angular+MongoDB Web程序就算是完成了。

源代码已经上传,点击这里

或者直接从github拉取,点击跳转到远程仓库

0 0
原创粉丝点击