用 Node.js 3周仿出一個小掘金

最近在學習使用 Node.js 框架,邊學習邊使用,花了大概 3周 時間作完這個 Web應用 且在 凌晨左右上線成功(其實就是把開發環境搬到服務器), 地址: a.lishaoy.nethtml

這個 Web應用 的代碼是開源的,如對這個應用感興趣,想知道代碼是如何運行的,能夠去我 GitHub 下載或 clone應用源碼node


首先,來看看用 3周 時間作出來的應用都有些什麼功能,以後再看看選用的 Node.js 框架,最後看看 Node.js 項目如何部署到服務器。nginx

Web應用功能

登陸、註冊驗證

登陸功能git

  • 輸入框沒有輸入點擊登陸會提示:用戶名、密碼不能爲空
  • 輸入的用戶錯誤或不存在會提示:用戶不存在
  • 輸入的密碼錯誤會提示:密碼錯誤
  • 登陸後會重定向到用戶上次訪問的地址

no-shadow
Login

註冊功能github

  • 輸入框沒有輸入點擊註冊會提示:用戶名、郵箱、密碼不能爲空
  • 用戶名和郵箱與其餘用戶相同會提示:用戶名、郵箱已存在
  • 密碼小於6位數會提示:最小長度是6位
  • 註冊成功後會發送驗證郵件到用戶郵箱,需點擊郵箱按鈕驗證

no-shadow
Register

文章列表

登陸進來,會顯示文章列表頁面,顯示內容以下:web

  • 文章標題:點擊可進入文章詳情頁
  • 做者頭像、做者名稱:點擊可進入做者信息頁
  • 時間:顯示建立時間(多久之前方式顯示)
  • 閱讀次數、點贊次數
  • 文章簡要:自動摘取章頭文章
  • 縮略圖:自動摘取文章第一張圖片

no-shadow
Post List

文章詳情

點擊文章標題可進入文章詳情頁面,內容以下:sql

  • 文章標題
  • 做者頭像、做者名稱
  • 發佈時間
  • 閱讀次數和點贊次數
  • 編輯按鈕(僅做者可見)
  • 左側浮動工具欄(點贊、發送郵件到本身郵箱、返回頂部、分享)
  • 點贊:文章被點贊後,做者能夠收到消息通知,且將文章收錄到點贊列表(支持匿名點贊,但不會記錄通知,只會加點贊數)

no-shadow
Post

編輯文章支持 Markdown

新建文章和修改文章都支持 Markdown 語法,且會每隔6秒鐘自動保存數據庫

no-shadow
Post Edit

我的信息

我的信息頁面顯示內容以下npm

  • 做者的頭像、姓名、簡介(支持emoji)
  • 信息欄:GitHub 連接、我的網站連接、發佈文章數、總閱讀次數、總點贊次數
  • 發佈文章列表:我的發佈的全部文章(有刪除和編輯按鈕)
  • 已贊文章列表:點過讚的文章會記錄在這裏
  • 關注者列表:關注你的用戶(關注過的用戶,關注按鈕高亮顯示)
  • 已關注列表:你關注的用戶(關注過的用戶,關注按鈕高亮顯示)
  • 關注按鈕:做者本人不可見,點擊可關注,再次點擊取消關注,關注後,用戶會收到消息通知

no-shadow
Profile

文章刪除編輯快捷入口,如圖bash

no-shadow
Edit && Delete post

下面是我用另外一個用戶登陸,進入到我的信息頁面就會顯示關注按鈕,如圖

no-shadow
Follow

文件上傳

點擊文件上傳小圖標可進入文件上傳頁面,點擊 Files 連接可進入文件上傳列表,顯示內容如圖:

no-shadow
File Upload

no-shadow
File List

文件預覽和編輯

從文件列表頁面點擊標題可進入文件預覽頁面,顯示內容以下:

  • 若是是圖片顯示圖片,若是是視頻顯示視頻
  • 工具欄:發送郵件到本身郵箱(登陸可見)、編輯按鈕、刪除按鈕(登陸本身上傳可見)
  • 文件名稱
  • 下載按鈕
  • 上傳者頭像

no-shadow
File Show

消息通知

點擊鈴鐺小圖標可進入消息通知頁面,內容以下:

  • 點贊消息列表:收到用戶點贊通知,最新的未讀消息會高亮顯示,點擊點贊者頭像進入我的信息頁面,點擊文章標題進入你的文章詳情頁面
  • 關注者列表:收到關注者的通知,最新未讀消息會高亮顯示,點關注按鈕也可關注他,再點擊取消關注
  • 系統消息:目前尚未作功能實現

no-shadow
Notification

工具欄列表

點擊我的頭像可展開工具欄列表,內容以下:

  • 寫文章:點擊可新建文章編輯頁面,和 ➕ 小圖標是一樣功能
  • 上傳文件:點擊可打開文件上傳頁面,和上傳小圖標是一樣功能
  • 我的信息: 點擊可進入我的信息頁面
  • 已贊:點擊可查看已贊過得文章
  • 設置:點擊可打開我的設置頁面
  • 登出:點擊退出登陸

no-shadow
Tool Menu

設置

點擊工具欄上的設置按鈕能夠設置頁面,內容以下:

我的信息設置

  • 頭像:頭像是使用的 Gravatar 提供的功能,根據郵箱生成頭像
  • 用戶名
  • 郵箱:已驗證經過會顯示驗證小圖標,沒有經過的會顯示提示
  • GitHub:只需填寫有戶名
  • 我的簡介:支持emoji
  • 我的網站

no-shadow
Setting Profile

修改密碼設置

需填寫原密碼,新密碼,再次輸入密碼

no-shadow
Setting Password

聊天室

點擊 Chatroom 連接可進入聊天室,固然這個是用的 websocket 作的,內容以下:

  • 狀態圖標:顯示連接狀態
  • 活動用戶:左側黑色區域會動態顯示活動用戶
  • 消息:會顯示發送消息,進入、離開房間通知消息(支持匿名發送消息,但不會保存消息)
  • 消息輸入:消息輸入框可輸入消息,CmdEnter 換行(Windows會顯示提示Ctrl+Enter),回車發送消息

no-shadow
Chart Room

加入房間和離開房間都有消息通知,如圖

no-shadow
Chart Room

Node.js 框架

這個應用的開發我選擇的是 Adonisjs 框架,他和 PHPLaravel 有些像,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' ])
複製代碼

使用 .prefixRoute.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 builderLUCID

首先,咱們能夠經過 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 builderLUCID 兩種方式

先來看看 Query builder

const Database = use('Database')

class UserController {

  async index (request, response) {
    return await Database
      .table('users')
      .where('username', 'admin')
      .first()
  }

}
複製代碼

查詢 usernameadmin 的用戶

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()
複製代碼

意思是,從用戶表和用戶我的信息表裏獲取用戶 id1 的用戶信息及我的信息,

其中,關係能夠定義爲 3一對1、一對多、多對多 ,多對多須要定義中間表

再來看看,上面的應用中的實際應用,如:

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 裏定義的關係自動把標籤和文章關聯起來保存到 poststags 表裏且把關聯關係保存到中間表 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.0v11.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 本地目錄 root@服務器IP:/var/www/

發項目文件上傳到服務器的指定目錄下,如:www

接下來,咱們能夠是 PM2 來管理 Node 進程,先須要安裝 PM2

安裝PM2

npm install pm2@latest --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.confinclude 進去就能夠,如:

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 服務和項目的服務,就大功告成了。

相關文章
相關標籤/搜索