蓝桥云课 Node.js 实现私人笔记本

蓝桥送了我三天会员 不知道过期了以后还能不能看了 所以把教程粘下来

第一章

实验介绍

实验内容
本实验将教大家使用 Node.js 技术完成一个私人笔记本项目。

实验重点部分源码:

wget https://labfile.oss.aliyuncs.com/courses/446/part_of_louNote.zip

知识点
Express 基础
适合人群
本课程难度一般,属于初级课程,适合具有 Node.js 基础的用户,学习 Node.js Web 开发。

实验步骤

本课程将通过使用 Node.js 及与其配合良好的 Web 应用框架 Express 搭建服务端,配合 mongoDB 存储数据,使用 ejs 模板渲染前端页面的方式完成一个简单的私人笔记应用。

这次实验不通过 Express 指令自动生成应用框架,而是选择从 npm init 开始,初始化项目,并一步步实现所有功能。

package.json 配置文件
首先新建一个文件夹 louNote,作为项目的根目录,同时也是我们的项目名称,然后执行命令 - cd louNote && npm init

会出现许多配置项需要你输入,同时这一过程也是引导你创建一个package.json 文件:

package name:项目名称
version:版本号
description:对项目的描述
entry point:项目的入口文件
test command:项目启动时执行脚本文件的命令(默认为 node app.js )
git repository:git 的仓库地址
keywords:项目关键字
author:作者
license:发行项目需要的证书
推荐一路使用回车,使用默认配置,完成配置项填写后,此文件就存在根目录 /louNote 下了。

使用 Express
项目还需要 Express 做服务托管,执行命令 - npm install express --save,

这里的 --save 参数就是将安装的模块及其使用版本号记录到 package.json 文件中。

这样当我们需要将代码提交到代码托管时就不必将 node_modules 下的所有模块都提交,项目部署时执行命令 npm install 即可安装依赖模块。

启动文件

根目录 /louNote 下执行命令

npm install --save body-parser

npm install --save ejs

在 /louNote 下新建 app.js 文件作为项目的启动文件,并敲入以下代码:

var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var crypto = require('crypto');

// 生成一个 express 实例
var app = express();

// 设置视图文件存放目录
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// 设置静态文件存放目录
app.use(express.static(path.join(__dirname, 'public')));

// 解析 urlencoded 请求体必备
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

// '/' 路由
app.get('/', function (req, res) {
  res.render('index', {
    title: '首页',
  });
});

app.listen(8080, function (req, res) {
  console.log('app is running at port 8080');
});

copy
以上就是简单的服务端代码,接着在 /louNote 下新建 /views/index.ejs, 并敲入以下代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title><%= title %></title>
  </head>
  <body>
    <%= title %>
  </body>
</html>

copy
作为首页的视图文件,在命令行中执行服务启动指令 - node app.js,当出现代码中的一段话 app is running at port 8080 时,说明服务已经启动,服务端口为 8080,打开工具,Web 服务 即可成功访问。

当你在自己的主机上操作的时候,要按照上述 localhost:8080 访问。但由于实验楼采用的是线上实验环境,localhost:8080 对应工具栏里 Web 服务打开后页面地址栏中的地址。localhost:8080 只需要打开工具 Web 服务即可,如果出现路由,例如 localhost:8080/login,只需在地址栏后缀上/login 即可。后续步骤、实验都采用相同的操作,不再赘述。

以后就需要通过不断修改 app.js 这个文件来完善这个项目。但在修改过后需要重启应用服务才能看到修改效果,这样会影响到开发效率。解决办法就是安装 supervisor 模块,这个模块能在保存修改的文件后自动重启应用服务,省去了手动重启服务的过程,非常方便。安装 supervisor 模块:

npm install supervisor -g
copy
需要提醒的是,安装 supervisor 模块后,项目的启动指令对应改为 supervisor app.js 。

路由及视图文件

接着需要设计路由控制访问不同的页面,并创建对应的视图文件。

功能设计
项目需要的功能有:

注册
登录
发布笔记
笔记列表
笔记详情
退出登录
对应的路由为:

app.get('/', function (req, res) {
  res.render('index', {
    title: '首页',
  });
});

app.get('/reg', function (req, res) {
  res.render('register', {
    title: '注册',
  });
});

app.post('/reg', function (req, res) {
  // TO DO
});

app.get('/login', function (req, res) {
  res.render('login', {
    title: '登录',
  });
});

