本文共 55962 字,大约阅读时间需要 186 分钟。
个人博客系统
欢迎访问我的博客~
技术 | 版本 |
---|---|
Node | ^14.3.0 |
ejs | ^3.1.3 |
express | ^4.17.1 |
cookie-session | ^1.4.0 |
mysql | ^2.18.1 |
技术 | 版本 |
---|---|
VSCode | ^1.47.3.0 |
MySql | ^8.0.12 |
PhpStudy | ^8.1.0.7 |
在终端输入命令
npm init
会生成一个 package.json
文件
npm i expressnpm i ejsnpm i mysql
会生成一个 package-lock.json
文件
/** * 入口函数 */const express = require("express");// 创建主应用const app = express();// 模板引擎的设置app.set("view engine", "html");app.set("views", `${ __dirname}/views`);app.engine("html", require("ejs").renderFile); // 用ejs模板渲染html// 静态资源配置app.use(express.static("static"));// 监听服务器app.listen(3000);
修改 package.json
文件
"scripts": { "test": "echo \"Error: no test specified\" && exit 1"},
自定义启动命令
把上面的代码改成以下代码
"scripts": { "start": "node index.js"},
静态资源目录
里面存放 css js img 等等的静态资源
模板目录
里面存放模板文件
子应用目录
将我们的程序进行模块化管理,每一个模块都将视为一个子应用
中间件目录
与路由器配套的一些操作方法,在路由加载页面之前获取所需要的一些数据
数据模型目录
操作数据库的一些文件
从数据库中进行增删改查,把得到的一些数据返回给中间件,由中间件返回给路由,由路由进行页面的渲染和加载,最后返回给客户端
在views目录和static目录下导入文件
导入完成之后可以把重复的代码提取出来,进行简化处理
<%- include('header.html') -%>html代码不重复的部分<%- include('footer.html') -%>
/** * 首页子应用(首页路由) */const express = require("express");// 首页子应用const indexApp = express();indexApp.get("/", (req, res) => { res.render("index");});// 把这个子应用导出去module.exports = indexApp;
然后在myblog下的 index.js
中调用首页子应用
// 调用首页子应用app.use(/\/(index)?/, require("./router/index"));
启动服务
node index.js
浏览器输入127.0.0.1:3000 出现以下页面说明搭建成功
在model目录下新建 model.js
文件
const mysql = require("mysql");/** * 数据模型的基类 * 封装了数据库操作 */module.exports = class Model { // 连接对象 static conn = null; /** * 数据库连接方法 */ static connection() { Model.conn = mysql.createConnection({ host: "127.0.0.1", user: "root", password: "lijiazhao123", database: "blog", }); Model.conn.connect((err) => { if (err) { console.log(`数据库连接失败:${ err.message}`); } }); } /** * 关闭数据库连接 */ static end() { if (null != Model.conn) { Model.conn.end(); } } /** * 通用查询方法 * @param {string} sql 要执行的SQL语句 * @param {Array} params 给SQL语句的占位符进行赋值的参数数组 */ static query(sql, params = []) { return new Promise((resolve, reject) => { this.connection(); Model.conn.query(sql, params, (err, results) => { if (err) { reject(err); } else { resolve(results); } }); this.end(); }); }};
在model目录下新建 article.js
文件
/** * 文章数据模型 */module.exports = class Article extends require("./model") { /** * * @param {integer} num 条目数 */ static getHot(num) { return new Promise((resolve, reject) => { let sql = "SELECT id,title,content,`time` FROM article WHERE hot = 1 LIMIT ?"; this.query(sql, num) .then((results) => { resolve(results); }) .catch((err) => { console.log(`获取热门文章失败:${ err.message}`); reject(err); }); }); }};
在middleware目录下新建 article.js
文件
const Article = require("../model/article");/** * 文章中间件 */module.exports = { /** * 获取热门文章 */ getHot: (req, res, next) => { Article.getHot(3) .then((results) => { req.hots = results; next(); }) .catch((err) => { next(err); }); },};
再由中间件返回给路由
将router目录下的 index.js
进行修改
/** * 首页子应用(首页路由) */const express = require("express");const article = require("../middleware/article");// 首页子应用const indexApp = express();indexApp.get("/", [article.getHot], (req, res) => { res.render("index", { hots: req.hots });});// 把这个子应用导出去module.exports = indexApp;
启动服务显示以下页面
发现这个时间的格式有点别扭,修改为本地时间
<%= hot.time.toLocaleString() %>
发现这个内容有点别扭
<%= hot.content.replace(/<[^>]+>/g,"").substring(0,100) %>
修改后如下图:
数据库查询
在model目录下的 article.js
文件中新增以下方法
/** * 获取文章列表 */ static getList() { return new Promise((resolve, reject) => { let sql = "SELECT id,title,content,`time` FROM article ORDER BY time DESC"; this.query(sql) .then((results) => { resolve(results); }) .catch((err) => { console.log(`获取文章列表失败:${ err.message}`); reject(err); }); }); }
把查询到的数据交给中间件
在middleware目录下的 article.js
文件中新增以下方法
/** * 获取最新文章 */ getList: (req, res, next) => { Article.getList() .then((results) => { req.articles = results; next(); }) .catch((err) => { next(err); }); },
在由中间件交给路由渲染到页面中
在router目录下的 index.js
文件中修改以下代码
indexApp.get("/", [article.getHot, article.getList], (req, res) => { let { hots, articles } = req; res.render("index", { hots, articles });});
开启服务,出现以下页面
数据库查询
在model目录下新建 category.js
文件
/** * 文章类目数据模型 */module.exports = class Category extends require("./model") { /** * 获取文章类目列表 */ static getList() { return new Promise((resolve, reject) => { let sql = "SELECT id,`name` FROM category ORDER BY `index` DESC"; this.query(sql) .then((results) => { resolve(results); }) .catch((err) => { console.log(`获取文章类目列表失败:${ err.message}`); reject(err); }); }); }};
把查询到的数据交给中间件
在middlewar目录下新建 category.js
文件
const Category = require("../model/category");/** * 文章类目中间件 */module.exports = { /** * 获取文章类目列表 */ getList: (req, res, next) => { Category.getList() .then((results) => { req.categories = results; next(); }) .catch((err) => { next(err); }); },};
在由中间件交给路由渲染到页面中
修改router目录下的 index.js
文件
/** * 首页子应用(首页路由) */const express = require("express");const article = require("../middleware/article");const category = require("../middleware/category");// 首页子应用const indexApp = express();indexApp.get( "/", [article.getHot, article.getList, category.getList], (req, res) => { let { hots, articles, categories } = req; res.render("index", { hots, articles, categories }); });// 把这个子应用导出去module.exports = indexApp;
修改views下的 header.html
文件
用ejs模板来显示路由渲染过来的数据
<% categories.forEach(category => { %>
启动服务。显示页面如下
在model目录下的 article.js
文件中新增方法
/** * 获取指定类目下的文章列表 * @param {integer} id 类目编号 */ static getListByCategoryId(id) { return new Promise((resolve, reject) => { let sql = "SELECT id,title,content,`time` FROM article WHERE category_id = ? ORDER BY time DESC"; this.query(sql, id) .then((results) => { resolve(results); }) .catch((err) => { console.log(`获取指定类目下的文章列表失败:${ err.message}`); reject(err); }); }); }
在middleware目录下的 article.js
文件新增属性
/** * 获取指定类目下的文章 */ getListByCategoryId: (req, res, next) => { /** * http://127.0.0.1:3000/article/list/1 * 获取路由下的id - 此时id为1 */ let id = req.params.id; Article.getListByCategoryId(id) .then((results) => { req.articles = results; next(); }) .catch((err) => { next(err); }); },
在router目录下新建 article.js
文件
/** * 文章子应用 */const express = require("express");const article = require("../middleware/article");const category = require("../middleware/category");// 文章子应用const articleApp = express();articleApp.get( "/list/:id", [article.getListByCategoryId, category.getList], (req, res) => { let { articles, categories } = req; res.render("list", { articles, categories }); });module.exports = articleApp;
修改views目录下的 list.html
文件
<% articles.forEach(article => { %> <% }) %>
最后在myblog目录下的 index.js
入口函数中新增
app.use("/article", require("./router/article"));
启动服务,浏览器访问 http://127.0.0.1:3000/article/list/1 ,出现以下页面
虽然现在输入url网址能够访问到指定类目列表,但是点击类目列表却不能访问
这时候应该去 header.html
模板文件中修改 href 路由
<% categories.forEach(category => { %>
然后就是修改当前栏目后面显示的数据
通过id查找数据库中的name,然后进行数据显示
在model目录下的 category.js
文件中新增方法
/** * 获取指定编号的类目详情 * @param {integer} id 类目编号 */ static getCategoryById(id) { return new Promise((resolve, reject) => { let sql = "SELECT id,`name`,`index` FROM category WHERE id = ?"; this.query(sql, id) .then((results) => { resolve(results[0]); }) .catch((err) => { console.log(`获取指定编号的类目详情失败:${ err.message}`); reject(err); }); }); }
在middleware目录下新增属性
/** * 获取指定的类目详情 */ getCategoryById: (req, res, next) => { let id = req.params.id; Category.getCategoryById(id) .then((results) => { req.category = results; next(); }) .catch((err) => { next(err); }); },
修改router目录下的 article.js
文件
articleApp.get( "/list/:id", [article.getListByCategoryId, category.getList, category.getCategoryById], (req, res) => { let { articles, categories, category } = req; res.render("list", { articles, categories, category }); });
修改views目录下的 list.html
模板文件
当前栏目:<%= category.name %>
启动服务,出现以下页面
在model目录下的 article.js
文件下新增方法
/** * 获取指定关键词的文章列表 * @param {integer} keyword 搜索内容 */ static getListByKeyword(keyword) { return new Promise((resolve, reject) => { let sql = "SELECT id,title,content,`time` FROM article WHERE title LIKE ? ORDER BY time DESC"; this.query(sql, `%${ keyword}%`) .then((results) => { resolve(results); }) .catch((err) => { console.log(`获取指定关键词的文章列表失败:${ err.message}`); reject(err); }); }); }
在middleware目录下的 article.js
文件中新增属性
/** * 获取指定类目下的文章 */ getListByKeyword: (req, res, next) => { /** * 要获取表单的keyword */ let keyword = req.query.keyword; Article.getListByKeyword(keyword) .then((results) => { req.articles = results; next(); }) .catch((err) => { next(err); }); },
在router目录下新建文件 search.js
/** * 搜索子应用 */const express = require("express");const article = require("../middleware/article");const category = require("../middleware/category");// 文章子应用const searchApp = express();searchApp.get("/", [article.getListByKeyword, category.getList], (req, res) => { let { articles, categories } = req; res.render("search", { articles, categories, keyword: req.query.keyword });});module.exports = searchApp;
修改views目录下的 search.html
模板文件
<%- include("header.html") -%> <%- include('footer.html') -%>
修改views目录下的 header.html
文件的form表单
启动服务,搜索css出现以下页面
在router目录下新建 article.js
文件
// 文章详情页articleApp.get("/:id", (req, res) => { let { categories } = req; res.render("article", { categories });});
启动服务,在浏览器输入url网址 http://127.0.0.1:3000/article/1 会出现以下页面
在model目录下的 article.js
文件中新增方法
/** * 获取指定文章的详情 * @param {integer} id 文章编号 */ static getArticleById(id) { return new Promise((resolve, reject) => { let sql = "SELECT a.id,a.title,a.content,a.`time`,a.hits,a.category_id,c.`name` FROM article a,category c WHERE a.id = ? AND a.category_id = c.id"; this.query(sql, id) .then((results) => { resolve(results[0]); }) .catch((err) => { console.log(`获取指定文章的详情失败:${ err.message}`); reject(err); }); }); }
在middleware目录下的 article.js
文件中新增属性
/** * 获取指定文章的详情 */ getArticleById: (req, res, next) => { let id = req.params.id; Article.getArticleById(id) .then((results) => { req.article = results; next(); }) .catch((err) => { next(err); }); },
修改router目录下的 article.js
文件
// 文章详情页articleApp.get("/:id", [article.getArticleById], (req, res) => { let { categories, article } = req; res.render("article", { categories, article });});
修改views目录下的 article.html
文件
<%- include("header.html") -%><%- include('footer.html') -%><%= article.title %>
发表时间:<%= article.time.toLocaleString() %> 点击:<%= article.hits %>
<%- article.content %>
注意:<%- article.content %> 用 ‘-’ 可以解析HTML代码
然后更改各文件路由
按需求来更改路由即可
参考路由
href="/article/<%= article.id %>"
启动服务,基本完成文章跳转功能
在model目录下新建 tab.js
文件
/** * 标签数据模型 */module.exports = class Tab extends require("./model") { /** * 获取指定文章的标签列表 * @param {integer} id 文章编号 */ static getListByArticleId(id) { return new Promise((resolve, reject) => { let sql = "SELECT id,`name` FROM tabs WHERE article_id = ?"; this.query(sql, id) .then((results) => { resolve(results); }) .catch((err) => { console.log(`获取指定文章的标签列表失败:${ err.message}`); reject(err); }); }); }};
在middleware目录下的 article.js
文件下新增属性
/** * 获取指定文章的标签列表 */ getListByArticleId: (req, res, next) => { let id = req.params.id; Tab.getListByArticleId(id) .then((results) => { req.tabs = results; next(); }) .catch((err) => { next(err); }); },
修改router目录下的 article.js
文件
// 文章详情页articleApp.get( "/:id", [article.getArticleById, article.getListByArticleId], (req, res) => { let { categories, article, tabs } = req; res.render("article", { categories, article, tabs }); });
修改views目录下的 article.js
文件
开启服务,出现以下页面
在model目录下的 article.js
文件中新建两个方法
/** * 获取上一篇文章 * @param {integer} id 当前文章编号 */ static getPrevArticle(id) { return new Promise((resolve, reject) => { let sql = "SELECT id,title FROM article WHERE id < ? ORDER BY id DESC LIMIT 1"; this.query(sql, id) .then((results) => { resolve(results[0]); }) .catch((err) => { console.log(`获取上一篇文章失败:${ err.message}`); reject(err); }); }); } /** * 获取下一篇文章 * @param {integer} id 当前文章编号 */ static getNextArticle(id) { return new Promise((resolve, reject) => { let sql = "SELECT id,title FROM article WHERE id > ? ORDER BY id ASC LIMIT 1"; this.query(sql, id) .then((results) => { resolve(results[0]); }) .catch((err) => { console.log(`获取下一篇文章失败:${ err.message}`); reject(err); }); }); }
在middleware目录下的 article.js
文件中新增两个属性
/** * 获取上一篇文章 */ getPrevArticle: (req, res, next) => { let id = req.params.id; Article.getPrevArticle(id) .then((results) => { req.prev = results; next(); }) .catch((err) => { next(err); }); }, /** * 获取下一篇文章 */ getNextArticle: (req, res, next) => { let id = req.params.id; Article.getNextArticle(id) .then((results) => { req.next = results; next(); }) .catch((err) => { next(err); }); },
修改router目录下的 article.js
文件
// 文章详情页articleApp.get( "/:id", [ article.getArticleById, article.getListByArticleId, article.getPrevArticle, article.getNextArticle, ], (req, res) => { let { categories, article, tabs, prev, next } = req; res.render("article", { categories, article, tabs, prev, next }); });
修改views目录下的 article.html
文件
开启服务,页面完成以下效果
首先在router目录下新建 login.js
文件
/** * 登录子应用(首页路由) */const express = require("express");// 登录子应用const loginApp = express();// 加载登录页面loginApp.get("/", (req, res) => { res.render("login");});// 把这个子应用导出去module.exports = loginApp;
在myblog目录下的 index.js
文件中调用登录子应用
// 调用登录子应用app.use("/login", require("./router/login"));
在model目录下新建 user.js
文件
/** * 用户数据模型 */module.exports = class User extends require("./model") { /** * 用户登录 * @param {string} username 登录账号 * @param {string} password 登录密码 */ static login(username, password) { return new Promise((resolve, reject) => { let sql = "SELECT id,username FROM `user` WHERE username=? AND `password`=?"; this.query(sql, [username, password]) .then((results) => { resolve(results[0]); }) .catch((err) => { console.log(`登录失败:${ err.message}`); reject(err); }); }); }};
因为用户登录不用中间件,所以直接在router目录下的 login.js
文件中实现登录操作
// 实现登录操作loginApp.post("/", (req, res, next) => { let { username, password } = req.body; User.login(username, password) .then((results) => { if (results) { res.redirect("/"); } else { res.render("login", { msg: "登录失败!用户名或密码错误" }); } }) .catch((err) => { next(err); });});
把登录失败的数据映射到页面上
修改在views目录下的 login.html
文件
<%= msg %>
在myblog下的 index.js
文件中进行post请求处理
// POST请求处理app.use(express.urlencoded({ extended: true }));
开启服务,出现以下效果
[
安装cookie-session模块
npm i cookie-session
在myblog下的 index.js
文件中引入cookie-session
const session = require("cookie-session");
配置session
// SESSION配置app.use( session({ keys: ["secret"], maxAge: 1000 * 60 * 30, // cookie的生命周期 }));
修改router目录下的 login.js
文件的登录操作功能
用户登录后会把从数据库查询到的结果results保存到session的user中
// 实现登录操作loginApp.post("/", (req, res, next) => { let { username, password } = req.body; User.login(username, password) .then((results) => { if (results) { // session存储(key=value) req.session.user = results; res.redirect("/"); } else { res.render("login", { msg: "登录失败!用户名或密码错误" }); } }) .catch((err) => { next(err); });});
在middleware目录下新建 auth.js
文件
/** * 权限中间件 */module.exports = { /** * 从session中读取用户 */ getUser: (req, res, next) => { // 从session中读取数据 req.user = req.session.user; next(); },};
在 article.js
、 index.js
、 search.js
文件中都加入user,目的是为了使未登录的用户不能访问这些页面。
const auth = require("../middleware/auth");articleApp.use(category.getList, auth.getUser);// 文章列表页articleApp.get( "/list/:id", [article.getListByCategoryId, category.getCategoryById], (req, res) => { let { articles, categories, category, user } = req; res.render("list", { articles, categories, category, user }); });// 文章详情页articleApp.get( "/:id", [ article.getArticleById, article.getListByArticleId, article.getPrevArticle, article.getNextArticle, ], (req, res) => { let { categories, article, tabs, prev, next, user } = req; res.render("article", { categories, article, tabs, prev, next, user }); });
const auth = require("../middleware/auth");indexApp.use(auth.getUser);// 加载首页页面indexApp.get( "/", [article.getHot, article.getList, category.getList], (req, res) => { let { hots, articles, categories, user } = req; res.render("index", { hots, articles, categories, user }); });
const auth = require("../middleware/auth");searchApp.use(auth.getUser);searchApp.get("/", [article.getListByKeyword, category.getList], (req, res) => { let { articles, categories, user } = req; res.render("search", { articles, categories, keyword: req.query.keyword, user, });});
修改views目录下的 header.html
文件,加入判断
如果user存在,说明已登录,则显示用户数据
如果user不存在,说明未登录,则显示登录
启动服务,用户未登录显示以下效果
用户已登录显示以下效果
在myblog目录下的 index.js
文件中实现退出登录
// 退出登录app.get("/user/logout", (req, res) => { req.session.user = null; res.render("login", { msg: "退出成功" });});
启动服务,实现以下效果
在views目录下新建admin后台管理目录
把需要的资源复制进来
同样,在static目录下新建admin目录
把需要的资源复制进来
把 pv.json
文件复制到myblog目录下
为了方便,把代码重复的部分提取到 header.html
模板中
然后用ejs模板引入即可
在router目录下新建admin目录,在router/admin目录下新建 index.js
文件
/** * 后台首页 */const express = require("express");const indexApp = express();indexApp.get("/", (req, res) => { res.render("admin/index");});module.exports = indexApp;
在myblog目录下的 index.js
文件中调用后台首页
// 调用后台首页app.use(/\/admin\/(index)?/, require("./router/admin/index"));
在middleware目录下的 auth.js
文件中新增属性
/** * 是否允许用户进入后台管理页 */ allowToAdmin: (req, res, next) => { let user = req.session.user; if (user) { req.user = user; next(); } else { res.redirect("/login"); } },
在 myblog/index.js
中加上进入后台的权限验证
// 进入后台的权限验证app.use("/admin/?*", require("./middleware/auth").allowToAdmin);
这样写就可以使所有的后台页面都需要登录之后才能访问
修改 views/admin/header.html
文件
在 views/header.html
中新增个人中心
在 model/user.js
文件中新增方法
/** * 最后一次登陆的时间 */ static lastLoginTime() { return new Promise((resolve, reject) => { let sql = "SELECT time FROM log WHERE handle = '登录' ORDER BY time DESC LIMIT 1"; this.query(sql) .then((results) => { resolve(results[0]); }) .catch((err) => { console.log(`登录失败:${ err.message}`); reject(err); }); }); }
在middleware目录下新建 user.js
文件
/** * 用户中间件 */const User = require("../model/user");module.exports = { /** * 最后一次登录时间 */ lastLoginTime: (req, res, next) => { User.lastLoginTime() .then((results) => { req.lastLoginTime = results; next(); }) .catch((err) => { next(err); }); },};
修改 router/admin/index.js
文件
/** * 后台首页 */const express = require("express");const user = require("../../middleware/user");const indexApp = express();indexApp.get("/", [user.lastLoginTime], (req, res) => { let { user, lastLoginTime } = req; res.render("admin/index", { user, lastLoginTime });});module.exports = indexApp;
修改 views/admin/index.html
文件
上次登录时间:<%= lastLoginTime.time.toLocaleString() %>
在model目录下新建 pv.js
文件
/** * 访问量数据模型 */module.exports = class PV extends require("./model") { /** * 获取总访问量 */ static getTotal() { return new Promise((resolve, reject) => { let sql = "SELECT SUM(hits) AS total FROM pv"; this.query(sql) .then((results) => { resolve(results[0].total); }) .catch((err) => { console.log(`获取总访问量失败:${ err.message}`); reject(err); }); }); }};
在 model/article.js
文件中新增方法
/** * 总博文数 */ static getCount() { return new Promise((resolve, reject) => { let sql = "SELECT COUNT(1) AS count FROM article"; this.query(sql) .then((results) => { resolve(results[0].count); }) .catch((err) => { console.log(`获取总博文数失败:${ err.message}`); reject(err); }); }); }
在 model/category.js
文件中新增方法
/** * 总类目数 */ static getCount() { return new Promise((resolve, reject) => { let sql = "SELECT COUNT(1) AS count FROM category"; this.query(sql) .then((results) => { resolve(results[0].count); }) .catch((err) => { console.log(`获取总类目数失败:${ err.message}`); reject(err); }); }); }
在middleware目录下新建 pv.js
文件
/** * 访问量中间件 */const Pv = require("../model/pv");module.exports = { /** * 获取总访问量 */ getTotal: (req, res, next) => { Pv.getTotal() .then((results) => { req.pvTotal = results; next(); }) .catch((err) => { next(err); }); },};
在 middleware/article.js
文件中新建属性
/** * 获取总博文数 */ getCount: (req, res, next) => { Article.getCount() .then((results) => { req.articleCount = results; next(); }) .catch((err) => { next(err); }); },
在 middleware/category.js
文件中新建属性
/** * 获取总类目数 */ getCount: (req, res, next) => { Category.getCount() .then((results) => { req.categoryCount = results; next(); }) .catch((err) => { next(err); }); },
修改 router/admin/index.js
文件
/** * 后台首页 */const express = require("express");const user = require("../../middleware/user");const pv = require("../../middleware/pv");const category = require("../../middleware/category");const article = require("../../middleware/article");const indexApp = express();indexApp.get( "/", [user.lastLoginTime, pv.getTotal, category.getCount, article.getCount], (req, res) => { let { user, lastLoginTime, pvTotal, categoryCount, articleCount } = req; res.render("admin/index", { user, lastLoginTime, pvTotal, categoryCount, articleCount, }); });module.exports = indexApp;
修改 views/admin/index.html
文件
总访问量<%= pvTotal %>总博文数<%= articleCount %>总类目数<%= categoryCount %>
在 model/pv.js
文件中新建获取全部记录的方法
/** * 获取全部记录 */ static getAll() { return new Promise((resolve, reject) => { let sql = "SELECT time,hits FROM pv ORDER BY time ASC"; this.query(sql) .then((results) => { resolve(results); }) .catch((err) => { console.log(`获取全部记录失败:${ err.message}`); reject(err); }); }); }
在 middleware/pv.js
文件中新建获取全部记录的属性
/** * 获取全部记录 */ getAll: (req, res, next) => { Pv.getAll() .then((results) => { req.pvs = results; next(); }) .catch((err) => { next(err); }); },
在 router/admin/index.js
文件中新建访问量接口
/** * 访问量接口 */indexApp.get("/pvs", [pv.getAll], (req, res) => { let { pvs } = req; let data = { }; data.data = pvs; data.start = pvs[0].time; data.end = pvs[pvs.length - 1].time; res.json(data);});
把 static/admin/js/public.js
文件中的url改为 /admin/index/pvs
let url = "/admin/index/pvs";
在 views/admin
目录下新建 navs.html
文件,是重复代码通用模板
然后按需替换即可
替换语法:
<%- include("navs.html") -%>
在 router/admin
目录下新建文件
category.js - 后台类目管理
/** * 后台类目管理 */const express = require("express");const categoryApp = express();categoryApp.get("/", (req, res) => { res.render("admin/category/index", { user: req.user });});module.exports = categoryApp;
article.js - 后台文章管理
/** * 后台文章管理 */const express = require("express");const articleApp = express();articleApp.get("/", (req, res) => { res.render("admin/article/index", { user: req.user });});module.exports = articleApp;
log.js - 后台日志管理
/** * 后台日志管理 */const express = require("express");const logApp = express();logApp.get("/", (req, res) => { res.render("admin/log/index", { user: req.user });});module.exports = logApp;
account.js - 后台账户管理
/** * 后台账户管理 */const express = require("express");const accountApp = express();accountApp.get("/", (req, res) => { res.render("admin/account/index", { user: req.user });});module.exports = accountApp;
在 myblog/index.js
中调用路由
// 调用后台文章管理app.use("/admin/article", require("./router/admin/article"));// 调用后台类目管理app.use("/admin/category", require("./router/admin/category"));// 调用后台日志管理app.use("/admin/log", require("./router/admin/log"));// 调用后台账户管理app.use("/admin/account", require("./router/admin/account"));
修改 views/admin
目录下的 navs.html
文件中的路由
最后在 static/js/public.js
中写个判断
if ($(".list-group-item").length) { let href = location.pathname; console.log(href); $(`.list-group-item a[href='${ href}']`).parent().addClass("active");}
开启服务,页面出现以下效果
在 model/article.js
文件中新建获取指定页文章列表的方法
/** * 获取指定页文章列表 */ static getPage() { return new Promise((resolve, reject) => { let sql = "SELECT id,title,thumbnail,hot FROM article ORDER BY time DESC"; this.query(sql) .then((results) => { resolve(results); }) .catch((err) => { console.log(`获取指定页文章列表失败:${ err.message}`); reject(err); }); }); }
在 middleware/article.js
文件中新建获取指定页文章列表的属性
/** * 获取指定页的文章列表 */ getPage: (req, res, next) => { Article.getPage() .then((results) => { req.pageList = results; next(); }) .catch((err) => { next(err); }); },};
在 router/admin/article.js
文件对pageList进行封装处理,为后面的分页操作打基础
/** * 后台文章管理 */const express = require("express");const article = require("../../middleware/article");const articleApp = express();articleApp.get("/", [article.getPage], (req, res) => { let { user, pageList } = req; let page = { }; page.list = pageList; res.render("admin/article/index", { user, page });});module.exports = articleApp;
对 views/admin/article/index.html
中的文件进行修改,用取得的数据库的数据重新渲染页面
<% page.list.forEach(article => { %> <%= article.id %> <%= article.title %> <% if (article.thumbnail) { %> <% } %><%= article.hot?'checked':'' %> />编辑 删除 <% }) %>
启动服务,呈现以下效果
在 router/admin/article.js
文件中对articleCount等的封装
/** * 后台文章管理 */const express = require("express");const article = require("../../middleware/article");const articleApp = express();articleApp.get("/", [article.getPage, article.getCount], (req, res) => { let { user, pageList, articleCount } = req; let size = 5; // 每页显示5条 let page = { }; page.count = articleCount; // 总页数 page.total = Math.ceil(page.count / size); // 最大页数 page.list = pageList; page.p = req.query.p ? req.query.p : 1; // 页数 page.p = page.p > page.total ? page.total : page.p; page.p = page.p < 1 ? 1 : page.p; res.render("admin/article/index", { user, page });});module.exports = articleApp;
修改 views/admin/index.html
文件
共 <%= page.count %> 条 / 共 <%= page.total %> 页 / 第 <%=page.p %> 页
开启服务,浏览器访问http://127.0.0.1:3000/admin/article?p=7,页面显示以下效果
在 model/article.js
文件中新建获取指定页文章列表的方法
/** * 获取指定页文章列表 * @param {integer} start 起始索引 * @param {integer} size 查询条目数 */ static getPage(start, size) { return new Promise((resolve, reject) => { let sql = "SELECT id,title,thumbnail,hot FROM article ORDER BY time DESC LIMIT ?,?"; this.query(sql, [start, size]) .then((results) => { resolve(results); }) .catch((err) => { console.log(`获取指定页文章列表失败:${ err.message}`); reject(err); }); }); }
给 middleware/article.js
文件中的getPage属性传上刚加上的参数
/** * 获取指定页的文章列表 */ getPage: (req, res, next) => { Article.getPage(res.start, res.size) .then((results) => { req.pageList = results; next(); }) .catch((err) => { next(err); }); },
对 router/admin/article.js
文件进行修改 - 异步操作
/** * 后台文章管理 */const express = require("express");const article = require("../../middleware/article");const articleApp = express();articleApp.get( "/", [article.getCount], (req, res, next) => { let { articleCount } = req; let size = 5; // 每页显示5条 req.page = { }; req.page.count = articleCount; // 总页数 req.page.total = Math.ceil(req.page.count / size); // 最大页数 req.page.p = req.query.p ? req.query.p : 1; // 页数 req.page.p = req.page.p > req.page.total ? req.page.total : req.page.p; req.page.p = req.page.p < 1 ? 1 : req.page.p; res.start = (req.page.p - 1) * size; res.size = size; next(); }, [article.getPage], (req, res) => { let { user, pageList, page } = req; page.list = pageList; res.render("admin/article/index", { user, page }); });module.exports = articleApp;
修改 views/admin/article/index.html
文件
启动服务,如下效果
在 router/admin/article.js
文件中新增类目列表数据
/** * 后台文章管理 */const express = require("express");const article = require("../../middleware/article");const category = require("../../middleware/category");const articleApp = express();articleApp.get( "/", [article.getCount], (req, res, next) => { let { articleCount } = req; let size = 5; // 每页显示5条 req.page = { }; req.page.count = articleCount; // 总页数 req.page.total = Math.ceil(req.page.count / size); // 最大页数 req.page.p = req.query.p ? req.query.p : 1; // 页数 req.page.p = req.page.p > req.page.total ? req.page.total : req.page.p; req.page.p = req.page.p < 1 ? 1 : req.page.p; res.start = (req.page.p - 1) * size; res.size = size; next(); }, [article.getPage, category.getList], (req, res) => { let { user, pageList, page, categories } = req; page.list = pageList; res.render("admin/article/index", { user, page, categories }); });module.exports = articleApp;
修改 views/admin/article/index.html
文件
修改 model/article.js
文件,附带两个参数category_id, hot
/** * 总博文数 */ static getCount(category_id, hot) { return new Promise((resolve, reject) => { let sql = "SELECT COUNT(1) AS count FROM article WHERE 1=1"; sql += category_id != -1 && category_id ? ` AND category_id=${ category_id}` : ""; sql += hot != -1 && hot ? ` AND hot=${ hot}` : ""; this.query(sql) .then((results) => { resolve(results[0].count); }) .catch((err) => { console.log(`获取总博文数失败:${ err.message}`); reject(err); }); }); } /** * 获取指定页文章列表 * @param {integer} start 起始索引 * @param {integer} size 查询条目数 */ static getPage(start, size, category_id, hot) { return new Promise((resolve, reject) => { let sql = "SELECT id,title,thumbnail,hot FROM article WHERE 1=1"; sql += category_id != -1 && category_id ? ` AND category_id=${ category_id}` : ""; sql += hot != -1 && hot ? ` AND hot=${ hot}` : ""; sql += " ORDER BY time DESC LIMIT ?,?"; this.query(sql, [start, size]) .then((results) => { resolve(results); }) .catch((err) => { console.log(`获取指定页文章列表失败:${ err.message}`); reject(err); }); }); }
再修改 middleware/article.js
文件
/** * 获取总博文数 */ getCount: (req, res, next) => { Article.getCount(req.query.category_id, req.query.hot) .then((results) => { req.articleCount = results; next(); }) .catch((err) => { next(err); }); }, /** * 获取指定页的文章列表 */ getPage: (req, res, next) => { Article.getPage(res.start, res.size, req.query.category_id, req.query.hot) .then((results) => { req.pageList = results; next(); }) .catch((err) => { next(err); }); },
再到路由中进行修改 router/admin/article.js
/** * 后台文章管理 */const express = require("express");const article = require("../../middleware/article");const category = require("../../middleware/category");const articleApp = express();articleApp.get( "/", [article.getCount], (req, res, next) => { let { articleCount } = req; let size = 3; // 每页显示5条 req.page = { }; req.page.count = articleCount; // 总页数 req.page.total = Math.ceil(req.page.count / size); // 最大页数 req.page.p = req.query.p ? req.query.p : 1; // 页数 req.page.p = req.page.p > req.page.total ? req.page.total : req.page.p; req.page.p = req.page.p < 1 ? 1 : req.page.p; res.start = (req.page.p - 1) * size; res.size = size; next(); }, [article.getPage, category.getList], (req, res) => { let { user, pageList, page, categories } = req; let { category_id, hot } = req.query; page.list = pageList; res.render("admin/article/index", { user, page, categories, category_id, hot, }); });module.exports = articleApp;
修改 views/admin/article/index.html
文件
首先在 model/article.js
文件中设置热门方法
/** * 设置热门 * @param {integer} id 文章编号 * @param {integer} hot 热门状态 */ static setHot(id, hot) { return new Promise((resolve, reject) => { let sql = "UPDATE article SET hot = ? WHERE id = ?"; this.query(sql, [hot, id]) .then((results) => { resolve(results.affectedRows); }) .catch((err) => { console.log(`设置热门失败:${ err.message}`); reject(err); }); }); }
然后再 middleware/article.js
文件中设置热门推荐属性
/** * 设置热门推荐 */ setHot: (req, res, next) => { let { id, hot } = req.query; Article.setHot(id, hot) .then((results) => { req.affectedRows = results; next(); }) .catch((err) => { next(err); }); },
在 router/admin/article.js
文件中设置热门推荐路由
articleApp.get("/sethot", article.setHot, (req, res) => { // 如果受影响的行数大于0,说明数据库更新成功,否则失败 if (req.affectedRows > 0) { res.json({ code: 1, msg: "设置成功" }); } else { res.json({ code: 0, msg: "设置失败" }); }});
在 views/admin/article/index.html
文件中进行修改
首先是页面部分
<%= article.hot?'checked':'' %> οnchange="sethot(this.value,this.checked)" />
其次是JavaScript脚本部分
function sethot(id,hot){ $.get("/admin/article/sethot",{ id,hot:hot?1:0},function(res){ if(res.code==1){ showToasts("成功","设置热门成功") }else{ showToasts("失败","设置热门失败") } })}
启动服务,即可完成页面点击热门推荐按钮对数据库的hot进行修改
在 router/admin/article.js
文件中新增显示添加博文的方法
// 显示添加博文页articleApp.get("/add", [category.getList], (req, res) => { let { user, categories } = req; res.render("admin/article/add", { user, categories });});
安装multer
npm i multer
在 myblog/index.js
文件中引入multer
const multer = require("multer");
上传配置
// 上传配置const upload = multer({ dest: "./static/upload", // 上传文件的存储目录 limits: { fileSize: 1024 * 1024 * 2, // 单个文件大小限制在2M },});
上传操作
const fs = require("fs");const path = require("path");// 上传操作app.post("/admin/*", upload.single("upload"), (req, res, next) => { // 上传成功后的文件对象 let { file } = req; if (file) { // file.originalname ==> 文件的原名称 let extname = path.extname(file.originalname); // file.path ==> 上传后的文件路径 fs.renameSync(file.path, file.path + extname); // file.filename ==> 上传后的文件名 req.uploadUrl = "/upload/" + file.filename + extname; } next();});
在 router/admin/article.js
文件中新增上传图片的路由
// ckeditor 上传articleApp.post("/ckeditor", (req, res) => { if (req.uploadUrl) { res.json({ uploaded: true, url: req.uploadUrl, }); } else { res.json({ uploaded: false, err: { message: "上传失败" }, }); }});
完成如下效果
在 model.article.js
中新增添加文章方法
/** * 添加文章 * @param {Object} article 文章对象 */ static add(article) { return new Promise((resolve, reject) => { let sql = "INSERT INTO article SET ?"; this.query(sql, article) .then((results) => { resolve(results.insertId); }) .catch((err) => { console.log(`添加文章失败:${ err.message}`); reject(err); }); }); }
在 middleware/article.js
文件中新增添加文章的属性
/** * 添加文章 */ add: (req, res, next) => { let { title, content, hot, category_id } = req.body; let article = { title, content, hot: hot ? 1 : 0, category_id: category_id, thumbnail: req.uploadUrl ? req.uploadUrl : null, }; Article.add(article) .then((results) => { req.insertId = results; next(); }) .catch((err) => { next(err); }); },
在 router/admin/article.js
文件中新增添加文章的路由
// 添加文章articleApp.post("/add", [article.add, category.getList], (req, res) => { let { user, categories } = req; if (req.insertId) { res.render("admin/article/add", { user, categories, code: 1 }); } else { res.render("admin/article/add", { user, categories, code: 2 }); }});
启动服务,上传文章到数据库
在 model/article.js
文件中新增删除文章的方法
/** * 删除文章 * @param {integer} id 文章编号 */ static del(id) { return new Promise((resolve, reject) => { let sql = "DELETE FROM article WHERE id = ?"; this.query(sql, id) .then((results) => { resolve(results.affectedRows); }) .catch((err) => { console.log(`删除文章失败:${ err.message}`); reject(err); }); }); }
在 middleware/article.js
文件中新增删除文章的属性
/** * 删除文章 */ del: (req, res, next) => { let { id } = req.query; Article.del(id) .then((results) => { req.affectedRows = results; next(); }) .catch((err) => { next(err); }); },
在 router/admin/article.js
文件中新增删除文章的路由
// 删除文章articleApp.get("/del", article.del, (req, res) => { if (req.affectedRows > 0) { res.json({ code: 1, msg: "删除成功" }); } else { res.json({ code: 2, msg: "删除失败" }); }});
修改 views/admin/article/index.html
文件
function del(id){ $.getJSON("/admin/article/del",{ id},function(res){ if(res.code==1){ showToasts("成功","删除成功") // 重新加载页面 setTimeout(function(){ location.reload() },2000) }else{ showToasts("失败","删除失败") } })}
启动服务,删除文章删除成功
在 model/article.js
文件中修改获取指定文章详情的方法
/** * 获取指定文章的详情 * @param {integer} id 当前文章编号 */ static getArticleById(id) { return new Promise((resolve, reject) => { let sql = "SELECT a.id,a.title,a.content,a.`time`,a.hits,a.category_id,c.`name`,a.`thumbnail`,a.`hot` FROM article a,category c WHERE a.id = ? AND a.category_id = c.id"; this.query(sql, id) .then((results) => { resolve(results[0]); }) .catch((err) => { console.log(`获取指定文章的详情失败:${ err.message}`); reject(err); }); }); }
在 router/admin/article.js
文件中新增路由
// 文章编辑articleApp.get( "/edit/:id", [category.getList, article.getArticleById], (req, res) => { let { user, categories, article } = req; res.render("admin/article/edit", { user, categories, article }); });
修改 views/admin/article/edit.html
文件
首页 <%- include("../header.html") -%><%- include("../navs.html") -%>文章管理
编辑文章
启动服务,实现对应文章页面显示
在 model/article.js
文件中新增编辑文章的方法
/** * 编辑文章 * @param {Object} article 文章对象 */ static edit(article) { return new Promise((resolve, reject) => { let sql = "UPDATE article SET title = ?, content = ?, hot = ?, category_id = ?, thumbnail = ? WHERE id = ?"; this.query(sql, [ article.title, article.content, article.hot, article.category_id, article.thumbnail, article.id, ]) .then((results) => { resolve(results.affectedRows); }) .catch((err) => { console.log(`编辑文章失败:${ err.message}`); reject(err); }); }); }
在 middleware/article.js
文件中新增编辑文章的属性
/** * 编辑文章 */ edit: (req, res, next) => { let { title, content, hot, category_id, thumbnail, id } = req.body; let article = { title, content, hot: hot ? 1 : 0, category_id, thumbnail: req.uploadUrl ? req.uploadUrl : thumbnail, id, }; Article.edit(article) .then((results) => { req.affectedRows = results; next(); }) .catch((err) => { next(err); }); },
在 router/admin/article.js
文件中新增路由
articleApp.post("/edit", [article.edit], (req, res) => { if (req.affectedRows > 0) { res.render("admin/alert", { code: true, title: "成功提示", message: "文章编辑成功", url: "/admin/article/", }); } else { res.render("admin/alert", { code: false, title: "失败提示", message: "文章编辑失败", url: "/admin/article/" + req.body.id, }); }
在 model/article.js
文件中修改两个方法,数据库查询的时候都加上thumbnail字段
/** * 获取热门推荐 * @param {integer} num 条目数 */ static getHot(num) { return new Promise((resolve, reject) => { let sql = "SELECT id,title,content,`time`,thumbnail FROM article WHERE hot = 1 LIMIT ?"; this.query(sql, num) .then((results) => { resolve(results); }) .catch((err) => { console.log(`获取热门文章失败:${ err.message}`); reject(err); }); }); } /** * 获取文章列表 */ static getList() { return new Promise((resolve, reject) => { let sql = "SELECT id,title,content,`time`,thumbnail FROM article ORDER BY time DESC"; this.query(sql) .then((results) => { resolve(results); }) .catch((err) => { console.log(`获取文章列表失败:${ err.message}`); reject(err); }); }); }
修改 views/admin/index.html
文件
使前台页面效果如下
在 model/category.js
文件中添加新增类目方法
/** * 新增类目 */ static add(name, index) { return new Promise((resolve, reject) => { let sql = "INSERT INTO category (`name`,`index`) VALUES (?,?)"; this.query(sql, [name, index]) .then((results) => { resolve(results.insertId); }) .catch((err) => { console.log(`新增类目失败:${ err.message}`); reject(err); }); }); }
在 middleware/category.js
文件中添加新增类目属性
/** * 添加类目 */ add: (req, res, next) => { let { name, index } = req.body; Category.add(name, index) .then((results) => { req.insertId = results; next(); }) .catch((err) => { next(err); }); },
在 router/admin/category.js
文件中新增路由
categoryApp.post("/add", [category.add], (req, res) => { if (req.insertId) { res.json({ code: 1, msg: "添加成功" }); } else { res.json({ code: 1, msg: "添加失败" }); }});
在 views/admin/category/index.html
文件中编写JS脚本
function save(t) { let name = $(t).parents("tr").find("[name='name']").val(); let index = $(t).parents("tr").find("[name='index']").val(); $.post("/admin/category/add", { name, index }, function (res) { if (res.code == 1) { showToasts("成功", "添加类目成功"); setTimeout(function () { location.reload(); }, 2000); } else { showToasts("失败", "添加类目失败"); } });}
效果如下
在 model/category.js
文件中新增删除类目的方法
/** * 删除类目 * @param {integer} id 类目编号 */ static del(id) { return new Promise((resolve, reject) => { let sql = "DELETE FROM category WHERE id = ?"; this.query(sql, id) .then((results) => { resolve(results.affectedRows); }) .catch((err) => { console.log(`删除类目失败:${ err.message}`); reject(err); }); }); }
在 middleware/category.js
文件中新增删除类目的属性
/** * 删除类目 */ del: (req, res, next) => { let { id } = req.query; Category.del(id) .then((results) => { req.affectedRows = results; next(); }) .catch((err) => { next(err); }); },
在 router/admin/category.js
文件中新增删除类目的路由
categoryApp.get("/del", category.del, (req, res) => { if (req.affectedRows > 0) { res.json({ code: 1, msg: "删除成功" }); } else { res.json({ code: 1, msg: "删除失败" }); }});
在 views/admin/category/index.html
文件中编写JS脚本
function del(id) { if (confirm("确认删除?")) { $.getJSON("/admin/category/del", { id }, function (res) { if (res.code == 1) { showToasts("成功", "删除类目成功"); setTimeout(function () { location.reload(); }, 2000); } else { showToasts("失败", "删除类目失败"); } }); }}
开启服务,实现如下效果
在 model
目录下新建 log.js
文件
/** * 日志数据模型 */module.exports = class Tab extends require("./model") { /** * 获取日志列表 */ static getPage(start, size) { return new Promise((resolve, reject) => { let sql = "SELECT handle,`time`,ip FROM `log` ORDER BY `time` DESC LIMIT ?,?"; this.query(sql, [start, size]) .then((results) => { resolve(results); }) .catch((err) => { console.log(`获取日志列表失败:${ err.message}`); reject(err); }); }); } /** * 获取日志总条目数 */ static getCount() { return new Promise((resolve, reject) => { let sql = "SELECT COUNT(1) as count FROM `log`"; this.query(sql) .then((results) => { resolve(results[0].count); }) .catch((err) => { console.log(`获取日志总条目数失败:${ err.message}`); reject(err); }); }); }};
在 middleware
目录下新建 log.js
文件
/** * 访问量中间件 */const Log = require("../model/log");module.exports = { /** * 获取日志列表 */ getPage: (req, res, next) => { let { p, size } = req.page; Log.getPage((p - 1) * size, size) .then((results) => { req.page.list = results; next(); }) .catch((err) => { next(err); }); }, /** * 获取总条目数 */ getCount: (req, res, next) => { Log.getCount() .then((results) => { req.count = results; next(); }) .catch((err) => { next(err); }); },};
在 router/admin
目录下新建 log.js
文件
/** * 后台日志管理 */const express = require("express");const log = require("../../middleware/log");const logApp = express();logApp.get( "/", log.getCount, (req, res, next) => { let page = { p: req.query.p ? req.query.p : 1, count: req.count, size: 3, }; page.total = Math.ceil(page.count / page.size); page.p = page.p > page.total ? page.total : page.p; page.p = page.p < 1 ? 1 : page.p; req.page = page; next(); }, log.getPage, (req, res) => { let { user, page } = req; res.render("admin/log/index", { user, page }); });module.exports = logApp;
修改 views/admin/log/index.html
文件
首页 <%- include("../header.html") -%><%- include("../navs.html") -%>查看日志
日志列表
<% page.list.forEach(log => { %> 时间 操作 IP <% }) %> <%= log.time.toLocaleString() %> <%= log.handle %> <%= log.ip %>
启动服务,实现以下效果
示例:做一个登录日志
在 router/admin/login.js
中修改实现登录操作的方法
const log = require("../middleware/log");// 实现登录操作loginApp.post("/", (req, res, next) => { let { username, password } = req.body; User.login(username, password) .then((results) => { if (results) { req.log = { time: new Date(), handle: "登录", ip: req.ip.split(":")[3], }; log.add(req, res, next); // session存储(key=value) req.session.user = results; res.redirect("/"); } else { res.render("login", { msg: "登录失败!用户名或密码错误" }); } }) .catch((err) => { next(err); });});
主要代码:
req.log = { time: new Date(), handle: "登录", ip: req.ip.split(":")[3],};log.add(req, res, next);
自由发挥即可
cookie-session会话延期实现
在 myblog/index.js
文件中新建SESSION延期
// SESSION延期app.use((req, res, next) => { req.session.nowInMinutes = Math.floor(Date.now() / 60e3);});
转载地址:http://wjclf.baihongyu.com/