# 项目需求与技术架构
完成一个新闻发布,修改,删除,展示的网站,及完成对新闻的 CRUD。因为要操作新闻数据,所以要使用 Mongoose + MongoDB,也需求处理用户的请求,需使用 Express + Node.js。
# 功能分析
## 项目效果图
通过查询准备好的演示项目看。
* 新闻列表:在此页面展示数据库中的新闻数据。
* 新闻发布:发布新闻页面,页面有一个表单,包括:新闻标题、作者、来源、发布时间、内容,填好提交。
* 新闻删除:新闻列表页面,点击删除链接,直接删除掉。
* 浏览新闻:点击新闻列表的标题进入查看详情页面,展示新闻详情。
* 新闻修改:新闻列表页面,点击修改链接,进入修改的页面,回显被修改的新闻数据,修改完之后提交。
## 数据从页面到数据库要经历哪些过程
数据库不会主动给数据,都是浏览器先发起请求。
* 浏览器发起一个请求。
* 请求提交数据,比如新闻发布的数据。
* 后端程序接收数据。
* 后端程序保存数据到数据库。
## 数据从数据库到页面要经历哪些过程
* 浏览器发起一个请求。
* 后端程序接收请求。
* 后端程序查询数据库。
* 后端程序把拿到的数据返回给浏览器。
# 项目搭建
## express-generator 使用
```
使用 express-generator 来完成项目搭建,使用命令搭建项目(),提高项目搭建效率。
```
```
// 全局安装 express-generator
npm install -g express-generator
// 设置使用的视图技术,并创建项目
express --view=ejs 项目所在位置
```
```
bin : 二进制,里面有一个 www
文件public : 静态资源目录
routers : 路由目录
views : 视图目录
app.js : 应用文件
```
## 安装包
在 package.json 中增加 mongoose:
```
"mongoose": "*",
```
通过 npm install 可以一次性安装所有依赖的包, 若不写具体的版本号 可以用 \* 表示安装最新版本。
## 项目启动
在 app.js 中的代码包含引入模块(包括路由模块),设置视图技术,应用中间件(静态处理等)。若要启动项目,修改app.js 文件代码, 删除导出代码, 加入 app.listen(8888, () => console.log('启动成功8888'))
之后再使用 nodemon app.js 运行项目, 并测试一下.
# RESTful API
RESTful 架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。
针对是动态资源路径的一种风格
* 每一个 URI 代表一种资源;
* 客户端和服务器之间,传递这种资源的某种表现层;
* 客户端通过四个 HTTP 动词,对服务器端资源进行操作,实现"表现层状态转化"。
以下符合 RESTful 风格的 URI:
* GET /zoos:列出所有动物园
* POST /zoos:新建一个动物园
* GET /zoos/ID:获取某个指定动物园的信息
* PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
* PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
* DELETE /zoos/ID:删除某个动物园
* GET /zoos/ID/animals:列出某个指定动物园的所有动物
* DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
其实遵循上述就已经符合 RESTful 风格了,若想遵循更标准的,对响应的内容也有要求,但因为东西过多,就不做描述了与实现,我们就统一响应 JSON 格式的数据,一下就是根据项目需求定义好 RESTful 接口。
* GET /api/articles:列出所有文章
* POST /api/articles:发布文章
* GET /api/articles/ID:获取某篇文章信息
* PUT /api/articles/ID:更新某篇文章
* DELETE /api/articles/ID:删除某篇文章
# 文章发布页面
把前端开发好的页面,若是内容会变化的,改成后缀为 .ejs,放置到项目根目录下 views 目录中;若是内容不会变化的,放置到 public/articles 目录,比如新闻发布的页面,修改页面,详情页面。
在浏览器输入 [http://localhost:3000/articles/new.html](http://localhost:3000/articles/new.html),即可跳转到新闻发布的页面。
# 文章发布
* 修改 new.html,修改页面表单元素的 name 属性值,使用 Ajax 提交请求。
~~~
$(function(){
$('#btnSave').click(function(){
$.ajax({
url: '/api/articles/',
type: 'POST',
data : $('#articleForm').serialize(),
success: function(data) {
// 提示保存结果
alert(data.msg);
// 再重新跳转文章列表页面, 重新获取文章数据
location.href = '/articles/list.html'
}
});
});
});
~~~
* 重命名 routers/user.js 为 routers/articlesRouter.js,修改 app.js 中对这个路由模块引入和应用代码。
~~~
var articlesRouter = require('./routes/articlesRouter');
app.use('/api', articlesRouter);
~~~
* 在 app.js 中编写连接数据库的代码。
~~~
const mongoose = require('mongoose');
// 在项目启动过程中完成数据库链接
mongoose.connect('mongodb://localhost/test', {user:'root', pass:'12345', authSource:'admin'});
mongoose.connection.on('connected', () => console.log('链接数据库成功'));
~~~
* 添加文件 models/articleModel.js,在此文件中定义文章模块。
~~~
const mongoose = require('mongoose');
// 定义 Schema
let ArticleSchema = new mongoose.Schema({
title:String,
author:String,
source:String,
content:String,
createTime:String
});
// 定义 Model
let ArticleModel = mongoose.model('article', ArticleSchema);
module.exports = ArticleModel;
~~~
* 修改 routers/articlesRouter.js 中的内容,针路径是 /api/articles 且 post 方式请求的处理,获取请求参数。
~~~
var express = require('express');
var router = express.Router();
var articleService = require('../services/articleService');
// 负责接收参数组成 js 对象
// 调用对应业务方法
// 根据调用结果做相应响应
router.post('/articles', (req, res) => {
let { title, author, content, source, createTime = new Date().toLocaleString() }
= req.body;
articleService.save({ title, author, content, source, createTime })
.then(function () { // 成功
res.json({ success: true, msg: '保存成功' })
}).catch(function () { // 失败
res.json({ success: false, msg: '保存失败' })
});
});
module.exports = router;
~~~
* 修改 services/articleService.js 中的内容,提供保存文章的方法,并导出。
~~~
var mongoose = require('mongoose');
var ArticleModel = require('../models/articleModel');
module.exports = {
save : art => new ArticleModel(art).save();
}
~~~
# 文章列表
* 准备一个静态页面 list.html 放置在 public 目录下articles 目录中
* 在 list.html 编写发送请求代码, 页面加载完发送请求获取文章数据
~~~
$(function(){
$.get('/api/articles', function(data){
data.forEach(function(article){ // 遍历数据
var trString = `
<tr>
<td><a href="#">${article.title}</a></td>
<td class="tdcenter">${article.source}</td>
<td class="tdcenter">${article.author}</td>
<td class="tdcenter">${article.createTime}</td>
<td><a href="#">修改</a><a class='del' data-id='' href="#">删除</a></td>
</tr>
`
// 页面表格的 id 属性值为 articleTable
$('#articleTable').append(trString);
})
});
});
~~~
* 修改 routers/articlesRouter.js 文件内容,针路径是 /api/articles 且 get 方式请求的处理。
~~~
router.get('/articles', (req, res) => {
articleService.selectAll()
.then(function (result) { // 成功
res.json(result)
}).catch(function () { // 失败
res.json([]);
});
});
~~~
* 修改 services/articleService.js 中的内容,提供查询所有文章的方法,并导出。
~~~
var mongoose = require('mongoose');
var ArticleModel = require('../models/articleModel');
module.exports = {
save: (art) => new ArticleModel(art).save(),
selectAll : Article.find()
}
~~~
# 文章删除
* 修改 views/articles/list.html 文件中删除 a 标签,给其绑定处理事件,发送 Ajax 请求,请求方式 delete,带上删除文文章的 id。
~~~
$(function(){
$('#articleTable').on('click', 'a.del', function(){
// console.log(this); // 被点击 a 标签
var id = $(this).attr('data-id');
console.log(id);
$.ajax({
type:'delete',
url:'/api/articles/' + id,
success: function(data){
// 提示删除结果
alert(data.msg);
// 再重新跳转文章列表页面, 重新获取文章数据
location.href = '/articles/list.html'
}
})
});
});
~~~
* 修改 routers/articlesRouter.js 中的内容,针路径是 /api/articles/ID 且 delete 方式请求的处理,获取被删除文章的 id。
~~~
router.delete('/articles/:id', function(req, res, next) {
articleService.deleteById(req.params.id)
.then(resutl => {
res.json({msg : '文章删除成功'});
}).catch(err => {
res.json({msg : '文章删除失败'});
});
});
~~~
* 修改 services/articleService.js 中的内容,提供根据 id 删除文章的方法,并导出。
~~~
module.exports = {
save: (art) => new ArticleModel(art).save(),
selectAll: () => ArticleModel.find({}),
deleteById: (id) => ArticleModel.findByIdAndRemove(id)
}
~~~
# 文章详情
* 修改 views/articles/list.html 文件中文章标题的 a 标签的 href 属性,值改为 /articles/view.html?id=${article.\_id}
* 修改 public/articles/view.html 文件,等页面加载完发送 Ajax 请求获取文章数据,并回显。
~~~
$(function(){
var id = location.href.split('=')[1];
$.get('/api/articles/' + id, function(data){
$('#title').html(data.title);
$('#author').html(data.author);
$('#source').html(data.source);
$('#createTime').html(data.createTime);
$('#content').html(data.content);
});
})
~~~
* 修改 routers/articlesRouter.js 中的内容,针路径是 /api/articles/ID 且 GET 方式请求的处理,获取被查询文章的 id。
~~~
router.get('/articles/:id', (req, res) => {
let id = req.params.id; // 获取被删除文章 id 值
articleService.selectById(id)
.then(function (result) { // 成功
res.json(result)
}).catch(function () { // 失败
res.json({})
});
});
~~~
* 修改 services/articleService.js 中的内容,提供根据 id 删除文章的方法,并导出。
~~~
module.exports = {
save: (art) => new ArticleModel(art).save(),
selectAll: () => ArticleModel.find({}),
deleteById: (id) => ArticleModel.findByIdAndRemove(id),
selectById: (id) => ArticleModel.findById(id)
}
~~~
# 文章修改页面
* 修改 views/articles/list.html 文件中文章修改 a 标签的 href 属性,值改为 /articles/edit.html?id=${article.\_id}
* 修改 public/articles/edit.html 文件,等页面加载完发送 Ajax 请求获取文章数据,并回显。
~~~
$(function(){
var id = location.href.split('=')[1]
$.get('/api/articles/' + id, function(data){
$('#title').val(data.title);
$('#author').val(data.author);
$('#source').val(data.source);
$('#content').val(data.content);
});
});
~~~
# 文章修改
* 修改 public/articles/edit.html 文件,给修改按钮绑定时间处理函数,使用 Ajax 发送修改文章的请求。
~~~
$('#btnUpdate').click(function(){
$.ajax({
type:'put',
url:'/api/articles/' + id,
data:$('#articleForm').serialize(),
success:function(data){
alert(data.msg);
location.href = '/articles/list.html'
}
})
});
~~~
* 修改 routers/articlesRouter.js 中的内容,针路径是 /api/articles/ID 且 PUT 方式请求的处理,获取被修改文章的 id及修改的数据。
~~~
router.put('/articles/:id', (req, res) => {
let id = req.params.id; // 获取被删除文章 id 值
let { title, author, content, source} = req.body;
articleService.updateById(id, {title, author, content, source})
.then(function (result) { // 成功
res.json({ success: true, msg: '修改成功' })
}).catch(function () { // 失败
res.json({ success: false, msg: '修改失败' })
});
});
~~~
* 修改 services/articleService.js 中的内容,提供根据 id 修改文章的方法,并导出。
```
module.exports = {
save: (art) => new ArticleModel(art).save(),
selectAll: () => ArticleModel.find({}),
deleteById: (id) => ArticleModel.findByIdAndRemove(id),
selectById: (id) => ArticleModel.findById(id),
updateById: (id, updates) => ArticleModel.findByIdAndUpdate(id, updates)
}
```