app.post('/login', function (req, res) {
  // TO DO
});

app.get('/quit', function (req, res) {
  console.log('退出成功!');
  return res.redirect('/login');
});

app.get('/post', function (req, res) {
  res.render('post', {
    title: '发布',
  });
});

app.post('/post', function (req, res) {
  // TO DO
});

app.get('/detail/:_id', function (req, res) {
  res.render('detail', {
    title: '笔记详情',
  });
});

copy
路由设计完毕,逻辑处理都会在这里完成,我们会在下一节中完善这些逻辑处理功能。接下来我们需要创建对应的视图文件。

创建文件
依据定下功能及设计好的路由,我们在 /louNote/views 目录下创建对应的视图文件:

// 登录 login.ejs // 注册 register.ejs // 发布笔记 post.ejs // 笔记详情
detail.ejs // 统一内容,此页面在下一节中被用作笔记列表。 index.ejs

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title><%= title %></title>
  </head>
  <body>
    <%= title %>
  </body>
</html>

copy
暂时使用统一内容做填充,待第二节中具体实现功能时再将他们补充完整。

使用 Bootstrap

为了使页面设计美观,同时要求不拘泥与设计,重点放在逻辑代码上,项目使用 Bootstrap 响应式框架,同时推荐 bower 管理前端资源依赖。

安装 bower
bower 需要全局安装,才可以使用相关命令,执行命令 npm install -g bower 即可。

安装 Bootstrap、jquery, 下载 common.css
在 /louNote 目录下新建 public 文件夹作为静态资源的存放目录,并在此目录下执行命令 bower install bootstrap jquery,完成后目录下会多出一个 bower_components 文件夹,其中就包含我们需要的 Bootstrap 以及其依赖的 jquery ,bower 能依据配置文件自动下载相关依赖,非常方便,这也是推荐使用 bower 的原因之一。

另外,我们还为页面写了一个 css 文件,在 /public 下创建 /css 文件夹,进入该文件夹,键入以下命令获取 css 文件:

wget https://labfile.oss.aliyuncs.com/courses/446/common.css

使用 ejs

在之前的代码中,已经有使用 ejs 模板的数据标签 <%= %>,这里再学着使用 ejs 模板的 include 机制 改造之前设计好的视图文件。

在之前的步骤中,我们已经安装了 ejs module 。

在 /louNote/views 目录下新建 header.ejs 文件,敲入以下代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title><%= title %></title>
    <link
      rel="stylesheet"
      type="text/css"
      href="/bower_components/bootstrap/dist/css/bootstrap.min.css"
    />
    <link rel="stylesheet" type="text/css" href="/css/common.css" />
    <script src="/bower_components/jquery/dist/jquery.min.js"></script>
    <script src="/bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
  </head>
  <body>
    <div class="text-center page-title"><%= title %></div>
  </body>
</html>

copy
这里将所有需要加载的静态资源文件写到一起,作为所有视图文件的共有页面,再通过 include 机制 引入各个视图中。

改造每个视图文件:

<%- include header %>
        <!-- 待编辑部分 -->
    </body>
</html>

copy
待编辑部分便是今后各个页面不同的部分。

这样就通过 ejs 统一加载了静态资源。

新建文件夹

在 /project 下执行以下命令:

mkdir -p /home/project/louNote/data/db

mkdir -p /home/project/louNote/data/log/mongodb
copy
mkdir -p 命令的含义是:确保目录名称存在,如果目录不存在的就新创建一个。

当上述命令执行完毕之后,就可以在 louNote/data 中看到新建立的两个文件夹。

启动 mongoDB 服务

在 Terminal 中输入如下命令:

sudo mongod --dbpath /home/project/louNote/data/db --logpath /home/project/louNote/data/log/mongodb/mongod.log
copy
在文件区右击空白处选择 Open in Terminal 打开一个新的命令行,输入 mongo 启动 mongoDB 服务。

连接 mongoDB 服务

执行命令 - npm install --save mongoose 安装 mongoose,mongoose 是 mongoDB 的模型工具,方便我们简化代码。

在 /louNote/app.js 中添加代码:

// 引入 mongoose
var mongoose = require('mongoose');

// 使用 mongoose 连接服务
mongoose.connect('mongodb://localhost:27017/notes', {
  useMongoClient: true,
});
mongoose.connection.on('error', console.error.bind(console, '连接数据库失败'));
copy

