zoukankan      html  css  js  c++  java
  • nodejs+express+mongodb搭建博客

    https://github.com/lanleilin/sayHelloBlog

    是可以运行的

    https://github.com/lanleilin/sayHelloBlog

    文件结构如下:

    config存放配置文件,

    lib存放连接数据库文件

    middlewares存放中间件

    public存放静态文件

    views存放模版文件

    routes存放路由文件

    model存放操作数据库文件

    logs存放日志

    index.js主程序

    配置文件 config/default.js

    module.exports = {
      port: 3000,
      session: {
        secret: 'myblog',
        key: 'myblog',
        maxAge: 2592000000
      },
      mongodb: 'mongodb://localhost:27017/myblog'
    };

    lib/mongo.js

    var config = require('config-lite');
    var Mongolass = require('mongolass');
    var mongolass = new Mongolass();
    mongolass.connect(config.mongodb);
    
    var moment = require('moment');
    var objectIdToTimestamp = require('objectid-to-timestamp');
    
    // 根据 id 生成创建时间 created_at
    mongolass.plugin('addCreatedAt', {
      afterFind: function (results) {
        results.forEach(function (item) {
          item.created_at = moment(objectIdToTimestamp(item._id)).format('YYYY-MM-DD HH:mm');
        });
        return results;
      },
      afterFindOne: function (result) {
        if (result) {
          result.created_at = moment(objectIdToTimestamp(result._id)).format('YYYY-MM-DD HH:mm');
        }
        return result;
      }
    });
    
    exports.User = mongolass.model('User', {
      name: { type: 'string' },
      password: { type: 'string' },
      avatar: { type: 'string' },
      gender: { type: 'string', enum: ['m', 'f', 'x'] },
      bio: { type: 'string' }
    });
    exports.User.index({ name: 1 }, { unique: true }).exec();// 根据用户名找到用户,用户名全局唯一
    
    exports.Post = mongolass.model('Post', {
      author: { type: Mongolass.Types.ObjectId },
      title: { type: 'string' },
      content: { type: 'string' },
      pv: { type: 'number' }
    });
    exports.Post.index({ author: 1, _id: -1 }).exec();// 按创建时间降序查看用户的文章列表
    
    exports.Comment = mongolass.model('Comment', {
      author: { type: Mongolass.Types.ObjectId },
      content: { type: 'string' },
      postId: { type: Mongolass.Types.ObjectId }
    });
    exports.Comment.index({ postId: 1, _id: 1 }).exec();// 通过文章 id 获取该文章下所有留言,按留言创建时间升序
    exports.Comment.index({ author: 1, _id: 1 }).exec();// 通过用户 id 和留言 id 删除一个留言

    middlewares/check.js

    module.exports = {
      checkLogin: function checkLogin(req, res, next) {
        if (!req.session.user) {
          req.flash('error', '未登录'); 
          return res.redirect('/signin');
        }
        next();
      },
    
      checkNotLogin: function checkNotLogin(req, res, next) {
        if (req.session.user) {
          req.flash('error', '已登录'); 
          return res.redirect('back');//返回之前的页面
        }
        next();
      }
    };

    models/post.js

    var marked = require('marked');
    var Post = require('../lib/mongo').Post;
    var CommentModel = require('./comments');
    
    // 给 post 添加留言数 commentsCount
    Post.plugin('addCommentsCount', {
      afterFind: function (posts) {
        return Promise.all(posts.map(function (post) {
          return CommentModel.getCommentsCount(post._id).then(function (commentsCount) {
            post.commentsCount = commentsCount;
            return post;
          });
        }));
      },
      afterFindOne: function (post) {
        if (post) {
          return CommentModel.getCommentsCount(post._id).then(function (count) {
            post.commentsCount = count;
            return post;
          });
        }
        return post;
      }
    });
    
    // 将 post 的 content 从 markdown 转换成 html
    Post.plugin('contentToHtml', {
      afterFind: function (posts) {
        return posts.map(function (post) {
          post.content = marked(post.content);
          return post;
        });
      },
      afterFindOne: function (post) {
        if (post) {
          post.content = marked(post.content);
        }
        return post;
      }
    });
    
    module.exports = {
      // 创建一篇文章
      create: function create(post) {
        return Post.create(post).exec();
      },
    
      // 通过文章 id 获取一篇文章
      getPostById: function getPostById(postId) {
        return Post
          .findOne({ _id: postId })
          .populate({ path: 'author', model: 'User' })
          .addCreatedAt()
          .addCommentsCount()
          .contentToHtml()
          .exec();
      },
    
      // 按创建时间降序获取所有用户文章或者某个特定用户的所有文章
      getPosts: function getPosts(author) {
        var query = {};
        if (author) {
          query.author = author;
        }
        return Post
          .find(query)
          .populate({ path: 'author', model: 'User' })
          .sort({ _id: -1 })
          .addCreatedAt()
          .addCommentsCount()
          .contentToHtml()
          .exec();
      },
    
      // 通过文章 id 给 pv 加 1
      incPv: function incPv(postId) {
        return Post
          .update({ _id: postId }, { $inc: { pv: 1 } })
          .exec();
      },
    
      // 通过文章 id 获取一篇原生文章(编辑文章)
      getRawPostById: function getRawPostById(postId) {
        return Post
          .findOne({ _id: postId })
          .populate({ path: 'author', model: 'User' })
          .exec();
      },
    
      // 通过用户 id 和文章 id 更新一篇文章
      updatePostById: function updatePostById(postId, author, data) {
        return Post.update({ author: author, _id: postId }, { $set: data }).exec();
      },
    
      // 通过用户 id 和文章 id 删除一篇文章
      delPostById: function delPostById(postId, author) {
        return Post.remove({ author: author, _id: postId })
          .exec()
          .then(function (res) {
            // 文章删除后,再删除该文章下的所有留言
            if (res.result.ok && res.result.n > 0) {
              return CommentModel.delCommentsByPostId(postId);
            }
          });
      }
    };

    models/users.js

    var User = require('../lib/mongo').User;
    
    module.exports = {
      // 注册一个用户
      create: function create(user) {
        return User.create(user).exec();
      },
    
      // 通过用户名获取用户信息
      getUserByName: function getUserByName(name) {
        return User
          .findOne({ name: name })
          .addCreatedAt()
          .exec();
      }
    };

    models/comment.js

    var marked = require('marked');
    var Comment = require('../lib/mongo').Comment;
    
    // 将 comment 的 content 从 markdown 转换成 html
    Comment.plugin('contentToHtml', {
      afterFind: function (comments) {
        return comments.map(function (comment) {
          comment.content = marked(comment.content);
          return comment;
        });
      }
    });
    
    module.exports = {
      // 创建一个留言
      create: function create(comment) {
        return Comment.create(comment).exec();
      },
    
      // 通过用户 id 和留言 id 删除一个留言
      delCommentById: function delCommentById(commentId, author) {
        return Comment.remove({ author: author, _id: commentId }).exec();
      },
    
      // 通过文章 id 删除该文章下所有留言
      delCommentsByPostId: function delCommentsByPostId(postId) {
        return Comment.remove({ postId: postId }).exec();
      },
    
      // 通过文章 id 获取该文章下所有留言,按留言创建时间升序
      getComments: function getComments(postId) {
        return Comment
          .find({ postId: postId })
          .populate({ path: 'author', model: 'User' })
          .sort({ _id: 1 })
          .addCreatedAt()
          .contentToHtml()
          .exec();
      },
    
      // 通过文章 id 获取该文章下留言数
      getCommentsCount: function getCommentsCount(postId) {
        return Comment.count({ postId: postId }).exec();
      }
    };

    public中存放css文件

    routes/index.js

    module.exports = function (app) {
      app.get('/', function (req, res) {
        res.redirect('/posts');
      });
      app.use('/signup', require('./signup'));
      app.use('/signin', require('./signin'));
      app.use('/signout', require('./signout'));
      app.use('/posts', require('./posts'));
    
      // 404 page
      app.use(function (req, res) {
        if (!res.headersSent) {
          res.render('404');
        }
      });
    };

    routes/post.js

    var express = require('express');
    var router = express.Router();
    
    var PostModel = require('../models/posts');
    var CommentModel = require('../models/comments');
    var checkLogin = require('../middlewares/check').checkLogin;
    
    // GET /posts 所有用户或者特定用户的文章页
    //   eg: GET /posts?author=xxx
    router.get('/', function(req, res, next) {
      var author = req.query.author;
    
      PostModel.getPosts(author)
        .then(function (posts) {
          res.render('posts', {
            posts: posts
          });
        })
        .catch(next);
    });
    
    // GET /posts/create 发表文章页
    router.get('/create', checkLogin, function(req, res, next) {
      res.render('create');
    });
    
    // POST /posts 发表一篇文章
    router.post('/', checkLogin, function(req, res, next) {
      var author = req.session.user._id;
      var title = req.fields.title;
      var content = req.fields.content;
    
      // 校验参数
      try {
        if (!title.length) {
          throw new Error('请填写标题');
        }
        if (!content.length) {
          throw new Error('请填写内容');
        }
      } catch (e) {
        req.flash('error', e.message);
        return res.redirect('back');
      }
    
      var post = {
        author: author,
        title: title,
        content: content,
        pv: 0
      };
    
      PostModel.create(post)
        .then(function (result) {
          // 此 post 是插入 mongodb 后的值,包含 _id
          post = result.ops[0];
          req.flash('success', '发表成功');
          // 发表成功后跳转到该文章页
          res.redirect(`/posts/${post._id}`);
        })
        .catch(next);
    });
    
    // GET /posts/:postId 单独一篇的文章页
    router.get('/:postId', function(req, res, next) {
      var postId = req.params.postId;
      
      Promise.all([
        PostModel.getPostById(postId),// 获取文章信息
        CommentModel.getComments(postId),// 获取该文章所有留言
        PostModel.incPv(postId)// pv 加 1
      ])
      .then(function (result) {
        var post = result[0];
        var comments = result[1];
        if (!post) {
          throw new Error('该文章不存在');
        }
    
        res.render('post', {
          post: post,
          comments: comments
        });
      })
      .catch(next);
    });
    
    // GET /posts/:postId/edit 更新文章页
    router.get('/:postId/edit', checkLogin, function(req, res, next) {
      var postId = req.params.postId;
      var author = req.session.user._id;
    
      PostModel.getRawPostById(postId)
        .then(function (post) {
          if (!post) {
            throw new Error('该文章不存在');
          }
          if (author.toString() !== post.author._id.toString()) {
            throw new Error('权限不足');
          }
          res.render('edit', {
            post: post
          });
        })
        .catch(next);
    });
    
    // POST /posts/:postId/edit 更新一篇文章
    router.post('/:postId/edit', checkLogin, function(req, res, next) {
      var postId = req.params.postId;
      var author = req.session.user._id;
      var title = req.fields.title;
      var content = req.fields.content;
    
      PostModel.updatePostById(postId, author, { title: title, content: content })
        .then(function () {
          req.flash('success', '编辑文章成功');
          // 编辑成功后跳转到上一页
          res.redirect(`/posts/${postId}`);
        })
        .catch(next);
    });
    
    // GET /posts/:postId/remove 删除一篇文章
    router.get('/:postId/remove', checkLogin, function(req, res, next) {
      var postId = req.params.postId;
      var author = req.session.user._id;
    
      PostModel.delPostById(postId, author)
        .then(function () {
          req.flash('success', '删除文章成功');
          // 删除成功后跳转到主页
          res.redirect('/posts');
        })
        .catch(next);
    });
    
    // POST /posts/:postId/comment 创建一条留言
    router.post('/:postId/comment', checkLogin, function(req, res, next) {
      var author = req.session.user._id;
      var postId = req.params.postId;
      var content = req.fields.content;
      var comment = {
        author: author,
        postId: postId,
        content: content
      };
    
      CommentModel.create(comment)
        .then(function () {
          req.flash('success', '留言成功');
          // 留言成功后跳转到上一页
          res.redirect('back');
        })
        .catch(next);
    });
    
    // GET /posts/:postId/comment/:commentId/remove 删除一条留言
    router.get('/:postId/comment/:commentId/remove', checkLogin, function(req, res, next) {
      var commentId = req.params.commentId;
      var author = req.session.user._id;
    
      CommentModel.delCommentById(commentId, author)
        .then(function () {
          req.flash('success', '删除留言成功');
          // 删除成功后跳转到上一页
          res.redirect('back');
        })
        .catch(next);
    });
    
    module.exports = router;

    routes/signin.js

    var sha1 = require('sha1');
    var express = require('express');
    var router = express.Router();
    
    var UserModel = require('../models/users');
    var checkNotLogin = require('../middlewares/check').checkNotLogin;
    
    // GET /signin 登录页
    router.get('/', checkNotLogin, function(req, res, next) {
      res.render('signin');
    });
    
    // POST /signin 用户登录
    router.post('/', checkNotLogin, function(req, res, next) {
      var name = req.fields.name;
      var password = req.fields.password;
    
      UserModel.getUserByName(name)
        .then(function (user) {
          if (!user) {
            req.flash('error', '用户不存在');
            return res.redirect('back');
          }
          // 检查密码是否匹配
          if (sha1(password) !== user.password) {
            req.flash('error', '用户名或密码错误');
            return res.redirect('back');
          }
          req.flash('success', '登录成功');
          // 用户信息写入 session
          delete user.password;
          req.session.user = user;
          // 跳转到主页
          res.redirect('/posts');
        })
        .catch(next);
    });
    
    module.exports = router;

    routes/signout.js

    var express = require('express');
    var router = express.Router();
    
    var checkLogin = require('../middlewares/check').checkLogin;
    
    // GET /signout 登出
    router.get('/', checkLogin, function(req, res, next) {
      // 清空 session 中用户信息
      req.session.user = null;
      req.flash('success', '登出成功');
      // 登出成功后跳转到主页
      res.redirect('/posts');
    });
    
    module.exports = router;

    routes/signup.js

    var fs = require('fs');
    var path = require('path');
    var sha1 = require('sha1');
    var express = require('express');
    var router = express.Router();
    
    var UserModel = require('../models/users');
    var checkNotLogin = require('../middlewares/check').checkNotLogin;
    
    // GET /signup 注册页
    router.get('/', checkNotLogin, function(req, res, next) {
      res.render('signup');
    });
    
    // POST /signup 用户注册
    router.post('/', checkNotLogin, function(req, res, next) {
      var name = req.fields.name;
      var gender = req.fields.gender;
      var bio = req.fields.bio;
      var avatar = req.files.avatar.path.split(path.sep).pop();
      var password = req.fields.password;
      var repassword = req.fields.repassword;
    
      // 校验参数
      try {
        if (!(name.length >= 1 && name.length <= 10)) {
          throw new Error('名字请限制在 1-10 个字符');
        }
        if (['m', 'f', 'x'].indexOf(gender) === -1) {
          throw new Error('性别只能是 m、f 或 x');
        }
        if (!(bio.length >= 1 && bio.length <= 30)) {
          throw new Error('个人简介请限制在 1-30 个字符');
        }
        if (!req.files.avatar.name) {
          throw new Error('缺少头像');
        }
        if (password.length < 6) {
          throw new Error('密码至少 6 个字符');
        }
        if (password !== repassword) {
          throw new Error('两次输入密码不一致');
        }
      } catch (e) {
        // 注册失败,异步删除上传的头像
        fs.unlink(req.files.avatar.path);
        req.flash('error', e.message);
        return res.redirect('/signup');
      }
    
      // 明文密码加密
      password = sha1(password);
    
      // 待写入数据库的用户信息
      var user = {
        name: name,
        password: password,
        gender: gender,
        bio: bio,
        avatar: avatar
      };
      // 用户信息写入数据库
      UserModel.create(user)
        .then(function (result) {
          // 此 user 是插入 mongodb 后的值,包含 _id
          user = result.ops[0];
          // 将用户信息存入 session
          delete user.password;
          req.session.user = user;
          // 写入 flash
          req.flash('success', '注册成功');
          // 跳转到首页
          res.redirect('/posts');
        })
        .catch(function (e) {
          // 注册失败,异步删除上传的头像
          fs.unlink(req.files.avatar.path);
          // 用户名被占用则跳回注册页,而不是错误页
          if (e.message.match('E11000 duplicate key')) {
            req.flash('error', '用户名已被占用');
            return res.redirect('/signup');
          }
          next(e);
        });
    });
    
    module.exports = router;

    index.js

    var path = require('path');
    var express = require('express');
    var session = require('express-session');
    var MongoStore = require('connect-mongo')(session);
    var flash = require('connect-flash');
    var config = require('config-lite');
    var routes = require('./routes');
    var pkg = require('./package');
    var winston = require('winston');
    var expressWinston = require('express-winston');
    
    var app = express();
    
    // 设置模板目录
    app.set('views', path.join(__dirname, 'views'));
    // 设置模板引擎为 ejs
    app.set('view engine', 'ejs');
    
    // 设置静态文件目录
    app.use(express.static(path.join(__dirname, 'public')));
    
    // session 中间件
    app.use(session({
      name: config.session.key,// 设置 cookie 中保存 session id 的字段名称
      secret: config.session.secret,// 通过设置 secret 来计算 hash 值并放在 cookie 中,使产生的 signedCookie 防篡改
      cookie: {
        maxAge: config.session.maxAge// 过期时间,过期后 cookie 中的 session id 自动删除
      },
      store: new MongoStore({// 将 session 存储到 mongodb
        url: config.mongodb// mongodb 地址
      })
    }));
    // flash 中间价,用来显示通知
    app.use(flash());
    // 处理表单及文件上传的中间件
    app.use(require('express-formidable')({
      uploadDir: path.join(__dirname, 'public/img'),// 上传文件目录
      keepExtensions: true// 保留后缀
    }));
    
    // 设置模板全局常量
    app.locals.blog = {
      title: pkg.name,
      description: pkg.description
    };
    
    // 添加模板必需的三个变量
    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();
    });
    
    // 正常请求的日志
    app.use(expressWinston.logger({
      transports: [
        new (winston.transports.Console)({
          json: true,
          colorize: true
        }),
        new winston.transports.File({
          filename: 'logs/success.log'
        })
      ]
    }));
    // 路由
    routes(app);
    // 错误请求的日志
    app.use(expressWinston.errorLogger({
      transports: [
        new winston.transports.Console({
          json: true,
          colorize: true
        }),
        new winston.transports.File({
          filename: 'logs/error.log'
        })
      ]
    }));
    
    // error page
    app.use(function (err, req, res, next) {
      res.render('error', {
        error: err
      });
    });
    
    if (module.parent) {
      module.exports = app;
    } else {
      // 监听端口,启动程序
      app.listen(config.port, function () {
        console.log(`${pkg.name} listening on port ${config.port}`);
      });
    }

    模版文件,模版引擎用的ejs

    比较多,贴一个post.ejs

    <%- include('header') %>
    
    <% posts.forEach(function (post) { %>
      <%- include('components/post-content', { post: post }) %>
    <% }) %>
    
    <%- include('footer') %>

    components/post-content.ejs

    <div class="post-content">
      <div class="ui grid">
        <div class="four wide column">
          <a class="avatar"
             href="/posts?author=<%= post.author._id %>"
             data-title="<%= post.author.name %> | <%= ({m: '男', f: '女', x: '保密'})[post.author.gender] %>"
             data-content="<%= post.author.bio %>">
            <img class="avatar" src="/img/<%= post.author.avatar %>">
          </a>
        </div>
    
        <div class="eight wide column">
          <div class="ui segment">
            <h3><a href="/posts/<%= post._id %>"><%= post.title %></a></h3>
            <pre><%- post.content %></pre>
            <div>
              <span class="tag"><%= post.created_at %></span>
              <span class="tag right">
                <span>浏览(<%= post.pv %>)</span>
                <span>留言(<%= post.commentsCount %>)</span>
                
                <% if (user && post.author._id && user._id.toString() === post.author._id.toString()) { %>
                  <div class="ui inline dropdown">
                    <div class="text"></div>
                    <i class="dropdown icon"></i>
                    <div class="menu">
                      <div class="item"><a href="/posts/<%= post._id %>/edit">编辑</a></div>
                      <div class="item"><a href="/posts/<%= post._id %>/remove">删除</a></div>
                    </div>
                  </div>
                <% } %>
    
              </span>
            </div>
          </div>
        </div>
      </div>
    </div>
  • 相关阅读:
    Linux 的启动流程(转)
    HDU 4686 Arc of Dream (矩阵快速幂)
    自己编写jQuery动态引入js文件插件 (jquery.import.dynamic.script)
    Oracle job procedure 存储过程定时任务
    Spring3 整合MyBatis3 配置多数据源 动态选择SqlSessionFactory
    Spring3 整合Hibernate3.5 动态切换SessionFactory (切换数据库方言)
    Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源 方法
    软件设计之UML—UML的构成[上]
    软件设计之UML—UML中的六大关系
    ActiveMQ 即时通讯服务 浅析
  • 原文地址:https://www.cnblogs.com/rlann/p/6552460.html
Copyright © 2011-2022 走看看