项目要求
a. 前台和后台的页面布局
- 前台要求有首页、列表页、详情页面、登录、注册
- 后台要求有登录页面、列表、添加修改页面
- 页面要求简洁、美观、大方
b. 后台功能要求
- 前台注册用户在后台的分页展示
- 后台可以对分类进行管理
- 后台可以对文章进行管理
- 后台可以针对文章的评论进行展示
- 后台需要登录才能进入后台管理系统
c. 前台功能要求
- 首页按照分类展示对应的最新几条文章
- 列表页可以根据不同的分类进行文章列表的切换
- 详情页在登录的前提下,可以对文章进行评论(未实现,jQuery可能出问题了)
- 前台用户可以正常的登录和注册
项目准备工作
流程图展示
后台管理流程图
环境的搭建
phpstudy
Navicat Premium
实际项目的编写
项目目录
代码(前端)
代码如下
article.html
<%- include('header.html') -%>
<div class="container mt-4">
<nav>
<ol class="breadcrumb bg-white">
<li class="breadcrumb-item"><a href="/">首页</a></li>
<li class="breadcrumb-item"><a href="/article/list/<%=article.category_id%>"><%=article.name%></a></li>
<li class="breadcrumb-item active"><%=article.title%></li>
</ol>
</nav>
<div class="card content">
<div class="card-header bg-white">
<h3 class="card-title m-0"><%=article.title%></h3>
<p class="text-muted small mt-2 m-0">
<span class="mr-3">发表时间:<%=article.time.toLocaleString()%></span>
<span class="mr-1">点击:<%=article.hits%></span>
</p>
</div>
<div class="card-body"><%-article.content%></div>
<div class="card-footer bg-white border-0">
<% tabs.forEach(tab => { %>
<span class="badge badge-pill"><%=tab.name%> </span>
<% }) %>
</div>
</div>
<nav>
<ul class="pagination mt-3">
<% if (prev) { %>
<li class="page-item">
<a class="page-link" href="/article/<%=prev.id%>">上一篇:<%=prev.title%></a>
</li>
<% } %>
<% if (next) { %>
<li class="page-item ml-auto">
<a class="page-link" href="/article/<%=next.id%>">下一篇:<%=next.title%></a>
</li>
<% } %>
</ul>
</nav>
</div>
footer.html
<footer class="bg-dark p-3 mt-3 text-center text-secondary small">
<a href="#" class="text-secondary small">苏ICP备00000000号</a>
</footer>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/holder/2.9.6/holder.min.js"></script>
<script src="/js/public.js"></script>
</body>
</html>
header.html
!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>文章详情页</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/style.css">
</head>
<body class="bg-light">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#">
<img src="/img/logo.png" width="30" height="30" alt="">
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">首页</a>
</li>
<% categories.forEach(category => { %>
<li class="nav-item">
<a class="nav-link" href="/article/list/<%=category.id%>"><%= category.name %> </a>
</li>
<% }) %>
</ul>
<form class="form-inline my-2 my-lg-0 ml-5" method="get" action='/search'>
<input class="form-control mr-sm-2" name="keyword" type="search" placeholder="请输入关键词...">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">搜索</button>
</form>
<ul class="navbar-nav ml-auto">
<% if (user) { %>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" data-toggle="dropdown">
<%=user.username%>
</a>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="/admin">个人中心</a>
<a class="dropdown-item" href="/user/logout">退出</a>
</div>
</li>
<% } else { %>
<li class="nav-item">
<a href="/login" class="nav-link">登录</a>
</li>
<% } %>
</ul>
</div>
</nav>
index.html
<div id="carouselExampleIndicators" class="carousel slide" data-ride="carousel">
<ol class="carousel-indicators">
<li data-target="#carouselExampleIndicators" data-slide-to="0" class="active"></li>
<li data-target="#carouselExampleIndicators" data-slide-to="1"></li>
<li data-target="#carouselExampleIndicators" data-slide-to="2"></li>
</ol>
<div class="carousel-inner">
<div class="carousel-item active">
<img data-src="holder.js/100px400?text=图一&random=yes" class="d-block w-100" alt="...">
</div>
<div class="carousel-item">
<img data-src="holder.js/100px400?text=图二&random=yes" class="d-block w-100" alt="...">
</div>
<div class="carousel-item">
<img data-src="holder.js/100px400?text=图三&random=yes" class="d-block w-100" alt="...">
</div>
</div>
<a class="carousel-control-prev" href="#carouselExampleIndicators" data-slide="prev">
<span class="carousel-control-prev-icon"></span>
</a>
<a class="carousel-control-next" href="#carouselExampleIndicators" data-slide="next">
<span class="carousel-control-next-icon"></span>
</a>
</div>
<div class="container mt-5">
<h2 class="mb-4">热门推荐</h2>
<div class="card-deck">
<% hots.forEach(hot => { %>
<div class="card">
<img src="<%= hot.thumbnail %> " data-src="holder.js/100px200" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title"><%= hot.title %> </h5>
<p class="card-text"><small class="text-muted"><%= hot.time.toLocaleString() %></small></p>
<p class="card-text"><%= hot.content.replace(/<[^>]+>/g,'').substring(0,100) %>...</p>
</div>
<div class="card-footer bg-white border-0">
<a href="/article/<%=hot.id%>" class="btn btn-primary float-right stretched-link">查看详情</a>
</div>
</div>
<% }) %>
</div>
</div>
<div class="container mt-5">
<h2 class="mb-4">最新博文</h2>
<div class="row row-cols-4">
<% articles.forEach(article => { %>
<div class="col my-3">
<div class="card h-100">
<img data-src="holder.js/100px150" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title"><%= article.title %></h5>
<p class="card-text"><small class="text-muted"><%= article.time.toLocaleString() %></small></p>
<p class="card-text"><%= article.content.replace(/<[^>]+>/g,'').substring(0,100) %>...</p>
<a href="/article/<%=article.id%>" class="stretched-link"></a>
</div>
</div>
</div>
<% }) %>
</div>
</div>
list.html
<div class="container mt-5">
<h2 class="mb-4">当前栏目:<%=category.name%></h2>
<div class="row row-cols-4">
<% articles.forEach(article => { %>
<div class="col my-3">
<div class="card h-100">
<img data-src="holder.js/100px150" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title"><%= article.title %></h5>
<p class="card-text"><small class="text-muted"><%= article.time.toLocaleString() %></small></p>
<p class="card-text"><%= article.content.replace(/<[^>]+>/g,'').substring(0,100) %>...</p>
<a href="/article/<%=article.id%>" class="stretched-link"></a>
</div>
</div>
</div>
<% }) %>
</div>
</div>
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>登录</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body class="vh-100 d-flex flex-column justify-content-center align-items-center bg-secondary">
<div class="card w-25">
<div class="card-header">
<h3 class="card-title">欢迎登陆</h3>
</div>
<div class="card-body">
<form action="/login" method="POST" class="was-validated">
<div class="form-row mb-3">
<label for="username" class="col-2 text-justify col-form-label text-right">账号:</label>
<input type="text" class="form-control col-7" id="username" name="username" required
placeholder="请填写账号">
<p class="invalid-feedback col-3 col-form-label">请填写账号</p>
</div>
<div class="form-row mb-3">
<label for="password" class="col-2 text-justify col-form-label text-right">密码:</label>
<input type="password" class="form-control col-7" id="password" name="password" required
placeholder="请填写密码">
<p class="invalid-feedback col-3 col-form-label">请填写密码</p>
</div>
<div class="form-row mb-3">
<input type="submit" class="btn btn-primary w-100 col-7 offset-2" value="登录">
</div>
<div class="form-row">
<div class="text-danger col offset-2"><%=msg%> </div>
</div>
</form>
</div>
<div class="small text-center card-footer">
<a href="/" class="text-secondary card-link">点击此处,返回首页</a>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/js/bootstrap.min.js"></script>
</body>
</html>
效果如下
后端(js部分)
入口模块
- 导入模块
const express = require('express')
const session = require('cookie-session')
const multer = require('multer')
const path = require('path')
const fs = require('fs')
- 区分用户和管理员
app.use('/admin/?*', require('./middleware/auth').allowToAdmin)
- 模板引擎的设置
app.set('view engine', 'html')
app.set('views', `${__dirname}/views`)
app.engine('html', require('ejs').renderFile)
整体代码
const express = require('express')
const session = require('cookie-session')
const multer = require('multer')
const path = require('path')
const fs = require('fs')
// 创建主应用
const app = express()
// 上传配置
const upload = multer({
dest: './static/upload', // 上传文件的存储目录
limits: {
fileSize: 1024 * 1024 * 2 // 单个文件大小限制在2M以内
}
})
// 模板引擎的设置
app.set('view engine', 'html')
app.set('views', `${__dirname}/views`)
app.engine('html', require('ejs').renderFile)
// 静态资源配置
app.use(express.static('static'))
// POST请求处理
app.use(express.urlencoded({
extended: true
}))
// SESSION配置
app.use(session({
keys: ['secret'],
maxAge: 1000 * 60 * 30
}))
// SESSION延期
app.use((req, res, next) => {
req.session.nowInMinutes = Math.floor(Date.now() / 60e3)
next()
})
// 调用首页子应用
app.use(/\/(index)?/, require('./router/index'))
// 调用文章子应用
app.use('/article', require('./router/article'))
// 调用搜索子应用
app.use('/search', require('./router/search'))
// 调用登录子应用
app.use('/login', require('./router/login'))
// 进入后台的权限验证
app.use('/admin/?*', require('./middleware/auth').allowToAdmin)
// 上传操作
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()
})
// 调用后台首页
app.use(/\/admin\/(index)?/, require('./router/admin/index'))
// 调用后台文章管理
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'))
// 退出
app.get('/user/logout', (req, res) => {
req.session.user = null
res.render('login', {
msg: '退出成功'
})
})
// 监听服务器
app.listen(3000)
文章数据模型
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`,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` FROM article ORDER BY TIME DESC'
this.query(sql).then(results => {
resolve(results)
}).catch(err => {
console.log(`获取文章列表失败:${err.message}`)
reject(err)
})
})
}
/**
* 获取指定类目下的文章列表
* @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)
})
})
}
/**
* 获取指定关键词的文章列表
* @param {string}} keyword 关键词
*/
static getListBykeywrod(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)
})
})
}
/**
* 获取指定文章的详情
* @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.`category_id` = c.`id` AND a.id = ?'
this.query(sql, id).then(results => {
resolve(results[0])
}).catch(err => {
console.log(`获取指定文章的详情失败:${err.message}`)
reject(err)
})
})
}
/**
* 上一篇文章
* @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)
})
})
}
/**
* 总博文数
*/
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)
})
})
}
/**
* 设置热门
* @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)
})
})
}
/**
* 添加文章
* @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)
})
})
}
/**
* 删除文章
* @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)
})
})
}
/**
* 编辑文章
* @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)
})
})
}
}
文章类目数据模型
module.exports = class Category extends require('./model') {
/**
* 获取文章类目列表
*/
static getList() {
return new Promise((resolve, reject) => {
let sql = 'SELECT id,`name`,`index` FROM category ORDER BY `index` DESC'
this.query(sql).then(results => {
resolve(results)
}).catch(err => {
console.log(`获取文章类目列表失败:${err.message}`)
reject(err)
})
})
}
/**
* 获取指定编号的类目详情
* @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)
})
})
}
/**
* 总类目数
*/
static getCount(id) {
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)
})
})
}
/**
* 新增类目
*/
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)
})
})
}
/**
* 删除类目
* @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)
})
})
}
/**
* 编辑类目名称
* @param {integer} id 类目编号
* @param {String} name 类目名称
*/
static setName(id, name) {
return new Promise((resolve, reject) => {
let sql = 'UPDATE category SET `name` = ? WHERE id = ?'
this.query(sql, [name, id]).then(results => {
resolve(results.affectedRows)
}).catch(err => {
console.log(`编辑类目名称失败:${err.message}`)
reject(err)
})
})
}
/**
* 编辑类目索引
* @param {integer} id 类目编号
* @param {String} index 类目索引
*/
static setIndex(id, index) {
return new Promise((resolve, reject) => {
let sql = 'UPDATE category SET `index` = ? WHERE id = ?'
this.query(sql, [index, id]).then(results => {
resolve(results.affectedRows)
}).catch(err => {
console.log(`编辑类目索引失败:${err.message}`)
reject(err)
})
})
}
}
日志数据模型
module.exports = class Log 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)
})
})
}
/**
* 日志添加
*/
static add(log) {
return new Promise((resolve, reject) => {
let sql = 'INSERT INTO `log` SET ?'
this.query(sql, log).then(results => {
resolve(results.affectedRows)
}).catch(err => {
console.log(`日志添加失败:${err.message}`)
reject(err)
})
})
}
}
数据模型的基类
module.exports = class Model {
// 连接对象
static conn = null
/**
* 数据库连接方法
*/
static connection() {
Model.conn = mysql.createConnection({
host: '127.0.0.1',
user: 'root',
password: '123456',
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()
})
}
}
访问量数据模型
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)
})
})
}
/**
* 获取全部访问量
*/
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)
})
})
}
}
标签数据模型
module.exports = class Tab extends require('./model') {
/**
* 获取指定文章的标签列表
*/
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)
})
})
}
}
用户数据模型
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)
})
})
}
/**
* 最后一次登录的时间
*/
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].time)
}).catch(err => {
console.log('登录失败:' + err.message)
reject(err)
})
})
}
}
子应用
文章子应用
const express = require('express')
const article = require('../middleware/article')
const category = require('../middleware/category')
const auth = require('../middleware/auth')
// 文章子应用
const articleApp = express()
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: articles, categories: categories, category: category, user: user })
})
// 文章详情页
articleApp.get('/:id', [article.getArticleById, article.getTabs, article.getPrev, article.getNext], (req, res) => {
let { article, categories, tabs, prev, next, user } = req
res.render('article', { article: article, categories: categories, tabs: tabs, prev: prev, next: next, user: user })
})
module.exports = articleApp
首页子应用(首页路由)
const express = require('express')
const article = require('../middleware/article')
const category = require('../middleware/category')
const auth = require('../middleware/auth')
// 首页子应用
const indexApp = express()
indexApp.use(auth.getUser)
// 加载首页页面
indexApp.get('/', [article.getHot, article.getList, category.getList], (req, res) => {
let { hots, articles, categories, user } = req
res.render('index', { hots: hots, articles: articles, categories: categories, user: user })
})
module.exports = indexApp
登录子应用
const express = require('express')
const User = require('../model/user')
const log = require('../middleware/log')
// 文章子应用
const loginApp = express()
// 加载登录页
loginApp.get('/', (req, res) => {
res.render('login', { msg: '' })
})
// 实现登录操作
loginApp.post('/', (req, res, next) => {
let { username, password } = req.body
User.login(username, password).then(result => {
if (result) {
req.log = {
time: new Date(),
handle: '登录',
ip: req.ip.split(':')[3]
}
log.add(req, res, next)
// session存储(key=value)
req.session.user = result
res.redirect('/')
} else {
res.render('login', { msg: '登录失败!用户名或密码错误' })
}
}).catch(err => {
next(err)
})
})
module.exports = loginApp
搜索子应用
const express = require('express')
const article = require('../middleware/article')
const category = require('../middleware/category')
const auth = require('../middleware/auth')
// 首页子应用
const searchApp = express()
// 加载首页页面
searchApp.get('/', [article.getListBykeywrod, category.getList, auth.getUser], (req, res) => {
let { articles, categories, user } = req
res.render('search', { articles: articles, categories: categories, keyword: req.query.keyword, user: user })
})
module.exports = searchApp