用 Node.js 快速開發出多功能的多人線上的文章分享平臺
最近在學習使用 Node.js
框架,邊學習邊使用,花了大概 3周 時間做完這個 Web應用 且在 凌晨左右上線成功(其實就是把開發環境搬到伺服器), 地址: a.lishaoy.net
這個 Web應用 的程式碼是開源的,如對這個應用感興趣,想知道程式碼是如何執行的,可以去我 GitHub 下載或 clone
:應用原始碼
首先,來看看用 3周 時間做出來的應用都有些什麼功能,之後再看看選用的 Node.js
框架,最後看看 Node.js
專案如何部署到伺服器。
Web應用功能
登入、註冊驗證
登入功能
- 輸入框沒有輸入點選登入會提示:使用者名稱、密碼不能為空
- 輸入的使用者錯誤或不存在會提示:使用者不存在
- 輸入的密碼錯誤會提示:密碼錯誤
- 登入後會重定向到使用者上次訪問的地址
註冊功能
- 輸入框沒有輸入點選註冊會提示:使用者名稱、郵箱、密碼不能為空
- 使用者名稱和郵箱與其他使用者相同會提示:使用者名稱、郵箱已存在
- 密碼小於6位數會提示:最小長度是6位
- 註冊成功後會傳送驗證郵件到使用者郵箱,需點選郵箱按鈕驗證
文章列表
登入進來,會顯示文章列表頁面,顯示內容如下:
- 文章標題:點選可進入文章詳情頁
- 作者頭像、作者名稱:點選可進入作者資訊頁
- 時間:顯示建立時間(多久以前方式顯示)
- 閱讀次數、點贊次數
- 文章簡要:自動摘取章頭文章
- 縮圖:自動摘取文章第一張圖片
文章詳情
點選文章標題可進入文章詳情頁面,內容如下:
- 文章標題
- 作者頭像、作者名稱
- 釋出時間
- 閱讀次數和點贊次數
- 編輯按鈕(僅作者可見)
- 左側浮動工具欄(點贊、傳送郵件到自己郵箱、返回頂部、分享)
- 點贊:文章被點贊後,作者可以收到訊息通知,且將文章收錄到點贊列表(支援匿名點贊,但不會記錄通知,只會加點贊數)
編輯文章支援 Markdown
新建文章和修改文章都支援 Markdown 語法,且會每隔6秒鐘自動儲存
個人資訊
個人資訊頁面顯示內容如下
- 作者的頭像、姓名、簡介(支援emoji)
- 資訊欄:GitHub 連結、個人網站連結、釋出文章數、總閱讀次數、總點贊次數
- 釋出文章列表:個人釋出的所有文章
- 已贊文章列表:點過讚的文章會記錄在這裡
- 關注者列表:關注你的使用者(關注過的使用者,關注按鈕高亮顯示)
- 已關注列表:你關注的使用者(關注過的使用者,關注按鈕高亮顯示)
- 關注按鈕:作者本人不可見,點選可關注,再次點選取消關注,關注後,使用者會收到訊息通知
下面是我用另一個使用者登入,進入到個人資訊頁面就會顯示關注按鈕,如圖
檔案上傳
點選檔案上傳小圖示可進入檔案上傳頁面,點選 Files 連結可進入檔案上傳列表,顯示內容如圖:
檔案預覽和編輯
從檔案列表頁面點選標題可進入檔案預覽頁面,顯示內容如下:
- 如果是圖片顯示圖片,如果是視訊顯示視訊
- 工具欄:傳送郵件到自己郵箱(登入可見)、編輯按鈕、刪除按鈕(登入自己上傳可見)
- 檔名稱
- 下載按鈕
- 上傳者頭像
訊息通知
點選鈴鐺小圖示可進入訊息通知頁面,內容如下:
- 點贊訊息列表:收到使用者點贊通知,最新的未讀訊息會高亮顯示,點選點贊者頭像進入個人資訊頁面,點選文章標題進入你的文章詳情頁面
- 關注者列表:收到關注者的通知,最新未讀訊息會高亮顯示,點關注按鈕也可關注他,再點選取消關注
- 系統訊息:目前還沒有做功能實現
工具欄列表
點選個人頭像可展開工具欄列表,內容如下:
- 寫文章:點選可新建文章編輯頁面,和 ➕ 小圖示是同樣功能
- 上傳檔案:點選可開啟檔案上傳頁面,和上傳小圖示是同樣功能
- 個人資訊: 點選可進入個人資訊頁面
- 已贊:點選可檢視已贊過得文章
- 設定:點選可打開個人設定頁面
- 登出:點選退出登入
設定
點選工具欄上的設定按鈕可以設定頁面,內容如下:
個人資訊設定
- 頭像:頭像是使用的
Gravatar
提供的功能,根據郵箱生成頭像 - 使用者名稱
- 郵箱:已驗證通過會顯示驗證小圖示,沒有通過的會顯示提示
- GitHub:只需填寫有戶名
- 個人簡介:支援emoji
- 個人網站
修改密碼設定
需填寫原密碼,新密碼,再次輸入密碼
聊天室
點選 Chatroom 連結可進入聊天室,當然這個是用的 websocket
做的,內容如下:
- 狀態圖示:顯示連結狀態
- 活動使用者:左側黑色區域會動態顯示活動使用者
- 訊息:會顯示傳送訊息,進入、離開房間通知訊息(支援匿名傳送訊息,但不會儲存訊息)
- 訊息輸入:訊息輸入框可輸入訊息,Cmd — Enter 換行(Windows會顯示提示Ctrl+Enter),回車傳送訊息
加入房間和離開房間都有訊息通知,如圖
Node.js 框架
這個應用的開發我選擇的是 Adonisjs
框架,他和 PHP
的 Laravel
有些像,Adonisjs
是在作業系統上執行的 Node.js
MVC 框架。
接下來,來看看 Adonisjs
框架有哪些特性:
環境安裝簡單
不管是開發環境還是生產環境,安裝 Adonisjs
執行環境都是非常簡單,先來看看開發環境的安裝,生產環境後面會提到。
首先,我們的電腦上需要安裝好 Node.js
大於 8.00 版本,管理 Node.js
可以使用 nvm
其次,就可以使用 npm
安裝 Adonis CLI
命令列工具(管理 npm
使用源可以使用 nrm
)
npm i -g @adonisjs/cli
複製程式碼
這樣就可以在全域性使用 adonis
命令
再次,可以是 adonis new
命令建立專案
adonis new adonis_pro
複製程式碼
在 cd
進入專案,執行 adonis serve --dev
執行專案
cd adonis_pro
adonis serve --dev
複製程式碼
這樣您的開發環境就搭建完成。
RMVC
RMVC
就是路由、模型、檢視、控制器。
路由
建立一條路由非常簡單,如
Route.get('liked/:userId/:postId', 'LikedController.liked')
複製程式碼
這條路由就是用來處理上面提到的點贊功能的
當然,Adonisjs
提供了 資源路由 以便您更方便的建立路由,例如
Route.resource('posts', 'PostController').middleware(
new Map([
[ [ 'create', 'store', 'edit', 'update', 'destroy' ], [ 'auth' ] ],
[ [ 'update', 'destroy', 'edit' ], [ 'own:post' ] ]
])
).validator(new Map([
[['posts.update', 'posts.store'], ['StorePost']]
]))
複製程式碼
這個路由是來處理上面應用提到的文章的 增、刪、改、查 ,這個可能有些複雜,使用了 中介軟體 來處理使用者登入狀態和操作許可權,使用了 驗證器 來處理表單驗證,這裡不介紹的太複雜,如想了解這些具體功能,可以需要花點時間瞭解學習。
我們可以去掉 中介軟體 和 驗證器 ,如下:
Route.resource('posts', 'PostController')
複製程式碼
這條資源路由,其實就包含了以下路由:
Route.get(url, closure)
Route.post(url, closure)
Route.put(url, closure)
Route.patch(url, closure)
Route.delete(url, closure)
複製程式碼
Adonisjs
還提供了路由組和其他一些功能,路由組如下:
Route.group(() => {
Route.get('profile', 'ProfileController.edit').as('profile.edit')
Route.post('profile', 'ProfileController.update').as('profile.update').validator('UpdateProfile')
Route.get('password', 'PasswordController.edit').as('password.edit')
Route.post('password', 'PasswordController.update').as('password.update').validator('UpdatePassword')
})
.prefix('settings')
.middleware([ 'auth' ])
複製程式碼
使用 .prefix
和 Route.group
來建立路由組,這條路由組是處理 個人資訊設定 功能的,這樣訪問頁面是就統一要帶上 settings/**
。
控制器
Adonisjs
提供了命令列來建立控制器,如
adonis make:controller User --type http
複製程式碼
這樣就建立了一個 User
控制器,自動生成程式碼如下:
'use strict'
class UserController {
}
module.exports = UserController
複製程式碼
當然,我們還可以使用 --resource
建立資源型別的控制器
adonis make:controller Post --resource
複製程式碼
自動生成程式碼,程式碼如下:
'use strict'
class PostController {
/**
* Show a list of all posts.
* GET posts
*/
async index ({ request, response, view }) {}
/**
* Render a form to be used for creating a new posts.
* GET posts/create
*/
async create ({ request, response, view }) {}
/**
* Create/save a new posts.
* POST posts
*/
async store ({ request, response, view }) {}
/**
* Display a single posts.
* GET posts/:id
*/
async show ({ request, response, view }) {}
/**
* Render a form to update an existing posts.
* GET posts/:id/edit
*/
async edit ({ request, response, view }) {}
/**
* Update posts details.
* PUT or PATCH posts/:id
*/
async update ({ request, response, view}) {}
/**
* Delete a posts with id.
* DELETE posts/:id
*/
async destroy ({ params, request, response }) {}
}
module.exports = PostController
複製程式碼
和上面的資源路由是對應的,如用 GET
請求訪問 posts 就會呼叫 index
方法(一般用來顯示) ,再如:用 DELETE
請求訪問 posts/1 就會執行 destroy
方法(一般用來刪除)。
模型
Adonisjs
提供了兩種模式來處理資料,Query builder
和 LUCID
首先,我們可以通過 adonis make:migration
來建立資料表
adonis make:migration users
複製程式碼
會自動生成程式碼,如下:
'use strict'
const Schema = use('Schema')
class UsersSchema extends Schema {
up () {
this.create('users', (table) => {
table.increments()
table.timestamps()
})
}
down () {
this.drop('users')
}
}
module.exports = UsersSchema
複製程式碼
這是我們只需在其中新增想要的欄位就行,如:
'use strict'
const Schema = use('Schema')
class UsersSchema extends Schema {
up () {
this.create('users', (table) => {
table.increments()
table.string('username', 80).notNullable().unique()
table.string('email', 254).notNullable().unique()
table.string('password', 60).notNullable()
table.timestamps()
})
}
down () {
this.drop('users')
}
}
module.exports = UsersSchema
複製程式碼
在執行 adonis migration:run
命令就可以在資料庫生成資料表
再來看看,如何獲取資料,可以使用 Query builder
和 LUCID
兩種方式
先來看看 Query builder
:
const Database = use('Database')
class UserController {
async index (request, response) {
return await Database
.table('users')
.where('username', 'admin')
.first()
}
}
複製程式碼
查詢 user
表 name
是 admin
的使用者
Adonisjs
提供了非常多的方法去操作資料,不是特複雜的關係都夠用,如果,關係比較複雜,還可以用原生的 sql
操作,如
'use strict'
const Database = use('Database')
class NotificationController {
async followNotice ({ auth, view }) {
const notices = await Database.raw('select users.id as user_id,users.username,users.email,b.title,b.created_at,b.is_read,b.id as post_id from adonis.users , (select posts.id,posts.title, a.user_id,a.created_at,a.is_read from adonis.posts,(SELECT post_user.post_id, post_user.user_id, post_user.created_at, post_user.is_read FROM adonis.post_user where post_user.post_id in (SELECT posts.id FROM adonis.posts where user_id = ?)) as a where posts.id = a.post_id) as b where b.user_id = users.id and b.user_id <> ? order by b.created_at desc limit 50',[ auth.user.id, auth.user.id ])
}
}
module.exports = NotificationController
複製程式碼
使用 Database.raw
來執行原生的 sql
,以上這條 sql
是用來查詢所有使用者給自己所有文章點讚的使用者資訊和文章資訊用於訊息通知。
再來看看,LUCID
的模式是如何操作資料的:
使用 LUCID
模式,我們先需要用命令列工具建立 Models
,如:
adonis make:model User
複製程式碼
自動生成程式碼如下:
'use strict'
const Model = use('Model')
class User extends Model {
}
module.exports = User
複製程式碼
模型和模型之間需要定義一些關係,如:
const Model = use('Model')
class User extends Model {
profile () {
return this.hasOne('App/Models/Profile')
}
}
module.exports = User
複製程式碼
意思是 一個使用者對應一個使用者資訊檔案,一對一 的關係
定義好關係之後,就可以方便的獲取資料,如:
const User = use('App/Models/User')
const user = await User.find(1)
const userProfile = await user.profile().fetch()
複製程式碼
意思是,從使用者表和使用者個人資訊表裡獲取使用者 id
是 1
的使用者資訊及個人資訊,
其中,關係可以定義為 3 種 一對一、一對多、多對多 ,多對多需要定義中間表
再來看看,上面的應用中的實際應用,如:
async update ({ params, request, response, session, auth }) {
const { title, content, user_id, tags } = request.all()
const post = await Post.findOrFail(params.id)
post.merge({ title, content})
await post.save()
await post.tags().sync(tags)
session.flash({
type: 'primary',
message: 'Post updated successfully.'
})
return response.redirect(
Route.url('PostController.show', {
id: post.id
})
)
}
複製程式碼
以上,是更新文章的方法,文章 和 標籤 是 多對多 的關係,一個標籤可以屬於多篇文章,一篇文章可以有多個標籤,await post.tags().sync(tags)
這句程式碼就可以通過 Models
裡定義的關係自動把標籤和文章關聯起來儲存到 posts
和 tags
表裡且把關聯關係儲存到中間表 post_tag
。
當然,Adonisjs
提供了很多方便的方法,想了解更多的話需要您花點時間去了解學習。
檢視
Adonisjs
框架裡檢視使用了 edge
模板,我們可以使用命令列工具建立檢視檔案,如:
adonis make:view post
複製程式碼
我看可以看下簡單的例子:
@loggedIn
<h2> You are logged in </h2>
@else
<p> <a href="/login">Click here</a> to login </p>
@endloggedIn
複製程式碼
檢視模板裡可以使用標籤來做邏輯判斷,檢視模板就沒什麼好說的,基本都是通用的,關於 edge
檢視模板更多語法 Edge官方文件
最後,Adonisjs
框架還提供了很多其它的實用工具,如:Middleware
中介軟體、Validator
驗證器、Error Handling
自定義異常、Events
事件、Mails
郵件、Websocket
等來處理各種問題。
Node.js專案釋出到阿里雲伺服器
首先,我們需要用 ssh
連線到阿里雲(或者其他伺服器供應商)的主機上,安裝一些必要的工具。
工具安裝
安裝 epel-release 軟體包倉庫
我們需要安裝 epel-release
軟體包倉庫,epel-release
裡面有很多最新的軟體包,如,之後安裝的 git
就會用到
sudo yum install epel-release - y
複製程式碼
安裝 Git 版本控制命令列工具
sudo yum install git -y
複製程式碼
準備 Node.js 執行環境
接下來,我們需要安裝 Node.js
以便我們的 Node.js
專案能夠跑起來,我們可以使用 nvm
安裝和管理 Node.js
,使用 nrm
來管理切換安裝源。
安裝 nvm
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
複製程式碼
安裝好之後,我們需要配置下環境變數,以便能夠在命令列使用 nvm
命令,用 vi ~/.bash_profile
編輯下配置檔案
vi ~/.bash_profile
複製程式碼
加入以下程式碼:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
複製程式碼
然後,在 source ~/.bash_profile
重新整理下配置檔案,讓它生效
source ~/.bash_profile
複製程式碼
此時,我們就可以使用 nvm
來安裝 Node.js
nvm install node
複製程式碼
安裝好後,可以使用 nvm list
來檢視有哪些版本可以使用
nvm list
複製程式碼
結果:
-> v10.13.0
v11.2.0
system
default -> v10.13.0
node -> stable (-> v11.2.0) (default)
stable -> 11.2 (-> v11.2.0) (default)
iojs -> N/A (default)
lts/* -> lts/dubnium (-> v10.13.0)
lts/argon -> v4.9.1 (-> N/A)
lts/boron -> v6.14.4 (-> N/A)
lts/carbon -> v8.13.0 (-> N/A)
lts/dubnium -> v10.13.0
複製程式碼
我使用的是 v10.13.0 的版本,預設安裝的都是比較新的版本,可能是 v11.2.0 或 v11.1.0,所以我們也可以用 nvm install v10.13.0
來安裝指定版本。
nvm install v10.13.0
複製程式碼
然後,就可以使用 nvm use v10.13.0
來使用指定版本
nvm use nvm v10.13.0
複製程式碼
結果:
Now using node v10.13.0 (npm v6.4.1)
複製程式碼
安裝 nrm 管理安裝源
使用 npm 安裝的程式包,預設的來源是 registry.npmjs.org,國內的下載速度會有些慢,我們可以是 nrm
來切換到 taobao
的源
安裝 nrm
npm install nrm --global
複製程式碼
切換到 taobao 源
nrm use taobao
複製程式碼
準備專案
以上工作完成之後,我們的伺服器就可以正常執行 Node.js
專案,現在我們需要把本地的專案上傳到伺服器,上傳方法有很多,如:
- 可以使用
git
,先把專案傳到 GitHub,然後用git
下載到伺服器 - 可以是 FTP 工具
- 可以是命令上傳
scp -r 本地目錄 [email protected]伺服器IP:/var/www/
發專案檔案上傳到伺服器的指定目錄下,如:www
接下來,我們可以是 PM2 來管理 Node 程序,先需要安裝 PM2
安裝PM2
npm install [email protected] --global
複製程式碼
這些工作作為之後,就可以來測試一下,啟動專案,在本地訪問伺服器 IP:PORT
來測試是否可以訪問
測試專案是否可以執行
在測試之前,我們需要改下應用的配置檔案,adonisjs
框架裡是 .env
檔案,修改下 HOST
的值:
HOST=0.0.0.0
PORT=3333
...
複製程式碼
HOST
預設是 127.0.0.1,需要改成 0.0.0.0 這樣就可以在自己電腦上用伺服器 IP:PORT
來訪問應用
改完後,進入到專案的根目錄,執行應用,adonisjs
的啟動檔案是 server.js
,如:
pm2 start server.js
複製程式碼
如啟動成功會提示:
[PM2] Applying action restartProcessId on app [server](ids: 0)
[PM2] [server](0) ✓
[PM2] Process successfully started
┌──────────┬────┬─────────┬──────┬──────┬────────┬─────────┬────────┬─────┬──────────┬──────┬──────────┐
│ App name │ id │ version │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ user │ watching │
├──────────┼────┼─────────┼──────┼──────┼────────┼─────────┼────────┼─────┼──────────┼──────┼──────────┤
│ server │ 0 │ 4.1.0 │ fork │ 7171 │ online │ 30 │ 0s │ 0% │ 3.4 MB │ root │ disabled │
└──────────┴────┴─────────┴──────┴──────┴────────┴─────────┴────────┴─────┴──────────┴──────┴──────────┘
Use `pm2 show <id|name>` to get more details about an app
複製程式碼
然後,在自己電腦上用伺服器 IP:PORT
來訪問應用。
Nginx 代理
為了讓伺服器更好地處理網路請求,我們需要新增使用 Nginx 反向代理 把請求轉發給 Node.js
應用
安裝 Nginx
sudo yum install nginx -y
複製程式碼
如果你的服務之前安裝過可不用安裝,我的阿里雲伺服器運行了 4 個站點之前安裝過,之後我只需新增配置就行。
啟動 Nginx
sudo systemctl start nginx
複製程式碼
配置 Nginx
一般情況 Nginx 安裝好後會有 /etc/nginx/conf.d 目錄,進入這個目錄,建立一個配置檔案為 Node.js
而準備,名字可隨意命名,如:adonis.conf
server {
listen 80;
location / {
proxy_pass http://127.0.0.1:3333;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
}
}
複製程式碼
然後,在 Nginx 的主配置檔案裡把剛才新建立的配置檔案(/etc/nginx/nginx.conf) include
進去就可以,如:
include /etc/nginx/conf.d/*.conf;
複製程式碼
因為,我的主機裡運行了4個站點,*
的意思就是載入這個目錄下的所有配置檔案
然後,記得把剛才專案裡的 .env 配置檔案改成 127.0.0.1 ,因為我們現在使用了代理,網路請求交給了 Nginx
再進入到專案的根目錄下執行:
pm2 stop server.js #停止專案
pm2 start server.js #啟動專案
複製程式碼
這時候再用伺服器 IP 訪問就是用的 Nginx 去處理請求
域名和SSL
如果你有域名可以去對應的供應商解析好,如想使用 https
協議,也可以去對應的供應商下載好證書(下載好的證書要放到伺服器某個目錄裡)。
再修改下剛才建立的配置檔案,讓它能夠支援 https
和 域名 訪問:
server {
listen 80;
listen 443 ssl http2; #SSL
server_name a.lishaoy.net; #域名
ssl on;
ssl_certificate /etc/letsencrypt/live/a.lishaoy.net/server.pem; #證書目錄
ssl_certificate_key /etc/letsencrypt/live/a.lishaoy.net/server.key; #證書目錄
ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
if ($ssl_protocol = "") {
rewrite ^(.*) https://$host$1 permanent;
}
error_page 497 https://$host$request_uri;
error_page 404 /404.html;
error_page 502 /502.html;
location / {
proxy_pass http://localhost:3333;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
}
}
複製程式碼
這樣再重啟 Ningx
服務和專案的服務,就大功告成了。