本文首發於 GitChat 平臺,免費 Chat,鏈接:全棧開發入門實戰:後臺管理系統
感謝你打開了這篇 Chat,在閱讀之前,需要讓你瞭解一些事情。
第一,本 Chat 雖然免費,不代表沒有價值,我會將個人全棧開發的經歷敘述給你,希望對你有一些幫助;
第二,文中所使用的技術棧並非最新,也並非最優。後臺管理系統更多是 2B 端的產品,通常是業務優先。本 Chat 的目的是爲了讓你能夠快速上手全棧開發。
第三,本 Chat 雖然名爲全棧開發,但是不會帶你做完一個完整的後臺管理系統項目。一是由於篇幅有限,二是由於時間關係,個人精力也有限。
正文
本 Chat 的內容,正如 Chat 簡介中所描述,將分爲以下 5 大塊:
- 開發準備
- 前臺樣式
- 數據庫連接
- 前後臺交互
- 線上部署
你可能會發現,好像不知道要做什麼,沒錯,後臺管理系統一般都是企業內部定製開發,通常是對業務的數據管理,具體做什麼功能由業務決定,但多數功能都是圍繞着表格或者表單。
上面列舉的僅僅是全棧開發的大致流程。首先,要做一些準備工作,例如:開發環境、編輯器環境以及依賴包配置等等工作;其次,我們要選定一個後臺模版樣式,快速套用,實現業務功能。(當然,你要自己開發也行,但不建議這麼做,除非爲了學習);然後,根據業務做數據庫的設計,編寫後臺數據處理邏輯,以及前後臺數據交互等功能;最後,測試並部署上線。
這裏的示例,將實現一個學生數據的在線管理需求,其實就是一個在線表格,包括添加,刪除功能,系統層面包括登錄退出功能。麻雀雖小,五臟俱全,整體架子搭好了,再在上面添加功能就簡單多了。好了,現在就開始全棧之旅吧。
開發準備
啓動項目
首先要做的是,開發環境的安裝,這裏就不多說了,關於 Node 環境的安裝,默認你已經搞定了。
既然採用 Express 作爲 Web 框架,Express 也是要安裝的,有了 Node 環境,安裝 Express 就簡單多了。我們直接上手 Express 的腳手架,全棧開發關鍵要速度。
npm install express-generator -g
一定記得是全局安裝。安裝完成之後,輸入 express -h
可以查看幫助。這裏選用 ejs 模版引擎,爲什麼?因爲我順手而已,這個不重要。找個合適的目錄,運行下面命令:
express -e node-web-fullstack-demo
生成項目目錄之後,首先要安裝依賴,如下命令:
cd example-node-web-fullstack
npm install
等待安裝完成,我們就可以啓動項目了,使用命令 npm start
,去瀏覽器中,打開網址:http://localhost:3000,看到寫着 Express 的首頁,代表你的項目啓動成功了。
編輯器環境配置
一個好的編碼環境,可以讓你項目開發效率加倍。
首先介紹一個編輯器配置 EditorConfig,這是一個編輯器的小工具。它有什麼作用呢?簡而言之,就是讓你可以在不同的編輯器上,獲得相同的編碼風格,例如:空格縮進還是 Tab 縮進?縮進幾個空格?
你可能覺得詫異,這個不是在編輯器上設置就可以了嗎?沒錯,假設你從始至終都是在同一個電腦同一個編輯器上編碼,那麼可以忽略它。如果存在多電腦配合,亦或是多個編輯器配合,那麼它就是神器。它幾乎支持所有的主流編輯器,不用單獨去編輯器中設置,配置文件就在項目中,用那個編輯器打開項目,都能獲得一致的編碼風格。
使用方法很簡單,在根目錄中新建 .editorconfig
文件即可。
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
[*.*]
charset = utf-8
# 4 space indentation
[*.js]
indent_style = space
indent_size = 4
# Indentation override for all JS under lib directory
[views/**.ejs]
indent_style = space
indent_size = 2
上面這個示例,設置了 js, ejs 的縮進,並通過 insert_final_newline
設置了文檔末尾默認插入一個空行。還有一些有意思配置,建議你查看一下官方文檔,就明白了,非常好用的一個小插件。
代碼檢查工具
使用 js 編碼,建議最好使用一款代碼檢查的工具,不然寫到後面就會很尷尬,往往一個小的語法錯誤,會讓你抓狂好久。代碼檢查工具推薦 ESLint。
使用方法也很簡單,首先安裝要它,npm install eslint --save-dev
,代碼檢查工具通常只需要安裝在開發依賴裏即可。緊接着你應該設置一個配置文件:./node_modules/.bin/eslint --init
,然後,你就可以在項目根目錄下運行 ESLint:./node_modules/.bin/eslint yourfile.js
用來校驗代碼了。如果你使用的是 IDE 編碼,一般都會有 ESLint 的插件來校驗代碼,例如在 VS Code 中安裝 eslint 插件,就可以實時校驗正在編輯的文件了。
配置文件裏的設置參數就多了,建議自行查看官方文檔。如下示例:
{
"rules": {
"semi": ["error", "always"],
"no-undef": "off",
"no-unused-vars": "off",
"no-console": "off"
},
"env": {
"node": true,
"es6": true
},
"extends": "eslint:recommended",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
}
}
有時候你可能會覺得很煩,默認推薦的 eslint 校驗規則管得太多了,上述中我就手動關掉了「不允許使用 console」「不允許存在未定義變量」等配置。有些人可能乾脆關掉了校驗,但是,我還是強烈建議啓用,畢竟規範的編碼效率會更高。
前臺樣式
前面做了那麼多工作,但似乎一點效果也看不到,彆着急,這一部分的內容,就能讓你直接看到效果。
前端的工作其實是非常費時的,它包括頁面設計及佈局等等,往往一個頁面光調樣式就要花費很長的時間。假如你是想快速搭建一個後臺管理系統,不建議自己去寫前端頁面代碼。很有可能,頁面樣式還沒有出來,就已經放棄了。除非你是單純想要學習。
建議的做法是,快速套用一個前端模版庫,將大體的頁面結構搭建出來,然後在上面添加業務功能。 這裏採用 AdminLTE 這套模版庫,大概效果圖如下:
它是基於 bootstrap3 的後臺模版,幾乎不再需要做 CSS 的開發,一下子就解決了前端界面設計的大問題。下面來看看如何套用它。
下載安裝
推薦採用 bower
包管理器來安裝,安裝命令:bower install adminlte
等待下載就可以了。下載完成後,你會發現bower_components/
目錄裏多了好多目錄,這些都是它的依賴包。
接下來,我們還要將整個項目下載下來,得到它的示例代碼,打開其中的 index.html
文件,分析一下它的頁面結構,好便於後面的拆解。如下圖:
拆解頁面結構
這是後臺首頁的一個基本結構,分爲:main-header
、main-sidebar
、content-wrapper
、main-footer
、 control-sidebar
,於是,我們按照這幾大模塊,拆分頁面,分別保存爲 layout 模版。在項目 views
下新建兩個目錄:backend
以及 frontend
,分別對應後臺頁面以及前臺頁面。
在 backend
裏再新建 layout
目錄以及 index.ejs
文件,layout
中用來保存通用的頁面代碼,index.ejs
則代表後臺首頁。拆解完的 index.ejs
代碼如下:
<% include ./layout/head.ejs %>
<!-- custom css files -->
<% include ./layout/header.ejs %>
<% include ./layout/main-sidebar.ejs %>
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
... 頁面代碼 ...
</div>
<% include ./layout/control-sidebar.ejs %>
<% include ./layout/footer.ejs %>
<!-- custom javascript files -->
<% include ./layout/foot.ejs %>
相信你一眼就能看出上面這段代碼的意思,將一個頁面拆分後,再重組,這就是最終的首頁。其他的所有頁面都可以通過這樣的方式,進行重組,我們要寫的代碼,僅僅只是修改 <div class="content-wrapper"></div>
裏的頁面即可。
配置靜態資源目錄
現在的頁面,我們還不能訪問,因爲頁面中鏈接的 CSS 以及 JS 文件路徑都不對,模版裏引用的都是相對路徑,我們需要將它改爲本項目裏的絕對路徑。打開 layout/head.ejs
文件,部分代碼如下:
<!-- Bootstrap 3.3.7 -->
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
<!-- Font Awesome -->
<link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.min.css">
<!-- Ionicons -->
<link rel="stylesheet" href="bower_components/Ionicons/css/ionicons.min.css">
<!-- Theme style -->
<link rel="stylesheet" href="dist/css/AdminLTE.min.css">
套用模塊的好處就是,這些鏈接庫的地址基本上沒太大的變化,我們只需要修改一下根目錄就可以了。修改之前,我們需要先配置一下項目的靜態資源目錄,打開項目根目錄下的 app.js
文件,加上一行代碼如下:
...
app.use(express.static(path.join(__dirname, 'public')));
// 新添靜態資源目錄 bower_componennts
app.use(express.static(path.join(__dirname, 'bower_components')));
...
新加的這句代碼意思是,將項目中 bower_components/
目錄設置爲靜態資源訪問的根目錄,那麼以上的那些靜態資源,我們就知道怎麼引入了,修改如下:
<!-- Bootstrap 3.3.7 -->
<link rel="stylesheet" href="/bootstrap/dist/css/bootstrap.min.css">
<!-- Font Awesome -->
<link rel="stylesheet" href="/font-awesome/css/font-awesome.min.css">
<!-- Ionicons -->
<link rel="stylesheet" href="/Ionicons/css/ionicons.min.css">
<!-- Theme style -->
<link rel="stylesheet" href="/admin-lte/dist/css/AdminLTE.min.css">
同樣的,JS 文件的引用路徑也要進行修改,打開 footer.ejs
文件,以 bower_components/
爲根目錄,修改對應的 JS 文件路徑即可。好了,該頁面所有引用的靜態資源路徑,都已經修改正確了,接下來就是編寫頁面路由了。
PS. 頁面中的 JS 庫並非都與自己的項目相匹配,後續要根據實際情況,進行增減。
編寫頁面路由
頁面準備好了,我們需要去編寫訪問路由,在這點上,Node 就與 PHP 和 Java 有所區別了,Node Web 開發不需要再搭建一個 Web 服務器,PHP 需要 Apache ,Java 需要 Tomcat,而 Node 把它交給你,讓你去自定義。我們打開項目 routes/index.js
路由文件,編寫代碼如下:
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
// My backend index page
router.get('/backend', function(req, res, next){
res.render('backend/index.ejs', {});
});
module.exports = router;
我們加了一個路由 /backend
,渲染了上面準備好的 index.ejs
頁面,好了,我們重啓一下項目,哦,對,如果已經安裝了 supervisor
,不需要手動重啓了,直接訪問 http://localhost:3000/backend
。
細心的你可能發現,這個頁面有些元素顯示不正常,這是因爲有些靜態資源,我們沒有安裝而導致的加載失敗,這個無需擔心,因爲實際頁面還需要很多的時間去打磨,這個要根據實際業務來決定頁面的內容,這裏就不去展開了。
接下來的事情,就是改寫頁面代碼了,整體的樣式結構都有了,剩下就是對頁面的刪刪減減了。
數據庫連接
根據上面的內容,相信你能夠添加完頁面路由。當全部頁面改寫完畢之後,我們就相當於得到了,一個靜態的後臺管理系統。要達到的效果就是:點擊左側相應的導航,可以實現頁面跳轉,但是沒有實際的數據,頁面中的數據都是寫「死的」,因爲我們還沒有連接數據庫,接下來的工作就涉及到數據庫的連接了。
設計數據表
不管你選擇哪一個數據庫,首先都要做設計數據表,對於數據表的設計以及業務埋點等內容,都可以當作一本書來講,這裏不會有那麼詳細的內容,來教你如何設計數據表,以及業務功能埋點。但有個建議需要提一下:不要着急去學習 SQL 語言,你可能會說,不學習 SQL 怎麼設計數據表呢?怎麼操作數據庫呢?
這裏需要強調的是,不是不需要學,而是開始的時候,不用着急去學。全棧開發重要的是快,不然你全棧幹啥呢?在實際的場景中,業務纔是最重要的。
首先打開你的 Excel(或在線表格應用) 把表的設計做出來再說。例如下圖是我當時一個項目的數據表設計:
最重要的是要去做這個事情,而不是去學數據庫,別擔心做得不夠好,一次一次的實戰會讓你越來越熟練。做完這個工作,全棧開發基本算是完成了 40%,在這個過程中,一定要深入去分析業務流程,把該用到的不該用到的都要考慮進去,前期可以不做開發,但是必要的字段一定要預留。
編寫數據庫模塊
通常情況下,最好找一個趁手的數據庫 GUI 工具,把數據庫以及相關的表創建出來。這一步做完之後就是編寫連接數據數據的代碼了,建議在項目的根目錄中,新建名爲 db
的目錄,用來存放所有關於數據庫操作的代碼。
這裏我們選用 MySQL 數據庫,Node 連接 MySQL 還需要安裝一個數據庫模塊。在項目目錄,執行命令 npm install --save mysql
即可,安裝完成之後,就可以通過下面的示例,嘗試連接數據庫了。
var mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'me',
password : 'secret',
database : 'my_db'
});
connection.connect();
connection.query('SELECT 1 + 1 AS solution', function(err, rows, fields) {
if (err) throw err;
console.log('The solution is: ', rows[0].solution);
});
connection.end();
這是官方提供的一個簡單例子,從上面的例子可以得出:使用createConnection(option)
方法創建一個連接對象,然後連接對象的connect()
方法創建連接,最後使用query()
方法執行SQL語句,返回結果作爲回調函數的參數rows
返回,rows
爲數組類型。
通常情況下,我們會使用連接池進行連接,連接池具體實現原理就不多展開了,可以查看一下官方文檔。下面直接給出使用示例。
// connect.js 使用getConnection方法
var mysql = require('mysql');
var config = require('./config.json');
// 將配置寫入配置文件中
var pool = mysql.createPool(config.mysql);
exports.querySQL = function(sql, callback){
pool.getConnection(function(err,conn){
conn.query(sql,function(err,rows,fields){
callback(err,rows,fields);
conn.release(); // 不要忘了釋放
});
});
}
使用的時候,直接使用querySQL
方法即可,如下:
// db.js 查詢用戶信息
var connect = require('./connect.js');
exports.getUser = function(username,callback){
var sql = 'select * from user where username = "' + username + '"';
connect.querySQL(sql,function(err,rows,fields){
callback(err,rows,fields);
});
};
上面示例中,直接將 sql 語句寫進代碼中了,通過執行 sql 語句來獲得數據庫數據。
這種方式優點是比較直觀,但是缺點就太多了。不安全,拼接字符串容易錯,可擴展性差等等。在實際項目中,我們往往都會選用一款 ORM 框架來連接數據庫。
使用 ORM 框架
關於 Node 使用 ORM 框架的介紹,我之前單獨寫了一篇 Chat,同樣是免費的,這裏我就不再贅述了。
請點擊鏈接查看:如何使用 Sequelize 框架快速進行 Node Web 開發
前後臺交互
通常情況下,前後臺交互的開發會涉及到多個部門多個崗位協作完成,除了前後臺,近幾年也分離出中臺的概念,專門提供接口服務。
而對於全棧開發來說,這個環節顯然要簡單得多了,雖然工作流程可以省,但是,必要的前中後臺的框架還是要有的,分離開的好處是儘量降低功能耦合性,便於後期維護。
前臺頁面獲取動態數據通常有的兩種方式:
- 一是,被動獲取,頁面被渲染,後臺傳參給前臺;
- 二是,主動獲取,前臺主動請求,異步獲取數據。
後臺傳參方式
關於頁面展示,前面已經講過了,根據模塊改寫頁面,然後編寫路由,就可以進行訪問了。但問題是,目前頁面上的一些內容都是靜態的,例如:站點的名稱,Logo以及標題等等。
通常情況下,這些內容不應該是寫死在頁面中的,而應該是動態的,從後臺傳來的,甚至應該是寫入數據庫的,將來做個修改頁面,就可以隨時修改網站標題以及 Logo 等信息。
這裏就涉及到後臺傳參的相關知識點了。Express 框架給我們提供了三種傳參的方式:
(1)應用級別的傳參
使用 app.locals
定義參數。例如:在 app.js 文件中,routes(app);
語句之前加入 app.locals.hello = "Node"
,我們在任何頁面中,使用 hello 這個參數都是沒問題的。如何使用?建議先了解下模版引擎的用法,這裏是 ejs。
(2)路由級別的傳參
使用 res.locals
定義參數。例如下面例子:
app.use(function (req, res, next) {
res.locals.web = {
title: 'STU INFO',
name: '數據管理',
desc: '學生數據管理',
verifier: 0
};
res.locals.userInfo = {
username: 'admin',
title: '管理員'
};
res.locals.url = req.url.split('?')[0];
next();
});
通過一箇中間件,將站點信息寫入 res.locals 對象中,於是我們在對應的頁面中,就可以使用 web 以及 userInfo 等對象參數了。例如下面登陸頁面部分代碼(注意:這裏是 ejs 模版引擎的寫法):
<div class="login-logo">
<a href="/" title="<%= web.desc %>"><b><%= web.title %> </b><%= web.name %></a>
</div>
(3)頁面級別的傳參
使用 res.render()
方法定義參數,這應該是最常用的一種方式,例如頁面的 title 屬性,每個頁面都是不一樣的,所以在渲染模版的時候,傳入 title 參數,是最合適的。例如登陸頁面的路由:
router.get('/login', function(req, res, next) {
res.render('login', {
pageInfo:{
title: '登陸頁面',
}
});
});
示例中,定義了一個 pageInfo 的對象,將其當作參數傳輸到了前臺。
前臺異步獲取
前臺主動獲取數據的例子很多,通常都是通過 Ajax 異步的方式。典型的例子就是在線表格功能,因爲後臺管理系統幾乎離不開在線表格的功能。
關於在線表格的內容,我之前寫過一篇 Chat,建議閱讀,同樣是免費的。鏈接:如何快速將線下表格數據線上化。
這裏我重點介紹下,後臺數據如何產出?首先要明白,數據肯定從數據庫中獲得,我們編寫數據庫 API 的時候,就應該將對應的方法封裝好,前臺通過訪問數據路由,路由調用對應的數據庫 API 即可獲得數據,並返回前臺。
下面看下路由的代碼示例:
// api
const studentHandler = require('../../db/handler/studentHandler.js');
// 當前學生列表
router.get('/list', function(req, res, next) {
let state = 0 ; // 代表當前在校
studentHandler.getStudentList(state, function(p){
res.send(p);
});
});
對應的數據 API 封裝在了 db/handler 目錄中,路由通過調用 getStudentList
方法,即可獲得對應的學生列表,並通過 res.send(p)
方法返回給前臺。
再來看看數據庫 API 是如何編寫的?代碼示例如下:
const { Student } = require('../relation.js');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
module.exports = {
// 根據狀態查詢學生列表
getStudentList: function(state, callback){
Student.findAll({
where: {
state: state
}
}).then(function(data){
callback(data);
}).catch(function(err){
callback(err);
});
},
// 批量添加學生
uploadStudent: function(data, callback){
Student.bulkCreate(data).then(function(){
callback();
}).catch(function(err){
callback(err);
});
}
};
重點看下 module.exports
對外的接口,篇幅原因,這裏只提供了兩個調用方法。如果這裏的代碼看不明白,請翻回去查看「使用 ORM 框架」這節內容中,推薦的那篇 Chat。
通常情況下,大部分功能都是通過「後臺傳參」以及「異步獲取」這兩種方式配合來實現的。
有時候甚至是取代關係,你可能會發現有些後臺管理系統,進去之後,從始至終 URL 就沒有變過,這一類的後臺管理系統的功能實現,全部採用前臺異步獲取的方式。我本人不太推薦這種方式,因爲想要單獨展現某個頁面的時候,就非常的尷尬,同時也非常不利於頁面維護。
線上部署
當項目開發完成,本地測試完畢之後。下一步,就是部署上線了。上線部署原本是個比較繁瑣的工作流程,特別針對多系統聯動的情況。這裏我們不考慮複雜的情況,單純講獨立開發的後臺管理系統如何快速部署到線上?
你可能會說,這有啥好講的,把本地代碼同步到服務器上,然後在服務器上啓動不就得了。沒錯,大體是這麼個意思,但是其中還是有一些點需要注意的。
使用進程管理工具啓動項目
這裏我推薦使用 supervisor,也沒其他原因,順手而已。它是一個進程管理工具,當線上的 Web 應用崩潰的時候,它可以幫助你重新啓動應用,讓應用一直保持線上狀態。
安裝方法很簡單,在命令行中,輸入 npm install -g supervisor
即可。安裝完成之後,我們需要修改一下項目的啓動命令,打開 package.json
文件,編輯如下:
...
"scripts": {
"start": "supervisor --harmony -i views/,public/ ./bin/www",
},
...
supervisor --help
查看使用方式,以上命令,配置了 --harmony
模式啓動 Node
,同時,使用 -i
參數忽略了 views/
以及 public/
目錄。
修改完成後,我們依然使用 npm start
啓動項目,不一樣的是,當我們修改了除 views/
以及 public/
目錄以外的文件後,服務將會自動重啓,以確保線上一直運行的是最新版項目。
使用 Git 同步代碼
將代碼從本地拷貝到線上,有很多種辦法。這裏推薦使用 Git 工具,如果沒有自己的 Git 服務器,那麼就使用 GitHub 類公共 Git 服務平臺。大可不必擔心代碼泄露的問題,GitHub 不是已經提供私有倉庫免費的功能了嘛,沒事,放心用吧。
具體操作我就不再贅述了。
使用 Nginx 反向代理服務器
爲了確保性能,不建議直接在線上環境,通過 npm 或 supervisor 直接啓動項目,雖然 node 本身的性能並不差,建議還是在 node 服務中間,再加一層 Web 服務器當作方向代理,與 Node 最配的當然是 Nginx 了。
安裝 Nginx 不必多說,下面貼出 Nginx 對應的配置文件內容供參考。
server {
listen 80;
server_name xxx.com
charset utf-8;
#此處配置你的訪問日誌,請手動創建該目錄:
access_log /var/log/nginx/js/access.log;
location / {
try_files /_not_exists_ @backend;
}
# 這裏爲具體的服務代理配置
location @backend {
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
#此處配置程序的地址和端口號
proxy_pass http://127.0.0.1:8080;
}
}
關鍵記住最後一句:proxy_pass 代理的是哪個程序地址和端口號。
最後,啓動 Node 服務,啓動 Nginx 服務,然後訪問對應服務地址,大功告成。
總結
這篇 Chat 從 5 個方面講述了全棧開發一個後臺管理系統的大致流程以及部分實戰內容。由於內容量遠超出了預期,在寫的過程中,不斷的刪減了很多篇幅。使得 Chat 的整體效果,沒有達到我的預期。
也許這個內容,通過一篇 Chat 確實很難容下吧。所以,我計劃寫完整個課程,內容包括 Node 基礎、進階應用以及Web 實戰等。從最基本的原理開始介紹,到最後全棧開發實戰。
課程以開源免費的形式公開,GitHub 倉庫地址:Node 全棧開發入門課程,更新頻率不定,歡迎關注。
同時,也可以關注我的微信公衆號:個人學習,以便了解最新進展。
謝謝觀看~