博客系统

项目要求

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值