node.js建立一個專案
快速使用node.js進行web前端開發
首先關於node.js的學習,這裡推薦一本比較好的教程,nodejs web開發指南,該書通俗易懂地將node.js語言特性講解完之後,又從一個專案角度帶領讀者使用node.js學習web開發。相信這是一個比較好的學習模式和過程。由於這本書是2012年出的,書中的一個web教學專案是開發一個微博。從2012到現在,node.js及其生態環境發生了很大改變,所以關於該書的學習如果照著書本顯然是過於陳舊的。到目前為止,node.js的web開發框架已經升級到了Express4.12.1,對於MongoDB的操作更多是使用mongoose這個物件模型,而不是之前mongoDB 官方提供的原生node.js的API,所以本文將基於nodejsV0.1033 + MongoDBV3.0.2+ Jade1.9.2 + mogooseV4.0.1來重構該書中的微博專案,這個組合也是目前最新的使用node.js進行web開發的常用組合之一,如果需要入門使用node.js進行web開發,正在學習
1.express框架安裝
1)在node命令列模式下輸入以下命令
npm install -g express
該命令在全域性環境下安裝express框架,在安裝完這一步之後,並不能直接使用express命令來生成express專案,需要再安裝一個express專案生成器,在express2.X的版本中是不需要的,express4.X版本之後將專案生成器和express本身分離了出來,如果不安裝express-generator這個生成器就使用express命令來生成專案,會遇到報express不是內部或外部命令這個錯誤,這是需要注意的地方,
2)安裝express-generator
npm install -g express-generator
3)生成一個專案
cd ..
mkdir microblog
cd microblog
express micorblog
這裡隨意在硬碟某個目錄下建立一個microblog的資料夾,進入該資料夾,然後使用express microblog命令建立了一個microblog的express專案。
生成結構如下:
其中app.js是專案入口檔案,package.json是npm 包管理檔案,bin資料夾裡面的www.js放一些全域性配置項以及命令列配置等。public 資料夾是用來存放專案靜態檔案目錄如js,css以及圖片,routes資料夾是用來存放路由監聽的程式碼相關檔案。views資料夾用來存放模板檔案,這裡需要注意的是express4.X使用jade作為專案的預設模板引擎,而在原書中是使用ejs作為模板引擎的,所以這裡預設生成的是jade檔案。無可否認ejs是要簡單些,但是原理都是一樣的,我們使用jade作為開發的模板引擎。
4)啟動專案並檢視
cd microblog
npm install
npm start
進入到microblog資料夾,安裝專案所需相關模組(根據pacakge.json檔案),然後啟動專案,這時候開啟瀏覽器檢視專案輸入地址localhost:3000,結果如下說明一切正常,
到目前為止,我們已經擁有了一個在瀏覽器中執行的web專案雛形。下面進行開發,原書中的微博專案的主要功能是使用者能夠註冊登入,許可權控制並讓使用者釋出微博在使用者個人主頁和專案首頁分別顯示,這些功能完整版程式碼會提供,由於篇幅原因,這裡以使用者註冊登入模組來說明如何進行一個完整流程的web開發。
2.頁面佈局
依照web開發流程,我們首先來構建一個專案主頁,專案主頁是由佈局檔案layout.jade和內容檔案index.jade組成,關於的jade的學習,這裡提供兩個地址,對於以前使用過類似模板引擎如smarty,razor等的,可以看看文件就能夠上手做了,基本原理都是大同小異。jade官網文件和慕課網jade教程,看看這兩個教程就基本可以掌握了,關於jade的學習,我認為主要是現學現用,對著文件編寫頁面即可,當然如果有很多時間也可深入學習。
開啟views檔案,將layout.jade檔案程式碼改寫如下:
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
nav.header
ul.list
li.logo
a(href='/') Microblog
li
a(href='/') 首頁
li
a(href='/login') 登入
li
a(href='/reg') 註冊
div.container
block content
hr
footer.footer
p
a(href='http://myzhibie.coding.io') myzhibie
| @2015
需要注意父級元素和子元素的換行之間縮排,jade是利用縮排來區別程式碼層級的。
首頁內容檔案index.jade
extends layout
block content
main.main
section.intro
if message
h3.indexmes #{message}
//如果使用者登入或者註冊成功並且沒有在登入狀態下點選註冊或者登入
if success&&user
h1.welcome #{success},歡迎 #{user} 來到 Microblog
else if !success&&user
h1.welcome 歡迎 #{user} 來到 Microblog
else
h1.welcome 歡迎來到 Microblog
h3.tech Microblog是一個基於Node.js,使用express4.12.1,jade1.9.2以及MongoDB搭建起來的微博系統,是對Node.js開發指南一書中教學專案的重構。
p.btnlist
if user
a.login(href='/logout') 退出
a.userlink(href='/users/#{user}') 發表文章
else
a.login(href='/login') 登入
a.register(href='/reg') 立即註冊
section.show
each val in posts
article.col
h3.author #{val.user}說
p
| #{val.post}
首頁內容是繼承了模板檔案layout.jade.原書中使用的bootstrap來構建頁面的css佈局和樣式,這裡我自己手寫了一個仿bootstrap風格的佈局樣式,沒有應用bootstrap,style.css檔案如下:
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
html,body,ul,p,hr,h3{
margin:0;
padding: 0;
}
a {
color: #00B7FF;
}
.header{
background:#337aB7;
width: 100%;
height: 60px;
color: #fff;
font-size: 22px;
overflow: hidden;
}
.list{
line-height: 60px;
}
.navigation{
overflow: hidden;
}
.list li{
list-style: none;
float: left;
display: inline-block;
margin-left: 20px;
margin-right: 20px;
}
.list li a{
text-decoration: none;
color: #fff;
}
.list li a:hover{
}
.list li:not(:first-child) a:hover{
font-size: 26px;
color: #F5F5F5;
}
.logo{
font-size: 26px;
font-weight: 700;
}
.container{
min-height: 500px;
text-align: center;
width: 100%;
}
.footer{
width: 100%;
height: 50px;
font-size: 22px;
background:#F5F5F5 ;
line-height: 50px;
}
.footer a{
color:#337aB7;
text-decoration: none;
}
.main{
color: #000000;
width: 96%;
margin: 30px auto;
}
.intro{
width: 100%;
margin:0 auto;
border-radius: 5px;
height: 300px;
background:#F5F5F5 ;
}
.userintro{
width: 100%;
margin:0 auto;
border-radius: 5px;
height: 200px;
background:#F5F5F5 ;
}
.welcome{
padding-top: 50px;
padding-left:50px;
font-size: 50px;
text-align: left;
padding-bottom: 0;
margin: 0;
}
.tech{
text-align: left;
padding-left:50px;
margin: 0;
}
.show{
overflow: hidden;
width: 100%;
}
.show li{
text-align: left;
font-size: 18px;
}
.col{
display: inline-block;
float: left;
width: 32%;
height: 100px;
overflow: hidden;
padding-right: 20px;
text-align: left;
text-overflow: ellipsis;
}
.author{
margin-top: 10px;
margin-bottom: 3px;
}
.btnlist{
padding-left: 50px;
text-align: left;
}
.login{
display: inline-block;
padding-left: 15px;
padding-right: 15px;
height: 38px;
line-height: 40px;
background: -webkit-gradient(linear, left top, left bottom, from(#0068A6), to(#337aB7));
color: #fff;
text-align: center;
border-radius: 5px;
font-size: 20px;
font-weight: 600;
border: 1px solid #ccc;
text-decoration: none;
margin-right: 10px;
}
.register{
display: inline-block;
padding-left: 15px;
padding-right: 15px;
height: 38px;
line-height: 40px;
background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#F5F5F5));
color: #000;
text-align: center;
border-radius: 5px;
font-size: 20px;
font-weight: 600;
border: 1px solid #ccc;
text-decoration: none;
}
.field{
margin-top: 20px;
margin-left: 50px;
text-align: left;
margin-bottom: 20px;
border:none;
border-bottom: 1px solid #ccc;
}
.label{
font-size: 18px;
font-weight: 600;
line-height: 100%;
display: inline-block;
width: 10%;
vertical-align: middle;
text-align: right;
padding-right: 10px;
}
.regheader{
text-align: left;
font-size: 24px;
font-weight: 600;
}
.regform{
text-align: left;
padding-left: 100px;
margin-bottom: 20px;
}
.regform input[type='text'],input[type='password']{
width: 200px;
height: 20px;
}
.regform input[type='submit']{
width: 120px;
height: 30px;
color: #fff;
background:-webkit-gradient(linear, left top, left bottom, from(#0068A6), to(#337aB7));
border-radius: 5px;
font-size: 20px;
}
.item{
margin:20px;
width: 100%;
}
.mess{
font-size: 18px;
color: #E73C3C;
background: #F2DEDE;
border-radius: 5px;
width: 300px;
text-align: center;
margin-left: 100px;
}
.indexmes{
height: 30px;
line-height: 30px;
background: #F2DEDE;
color: #E73C3C;
}
.article{
width: 60%;
height: 30px;
border-radius: 3px;
border: 1px solid #A3C732;
margin-top: 5px;
font-size: 20px;
}
.submit{
height: 40px;
vertical-align: middle;
padding: 0;
margin-top: -5px;
margin-left: 5px;
width: 80px;
background: #A3c732;
font-size: 20px;
border: none;
border-radius: 5px;
color: #fff;
}
.submitform{
margin-top: 25px;
margin-left: -10px;
}
.userlink{
display: inline-block;
text-decoration: none;
line-height: 38px;
height: 38px;
vertical-align: middle;
padding: 0;
margin-top: -8px;
margin-left: 5px;
width: 90px;
text-align: center;
background: #A3c732;
font-size: 20px;
font-weight: 600;
border-radius: 5px;
color: #fff;
border: 1px solid #ccc;
}
.usertitle{
text-align: left;
padding-top: 5px;
padding-bottom: 0;
padding-left: 5px;
margin-bottom: 8px;
}
.usersuccess{
height: 30px;
background: #DFF0D8;
line-height: 30px;
color: #3C7668;
}
使用者登陸模板login.jade如下:
extends layout
block content
h3.field.regheader #{title}
form.regform(method='post')
p.mess #{message}
div.item
label.label(for='username') 使用者名稱
input(type='text',placeholder='輸入登陸使用者名稱',id='username',name='username')
div.item
label.label(for='password') 使用者密碼
input(type='password',placeholder='使用者密碼',id='password',name='password')
div.item
label.label
input(type='submit' id='sub',name='sub' value='登陸')
終端使用者註冊效果如下:
使用者登入模組和這個效果相仿,就不查看了,少了一個重複密碼的input而已。
下面我們需要編寫使用者註冊的邏輯,在編寫使用者註冊邏輯的前,使用者資料需要持久化,所以首先要安裝MongoDB資料庫在自己的機器上,關於其安裝請參考菜鳥教程上的-windows系統安裝MongoDB,
MongoDB這種nosql型別的資料庫,非常適合使用者儲存JSON物件型別的資料,有了mongoDB,就可以免去資料庫表設計部分的工作,對比以前使用的mysql,sqlserver以及oracle還是非常方便的。關於mongoDB資料庫的熟悉和學習,推薦其官網,官網詳細介紹了該資料庫的一切。英文不好可以去中文社群。同時為了使用nodejs來操作mongoDB資料庫,我們使用mongoose這個物件模型,它是將mongoDB中的一個集合對映為nodejs中的一個model,然後在該model上提供操作這個集合的一些方法,使用它就可以避免我們自己利用nodejs提供的原生操作mongoDB資料庫的語法去手寫資料庫CURD的方法,大大見晒了工作量,提高了開發效率。關於mongoose的學習,推薦去其官網,裡面詳述了它的安裝,使用以及API呼叫情況。
解決了mongoDB安裝和操作問題,我們來對資料庫操作的model類,首先在專案路徑下建立一個db.js檔案,用來連線資料庫並對資料庫進行全域性配置,如下
db.js
var settings=require("./settings");
var mongoose=require('mongoose');
mongoose.connect("mongodb://"+settings.ip+"/"+settings.db);
var db=mongoose.connection;
module.exports={
"dbCon":db,
"mongoose":mongoose
};
這裡首先載入了配置檔案settings.js檔案,為了資料庫便於靈活修改,我們將某些資訊儲存在配置檔案中。然後加在了之前安裝的mongoose模組,然後呼叫該模組的connect方法來連線我們配置的資料庫,然後將連線以物件的形式返回供外部呼叫。
settings.js
module.exports={
"ip":"localhost",
"db":"microblog",
"host":27071
};
MongoDB的預設埠是27071,一般可以使用預設埠即可,資料庫連線大時候可以不指定埠,資料庫名為microblog.
然後以db.js返回的資料庫連線物件為基礎,我們在專案根目錄下建立一個models資料夾,用來存放資料模型。建立一個user.js對映我們資料庫中的user集合(可以理解為user表),程式碼如下:
var mongoose=require('../db').mongoose;
var schema=new mongoose.Schema({
name:'string',
password:'string'
});
var User=mongoose.model('User',schema);
module.exports=User;
這裡首先獲得db.js中定義的連線物件,並以該物件為基礎構造一個Schema(架構),mogoose操作資料庫是以架構為基礎的,類似於我們其他ORM模型中屬性和方法的定義。這裡我們定義了一個架構,擁有兩個屬性,name和password,都是string型別,對應使用者的使用者名稱和密碼。然後利用該架構去建立一個model,該model上定義了對資料集合的增刪改查等方法,不用我們自己再去定義和編寫其他程式碼。在原書中這一節是利用node.js操作MongoDB資料庫的原生API去定義了一個user物件,然後在user物件上自定義了一些CRUD的方法。可以看出,直接使用Mongoose可以大大減少開發量並且擁有更好的效率和效能。
到目前為止,我們已經有了介面(view),資料模型(model),就差邏輯程式碼(controller)沒有編寫了。在編寫邏輯程式碼之前需要先說下express框架的特點以及它的整體執行方式。由於本人使用過一些類似的如Asp.net mvc,Yii以及thinkphp等MVC框架,使用express之後最大的感覺是這個框架夠輕量級,尤其是express4.X之後,它僅僅保留了靜態檔案路徑對映模組作為該框架本身的內建模組,其他的功能都以中介軟體的形式採用require(modulename)進行引入,只有引入後才能夠使用該模組提供的功能。
express的工作原理是客戶端傳送一個request,express接到該請求,可以將它進行處理之後傳遞給其他中介軟體進行處理,最終處理完成之後,採用respond.end或者response.render進行頁面渲染或響應,進行頁面渲染的時候,採用引數傳遞頁面需要的資料給對應模板引擎,模板引擎收到資料然後按照自己的語法進行替換生成對應的html,最終返回給瀏覽器進行渲染。
在express中,最關鍵的部分就是路有機制,我們所有基於請求做出的響應都是對該路由進行監聽捕獲的結果。舉個例子,如果我們請求一個路徑為http://localhost:3000/user,那麼必須在routes資料夾下面的路徑監聽(暫且叫做監聽吧)的js檔案中編寫對該請求的響應程式碼,諸如app.post('/user',function(...){...})之類的程式碼,如果不存在這樣的程式碼,就會報一個404錯誤,因為請求沒有得到響應,express例項不知道怎麼去響應這個請求。以上就是express大致的原理和工作流程,對於它的學習,推薦去express官網直接去看文件,講的很詳細。
現在回到使用者註冊模組,我們註冊使用者常見的做法是註冊成功之後就預設使用者已經登入,直接跳轉到歡迎登陸介面。在這裡我們需要將使用者資料在註冊成功之後儲存在session中,express框架對於session的支援是通過中介軟體express-session來的,使用方式依然是在npm 下安裝,然後在專案主檔案中使用require載入,最後呼叫其提供的API,為了使用session,必須先安裝cookie的支援,這裡利用cookie-parser這個中介軟體來為express框架提供cookie支援,它的具體使用方式可以去上面提供的地址自行檢視。對於session,我們常見框架的做法是在伺服器端將其存放到檔案當中,由於這裡我們有了MongoDB資料庫,更理想的狀態是將它存在資料庫中,這樣可以更靈活去控制。使用connect-mongo中介軟體可以將session儲存到mongoDB中,具體使用方式可按地址檢視。
上述概念明確之後,我們在專案根目錄下的app.js(專案入口檔案)中載入我們需要的中介軟體模組和自定義的模組如下:
app.js模組載入程式碼:
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var routes = require('./routes/index');
var users = require('./routes/users');
var session = require("express-session");
var MongoStore=require('connect-mongo')(session);
var db = require('./db');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({
secret:"myzhibie",
store:new MongoStore({
mongooseConnection:db.dbCon
})
}));
app.use('/', routes);
app.use('/users', users);
上述程式碼就是載入各個中介軟體模組並採用app.use來load這個模組,其中上述程式碼的最後一指定了將session儲存在MongoDB資料庫中,secret屬性是對session的簽名,通常是一個字串,這是必選項,如果不寫,
是無法完成將session儲存進入資料庫的,關於該功能的更詳細介紹請檢視文件,最後兩句app.use('/',routes)和app.use('/users',users)代表對於這兩個路由的訪問處理程式碼我們封裝在了routes和users模組中,er這兩個模組都在routes資料夾下面。
完成了模組引入載入和一些基本的設定,現在來編寫使用者註冊的邏輯程式碼,上面說到對於路徑/的訪問處理在routes模組中,這個模組指的就是routes資料夾下面的index.js,部分程式碼如下:
var express = require('express');
var crypto = require('crypto');
var router = express.Router();
var db=require('../db');
var User=require('../models/user');
var Post=require('../models/post');
/* GET home page. */
router.get('/', function(req, res, next) {
Post.find({},function(err,posts){
if(err){
req.session.message=err.message;
return res.redirect('/');
}
res.render('index',{
posts:posts
});
});
});
//發表微博
router.post('/post',function(req, res, next){
var currentUser=req.session.user;
var post=new Post({
user:currentUser.name,
post:req.body.article,
updated:getTime(new Date())
});
post.save(function(err){
if(err){
req.session.message=err.message;
return res.redirect('/reg');
}
req.session.success="發表成功";
res.redirect('/users/'+currentUser.name);
});
});
function getTime(date){
return date.getFullYear()+
"-"+date.getMonth()+1+"-"+
date.getDate()+" "+
date.getHours()+":"+
date.getMinutes();
}
router.get('/reg', isLogin);
//使用者進入註冊頁面
router.get('/reg',function(req,res){
res.render('reg',{title:"使用者註冊"});
});
router.post('/reg', isLogin);
//使用者點選註冊按鈕
router.post('/reg',function(req,res){
if(req.body['password']!= req.body['passwordconf']){
req.session.error="兩次密碼不一致";
return res.redirect('/reg');
}
var md5=crypto.createHash('md5');
var password=md5.update(req.body.password).digest('base64');
var newUser=new User({
name:req.body['username'],
password:password
});
User.findOne({name:newUser.name},function(err,user){
if(user){
err="使用者名稱已經存在";
}
if(err){
req.session.error=err;
return res.redirect('/reg');
}
newUser.save(function(err){
if(err){
req.session.error=err.message;
return res.redirect('/reg');
}
req.session.user=newUser;
req.session.success="註冊成功";
res.redirect('/');
});
});
});
router.get('/login',isLogin);
router.get('/login',function(req,res){
res.render('login',{title:"使用者登陸"});
});
router.post('/login',isLogin);
router.post('/login',function(req,res){
var md5=crypto.createHash('md5');
var password=md5.update(req.body.password).digest('base64');
User.findOne({name:req.body.username},function(err,user){
if(!user){
req.session.error="使用者不存在";
return res.redirect('/login');
}
if(user.password!=password){
req.session.error="密碼錯誤";
return res.redirect('/login');
}
req.session.user=user;
req.session.success="登入成功";
res.redirect('/');
});
});
router.get('/logout',function(req,res){
req.session.user=null;
res.redirect('/');
});
function isLogin(req,res,next){
if(req.session.user){
req.session.message="使用者已登入";
return res.redirect('/');
}
next();
}
module.exports = router;
上述程式碼1-6行都是對外部模組的引入,8-19行是對首頁路由/的處理程式碼。117行將該模組定義為router供外部呼叫。我們主要看54-83行,這些程式碼就是使用者註冊的程式碼,54行監聽來自使用者對於/reg路由的post請求,首先判斷兩次密碼是否一致,如果不一致在session中儲存一個錯誤資訊然後跳轉到到當前頁面顯示錯誤資訊,該錯誤資訊供模板引擎顯示給使用者。如果兩次密碼一致首先對密碼進行md5加密,使用的是nodejs提供的核心模組crypto,並生成一個物件模型User,該物件模型是mongoose中提供的一個model的例項,mongoose在它上面定義了一些操作資料庫的方法。然後呼叫這個例項的findOne方法檢測該使用者是否已經存在,如果存在就儲存錯誤資訊到session並跳轉到當前頁顯示錯誤。如果不存在這樣一個使用者就使用save方法進行使用者資訊儲存,註冊成功後將使用者資訊儲存在session中,並儲存一個success的提示資訊,然後跳轉到首頁。這裡需要注意一個坑,以前做php或者.net的時候,我們通常都是先查詢資料庫等資料庫返回結果提示使用者是否存在之後再進行使用者的save然後在跳轉,這是一種同步方式,跳轉操作需要等待findOne操作返回結果之後才能進行。而nodejs中採用非同步IO,最後的跳轉操作需要放在findOne操作的回撥函式中進行,跳轉操作不必等待findone操作結束後執行,兩者是非同步的。如果將最後的redirect操作放在findOne操作外部而不是回撥函式中,你會在控制檯上得到一個Can't set headers after they are sent的錯誤,這是因為在fineOne以及save操作之前已經進行行了跳轉,response響應已經結束,不能夠重複響應請求。
到目前為止,使用者註冊模組基本上已經差不多完成了,最後需要說一下如何在頁面上顯示提示資訊或者錯誤資訊,之前我們將提示資訊或者錯誤資訊都儲存在了session中,jade要顯示錯誤資訊,它是不能夠直接訪問session的,在express2.X即原書中是利用req.flash API+動態檢視助手來實現的,就是發生錯誤的時候先將其利用req.flash方法儲存下來,然後利用動態檢視助手結合模板去渲染給使用者。express4.X廢棄了這種方式,我們可以利用req.flash 的原理來自己模擬一個這種機制,同時利用res.locals變數被儲存起來,模板在渲染的時候是能夠訪問到服務端這個變數的。關於res.locals的更多介紹請檢視文件。
為了模擬這種req.flash機制,我們在專案入口檔案app.js(專案根目錄下)新增一段程式碼如下:
app.use(function(req,res,next){
// res.locals.user=req.session.user;
var err=req.session.error;
var success=req.session.success;
var user=req.session.user;
var mess=req.session.message;
delete req.session.success;
delete req.session.error;
delete req.session.message;
if(err){
res.locals.message="*"+err;
}
if(mess){
res.locals.message="*"+mess;
}
if(success){
res.locals.success=success;
}
if(user){
res.locals.user=user.name;
}
next();
});
這段程式碼的意思是使用者請求和響應的時候,捕獲session中儲存的錯誤資訊和使用者提示,將其儲存在response.locals變數中,這樣模板就能夠獲取。對於錯誤資訊和提示,由於只使用一次,儲存後立即使用delete刪除,對於使用者資訊,需要持久儲存下來,則不刪除。
這樣,就能夠顯示使用者提示或者錯誤資訊。
下面演示一下完整的使用者註冊流程以及錯誤資訊提示。
當用戶名存在或密碼不一致時,
當註冊成功後跳轉到首頁並顯示使用者註冊成功
同時對於註冊成功和登陸成功擁有不同提示,如果該使用者已經是登入狀態則顯示退出和發表文章按鈕,如果沒有登入,則顯示的是登陸和立即註冊按鈕。
以上就是利用nodejs及express,mongoose,mongoDB,jade進行web開發的主要流程,由於該專案是對nodejs web開發指南一書中微博專案的重構,所以完整版的專案程式碼還有使用者許可權控制(已登入使用者不能夠註冊或登陸並提示),使用者進入個人頁面釋出微博並列表顯示,同時首頁顯示最近釋出的微博資訊等功能。由於篇幅以及時間問題,上述要點不可能一一展開討論,本文就作為一個提綱,是對nodejs web開發的一個綜述。