圖書商城項目練習②後端服務Node/Express/Sqlite

image.png

本系列文章是爲學習Vue的項目練習筆記,儘量詳細記錄一下一個完整項目的開發過程。面向初學者,本人也是初學者,搬磚技術還不成熟。項目在技術上前端爲主,包含一些後端代碼,從基礎的數據庫(Sqlite)、到後端服務Node.js(Express),再到Web端的Vue,包含服務端、管理後臺、商城網站、小程序/App,分爲下面多個篇文檔。

🪧系列目錄


00、後端服務Node/Express/Sqlite

後端服務實現的比較簡單,僅僅只是爲了滿足前端的最小需要。實現了登錄、文件上傳下載、一些數據的增刪改查等基礎功能,正式後端服務中的權限、安全、性能並沒有考慮,主要是也不太熟悉。用Node做一個簡單的後端服務還是比較容易的,前端開發還是有必要了解一下,不僅要和前端同行卷,更要往外卷,捲起來!—— 跟自己卷就行了!(不得不)持續學習,(被迫)不斷進步!

image.png

🔸技術路線

  • Node.js,開發運行環境v16.17.1
  • express,Web組件v4.18
  • sqlite3,數據庫

🔸相關組件

  • express.static:靜態資源託管,express提供的,無需額外安裝。
  • multer:文件上傳

🔸源代碼地址Github / KWebNoteGitee / KWebNote,後臺服務端代碼在目錄📁server下。


01、數據庫Database

1.1、用什麼數據庫?Sqlite

Node支持多種數據,官網Database integration。本項目採用的是Sqlite數據庫,Sqlite是一款輕量的關係型數據庫,無需安裝、無需配置、無需單獨啓動。SQLite是一種嵌入式數據庫,它的數據庫就是一個db庫文件,極度輕量,該有的功能都有。可嵌入到多種語言中使用,當然也支持JS,引入Sqlite的JS庫“sqlite3”即可進行數據庫增刪改查操作了。

image.png

🗃️項目中的Sqlite數據庫文件就一個db文件“book_db.db ”單文件20KB。

GUI管理工具推薦下載 SQLiteStudio,非常好用,免費開源,功能齊全,支持Windows、IOS、Linux。

image.png

1.2、數據庫結構設計

基於項目需求,設計了5個表,表結構相對比較簡單,設計比較也比較隨意。

🗓️書籍信息表“books”

編碼 名稱 描述/備註
id 編號ID 主鍵,自增長
name 書籍名稱
catgory 圖書分類編號 來自字典數據,字典類型dictype.code=bookType,關聯字典數據dicdata.code
tag 促銷標籤 來自字典數據,字典類型dictype.code=bookTag,關聯字典數據dicdata.code
author 作者
price 價格
imgs 圖片 圖片集合字符串,逗號分割,最多8張
comments 評論數 評論內容就模擬隨機產生了
introduction 詳情介紹 富文本內容,長度最大8000,支持上傳圖片
status 狀態 枚舉值:正常、下架
createtime 創建時間 時間戳
lasttime 修改時間 時間戳

🗓️訂單表“order”

編碼 名稱 描述/備註
id 編號ID 主鍵,自增長
uid 用戶id 下單的用戶
money 訂單金額
products 商品 購買的圖書商品列表,書名、數量、價格的Json字符串
status 狀態 枚舉值:unpay(未支付)、canceled(已取消)、done(已完成)
createtime 創建時間 時間戳
INSERT INTO [order] (createtime,status,products,money,uid)
VALUES (1675319639624,'unpay','[{"name":"論語","price":155,"total":1},{"name":"論語2","price":15,"total":2}]',113,1);

數據:

id uid money products status createtime
1 1 113 [{"name":"論語","price":155,"total":1},{"name":"論語2","price":15,"total":2}] unpay 1675319639624
2 1 1334 [{"name":"論語","price":155,"total":1},{"name":"論語2","price":15,"total":2}] canceled 1675319639624
3 2 333 [{"name":"論語","price":155,"total":1},{"name":"論語2","price":15,"total":2}] done 1675319639624