重启服务后没有出现 “连接数据库失败”即说明连接服务成功,通过 mongoDB 命令可以查看数据内容:

mongo 即可进入数据库进行操作;
show dbs 显示数据库列表;
use notes 切换数据库;
show collections 显示当前数据库中的集合(collection),当然现在还没有数据。

设计数据模型
接着需要设计数据模型,mongoDB 的数据结构类似 json 格式,所以这一步骤也可以简单理解为设计 json 文件格式。

在 /louNote 目录下新建 models/models.js ,并敲入

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

copy
用户数据模型
在 /louNote/models/models.js 文件下定义用户数据模型

var userSchema = new Schema({
  username: String,
  password: String,
  email: String,
  createTime: {
    type: Date,
    default: Date.now,
  },
});

exports.User = mongoose.model('User', userSchema);

copy
笔记数据模型
在 /louNote/models/models.js 文件下定义笔记户数据模型

var noteSchema = new Schema({
  title: String,
  author: String,
  tag: String,
  content: String,
  createTime: {
    type: Date,
    default: Date.now,
  },
});

exports.Note = mongoose.model('Note', noteSchema);

copy
这样,前期的框架搭建及数据库服务就准备完毕,接下来就是控制层的代码实现。请进入下一个实验。

总结

在本节中,我们完成了私人笔记的大致框架的编码,具体内容还需要再下节实验中进行填充。我们简单的操作了 mongoDB,对该数据库作了一定的了解。对于本实验的步骤,请大家一定要仔细阅读,小心编码,实验的步骤到实验二的结束才真正完成,项目才能很好的运行起来,在这之前会有一些小瑕疵,请大家耐心的跟随实验步骤进行操作。

逻辑功能实现和路由限制

逻辑功能实现

在第一节实验中,我们完成了私人笔记的大致框架的搭建和数据库的连接以及简单操作,接下来我们将会将框架里的具体内容一一补充完整,这需要大家对实验一框架进行一定的理解,大致体会每一部分的功能,这样在本节对框架内容进行填充的时候,才不会出现因为理解错误所导致的编码错误。

建立表单

修改 /louNote/views/register.ejs 文件为:

<%- include header %>
        <div class="container">
            <div class="row">
                <div class="col-md-4 group-center">
                    <form method="post">

                        <!-- 用户名表单 -->
                        <div class="form-group">
                            <input type="text" class="form-control" name="username" placeholder="用户名">
                        </div>

                        <!-- 密码表单 -->
                        <div class="form-group">
                            <input type="password" class="form-control" name="password" placeholder="密码">
                        </div>

                           <!-- 确认密码表单 -->
                        <div class="form-group">
                            <input type="password" class="form-control" name="passwordRepeat" placeholder="确认密码">
                        </div>

                        <!-- 注册按钮 -->
                        <button type="submit" class="btn btn-default center-block">注册</button>
                    </form>
                </div>
            </div>
        <div>
    </body>
</html>
copy

编写逻辑代码
修改 /louNote/app.js 文件的 /reg 路由为:

// get 请求
app.get('/reg', function (req, res) {
  res.render('register', {
    title: '注册',
    user: req.session.user,
    page: 'reg',
  });
});

// post 请求
app.post('/reg', function (req, res) {
  var username = req.body.username,
    password = req.body.password,
    passwordRepeat = req.body.passwordRepeat;

  //检查两次输入的密码是否一致
  if (password != passwordRepeat) {
    console.log('两次输入的密码不一致!');
    //重定向到 /reg
    return res.redirect('/reg');
  }

  //检查用户名是否已经存在
  User.findOne({ username: username }, function (err, user) {
    if (err) {
      console.log(err);
      return res.redirect('/reg');
    }

    if (user) {
      console.log('用户名已经存在');
      return res.redirect('/reg');
    }

    //对密码进行 md5 加密
    var md5 = crypto.createHash('md5'),
      md5password = md5.update(password).digest('hex');

    var newUser = new User({
      username: username,
      password: md5password,
    });

    // 判断是否注册成功
    newUser.save(function (err, doc) {
      if (err) {
        console.log(err);
        return res.redirect('/reg');
      }
      console.log('注册成功!');
      newUser.password = null;
      delete newUser.password;
      req.session.user = newUser;
      return res.redirect('/');
    });
  });
});

登录

