express 是 Node.js 上最流行的 Web 开发框架,正如他的名字一样,使用它我们可以快速的开发一个 Web 应用。我们用 express 来搭建我们的博客,打开命令行,输入:
$ npm install -g express-generator
安装 express 命令行工具,使用它我们可以初始化一个 express 项目。
在命令行中输入:
$ express -e zhufengpeixunblog
$ cd zhufengpeixunblog && npm install
执行结果中会显示生成的文件并提示后续的命令
E:\>express -e zhufengpeixunblog
create : zhufengpeixunblog
create : zhufengpeixunblog/package.json
create : zhufengpeixunblog/app.js
create : zhufengpeixunblog/public
create : zhufengpeixunblog/routes
create : zhufengpeixunblog/routes/index.js
create : zhufengpeixunblog/routes/users.js
create : zhufengpeixunblog/public/images
create : zhufengpeixunblog/public/javascripts
create : zhufengpeixunblog/views
create : zhufengpeixunblog/views/index.ejs
create : zhufengpeixunblog/views/error.ejs
create : zhufengpeixunblog/public/stylesheets
create : zhufengpeixunblog/public/stylesheets/style.css
create : zhufengpeixunblog/bin
create : zhufengpeixunblog/bin/www
install dependencies:
> cd zhufengpeixunblog && npm install
run the app:
> SET DEBUG=zhufengpeixunblog:* & npm start
进入生成的目录并安装依赖
$ E:\>cd zhufengpeixunblog && npm install
debug@2.2.0 node_modules\debug
└── ms@0.7.1
morgan@1.6.1 node_modules\morgan
├── on-headers@1.0.1
├── basic-auth@1.0.3
├── depd@1.0.1
└── on-finished@2.3.0 (ee-first@1.1.1)
cookie-parser@1.3.5 node_modules\cookie-parser
├── cookie@0.1.3
└── cookie-signature@1.0.6
serve-favicon@2.3.0 node_modules\serve-favicon
├── ms@0.7.1
├── etag@1.7.0
├── fresh@0.3.0
└── parseurl@1.3.0
body-parser@1.13.3 node_modules\body-parser
├── bytes@2.1.0
├── content-type@1.0.1
├── depd@1.0.1
├── on-finished@2.3.0 (ee-first@1.1.1)
├── qs@4.0.0
├── iconv-lite@0.4.11
├── http-errors@1.3.1 (statuses@1.2.1, inherits@2.0.1)
├── type-is@1.6.9 (media-typer@0.3.0, mime-types@2.1.7)
└── raw-body@2.1.4 (unpipe@1.0.0, iconv-lite@0.4.12)
express@4.13.3 node_modules\express
├── escape-html@1.0.2
├── array-flatten@1.1.1
├── path-to-regexp@0.1.7
├── merge-descriptors@1.0.0
├── content-type@1.0.1
├── methods@1.1.1
├── utils-merge@1.0.0
├── content-disposition@0.5.0
├── range-parser@1.0.3
├── serve-static@1.10.0
├── vary@1.0.1
├── depd@1.0.1
├── cookie@0.1.3
├── cookie-signature@1.0.6
├── fresh@0.3.0
├── etag@1.7.0
├── on-finished@2.3.0 (ee-first@1.1.1)
├── parseurl@1.3.0
├── qs@4.0.0
├── finalhandler@0.4.0 (unpipe@1.0.0)
├── accepts@1.2.13 (negotiator@0.5.3, mime-types@2.1.7)
├── type-is@1.6.9 (media-typer@0.3.0, mime-types@2.1.7)
├── proxy-addr@1.0.8 (forwarded@0.1.0, ipaddr.js@1.0.1)
设置环境变量并启动服务器,在命令行中执行下列命令
$ SET DEBUG=zhufengpeixunblog:* & npm start
执行完成后会显示
zhufengpeixunblog:server Listening on port 3000 +0ms
在浏览器里访问 http://localhost:3000 就可以显示欢迎页面
Express
Welcome to Express
刚才我们就用express生成器生成了一个使用ejs模板的示例工程。
debug模块
var colors = require('colors');
colors.setTheme({
name: 'cyan',
cost: 'green'
});
module.exports = function(name){
return function(msg){
var start = Date.now();
//判断环境变量中的DEUBG的值是是否匹配当前日志记录器的名称
var env = process.env.DEBUG;
env = env.replace('*','.*')
if(new RegExp(env).test(name)){
console.log(name.name,msg,('+'+(Date.now()-start)+'ms').cost);
}
}
}
使用debug模块
var debug = require('./debug');
//生成一个日志记录器 名字叫warn'
// set DEBUG=logger:*
var logger_warn = debug('logger:warn');
logger_warn('warn');
//生成一个日志记录器 名字叫error
var logger_error = debug('logger:error');
logger_error('error');
npm install
,npm 会检查当前目录下的 package.json,并自动安装所有指定的模块var express = require('express'); 加载node_modules下的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 app = express(); 生成一个express实例 app
// 设置 views 文件夹为存放视图文件的目录, 即存放模板文件的地方,
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs'); 设置视图模板引擎为 ejs。
//设置/public/favicon.ico为favicon图标
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev')); 加载日志中间件。
app.use(bodyParser.json()); 加载解析json的中间件。
//加载解析urlencoded请求体的中间件。
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser()); 加载解析cookie的中间件。
/设置public文件夹为存放静态文件的目录。
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', routes); 根目录的路由
app.use('/users', users); 用户路由
// 捕获404错误,并转发到错误处理器。
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handlers 错误处理器
// development error handler 开发环境下的错误处理
// will print stacktrace 将打印出堆栈信息
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
//生产环境下的错误处理
// 不向用户暴露堆栈信息
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
module.exports = app; 导出app供 bin/www 使用
!/usr/bin/env node 表明是 node 可执行文件。
//引入我们上面app.js导出的app实例
var app = require('../app');
//引入打印调试日志的debug模块,并设置名称。
var debug = require('debug')('zhufengpeixunblog:server');
var http = require('http');
//从环境变量中获取端口号并存放到express中
var port = normalizePort(process.env.PORT || '3000');
//设置端口号
app.set('port', port);
//创建http服务器
var server = http.createServer(app);
//在所有的网络接口中监听提供的端口
server.listen(port);
//监听错误事件
server.on('error', onError);
//启动工程并监听3000端口,成功后打印 。
server.on('listening', onListening);
//把一个端口处理成一个数字或字符串或者false
function normalizePort(val) {
//先试图转成10进制数字
var port = parseInt(val, 10);
if (isNaN(port)) {
// 转不成数字就当作命名管道来处理
return val;
}
if (port >= 0) {
// port number 如果端口大于0就返回端口
return port;
}
//不是命名管道,也不是正常端口就返回false
return false;
}
//服务器的错误事件监听器
function onError(error) {
//如果系统调用不是监听则抛出错误
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
//更友好的处理特定的监听错误
switch (error.code) { 错误编码
case 'EACCES': 没有权限
//没有权限绑定指定的端口
console.error(bind + ' requires elevated privileges');
process.exit(1); 异常退出
break;
case 'EADDRINUSE': 端口被占用
console.error(bind + ' is already in use');
process.exit(1);异常退出
break;
default:
//其它的情况抛出错误并中止进程
throw error;
}
}
//服务器的监听端口成功事件回调
function onListening() {
//取得服务器的地址
var addr = server.address();
//监听地址如果是字符串返回命名管道名,如果是数字返回端口
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
//记录日志
debug('Listening on ' + bind);
}
//导入express模块
var express = require('express');
//生成一个路由实例
var router = express.Router();
//当用户访问根目录也就是 / 的时候执行此回调
router.get('/', function(req, res, next) {
//渲染views/index.ejs模版并显示到浏览器中
res.render('index', { title: 'Express' });
});
//导出这个路由并在app.js中通过app.use('/', routes); 加载
module.exports = router;
在渲染模板时我们传入了一个变量 title 值为 express 字符串,模板引擎会将所有 <%= title %> 替换为 express ,然后将渲染后生成的html显示到浏览器中
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
</body>
</html>
搭建一个简单的具有多人注册、登录、发表文章、登出功能的博客。
我们已经把设计的构想图贴出来了,接下来的任务就是完成路由规划了。路由规划,或者说控制器规划是整个网站的骨架部分,因为它处于整个架构的枢纽位置,相当于各个接口之间的粘合剂,所以应该优先考虑。
注意实现路由的时候为了避免出错,新增加的所有的路径、文件名约定都不要加
s
routes/user.js 中添加下列代码
/**
* 用户注册
*/
router.get('/reg', function (req, res) {
res.render('user/reg', {title: '注册'});
});
/**
* 当填写用户注册信息提交时的处理
*/
router.post('/reg', function (req, res) {
});
/**
* 显示用户登录表单
*/
router.get('/login', function (req, res) {
res.render('user/login', {title: '登录'});
});
/**
* 当填写用户登录信息提交时的处理
*/
router.post('/login', function (req, res) {
});
router.get('/logout', function (req, res) {
});
routes/articles.js 中添加下列代码
router.get('/add', function (req, res) {
res.render('article/add', { title: '发表文章' });
});
router.post('/add', function (req, res) {
});
app.js中添加以下代码
var article = require('./routes/article');
app.use('/article', article);
views下增加以下文件
<%= title %>
<%= title %>
<%= title %>
http://localhost:3000/user/reg
http://localhost:3000/user/login
http://localhost:3000/article/add
$ npm install bower -g
bower init
E:\zhufengpeixunblog>bower init
? name: zhufengpeixunblog
? version: 0.0.0
? description:
? main file:
? what types of modules does this package expose?
? keywords:
? authors: zhangrenyang-t510 <zhang_renyang@>
? license: MIT
? homepage:
? set currently installed components as dependencies? Yes
? add commonly ignored files to ignore list? Yes
{
name: 'zhufengpeixunblog',
version: '0.0.0',
authors: [
'zhangrenyang-t510 <zhang_renyang@>'
],
license: 'MIT',
ignore: [
'**/.*',
'node_modules',
'bower_components',
'test',
'tests'
]
}
? Looks good? Yes
执行完此命令后会生成一个 `bower.json` 文件。
里面内容如下
{"directory":"./public/lib"}
这表示以后bower安装的模块都安装在
./public/lib
下面
$ bower install bootstrap --save
大家可以看到,不但安装了bootstrap,也安装了依赖的jquery
E:\zhufengpeixunblog>bower install bootstrap --save
bower bootstrap#~3.3.5 install bootstrap#3.3.5
bower jquery#>= 1.9.1 install jquery#2.1.4
bootstrap#3.3.5 public\lib\bootstrap
└── jquery#2.1.4
jquery#2.1.4 public\lib\jquery
在views下创建include文件夹
在include下添加包含的头部
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>珠峰博客</title>
<link href="/lib/bootstrap/dist/css/bootstrap.css" rel="stylesheet">
</head>
<body>
<!--最外面的容器nav标签,并添加nav-bar样式类,表示这里面属于导航条 -->
<nav class="navbar navbar-default" role="navigation">
<!--第二步:增加header-->
<div class="navbar-header">
<!--最左侧的,默认不可见 按钮标签里嵌套了三个span的icon。
然后给与navbar-toggle样式类和属性collapse(收起),
点击的时候目标为data-target。当窗口缩小到一定程度,右侧的效果显现。-->
<button type="button" class="navbar-toggle"
data-toggle="collapse" data-target="#menus">
<span class="sr-only">珠峰博客</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="/" class="navbar-brand">珠峰博客</a>
</div>
<!-- 放置其它的导航条 -->
<div class="collapse navbar-collapse" id="menus">
<ul class="nav navbar-nav">
<li class="active"><a href="/user/reg">注册</a></li>
<li><a href="/user/login">登录</a></li>
<li><a href="/article/add">发表文章</a></li>
<li><a href="/user/logout">登出</a></li>
</ul>
</div>
</nav>
在include下添加包含的尾部
<script src="/lib/jquery/dist/jquery.js"></script>
<script src="/lib/bootstrap/dist/js/bootstrap.js"></script>
</body>
</html>
views/index.ejs
<% include include/header.ejs%>
<div class="container">
主页
</div>
<% include include/footer.ejs%>
效果如下
views/user/reg.ejs
<% include ../include/header.ejs%>
<div class="container">
<form action="/user/reg" method="post" role="form"
class="form-horizontal">
<div class="form-group">
<label for="username" class="col-sm-2 control-label">用户名</label>
<div class="input-group col-sm-10">
<div class="input-group-addon">
<span class="glyphicon glyphicon-user"></span> </div>
<input type="text" class="form-control"
id="username" name="username" placeholder="用户名"/>
</div>
</div>
<div class="form-group">
<label for="email" class="col-sm-2 control-label">邮箱</label>
<div class="input-group col-sm-10">
<div class="input-group-addon">
<span class="glyphicon glyphicon-envelope"></span></div>
<input type="email" class="form-control"
name="email" id="email" placeholder="邮箱"/>
</div>
</div>
<div class="form-group">
<label for="password" class="col-sm-2 control-label">密码</label>
<div class="input-group col-sm-10">
<div class="input-group-addon">
<span class="glyphicon glyphicon-lock"></span></div>
<input type="password" class="form-control"
name="password" id="password" placeholder="密码"/>
</div>
</div>
<div class="form-group">
<label for="repassword" class="col-sm-2 control-label">确认密码</label>
<div class="input-group col-sm-10">
<div class="input-group-addon">
<span class="glyphicon glyphicon-lock"></span></div>
<input type="password" class="form-control"
name="repassword" id="repassword" placeholder="确认密码"/>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">提交</button>
<button type="reset" class="btn btn-default">重置</button>
</div>
</div>
</form>
</div>
<% include ../include/footer.ejs%>
效果如下
views/user/login.ejs
<% include ../include/header.ejs%>
<div class="container">
<form action="/user/login" method="post"
role="form" class="form-horizontal">
<div class="form-group">
<label for="username" class="col-sm-2 control-label">用户名</label>
<div class="input-group col-sm-10">
<div class="input-group-addon">
<span class="glyphicon glyphicon-user"></span> </div>
<input type="text" class="form-control"
id="username" name="username" placeholder="用户名"/>
</div>
</div>
<div class="form-group">
<label for="password" class="col-sm-2 control-label">
密码</label>
<div class="input-group col-sm-10">
<div class="input-group-addon">
<span class="glyphicon glyphicon-lock"></span></div>
<input type="password" class="form-control"
name="password" id="password" placeholder="密码"/>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">提交</button>
<button type="reset" class="btn btn-default">重置</button>
</div>
</div>
</form>
</div>
<% include ../include/footer.ejs%>
效果如下
views/article/add.ejs
<% include ../include/header.ejs%>
<div class="container">
<form action="/article/add" method="post"
role="form" class="form-horizontal">
<div class="form-group">
<label for="title" class="col-sm-2 control-label">标题</label>
<div class="col-sm-10">
<input type="text" value="" class="form-control"
id="title" name="title" placeholder="标题"/>
</div>
</div>
<div class="form-group">
<label for="content" class="col-sm-2 control-label">正文</label>
<div class="col-sm-10">
<textarea class="form-control" id="" cols="30" rows="10"
id="content" name="content" placeholder="请输入内容"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">提交</button>
<button type="reset" class="btn btn-default">重置</button>
</div>
</div>
</form>
</div>
<% include ../include/footer.ejs%>
效果如下
安装mongodb模块到node_modules下面并把此配置添加到package.json
文件中
$ npm install mongoose --save
在工程根目录下创建 settings.js
文件,内容如下
module.exports = {
cookieSecret:'zhufengkey', 用于 Cookie 加密与数据库无关
url:"mongodb://123.57.143.189:27017/zhufengblog"
}
在db文件夹下创建文件index.js
,此文件存放着所有的模型
此文件负责向外暴露模型,因为Model赋给了global作为属性,那就意味着只需要引入一次就可以在程序任何地方都可以调用了
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
models = require('./models');
var settings = require('../settings');
mongoose.connect(settings.url);
mongoose.model('User', new Schema({
username:{type:String,required:true},用户名
password:{type:String,required:true},密码
email:{type:String,required:true},邮箱
avatar:{type:String,required:true}头像
}));
mongoose.model('Article', new Schema( {
user:{type:ObjectId,ref:'User'}, 用户
title: String, 标题
content: String, 内容
createAt:{type: Date, default: Date.now()} 创建时间
}));
global.Model = function (modelName) {
return mongoose.model(modelName);
}
require('./db'); //导入db模块
使用 express-session
和 connect-mongo
模块实现了将会话信息存储到mongodb
中。
$ npm install express-session --save
$ npm install connect-mongo --save
var settings = require('./settings');
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
app.use(session({
secret: settings.cookieSecret,//secret 用来防止篡改 cookie
key: settings.db,//key 的值为 cookie 的名字
//设定 cookie 的生存期,这里我们设置 cookie 的生存期为 30 天
cookie: {maxAge: 1000 * 60 * 60 * 24 * 30},
resave:true,
saveUninitialized:true,
//设置它的 store 参数为 MongoStore 实例,把会话信息存储到数据库中,
//以避免重启服务器时会话丢失
store: new MongoStore({
db: settings.db,
host: settings.host,
port: settings.port,
})
}));
添加完了以后我们就可以路由中通过request.session
来操作会话对象了
在routes/users.js
中增加md5加密的工具方法
function md5(val){
return require('crypto').createHash('md5').update(val).digest('hex');
}
注册表单的form中action="/user/reg"
,所以我们要实现用户注册的路由
修改 routes/users.js
router.post('/reg', function (req, res) {
//就是 POST 请求信息解析过后的对象
var user = req.body;//
if(user.password != user.repassword){
req.flash('error','两次输入的密码不一致');
return res.redirect('/user/reg');
}
//由于repassword不需要保存,所以可以删除
delete user.repassword;
//对密码进行md5加密
user.password = md5(user.password);
//得到用户的头像
user.avatar = "https://secure.gravatar.com/avatar/"
+md5(user.email)+"?s=48";
new Model('User')(user).save(function(err,user){
if(err){
req.flash('error',err);
return res.redirect('/user/reg');
}
//用户信息存入 session
req.session.user = user;
//注册成功后返回主页
res.redirect('/');
});
});
router.post('/login', function (req, res) {
var user = req.body;
user.password = md5(user.password);
Model('User').findOne(user,function(err,user){
if(err){
req.flash('error',err);
return res.redirect('/user/login');
}
req.session.user = user;//用户信息存入 session
res.redirect('/');//注册成功后返回主页
});
});
router.get('/logout', function (req, res) {
req.session.user = null;//用户信息存入 session
res.redirect('/');//注册成功后返回主页
});
发表文章表单的form中action="/article/add"
,所以我们要实现发表文章路由
修改 routes/article.js
中的 post('/add')
router.post('/add', function (req, res) {
req.body.user = req.session.user._id;
new Model('Article')(req.body).save(function(err,article){
if(err){
return res.redirect('/article/add');
}
//发表文章成功后返回主页
res.redirect('/');
});
});
我们需要引入 flash 模块来实现页面通知(即成功与错误信息的显示)的功能。
我们所说的 flash 即 connect-flash 模块(https://github.com/jaredhanson/connect-flash),flash 是一个在 session 中用于存储信息的特定区域。信息写入 flash ,下一次显示完毕后即被清除。典型的应用是结合重定向的功能,确保信息是提供给下一个被渲染的页面。
$ npm install connect-flash --save
在app.js中添加调用此模块
var flash = require('connect-flash');
app.use(flash());
在发表文章的路由里放置flash提示信息
router.post('/add', function (req, res) {
req.body.user = req.session.user._id;
new Model('Article')(req.body).save(function(err,article){
if(err){
req.flash('error', '更新文章失败!'); //放置失败信息
return res.redirect('/article/add');
}
req.flash('success', '更新文章成功!'); //放置成功信息
res.redirect('/');//发表文章成功后返回主页
});
});
修改 views/include/header.ejs 在最底部增加以下区域
<div class="container text-center">
<% if (success) { %>
<div class="alert alert-success" role="alert"><%= success %></div>
<% } %>
<% if (error) { %>
<div class="alert alert-danger" role="alert"><%= error %></div>
<% } %>
</div>
在app.js 中增加以下中间件
app.use(function(req,res,next){
res.locals.user = req.session.user;
res.locals.success = req.flash('success').toString();
res.locals.error = req.flash('error').toString();
next();
});
用户是否登陆看到的页面应该是不一样的,所以应该根据用户登陆状态来控制导航菜单的显示状态
修改 views/include/header.ejs
<ul class="nav navbar-nav">
<%
if(!user){%>
<li class="active"><a href="/user/reg">注册</a></li>
<li><a href="/user/login">登录</a></li>
<%}else{
%>
<li><a href="/article/add">发表文章</a></li>
<li><a href="/user/logout">登出</a></li>
<%}%>
</ul>
用户登陆后显示发表文章和登出按钮,用户登陆前显示注册和登录按钮。
我们虽然已经完成了用户注册与登陆的功能,但并不能阻止比如已经登陆的用户访问 http://localhost:3000/user/reg 页面
为此,我们需要为页面设置访问权限。即注册和登陆页面应该阻止已登陆的用户访问
登出及后面我们将要实现的发表页只对已登录的用户开放
如何实现页面权限的控制呢?我们可以把用户登录状态的检查放到路由中间件中,在每个路径前增加路由中间件,即可实现页面权限控制
我们添加checkNotLogin
和 checkLogin
函数来实现这个功能
添加middleware文件夹
在middleware
下添加index.js
文件
middleware/index.js
exports.checkLogin = function(req, res, next) {
if (!req.session.user) {
req.flash('error', '未登录!');
return res.redirect('/user/login');
}
next();
}
exports.checkNotLogin = function(req, res, next) {
if (req.session.user) {
req.flash('error', '已登录!');
return res.redirect('back');//返回之前的页面
}
next();
}
修改 routes/users.js
router.get('/reg',middleware.checkNotLogin, function (req, res) {
res.render('user/reg', {title: '注册'});
});
修改routes/article.js
router.get('/add',middleware.checkLogin, function (req, res) {
res.render('article/add', { title: '发表文章' });
});
凡是不能登陆的中间加入 checkNotLogin
凡是需要登陆的中间加入 checkLogin
views/index.ejs
<% include include/header.ejs%>
<div class="container">
<ul class="media-list">
<%
articles.forEach(function(article){
%>
<li class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="<%=article.user.avatar%>" alt="">
</a>
</div>
<div class="media-body">
<h4 class="media-heading"><a href="/article/detail/<%=article._id%>">
<%=article.title%></a></h4>
<p class="media-left"><%- article.content%></p>
</div>
<div class="media-bottom">
作者:<%=article.user.username%>
发表时间:<%=article.createAt.toLocaleString()%>
</div>
</li>
<%
});
%>
</ul>
</div>
<% include include/footer.ejs%>
routes/index.js
router.get('/', function(req, res, next) {
Model('Article').find({}).populate('user').exec(function(err,articles){
res.render('index', {title: '主页',articles:articles});
});
});
$ npm install markdown -save
routes/index.js
markdown = require('markdown').markdown;
router.get('/', function(req, res, next) {
Model('Article').find({}).populate('user').exec(function(err,articles){
articles.forEach(function (article) {
article.content = markdown.toHTML(article.content);
});
res.render('index', {title: '主页',articles:articles});
});
});
views/index.ejs
<h4 class="media-heading">
<a href="/article/detail/<%=article._id%>"><%=article.title%></a></h4>
routes/article.js
router.get('/detail/:_id', function (req, res) {
Model('Article').findOne({_id:req.params._id},function(err,article){
article.content = markdown.toHTML(article.content);
res.render('article/detail',{title:'查看文章',article:article});
});
});
views/article/detail.ejs
<% include ../include/header.ejs%>
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<%=article.title%>
</div>
<div class="panel-body">
<%-article.content%>
</div>
<div class="panel-footer">
<a href="/article/edit/<%=article._id%>"
class="btn btn-warning">编辑</a>
<a href="/article/delete/<%=article._id%>"
class="btn btn-danger">删除</a>
</div>
</div>
</div>
<% include ../include/footer.ejs%>
views/article/detail.ejs
<a href="/article/delete/<%=article._id%>"
class="btn btn-danger">删除</a>
routes/article.js
router.get('/delete/:_id', function (req, res) {
Model('Article').remove({_id:req.params._id},function(err,result){
if(err){
req.flash('error',err);
res.redirect('back');
}
req.flash('success', '删除文章成功!');
res.redirect('/');//注册成功后返回主页
});
});
views/article/detail.ejs
<a href="/article/edit/<%=article._id%>" class="btn btn-warning">编辑</a>
views/article/add.ejs
<form action="/article/add" method="post" role="form"
class="form-horizontal" enctype="multipart/form-data">
<input type="hidden" value="<%=article._id%>" name="_id"/>
<div class="form-group">
<label for="title" class="col-sm-2 control-label">标题</label>
<div class="col-sm-10">
<input type="text" value="<%=article.title%>" class="form-control"
id="title" name="title" placeholder="标题"/>
</div>
</div>
<div class="form-group">
<label for="content" class="col-sm-2 control-label">正文</label>
<div class="col-sm-10">
<textarea class="form-control" id="" cols="30" rows="10" id="content"
name="content" placeholder="请输入内容" ><%=article.content%></textarea>
</div>
</div>
<div class="form-group">
<label for="img" class="col-sm-2 control-label">图片</label>
<div class="col-sm-10">
<%
if(article.img){
%>
<img src="<%=article.img%>" style="width:100px;height:100px" alt=""/>
<%
}
%>
<input type="file" class="form-control" name="img" id="img"/>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">提交</button>
<button type="reset" class="btn btn-default">重置</button>
</div>
</div>
</form>
routes/article.js
router.post('/add',middleware.checkLogin,upload.single('img'),
function (req, res) {
var _id = req.body._id;
if(_id){
var set = {title:req.body.title,content:req.body.content};
Model('Article').update({_id:_id},{$set:set},function(err,result){
if(err){
req.flash('error',err);
return res.redirect('back');
}
req.flash('success', '更新文章成功!');
res.redirect('/');//注册成功后返回主页
});
}else{
req.body.user = req.session.user._id;
new Model('Article')(req.body).save(function(err,article){
if(err){
req.flash('error',err);
return res.redirect('/article/add');
}
req.flash('success', '发表文章成功!');
res.redirect('/');//注册成功后返回主页
});
}
});
router.get('/edit/:_id', function (req, res) {
Model('Article').findOne({_id:req.params._id},function(err,article){
res.render('article/add',{title:'编辑文章',article:article});
});
});
views/include/header.ejs
<form class="navbar-form navbar-right" role="search" method="get"
action="/article/list/1/2">
<div class="form-group">
<label for="keyword">关键字</label>
<input type="text" name="keyword" id="keyword"
class="form-control" value="<%=keyword%>"/>
</div>
<button type="submit" class="btn btn-default" value="search"
name="searchBtn">搜索</button>
</form>
views/index.ejs
<ul class="pagination">
<%
for(var i=1;i<=totalPage;i++){
%>
<li><a href="/article/list/<%=i%>/<%=pageSize%>?keyword=<%=keyword%>">
<%=i%></a></li>
<%
}
%>
</ul>
routes/index.js
router.get('/', function(req, res, next) {
res.redirect('/article/list/1/2');
});
routes/article.js
router.get('/list/:pageNum/:pageSize',function(req, res, next) {
var pageNum = req.params.pageNum&&req.params.pageNum>0?
parseInt(req.params.pageNum):1;
var pageSize =req.params.pageSize&&req.params.pageSize>0?
parseInt(req.params.pageSize):2;
var query = {};
var searchBtn = req.query.searchBtn;
var keyword = req.query.keyword;
if(searchBtn){
req.session.keyword = keyword;
}
if(req.session.keyword){
query['title'] = new RegExp(req.session.keyword,"i");
}
Model('Article').count(query,function(err,count){
Model('Article').find(query).sort({createAt:-1})
.skip((pageNum-1)*pageSize)
.limit(pageSize).populate('user').exec(function(err,articles){
articles.forEach(function (article) {
article.content = markdown.toHTML(article.content);
});
res.render('index',{
title:'主页',
pageNum:pageNum,
pageSize:pageSize,
keyword:req.session.keyword,
totalPage:Math.ceil(count/pageSize),
articles:articles
});
});
});
});
views/article/detail.ejs
<div class="panel panel-default">
<div class="panel-heading">
评论列表
</div>
<div class="panel-body" style="height:300px;overflow-y: scroll">
<ul class="media-list">
<%
article.comments.forEach(function(comment){
%>
<li class="media">
<div class="media-left">
<a href="#">
<img class="media-object"
src="<%=comment.user.avatar%>" alt="">
</a>
</div>
<div class="media-body">
<p class="media-left"><%- comment.content%></p>
</div>
<div class="media-bottom">
<%=comment.user.username%>
<%=comment.createAt.toLocaleString()%>
</div>
</li>
<%
});
%>
</ul>
</div>
</div>
<div class="panel panel-default">
<form action="/article/comment" method="post">
<input type="hidden" value="<%=article._id%>" name="_id"/>
<div class="panel-body">
<textarea class="form-control" id="" cols="30" rows="10"
id="content" name="content" placeholder="请输入评论" ></textarea>
</div>
<div class="panel-footer">
<button type="submit" class="btn btn-default">提交</button>
</div>
</form>
</div>
db/models.js
comments: [{user:{type:ObjectId,ref:'User'},content:String,
createAt:{type: Date, default: Date.now}}],
routes/article.js
router.post('/comment',middleware.checkLogin, function (req, res) {
var user = req.session.user;
Model('Article').update({_id:req.body._id},
{$push:{comments:{user:user._id,content:req.body.content}}},
function(err,result){
if(err){
req.flash('error',err);
return res.redirect('back');
}
req.flash('success', '评论成功!');
res.redirect('back');
});
});
$ npm install async --save
db/models.js
pv: {type:Number,default:0},
routes/article.js
var async = require('async');
router.get('/detail/:_id', function (req, res) {
async.parallel([function (callback) {
Model('Article').findOne({_id: req.params._id})
.populate('user').populate('comments.user')
.exec(function (err, article) {
article.content = markdown.toHTML(article.content);
callback(err, article);
});
}, function (callback) {
Model('Article').update({_id: req.params._id}
,{$inc: {pv: 1}}, callback);
}], function (err, result) {
if (err) {
req.flash('error', err);
res.redirect('back');
}
res.render('article/detail',
{title: '查看文章', article: result[0]});
});
});
views/index.ejs
阅读:<%= article.pv %>|
评论:<%= article.comments.length%>
app.js
// catch 404 and forward to error handler
app.use(function(req, res, next) {
res.render("404");
});
views/404.ejs
<% include include/header.ejs%>
<div class="container">
<img src="" alt="404"/>
</div>
<% include include/footer.ejs%>
现在给博客增加日志,实现访问日志(access.log)和错误日志(error.log)功能 把日志保存为日志文件 app.js
//正常日志
var accessLog = fs.createWriteStream('access.log', {flags: 'a'});
app.use(logger('dev',{stream: accessLog}));
//错误日志
var errorLog = fs.createWriteStream('error.log', {flags: 'a'});
app.use(function (err, req, res, next) {
var meta = '[' + new Date() + '] ' + req.url + '\n';
errorLog.write(meta + err.stack + '\n');
next();
});
可用qq,不能126 163等
点击右上角的创建app按钮
要授权github登陆