這是算是一個前端萌新第一次涉及到先後端的全棧開發項目,可能涉及到的技術棧用的並非深刻,對許多中間件的總結所述必然會有所欠缺,但本文旨在淺析整個項目過程,同時去還原深化一些相關的概念。並且第一次寫文章思路排版等不是很明確,有不對的地方歡迎各位大佬們指正!css
項目實質: 一個基於nodejs、express框架及mongoDB數據庫搭建的簡易博客系統html
效果實現 :主體分爲先後頁面,前臺包括用戶註冊登陸面板,文章內容的分頁、分類展現;內容詳情頁有文章內容展現,底部有評論信息展現。後臺管理頁面包括管理首頁、註冊用戶詳細信息、文章分類管理頁、文章分類添加頁、全部文章信息頁、添加文章頁。實現對整站整站全部內容的增刪改查。整站部分頁面用bootstrap組件搭建,自然響應式,可是樣式很通常。前端
頁面預覽 :
node
技術棧 :jquery
-project
db //數據庫文件,存取整站頁面上全部數據
-models //數據庫模型,和schemas下的表結構一一對應
user.js
category.js
content.js
-schemas //表結構,一個js文件對應一張表,定義每張表的數據結構
users.js
categories.js
contents.js
node_modules
-public //靜態資源存放區
css
img
font
-js
jquery.js
bootstrap.js
index.js
-routers //三個路由模塊,分別處理不一樣的業務邏輯
api.js //api模塊;負責處理前臺頁面登陸註冊及提交評論等
main.js //負責接收前臺操做請求、渲染前臺頁面
admin.js //負責接收後臺管理操做請求、渲染後臺頁面
-views //全部瀏覽請求後端返回的頁面都從這裏取
mian
-admin
index.html
layout.html
view.html
app.js //入口文件,運行它就等於開啓了咱們的服務器
package.json //在這裏能夠查詢你安裝的中間件及其版本號複製代碼
/**
* Created by Administrator on 2017/10/24.
*/
//加載express模塊
var express = require("express");
//加載swig模塊
var swig = require("swig");
//加載mongoose模塊,這個中間件是nodejs與mongoDB數據庫的橋樑
var mongoose = require("mongoose");
//加載用戶表模型,模型從表結構構建出來,而後咱們操做模型操做數據
var User = require("./models/user");
//加載kooies模塊,用於在登陸成功後再req中寫入cookie,而後就能夠再刷新或請求頁面時
//將cookie變量傳遞給模板用於渲染驗證
var Cookies = require('cookies');
//建立一個新的服務器,至關於httpcreateServer
var app = express();
//靜態文件資源託管,js css img等,瀏覽器在解析頁面是遇到的全部url都會發送請求給後端,
//咱們不可能在後端給每一個js、css或img的url都設置路由監聽,這樣以/public開頭的請求都會被
//指引到public目錄下去調取資源並返回
app.use("/public",express.static( __dirname+"/public"));
//定義應用使用的模板引擎,第一個參數:所要渲染模板文件的後綴,也是模板引擎的名稱,第二個參數:渲染的方法
app.engine("html",swig.renderFile);
//定義模板文件存放的路徑,第一個參數必須是views,這是模塊內指定的解析字段,第二個參數爲路徑:./表示根目錄
app.set("views","./views");
//註冊使用模板引擎;第一個參數不能變,第二個參數和上面的html一致
app.set("view engine","html");
//設置完就能夠直接在res中渲染html文件了:res.render("index.html",{要渲染的變量})
//第一個參數是相對於views文件夾的路徑
//在開發過程當中要取消模板緩存,便於調試,在模板頁面有任何修改保存後瀏覽器就能同步更新了
swig.setDefaults({cache : false});
//var User = require("./models/user");
//加載bodyparser模塊,用來解析前端post方式提交過來的數據
//詳細文檔:https://github.com/expressjs/body-parser
var bodyparser = require("body-parser");
app.use(bodyparser.urlencoded({extended:true}));
//app.use裏的函數是一個通用接口,全部的頁面的刷新及請求都會執行這個函數
app.use( function(req, res, next) {
req.cookies = new Cookies(req, res);
//在req對象下創建一個cookie屬性,在登陸成功後就會被附上用戶的信息,以後頁面的刷新和
//請求的請求頭裏都會附帶這個cookie發送給後端,且其會一直存在直到退出登陸或關閉瀏覽器,
//固然也能夠設置它的有效時間
req.userInfo = {};
if(req.cookies.get('userInfo')){
var str1 = req.cookies.get('userInfo');
req.userInfo=JSON.parse(str1);
User.findById(req.userInfo._id).then(function(userInfodata){
req.userInfo.isadmin = Boolean(userInfodata.isadmin);
});
}
next();
} );
//分模塊開發,便於代碼管理,分爲前臺展現模塊,後臺管理模塊及邏輯接口模塊
app.use("/admin" ,require("./routers/admin"));
app.use("/" ,require("./routers/main"));
app.use("/api" ,require("./routers/api"));
//連接數據庫,成功以後再開啓端口監聽
mongoose.connect("mongodb://localhost:27017/myBlog");
var db = mongoose.connection;
db.once("open", function () {
console.log("Mongo Connected");
app.listen(8888);
});
db.on("error", console.error.bind(console, "Mongoose Connection Error"));複製代碼
var mongoose = require("mongoose");
module.exports = new mongoose.Schema({
username: String,
password: String,
isadmin:{
type:Boolean,
default:false
}
});複製代碼
var mongoose = require("mongoose");
var userschama = require("../schemas/users");
module.exports = mongoose.model("User",userschama);複製代碼
這樣咱們在路由js文件中根據相對路徑引入user.js用戶表模型就能用mongoose的語法來對這張表的數據進行增刪改查了,之後端登陸驗證代碼爲例:webpack
router.post("/user/login",function(req ,res ){
var username = req.body.username;
//經過body-parser中間件post提交的數據都在req對象下的bodys屬性中
var password = req.body.password;
if(username == ""||password==""){
resdata.code=1;
resdata.message="用戶名和密碼不能爲空!";
res.json(resdata);
return;
}
//User爲引入的用戶信息表模型,根據前端提交的數據做爲第一個參數進行查詢
User.findOne({
username:username,
password:password
},function(err,userinfo){
if(err){
console.log(err);
}
if(!userinfo){
resdata.code = 2;
resdata.message = "用戶名或密碼錯誤!";
res.json(resdata);
return false;
}
resdata.message = "登陸成功!";
resdata.userinfo={
id:userinfo._id ,
username:userinfo.username
};
//登陸成功後給cookie設置對象字符串
req.cookies.set('userInfo', JSON.stringify({
"_id": userinfo._id,
"username": userinfo.username
}));
res.json(resdata);
})
});複製代碼
給渲染文件傳遞變量,模板就會根據變量來決定渲染那些部分git
//渲染首頁
router.get("/", function(req, res) {
res.render('main/index', {
userInfo:req.userInfo
//req.userInfo在入口文件中根據cookie作過統一配置,裏面包含當前用戶是否爲管理員的屬性
});
});複製代碼
在頁面模板中es6
{% if userInfo.isadmin %}
<p>尊敬的管理員! <a href="/admin/"> 點擊這裏</a>進入管理頁面</p>
{% else %}
<p>你好,歡迎光臨個人博客!</p>
{% endif %}複製代碼
在文件目錄中咱們看到,前臺頁面寫了三個,layout,index,view,分別是頭部和側邊欄共用的佈局模板,主頁面及內容詳情頁。這裏用到了模板的繼承,後面會用到分頁的引用,二者差很少,都是將共用的部分寫到layout.html裏面,而後在主頁面:github
layout.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--共用部分-->
{% block content %}
{% endblock %}
<!--共用部分-->
</head>
<body>
</body>
</html>
index.html:
{% extends "layout.html" %}
{% block content %}
<!--不一樣的部分-->
{% endblock %}複製代碼
用戶信息的分頁處理:web
router.get("/user",function(req,res){
<!--page的值在分頁按鈕url的hash值中設定-->
var page = Number(req.query.page||1);
var limit = 8;
var skip = (page-1)*limit;
var total;
var counts;
User.count().then(function(count){
total = Math.ceil(count/limit);
page = Math.max(1,page);
page = Math.min(page,total);
counts = count;
});
<!--limit爲限制獲取的條數,skip爲跳過多少條選取-->
User.find().limit(limit).skip(skip).then(function(users){
res.render("admin/userindex",{
userInfo:req.userInfo,
users:users,
page:page,
total:total,
counts:counts
})
});
});
<!--以後在模板中根據接收到的數據進行渲染:-->
<tr >
<th>用戶ID</th>
<th>用戶名</th>
<th>密碼</th>
<th>是否爲管理員</th>
</tr>
{% for user in users %}
<tr>
<td>{{user._id.toString()}}</td>
<td>{{user.username}}</td>
<td>{{user.password}}</td>
<td>{{user.isadmin}}</td>
</tr>
{% endfor %}複製代碼
文章或分類的添加、修改則在每篇文章或分類的連接hash值鍵入響應的ID,這樣在get頁面的時候,數據才能根據ID來提取響應的信息賦給新頁面渲染。
router.get("/category/edit",function(req,res){
var cateid = req.query.id||"";
//獲取ID,而後根據ID查詢
Category.find({id:cateid}).then(function(cateinfo){
res.render("admin/categoryedit",{
userInfo:req.userInfo ,
name:cateinfo.name
});
});
});
router.post("/category/edit",function(req,res){
var name =req.body.name||"";
var id = req.query.id||"";
if(name==""){
res.render("admin/error",{userInfo:req.userInfo});
return false;
}else{
Category.findOne({_id:id},function(err,info){
if(err){
console.log(err);
}
if(info){
console.log(info);
info.name = name;
info.save();
res.render("admin/success",{userInfo:req.userInfo});
}
});
}
});複製代碼
表字段的關聯與引入,在文章表結構中,其做者跟分類都是與用戶表和分類表關聯的:
var mongoose = require("mongoose");
module.exports = new mongoose.Schema({
title: String,
category : {
type:mongoose.Schema.Types.ObjectId,
ref : "Category"
},
// 分類數據類型爲對象id,關聯了Category,Category爲分類模型中定義的名字,必須一致
composition:{
type: String,
default : ""
},
description :{
type: String,
default : ""
},
user:{
type:mongoose.Schema.Types.ObjectId,
ref : "User"
},
num:{
type:Number,
dafault:0
},
addtime:{
type:Date,
default: new Date()
},
comment:{
type:Array,
default:[]
}
});
//get全部文章主頁面中,用.populate方法就能相對應的值了
router.get("/content",function(req,res){
Content.find().populate(["category","user"]).sort({_id:-1}).then(function(contents){
//console.log(contents);
res.render("admin/content",{
userInfo:req.userInfo,
contents:contents
});
});
});複製代碼
router.get('/pinglun', function(req, res) {
var contentid = req.query.contentid || '';
Content.findOne({
_id: contentid
}).then(function(content) {
resdata.postdata = content;
//resdata.data.comments.reverse();
res.json(resdata);
})
});複製代碼
3.通用模塊處理的時候不要忘了next()函數的執行,如:
//統一返回給前端的數據格式
var resdata;
router.use(function(req,res,next){
resdata = {
code:0,
message:""
};
next();
});複製代碼