# 項目需求與技術架構
完成一個新聞發布,修改,刪除,展示的網站,及完成對新聞的 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)
}
```