🗓️用戶表“user_info”

編碼 名稱 描述/備註
id 編號ID 主鍵,自增長
name 用戶名
pwd 密碼 明文存儲,沒有加密。實際項目中不會這麼幹

數據:

id pwd name
1 123 admin
2 123456 ading
3 test test

🗓️字典類型“dictype”

編碼 名稱 描述/備註
id ID 主鍵,自增長
code 類型編碼 不可重複,如booktype(圖書分類)
name 名稱
tree 是否樹形結構 1=樹形結構,0=非樹形結構,主要是區分數據的結構

數據:

id code name tree
1 bookTag 商品標籤 0
2 bookType 書籍分類 1

🗓️字典數據“dicdata”

編碼 名稱 描述/備註
id 編號ID 主鍵,自增長
name 名稱
code 類型code dictype.code,用於區分不同字典數據
pid 父ID 一級(根)的值爲0,以此來判等根節點並組裝爲樹

數據:

id name code sort pid
1 促銷 bookTag 1 0
2 熱賣 bookTag 2 0
3 新上市 bookTag 3 0
4 買它! bookTag 4 0
5 科技 bookType 1 0
6 計算機/網絡 bookType 1 5
7 人文歷史 bookType 2 0
8 醫學12333 bookType 2 5
25 小說 bookType 1 7
26 歷史 bookType 2 7

02、創建項目

創建一個“server”目錄,就創建完畢了。後端服務做的比較簡陋,文件結構也比較清晰。

image.png

  • 📁api目錄:服務端的api接口,使用Express的路由來構建。
    • base.js:基礎公共api,如登錄、首頁數據
    • book.js:書籍管理api
    • file.js:文件上傳api
    • order.js:訂單模塊api
    • system.js:系統管理模塊api,如字典管理
  • 📁db目錄:
    • book_db.db:sqlite數據庫文件,就一個db文件,非常輕量。
    • db.js:sqlite數據庫訪問的封裝。
  • 📁file目錄:上傳的文件存儲在這裏的。
  • index.js:入口JS文件,組裝📁api目錄下的API接口、註冊HTTP服務。

服務端運行也同樣簡單,用node啓動入口JS文件index.js即可。

node index.js

03、sqlite3數據庫api

安裝sqlite3的Node組件:cnpm install sqlite3 -S,在📁server目錄下運行。

數據庫的操作接口很簡單,指定sqlite的db文件,申明一個sqlite3實例對象,就可幹活了。主要就是執行sql(新增insert、修改update、刪除delete)、查詢(select)數據!

let sqlite3 = require('sqlite3').verbose();
let db = new sqlite3.Database('file-name.db');  //指定sqlite的db文件
//查詢,參數row爲查詢的結果結合
db.all("select * from 表名",function(err,row){
  console.log(row);
})
//執行sql:新增、修改、刪除
const sql = "delete from dicdata where id=?";
const params = [req.body.id];
db.run(sql, params, function (error) {  })

可以做一個簡單的封裝,統一返回的數據結構。

  • db.queryData:查詢數據
  • db.executeSql:執行sql:增、刪、修改
//加載sqlite並創建數據庫實例
let sqlite3 = require('sqlite3').verbose();
let db = new sqlite3.Database('./db/book_db.db');

// 響應數據結構
function ResponsData(error) {
  this.status = error ? 'Error' : 'OK';
  this.message = error ? error : null;
}
//異常處理
db.printError = function (error) {
  if (error) console.error(error);
}
//查詢數據
db.queryData = function (sql, params, callback) {
  if (!params) params = {};
  db.all(sql, params, function (error, rows) {
    db.printError(error);
    let resData = new ResponsData(error);
    callback(resData, rows);
  })
}
//執行sql:增、刪、修改
db.executeSql = function (sql, params, callback) {
  db.run(sql, params, function (error) {
    db.printError(error);
    let resData = new ResponsData(error);
    callback(resData);
  })
}
module.exports = db;

