## 九、项目 - 个人网站管理

### 9.1 介绍

### 9.2 功能模块

#### 整体采用前后端分离

后端： nodejs + expressjs + mysql 完成数据存取，如何验证是否已经登录(session、cookie)

前端：vue + element-ui

#### 模块

**用户模块**：管理用户的登录、退出、信息修改等

**文章模块**：添加文章、修改文章、查询文章、删除文章、文件的上传等 -

**前台模块** : 查询文章列表(分页|搜索)、查询文章的详情、更新文章的数据、文章的搜索、

**评论模块**：添加评论、查询评论、删除评论(管理员)

### 9.3 快速开始 - 搭建项目框架

1、使用npm初始化项目

```shell
mkdir pro-blog
cd pro-blog
# 使用npm初始化项目
npm init -y 
```

2、安装项目所需的依赖

```shell
npm i express mysql vue element-ui
npm i -D nodemon
```

3、package.json - 项目的信息

提交项目时不要提交node_modules,只需要有package.json文件，执行`npm i`

```json
{
	"name": "pro-blog",
	"version": "1.0.0",
	"description": "",
	"main": "index.js",
	"scripts": {
		"start": "nodemon index.js"
	},
	"keywords": [],
	"author": "",
	"license": "ISC",
	"dependencies": {
		"element-ui": "^2.15.8",
		"express": "^4.18.1",
		"mysql": "^2.18.1",
		"vue": "^3.2.33"
	},
	"devDependencies": {
		"nodemon": "^2.0.16"
	}
}
```

4、入口文件 - index.js

```javascript
const express = require("express")
const user = require('./modules/user') // 用户模块
const article = require('./modules/article') // 文章

const app = express(); // 创建express应用

app.use(express.static('public')) // 设置静态文件路径
// 使用路由模块
app.use('/api/user',user)
app.use('/api/article',article)

app.listen(3000); // 监听端口
console.log('server run success! http://localhost:3000')
```

5、user 与 article 模块

```javascript
// article.js
const router = require('express').Router(); // 创建一个路由对象
// 实现相应路由功能
// ....
module.exports = router // 暴露路由对象

// user.js
const router = require('express').Router();
module.exports = router
```

### 9.4 数据库结构

用户登录表:  登录信息ID、登录时间、登录IP、客户端标识、有效期

文章表: 文章ID、标题、封面、发布时间、描述(一部分的文章内容)、文章内容、分类、阅读量、评论数、状态

评论表: 评论ID、文章ID、评论内容、发布时间、评论者IP

```sql
create DATABASE pro_blog;
use pro_blog;

-- 用户登录表:  登录信息ID、登录时间、登录IP、客户端标识、有效期
create table login(
	id int(10) PRIMARY KEY AUTO_INCREMENT,
	login_time datetime not null,
	BIGINT -- xxx.xxx.xxx.xx -> ipv4
	-- xxxx:xxxx:xxxx::xxxx -> ipv6
	ip varchar(40) not null,
	client varchar(200) null,
	expire_time datetime null
);

-- 文章表: 文章ID、标题、封面、发布时间、描述(一部分的文章内容)、文章内容、分类、阅读量、评论数、状态
create table article(
	id int(10) PRIMARY KEY AUTO_INCREMENT,
	title varchar(50) not null,
	cover varchar(200) null,
	publish_time datetime null,
	description varchar(500) null,
	content text not null,
	category varchar(20) not null,
	view_count int default 0,
	comment_count int default 0,
	status tinyint(1) default 1 -- 状态 1标识草稿 2表示已发布 0表示已删除
);

-- 评论表: 评论ID、文章ID、评论内容、发布时间、评论者IP
create table comment
(
    id int(10) PRIMARY KEY AUTO_INCREMENT,
	article_id int(10) not null,
    content varchar(500) not null,
    publish_time datetime not null,
	ip varchar(40) not null
);
```



### 9.6 功能实现

#### 9.6.1 文章模块

##### 1、公共数据库查询工具模块

```javascript
// 数据查询工具封装
const mysql = require('mysql')
// 创建数据库连接池
const pool = mysql.createPool({
    user: 'root',
    password: '123',
    database: 'pro_blog',
    timezone: 'Asia/Shanghai'
});
// 执行查询操作 返回Promise对象
function query(sql, values = []) {
    return new Promise(function (resolve, reject) { // resolve是成功的回调函数 reject是错误的回调函数
        pool.query(sql, values, function (err, result) {
            if (err) {
                reject(err) // 有错误 直接回调错误的函数
            } else {
                resolve(result) // 直接正常的回调
            }
        });
    });
}
// 暴露接口
module.exports = {
    pool,
    createConnection: pool.getConnection,
    query
}
```

##### 2、实现分页查询文章列表

article.js

```javascript
const db = require('./db'); // 
// 文章模块
const router = require('express').Router();

// 1.查询文章列表(分页)
router.get("/web/list",async function (req, res) {
    const page = req.query['page'] || 1; // 获取页码
    // 默认每页5条
    const start = (page - 1) * 5;
    try{
        const listResult = await db.query("select * from article limit ?,5", [start]); // 执行查询语句并等待结果出来后赋值给变量
        const totalCount = await db.query('select count(*) as total from article'); // 执行获取总条数
        res.send({count:totalCount[0]['total'],list:listResult,size:5});
    }catch(e){
        console.log(err)
        res.send({count:0,list:[]});
    }
});
module.exports = router
```

##### 3、实现关键字查询

article.js

```javascript
router.get("/web/list", async function (req, res) {
    const page = parseInt(req.query['page'] || 1); // 获取页码
    const size = parseInt(req.query['size'] || 5); // 每天条数
    // 默认每页5条
    const start = (page - 1) * size;
    const search = '%' + (req.query['search'] || '') + '%'; // 搜索关键字
    try {
        const listResult = await db.query(
            `select id,title,category,publish_time,cover,description,view_count,comment_count
             from article where title like ? or content like ? limit ?,?`,
            [search, search, start, size]); // 执行查询语句并等待结果出来后赋值给变量
        const totalCount = await db.query(
            `select count(*) as total from article where title like ? or content like ?`, [search, search]); // 执行获取总条数
        res.send({ total: totalCount[0]['total'], list: listResult, size: size });
    } catch (e) {
        console.log(e)
        res.send({ count: 0, list: [] });
    }
});
```

##### 4、实现查询文章详情

article.js

```javascript
// 2.查询单个文章详情
router.all('/web/detail',async (req,res)=>{
    let id = req.query['id'];
    if(!id || id <1){
        res.send([])
    }else{
        const article = await db.query('select * from article where id =?',[id]);
        res.send(article)
    }
});
```

##### 5、实现更新文章阅读数

article.js

```javascript
router.all('/web/update',async (req,res)=>{
    let id = req.query['id'];
    if(!id || id <1){
        res.send("参数不对")
    }else{
        const article = await db.query('update article set view_count=view_count + 1 where id =?',[id]);
        res.send("true")
    }
});
```