建立表单
修改 /louNote/views/login.ejs 文件为:

<%- include header %>
        <div class="container">
            <div class="row">
                <div class="col-md-4 group-center">
                    <form method="post">

                        <div class="form-group">
                        <!-- 登录用户名表单 -->
                            <input type="text" class="form-control" name="username" placeholder="用户名">
                        </div>

                        <div class="form-group">
                        <!-- 登录密码表单 -->
                            <input type="password" class="form-control" name="password" placeholder="密码">
                        </div>

                        <!-- 登录按钮 -->
                        <button type="submit" class="btn btn-default center-block">登录</button>
                    </form>
                </div>
            </div>
        </div>
    </body>
</html>
copy

编写逻辑代码
修改 /louNote/app.js 文件中的 /login 路由为:

// get 请求
app.get('/login', function (req, res) {
  res.render('login', {
    title: '登录',
    user: req.session.user,
    page: 'login',
  });
});

// post 请求
app.post('/login', function (req, res) {
  var username = req.body.username,
    password = req.body.password;

  // 检查用户名是否存在
  User.findOne({ username: username }, function (err, user) {
    if (err) {
      console.log(err);
      return next(err);
    }
    if (!user) {
      console.log('用户不存在!');
      return res.redirect('/login');
    }
    //对密码进行 md5 加密
    var md5 = crypto.createHash('md5'),
      md5password = md5.update(password).digest('hex');
    // 进行密码判断
    if (user.password !== md5password) {
      console.log('密码错误!');
      return res.redirect('/login');
    }
    console.log('登录成功!');
    user.password = null;
    delete user.password;
    req.session.user = user;
    return res.redirect('/');
  });
});
copy

会话机制

完成以上两步就实现了项目的注册和登录功能,但运行时会报错,原因有二:

上一个实验的末尾创建了两个数据模型 userSchema 和 noteSchema,我们还没有引入模型就开始数据操作,当然会报错,需要 app.js 文件头部引入模型并实例化:
// 引入模型并实例化
var models = require(’./models/models’);
var User = models.User;
var Note = models.Note;
copy
是为了方便讲解,提前运用 session 机制,但还没有引入相关的模块。接着就开始学习运用 web 开发里很常见的 session 机制。
建立 session 模型
在 Express 框架中需要安装两个模块 express-session 和 connect-mongo,执行命令 npm install --save express-session connect-mongo,并在 app.js 文件中添加以下代码:

// 引入建立 session 必备的模块
var session = require(‘express-session’);
var MongoStore = require(‘connect-mongo’)(session);

// 建立 session 模型

app.use(
  session({
    key: 'session',
    secret: 'Keboard cat',
    cookie: { maxAge: 1000 * 60 * 60 * 24 },
    store: new MongoStore({
      db: 'notes',
      mongooseConnection: mongoose.connection,
    }),
    resave: false,
    saveUninitialized: true,
  })
);
copy

保存会话
项目需要通过 seesion 判断用户的登录状态,所以只需要保存用户的登录后的基本信息,注册和登录的操作就需要保存这一信息,也就是上面代码中的 req.session.user = user 一句。

有了保存会话功能,我们就能通过判断用户的登录状态来显示不同的信息,修改 /louNote/views/header.ejs 文件,在header.ejs文件中,进行用户登录状态的判断,修改为:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title><%= title %></title>
    <link
      rel="stylesheet"
      type="text/css"
      href="/bower_components/bootstrap/dist/css/bootstrap.min.css"
    />
    <link rel="stylesheet" type="text/css" href="/css/common.css" />
    <script src="/bower_components/jquery/dist/jquery.min.js"></script>
    <script src="/bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
  </head>
  <body>
    <div class="clearfix page-nav">
      <!--如果用户已经登录,则显示发布和退出按钮-->
      <% if(user) { %>
      <div class="pull-left nav-username">
        Hello! <a href="/"><%= user.username %></a>
      </div>
      <div class="pull-left"><a href="/post">发表</a></div>
      <div class="pull-right nav-quit"><a href="/quit">退出</a></div>
      <% } else { %><!--如果用户未登录,则显示注册和登录按钮-->
      <% if(page == 'login') { %>
      <div class="pull-right nav-register"><a href="/reg">注册</a></div>
      <% } else { %>
      <div class="pull-right nav-login"><a href="/login">登录</a></div>
      <% } %> <% } %>
    </div>
    <div class="text-center page-title"><%= title %></div>
  </body>