📢參數化:使用參數化單獨管理sql中的變量參數,更安全,這是防止SQL注入的基礎手段。

  • 使用問號?”作爲參數的佔位符,參數按照順序組裝爲數組。insert into books values(NULL,?,?,?,?,?,?,?,?,?,?,?)
  • 模糊查詢時,在參數中構造模糊查詢表達式:wheresql = " where name like ?"; params.push("%" + req.body.name + "%");

更多SQL使用詳見Github / KWebNote


04、Express開發後臺API

4.1、Express入門

express 是Node.js 的組件_(express /ɪkˈspres/ 快速、快遞)_,一個快速、開放、極簡的 Web 開發框架, 提供一系列強大特性幫助你創建各種Web應用。使用非常簡便、靈活,用於搭建WEB服務,所以我們就用Express來搭建後端API服務。

image.png

  • 1、安裝express:cnpm install express -S,在📁server目錄下運行。
  • 2、創建實例,添加接口、監聽端口。
  • 3、運行,在命令行中用node命令運行該JS文件,啓動Express服務:node server.js
//加載組件
let express = require('express');
//創建一個服務端服務實例server
let server = new express();

//定義一個get接口
server.get('/hello', (req, res) => {
  res.send('Hello World!')
})
//定義一個post接口
server.get('/send', (req, res) => {
  console.log(req.body); //post發送的數據
})
//*****  配置監聽端口 *****/
server.listen(801, err => {
  if (!err)
    console.log('服務器啓動成功,地址:http://localhost:801')
})

啓動:node server.js

image.png

測試:http://localhost:801/hello

image.png

4.2、Express路由/API接口

後端的API接口的封裝比較簡單了,監聽路由API地址,接收請求、處理(操作數據庫),然後返回響應結果。爲了更好管理後端的API,使用Express的路由express.Router()來組織不同模塊的API接口。各個模塊的API可以單獨管理,然後統一組裝。

比如book.js,圖書管理api:

//引入express
let express = require('express');
//獲取Express的路由
let router = express.Router();
//引入db庫
const db = require('../db/db.js');

//設置路由:獲取單個book數據,get方式
router.get('/book/id', (req, res) => {
  //sql語句
  let sql = "select id,name,author,introduction,imgs,status,catgory,price,tag,comments,createtime,lasttime from books where id =?";
  //調用db封裝的查詢api,返回數據
  db.queryData(sql, [req.query.id], (resData, rows) => {
    resData.data = rows[0];
    res.send(resData);
  });
})
//設置路由:新增、修改,post方式
router.post('/book/save', (req, res) => {
  let sql = '';
  // 參數,在sql中用?佔位(按照順序)
  let params = [req.body.name, req.body.author, req.body.introduction, req.body.imgs, req.body.status, req.body.catgory, req.body.price, req.body.tag, req.body.comments, req.body.createtime ?? Date.now(), Date.now()];
  //update 修改數據
  if (req.body.id) {
    sql = "update books set name=?,author=?,introduction=?,imgs=?,status=?,catgory=?,price=?,tag=?,comments=?,createtime=?,lasttime=? where id=?";
    params.push(req.body.id);
  }
  else //insert 新增數據 (name,author,introduction,img,status) 
    sql = "insert into books values(NULL,?,?,?,?,?,?,?,?,?,?,?)";
  db.executeSql(sql, params, resData => {
    if (resData.status == 'OK')
      resData.message = '保存成功';
    res.send(resData);
  });
})

// 導出路由router
module.exports = router;

在入口文件index.js中引用安裝book.js的路由。

//加載組件
let express = require('express');
//創建一個服務端服務實例server
let server = new express();

// 書籍模塊api
const book = require('./api/book.js');
const path='/api';
server.use(path, book);

完整API接口詳見Github / KWebNote

4.3、文件的上傳

常用的圖片、Excel等附件的上傳,文件本質上也是數據,同其他API接口一樣,通過POST方式提交文件數據。HTML的FORM表單提交支持多種類型,其中multipart/form-data混合類型就是專用於提交二進制文件的。

