一、需求分析
二、開(kāi)發(fā)工具
1、NodeJs:基礎(chǔ)核心開(kāi)發(fā)語(yǔ)言
2、express:一個(gè)簡(jiǎn)潔而靈活的nodejs web應(yīng)用框架,提供一系列強(qiáng)大的特效幫助我們創(chuàng)建各種web應(yīng)用
從本質(zhì)上來(lái)說(shuō),一個(gè) express 應(yīng)用就是在調(diào)用各種中間件。
3、MongoDB:數(shù)據(jù)庫(kù)
4、第三方模塊/中間件
bodyParser:解析post請(qǐng)求數(shù)據(jù)
cookies:讀/寫(xiě)cookie
swig:模板解析引擎
mongoose:操作mongodb數(shù)據(jù)
markdown:markdown語(yǔ)法解析生成模塊
讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來(lái)自于我們對(duì)這個(gè)行業(yè)的熱愛(ài)。我們立志把好的技術(shù)通過(guò)有效、簡(jiǎn)單的方式提供給客戶,將通過(guò)不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:主機(jī)域名、虛擬主機(jī)、營(yíng)銷軟件、網(wǎng)站建設(shè)、白水網(wǎng)站維護(hù)、網(wǎng)站推廣。
require一個(gè)模塊,如果是通過(guò)npm安裝的或者是express內(nèi)置的模塊,直接require(‘模塊名’)
如果是自定義的模塊,必須加上相對(duì)路徑
三、開(kāi)發(fā)實(shí)戰(zhàn)
1、構(gòu)建項(xiàng)目目錄結(jié)構(gòu)、安裝各種所需要的模塊框架等等
npm init
npm install --save XXX
2、目錄結(jié)構(gòu)
3、創(chuàng)建應(yīng)用,監(jiān)聽(tīng)端口
//app.js
var express = require("express");
var app = express();
var server = app.listen(8888, function(){
console.log("Server is running on http://localhost:8888");
});
4、用戶訪問(wèn)流程
用戶通過(guò)URL訪問(wèn)web應(yīng)用,如:http://localhost:8888
web后端根據(jù)用戶訪問(wèn)的URL處理不同的業(yè)務(wù)邏輯
5、路由綁定
通過(guò)app.get()或ap.post()等方法可以把一個(gè)url路徑和一個(gè)/N個(gè)函數(shù)進(jìn)行綁定
app.get( ‘/’,function(req,res,next){
//req:request對(duì)象,保存客戶端發(fā)送請(qǐng)求的一些數(shù)據(jù) === http.request
//res:response對(duì)象,服務(wù)端輸出對(duì)象,提供了一些服務(wù)器端輸出的相關(guān)方法 === http.response
res下面有write、end、statusCode、setHeader、writeHead等屬性和方法
//next:方法,用于執(zhí)行下一個(gè)和路徑匹配的函數(shù)
} )
6.1、直接內(nèi)容輸出
通過(guò)res.send(string)發(fā)送內(nèi)容至客戶端
6.2、通過(guò)模板渲染內(nèi)容
res.render():將會(huì)根據(jù)views中的模板文件進(jìn)行渲染
如果不想使用views文件夾,想自己設(shè)置文件夾名字,那么app.set("views","aaaa");
注:如果想寫(xiě)一個(gè)快速測(cè)試頁(yè),當(dāng)然可以使用res.send(),send這個(gè)函數(shù)將根據(jù)內(nèi)容,自動(dòng)幫我們?cè)O(shè)置了Content-Type頭部和200狀態(tài)碼。send()只能用一次,和end一樣。和end不一樣在哪里?能夠自動(dòng)設(shè)置MIME類型。
7、模板的使用
后端邏輯和頁(yè)面表現(xiàn)分離-即前后端分離
8、模板的配置
var swig = require('swig');
app.engine('html',swig.renderFile);
//定義當(dāng)前應(yīng)用所使用的模板引擎,使用swig.renderFile方法解析為后綴為html的文件
app.set('views','./views');
//設(shè)置模板存放目錄
app.set('view engine','html');
//注冊(cè)模板引擎
swig.setDefault({cache:false});
9、設(shè)置靜態(tài)文件托管目錄
css、img、js等都通過(guò)下面的方法進(jìn)行托管
app.use('/public',express.static(dirname+'/public'));
在public目錄下劃分并且存放好相關(guān)的靜態(tài)資源文件
意思是:當(dāng)用戶訪問(wèn)的url以/public開(kāi)始,那么直接返回對(duì)應(yīng)dirname+'/public'下的文件
10、前后端工作流程:
用戶發(fā)送http請(qǐng)求 -> 這個(gè)請(qǐng)求是一個(gè)url -> 后端解析url,提取信息 -> 找到匹配的規(guī)則 -> 滿足規(guī)則以后,執(zhí)行綁定函數(shù),返回對(duì)應(yīng)內(nèi)容給用戶
劃分:靜態(tài)文件、動(dòng)態(tài)文件處理兩部分
靜態(tài)文件:
訪問(wèn)/public下的文件 -> 靜態(tài)文件 -> 直接讀取指定目錄下的文件,直接渲染,返回給用戶
動(dòng)態(tài)文件:
處理業(yè)務(wù)邏輯,加載模板,解析模板 -> 返回?cái)?shù)據(jù)給用戶
通過(guò)路由匹配來(lái)獲取指定文件內(nèi)容
11、模塊劃分
根據(jù)功能進(jìn)行模塊劃分
前臺(tái)模塊 main.js
后臺(tái)管理模塊 admin.js
API模塊(頁(yè)面使用ajax請(qǐng)求的模塊)api.js
app.js:整個(gè)應(yīng)用的入口文件
//使用app.use()進(jìn)行模塊劃分
app.use('/admin',require('./router/admin')); //請(qǐng)求admin.js,當(dāng)url匹配都/admin,就會(huì)請(qǐng)求./router/admin這個(gè)下面的admin.js文件,下面類似
app.use('/api',require('./router/api')); //請(qǐng)求api.js
app.use('/',require('./router/main')); //請(qǐng)求main.js
admin.js
let express = require('express');
let router = express.Router();
router.get('/',(req,res,next)=>{
res.send('admin');
})
module.exports = router;
12、數(shù)據(jù)庫(kù)連接、表結(jié)構(gòu)Schema定義、Model創(chuàng)建
進(jìn)入到mongoDB安裝目錄下的bin目錄,執(zhí)行命令:
mongod --dbpath=D:\妙味課堂文件夾\190122Nodejs開(kāi)發(fā)博客系統(tǒng)\db --port=27017
定義數(shù)據(jù)庫(kù)存放位置和端口號(hào)
然后連接數(shù)據(jù)庫(kù),這里通過(guò)可視化界面工具來(lái)連接數(shù)據(jù)庫(kù):Robomongo軟件來(lái)連接
13、連接數(shù)據(jù)庫(kù)成功之后,在schemas文件夾下,新建用戶表結(jié)構(gòu),user.js,定義用戶的表
但是這里不是直接操作這個(gè)表結(jié)構(gòu)的,而是通過(guò)model模型類來(lái)對(duì)表進(jìn)行增刪改查,
model模型類:可以理解成對(duì)數(shù)據(jù)操作的封裝,Model中封裝對(duì)數(shù)據(jù)進(jìn)行處理的邏輯業(yè)務(wù)。
14、ajax發(fā)送請(qǐng)求的url地址:url : '/api/user/register',發(fā)送信息到后臺(tái),后臺(tái)進(jìn)行接收前臺(tái)傳過(guò)來(lái)的數(shù)據(jù)
‘a(chǎn)pi/user/register’ 是解析router配置的第一級(jí)路徑'/api'的,然后在二級(jí)router下再匹配對(duì)應(yīng)的 ‘user/register’
對(duì)應(yīng)api.js文件下的 '/user/register' 路由
這里使用post請(qǐng)求,接收前端傳遞過(guò)來(lái)的數(shù)據(jù),使用body-parser中間件
使用:
//在入口文件app.js里面:
//加載body-parser,用來(lái)處理post提交過(guò)來(lái)的數(shù)據(jù)
var bodyParser = require('body-parser');
//bodyparser設(shè)置
app.use( bodyParser.urlencoded({extended: true}) );
這樣在模型類api.js里面,通過(guò)req.body就可以獲取到前端傳遞過(guò)來(lái)的值
router.post('/user/register',(req,res,next)=>{
console.log(req.body); //{ username: 'xiaoxiao', password: '11', repassword: '11' }
res.json(responseData); //將responseData對(duì)象轉(zhuǎn)為json格式,返回給前端
})
就可以獲取到post提交過(guò)來(lái)的數(shù)據(jù),
反正前端通過(guò)post提交表單數(shù)據(jù),那么后端就可以通過(guò)req.body來(lái)獲取到該數(shù)據(jù)
數(shù)據(jù)的key值是input表單的name值,value值是用戶輸入的值
//查詢數(shù)據(jù)庫(kù)中相同用戶名和密碼的記錄是否存在,如果存在則登錄成功
15、獲取到了前臺(tái)傳遞過(guò)來(lái)的數(shù)據(jù)之后,后臺(tái)再進(jìn)入數(shù)據(jù)庫(kù)進(jìn)行操作,分2種情況:
注冊(cè):此時(shí),數(shù)據(jù)庫(kù)如果有該信用戶名,說(shuō)明數(shù)據(jù)庫(kù)已經(jīng)存在,則返回信息給前端,提示用戶名已經(jīng)存在
數(shù)據(jù)庫(kù)如果沒(méi)有注冊(cè)信息,則需要把注冊(cè)信息存入數(shù)據(jù)庫(kù),提示前臺(tái)注冊(cè)成功
這里面就涉及到了對(duì)數(shù)據(jù)的查找,可以使用find或者findOne進(jìn)行查找
沒(méi)有數(shù)據(jù)還要執(zhí)行存儲(chǔ)使用save進(jìn)行操作
16、
Request Headers:是瀏覽器向服務(wù)器發(fā)送請(qǐng)求的時(shí)候,傳遞給服務(wù)端的數(shù)據(jù)
Response Headers:是服務(wù)器發(fā)傳遞給客戶端的數(shù)據(jù)
17、渲染模板的同時(shí)帶入數(shù)據(jù)
res.render('admin/index',{
userCookie:req.userCookie //userCookie可以直接帶入到模板里面,使用{{}}就可以獲取到數(shù)據(jù)
})
18、模板的使用
在index頁(yè)面引入layout頁(yè)面:{% extends 'layout.html' %}
在index頁(yè)面重寫(xiě)layout頁(yè)面內(nèi)容
layout頁(yè)面需要被重寫(xiě)的內(nèi)容使用:{%block main%}{%endblock%}代替
index頁(yè)面,使用語(yǔ)法:
{% block main %}
//這里面是重寫(xiě)的內(nèi)容
{% endblock %}
即把layout頁(yè)面需要被重寫(xiě)的內(nèi)容剪切到index頁(yè)面,然后各自用語(yǔ)法代替,語(yǔ)法如上所示
19、各自路徑的設(shè)置
①、html頁(yè)面里的href路徑,需要寫(xiě)全,即哪個(gè)路由下面的某某路徑,比如
用戶管理
如果是三層,比如:添加分類,那么對(duì)應(yīng)的請(qǐng)求接口為:
router.get('/category/add')
②、與此相對(duì)應(yīng)的,是router下面的路由文件,因?yàn)槁酚晌募热绫旧硪呀?jīng)命名為admin.js,那么下面請(qǐng)求的接口只需要寫(xiě)router.get('/user')即可
③、res.render渲染的模板路徑,也要寫(xiě)全,是哪個(gè)文件夾下的某某文件,比如:
res.render('admin/category_add'),后綴名可以不用寫(xiě)
④添加分類,跳轉(zhuǎn)到添加分類頁(yè)
這種寫(xiě)法默認(rèn)是get請(qǐng)求,所以處理路由就得使用get進(jìn)行:router.get('/category_add')
如果要傳遞數(shù)據(jù)過(guò)去,在表單里面
20、a鏈接點(diǎn)擊實(shí)現(xiàn)post方式提交,
修改
//點(diǎn)擊頁(yè)面會(huì)跳轉(zhuǎn)到category_edit,匹配了get和post路由
router.get('/category_add')、router.post('/category_add')
//此時(shí),form表單可以不需要寫(xiě)action的url地址,因?yàn)檫€是在當(dāng)前頁(yè),當(dāng)前頁(yè)的路由還是/category_add,還是會(huì)匹配到對(duì)應(yīng)的路由,執(zhí)行對(duì)應(yīng)的操作
對(duì)應(yīng)的form表單:
post表單用于有前臺(tái)信息(一般是form表單)傳遞到后臺(tái)、其他情況可以使用get請(qǐng)求
錯(cuò)誤集結(jié):
1、throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))
這種錯(cuò)誤一般是模塊沒(méi)有導(dǎo)出,使用module.exports = 導(dǎo)出即可
2、
報(bào)錯(cuò):Cannot set headers after they are sent to the client
nodejs+express中出現(xiàn)這個(gè)錯(cuò)誤都在路由里,大部分是程序運(yùn)行了res.xxx之后,后面還有和請(qǐng)求,響應(yīng)相關(guān)的操作造成的.
主要是程序運(yùn)行的先后順序沒(méi)理解透,也就是nodejs的一大特性 : 異步.初學(xué)者特別要注意.
-- 理解到了nodejs程序運(yùn)行的先后順序,這個(gè)問(wèn)題就迎刃而解.
-- 還有就是res.xxx操作之后最好不要再有代碼,就算是打印輸出的代碼也寫(xiě)在res.xxx之前,
-- 另外,nodejs程序就算res.xxx響應(yīng)以后,程序還會(huì)繼續(xù)執(zhí)行,return下更好.
3、Error: Failed to lookup view "index" in views directory "/views"
view模板文件目錄找不到
views錯(cuò)誤與否取決你是怎么運(yùn)行app.js,我使用cmd到指定目錄下運(yùn)行app.js就不會(huì)出現(xiàn)這個(gè)問(wèn)題,而使用webstorm直接運(yùn)行app.js就會(huì)出現(xiàn)這個(gè)問(wèn)題。這個(gè)應(yīng)該是文件目錄的問(wèn)題。想要在兩種方式下都可以找到views的方法是使用:
var path = require('path');
app.set('views', path.join(__dirname, 'views'));
4、報(bào)下面的錯(cuò)誤CastError: Cast to ObjectId failed for value "" at path "_id" for model "Category"
是因?yàn)椋簊ubmit提交還是在當(dāng)前頁(yè)面,路由不變,還是會(huì)走router.post('/category_edit'),而form表單加了action="/admin/category_edit",所以估計(jì)是重復(fù)報(bào)錯(cuò),刪掉form表單的action就行了;
以后注意:如果提交的頁(yè)面還是在當(dāng)前頁(yè)面,那么就不用加action了,當(dāng)前頁(yè)面不變,路由自然就會(huì)繼續(xù)執(zhí)行