</html>
copy

当用户登录后,就能看到发布和退出按钮,未登录时则看到注册和登录按钮,这也很符合逻辑操作。

发布笔记

建立表单
修改 /louNote/views/post.ejs 文件为:

<%- include header %>
        <div class="container">
            <div class="row">
                <div class="col-md-5 group-center">
                    <form method="post">

                        <div class="form-group">
                        <!-- 笔记标题 -->
                            <input type="text" class="form-control" name="title" placeholder="标题">
                        </div>

                        <div class="form-group">
                        <!-- 笔记标签 -->
                            <input type="text" class="form-control" name="tag" placeholder="标签">
                        </div>

                        <div class="form-group">
                        <!-- 笔记内容 -->
                            <textarea class="form-control" name="content" placeholder="文章内容"></textarea>
                        </div>

                        <!-- 发表按钮 -->
                        <button type="submit" class="btn btn-default center-block">发表</button>
                    </form>
                </div>
            </div>
        </div>
    </body>
</html>
copy

编写逻辑代码
修改 /louNote/app.js 文件中的 /post 路由为:

// get 请求
app.get('/post', function (req, res) {
  res.render('post', {
    title: '发布',
    user: req.session.user,
  });
});

// post 请求
app.post('/post', function (req, res) {
  var note = new Note({
    title: req.body.title,
    author: req.session.user.username,
    tag: req.body.tag,
    content: req.body.content,
  });

  // 检查文章发表状况
  note.save(function (err, doc) {
    if (err) {
      console.log(err);
      return res.redirect('/post');
    }
    console.log('文章发表成功!');
    return res.redirect('/');
  });
});
copy

笔记列表和详情

对于笔记列表,我们修改 /louNote/views/index.ejs 文件 为:

<%- include header %>
        <div class="container">
            <div class="row">
                <% arts.forEach(function(art) { %>
                    <div class="col-xs-8 col-xs-offset-2 group-center">
                        <div class="article-box">
                        <!-- 笔记标题 -->
                            <a href="/detail/<%= art._id %>">
                                <h3 class="text-center"><%= art.title %></h3>
                            </a>

                            <p class="text-center">
                            <!-- 笔记标签 -->
                                <span class="label label-info"><%= art.tag %></span> |
                                <!-- 笔记创建时间 -->
                                <span class="create-time">
                                    <span class="text-bold">时间:</span>
                                    <%= art.createTime %>
                                </span>
                            </p>
                        </div>
                    </div>
                <% }); %>
            </div>
        </div>
    </body>
</html>
copy

这里还运用到了 ejs 模板另一标签 <% %>,可以直接执行 javascript 语句,这里我们遍历了由模板传来的 arts 数据,就生成了所有的笔记列表,笔记列表用于展示用户创建的所有笔记,笔记详情只展示其中一个,所以就不需要这个遍历,将其删去,所以修改笔记详情页面 /louNote/views/detail.ejs 为:

<%- include header %>
    <div class="container">
        <div class="row">
            <div class="col-xs-8 group-center">
                <div class="article-box">
                    <h3 class="text-center"><%= art.title %></h3>
                    <p class="text-center">
                        <span class="label label-info"><%= art.tag %></span> |
                        <span class="create-time">
                            <span class="text-bold">时间:</span>
                                <%= art.createTime %>
                        </span>
                    </p>
                    <p><%= art.content %></p>
                </div>
            </div>
        </div>
    </div>
    </body>
</html>
copy

编辑笔记列表和笔记详情的逻辑代码,修改 /louNote/app.js 文件中的 / 和 /detail 路由为:

// 笔记列表

app.get('/', function (req, res) {
  Note.find({ author: req.session.user.username }).exec(function (err, arts) {
    if (err) {
      console.log(err);
      return res.redirect('/');
    }
    res.render('index', {
      title: '笔记列表',
      user: req.session.user,
      arts: arts,
      //需引入 moment 模块
      //moment: moment(art.createTime).format('MMMM Do YYYY, h:mm:ss a')
    });
  });
});

// 笔记详情
app.get('/detail/:_id', function (req, res) {
  Note.findOne({ _id: req.params._id }).exec(function (err, art) {
    if (err) {
      console.log(err);
      return res.redirect('/');
    }
    if (art) {
      res.render('detail', {
        title: '笔记详情',
        user: req.session.user,
        art: art,
        //需引入 moment 模块
        //moment: moment(art.createTime).format('MMMM Do YYYY, h:mm:ss a')
      });
    }
  });
});
copy