🪧HTML中 <form> 表單enctype:編碼類型(encode type),規定了form表單在發送到服務器時候編碼方式。

  • application/x-www-form-urlencoded:編碼所有字符(默認),傳輸字符內容。
  • multipart/form-data :混合類型, 表單中有文件上傳時使用。
  • text/plain:純文體,空格轉換爲 “+” 加號,不對特殊字符編碼。

在Express中上傳文件,使用中間件multer,是專門用來處理複合表單multipart/form-data數據,用來處理後端的文件上傳。

  • 安裝multer插件:cnpm i -S multer。,在📁server目錄下運行。
  • 配置文件上傳接口“/upload”:
let express = require('express');
let router = express.Router();
let multer = require('multer');

// 申明multer實例,並配置文件保存路徑、文件名
let upload = multer({
  storage: multer.diskStorage({
    //文件存儲位置
    destination: function (req, file, callback) {
      callback(null, './file/')
    },
    //文件命名
    filename: function (req, file, callback) {
      callback(null, Date.now() + '-' + file.originalname);
    }
  })
});
// 配置文件上傳接口
router.post('/upload', upload.single('file'), function (req, res, next) {
  if (!req.file) {
    res.json({ status: 'Error', message: '文件上傳錯誤:文件不存在' });
    return;
  }
  let file = req.file;
  //返回響應消息:文件的相對URL地址
  res.json({
    status: 'OK',
    name: file.filename,
    url: '/file/' + file.filename,
  });
});

測試一下,注意字段名稱應爲“file”,與後端保持一致。

image.png


05、託管靜態資源/部署網站

5.1、靜態資源託管

前面上傳了文件,那客戶端如何訪問靜態文件呢?官方文檔:利用 Express 託管靜態文件

在Node中使用express自帶的靜態資源管理插件,即可託管靜態文件,如圖片、網頁文件等。因此不僅可以實現文件下載,還支持部署靜態網站。

// 使用內置的“express.static”實現靜態文件代理,參數爲資源地址。

//圖片靜態資源,第一個參數爲url路由,“./file”爲存放文件的文件夾
server.use('/file', express.static('./file')); 

image.png

這就完了?是的,很簡單!訪問一下試試:

http://localhost:3000/file/f1.jpg
http://localhost:3000/file/1676274037570-gif007.gif

5.2、部署網站/前端路由重定向

前面通過express.static可以託管靜態資源,也就意味着可以部署前端網站,可以把編譯好的管理後臺“book_admin”通過Node來部署試試。

//部署管理後臺網站
server.use('/bookadmin', express.static('./book_admin'));

登錄可用,頁面訪問正常!

image.png

刷新一下,糟了,出錯了,無法訪問此頁面~

image.png

❓這是什麼原因呢?

  • Node後端只配置了路由地址“/bookadmin”,沒有配置“/bookadmin/home”的路由,後端就沒人響應。
  • /home”是前端路由,前端路由vue-router使用的是history模式,如果用hash模式就沒問題。

✅怎麼解決?

  • 方法1:前端路由改用hash模式,/bookadmin#home,hash值#home後端不會管,前端處理。
  • 方法2:後端處理,重定向到單頁應用的頁面即可。具體方式也有多個:
//管理後臺"book_admin"的部署
//靜態資源
server.use('/bookadmin', express.static('./book_admin'));

const fs = require('fs')
const rpath = require('path')
//前端路由的重定向
server.get('/bookadmin/*', function(req, res) {
  const html = fs.readFileSync(rpath.resolve(__dirname, '../server/book_admin/index.html'), 'utf-8')
  res.send(html)
})

重啓node服務解決

D:\Project_Files\kwongGit\KWebNote\server>node index.js
服務器啓動成功,地址:http://localhost:3000

參考資料:


©️版權申明:版權所有@安木夕,本文內容僅供學習,歡迎指正、交流,轉載請註明出處!原文編輯地址-語雀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章