其中我们可以看到两者的区别就是查询条件,一个是 find() 方法,另一个是 findOne() 方法,由字面意义也能看出前者返回所有数据,后者只返回一个数据,这样就分别实现了笔记列表和笔记详情所对应的需求功能。

逻辑设计·

完成以上步骤,项目的基本功能已经初步实现,但还存在一些 bug 需要修复,比如当我们未登录时,访问 localhost:8080 会因为 session 未保存用户登录信息,会报出错误。这就需要我们对访问做出限制。

当用户已登录时,使用户不能通过输入 url 访问登录和注册页面。
当用户未登录时,使用户不能通过输入 url 访问笔记列表页、笔记详情和发布笔记页面。

检测登录状态

新建 /louNote/checkLogin.js 文件,该文件用于判断用户是否已经登录并完成访问限制功能,代码如下:

// 已登录
function login(req, res, next) {
  if (req.session.user) {
    console.log('您已经登录!');
    return res.redirect('back'); //返回之前的页面
  }
  next();
}

// 未登录
function noLogin(req, res, next) {
  if (!req.session.user) {
    console.log('抱歉,您还没有登录!');
    return res.redirect('/login'); //返回登录页面
  }
  next();
}

exports.login = login;
exports.noLogin = noLogin;
copy

修改 app.js 文件,将路由修改为如下的形式。即在每一个路由的逻辑处理函数之前,再添加一个同样的路由,只不过处理函数为 checkLogin.js 提供的两个接口:login 和 noLogin,利用这样的两次路由,即可实现路由限制:

// 引入检测登录文件
var checkLogin = require('./checkLogin.js');

// 在注册和登录的 get 请求前加入已登录判断,即在实际处理的路由之前,添加一个使用 checkLogin 提供的接口对登录状态进行判断的路由
app.get('/reg', checkLogin.login);
app.get('/reg', function (req, res) {
  //原路由处理函数
});

app.get('/login', checkLogin.login);
app.get('/login', function (req, res) {
  //原路由处理函数
});

// 在笔记列表、发布笔记和笔记详情前加入未登录判断
app.get('/', checkLogin.noLogin);
app.get('/', function (req, res) {
  //原路由处理函数
});

app.get('/post', checkLogin.noLogin);
app.get('/post', function (req, res) {
  //原路由处理函数
});

app.get('/detail/:_id', checkLogin.noLogin);
app.get('/detail/:_id', function (req, res) {
  //原路由处理函数
});

copy
这样就基本完成了项目功能,当然还有许多可扩展的功能和可以优化的细节。比如,允许修改、删除和查询笔记,显示笔记的创建时间。推荐大家在实验结束之后按照自己的理想需求进行相关拓展,大胆尝试一下你会有不一样的收获哦。下面就演示一下显示笔记创建时间功能的拓展。

笔记的创建时间
如果想要添加显示笔记的创建时间功能,我们还需要在/louNote 下载 moment 模块:

npm install --save moment
copy
在 app.js 头部引入 var moment = require(‘moment’); , 并将笔记列表和笔记详情逻辑部分的对 moment 的注释去除,然后将 index.ejs 和 detail.ejs 下的 art.createTime 改为 moment。

app.js 完整代码
完成后需要调整 app.js 代码顺序为如下,否则可能因为模块相互间的依赖问题而报错

var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var crypto = require('crypto');
// 引入 mongoose
var mongoose = require('mongoose');
var models = require('./models/models');
var User = models.User;
var Note = models.Note;
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
var checkLogin = require('./checkLogin.js');
//引入 moment.js
var moment = require('moment');
var app = express();

// 设置视图文件存放目录
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// 设置静态文件存放目录
app.use(express.static(path.join(__dirname, 'public')));
// 解析 urlencoded 请求体必备
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// 建立 session 模型
app.use(
  session({
    key: 'session',
    secret: 'Keboard cat',
    cookie: { maxAge: 1000 * 60 * 60 * 24 },
    store: new MongoStore({
      db: 'notes',
      mongooseConnection: mongoose.connection,
    }),
    resave: false,
    saveUninitialized: true,
  })
);

app.get('/reg', checkLogin.login);
// get 请求
app.get('/reg', function (req, res) {
  res.render('register', {
    title: '注册',
    user: req.session.user,
    page: 'reg',
  });
});

// post 请求
app.post('/reg', function (req, res) {
  var username = req.body.username,
    password = req.body.password,
    passwordRepeat = req.body.passwordRepeat;

  //检查两次输入的密码是否一致
  if (password != passwordRepeat) {
    console.log('两次输入的密码不一致!');
    //重定向到 /reg
    return res.redirect('/reg');
  }

  //检查用户名是否已经存在
  User.findOne({ username: username }, function (err, user) {
    if (err) {
      console.log(err);
      return res.redirect('/reg');
    }

    if (user) {
      console.log('用户名已经存在');
      return res.redirect('/reg');
    }

    //对密码进行md5加密
    var md5 = crypto.createHash('md5'),
      md5password = md5.update(password).digest('hex');

    var newUser = new User({
      username: username,
      password: md5password,
    });

    newUser.save(function (err, doc) {
      if (err) {
        console.log(err);
        return res.redirect('/reg');
      }
      console.log('注册成功!');
      newUser.password = null;
      delete newUser.password;
      req.session.user = newUser;
      return res.redirect('/');
    });
  });
});
app.get('/login', checkLogin.login);
app.get('/login', function (req, res) {
  res.render('login', {
    title: '登录',
    user: req.session.user,
    page: 'login',
  });
});

app.post('/login', function (req, res) {
  var username = req.body.username,
    password = req.body.password;

  User.findOne({ username: username }, function (err, user) {
    if (err) {
      console.log(err);
      return next(err);
    }
    if (!user) {
      console.log('用户不存在!');
      return res.redirect('/login');
    }
    //对密码进行md5加密
    var md5 = crypto.createHash('md5'),
      md5password = md5.update(password).digest('hex');
    if (user.password !== md5password) {
      console.log('密码错误!');
      return res.redirect('/login');
    }
    console.log('登录成功!');
    user.password = null;
    delete user.password;
    req.session.user = user;
    return res.redirect('/');
  });
});

app.get('/post', checkLogin.noLogin);
app.get('/post', function (req, res) {
  res.render('post', {
    title: '发布',
    user: req.session.user,
  });
});

app.post('/post', function (req, res) {
  var note = new Note({
    title: req.body.title,
    author: req.session.user.username,
    tag: req.body.tag,
    content: req.body.content,
  });

  note.save(function (err, doc) {
    if (err) {
      console.log(err);
      return res.redirect('/post');
    }
    console.log('文章发表成功!');
    return res.redirect('/');
  });
});
app.get('/', checkLogin.noLogin);
app.get('/', function (req, res) {
  Note.find({ author: req.session.user.username }).exec(function (err, arts) {
    if (err) {
      console.log(err);
      return res.redirect('/');
    }
    res.render('index', {
      title: '笔记列表',
      user: req.session.user,
      arts: arts,
      //需引入 moment 模块
      moment: moment(arts.createTime).format('MMMM Do YYYY, h:mm:ss a'),
    });
  });
});
app.get('/detail/:_id', checkLogin.noLogin);
// 笔记详情
app.get('/detail/:_id', function (req, res) {
  Note.findOne({ _id: req.params._id }).exec(function (err, art) {
    if (err) {
      console.log(err);
      return res.redirect('/');
    }
    if (art) {
      res.render('detail', {
        title: '笔记详情',
        user: req.session.user,
        art: art,
        //需引入 moment 模块
        moment: moment(art.createTime).format('MMMM Do YYYY, h:mm:ss a'),
      });
    }
  });
});

// 使用 mongoose 连接服务
mongoose.connect('mongodb://localhost:27017/notes', {
  useMongoClient: true,
});
mongoose.connection.on('error', console.error.bind(console, '连接数据库失败'));

app.listen(8080, function (req, res) {
  console.log('app is running at port 8080');
});
copy

到此为止,我们使用 Node.js 实现了私人笔记的大部分功能,达到了我们实验的目的。通过这个实验,我们能够掌握 ejs 的书写,express 框架的使用,以及路由限制的实现。本实验内容稍微有一些繁琐,希望大家耐下心来,仔细阅读实验步骤,按照实验步骤的要求进行实验。

猜你喜欢

转载自blog.csdn.net/xiaozhazhazhazha/article/details/121255677