這個開發的想法是這樣來的,大概兩個月前,騰訊雲的工做人員打電話給我,說個人域名沒有解析到騰訊雲的服務器上,並且頁腳也沒有備案號。我當時就震驚了,竟然會打電話給我,然而個人大學時代買的服務器已通過期了...因而爲了拯救個人域名,拯救我申請了好久的備案號,決意要全棧打造一個屬於本身的博客系統。javascript
服務端渲染則把Ajax
請求放到服務端,頁面加載到瀏覽器或客戶端前就已經把數據填充到頁面模板行程完整的頁面。css
http
請求,在服務端請求首屏數據,直接渲染html
SEO
,網絡爬蟲能夠抓取到完整的頁面信息客戶端渲染就是就是在客戶端經過Ajax
請求獲取數據,而後在客戶端生成DOM
插入到html
html
JS
和CSS
文件門戶網站、博客網站等須要SEO
優化的網站使用服務端渲染,管理後臺等內部系統或不須要SEO
優化的網站使用客戶端渲染前端
在這裏,要使用CSS3
變量配合scss
進行控制,經過控制<body>
標籤的id
來約束白晝或黑夜的顏色值,再給相應的屬性加上transition
屬性實現顏色切換時的過渡,請看下面的示例:vue
@mixin theme(
$theme-primary
) {
--theme-primary: #{$theme-primary}
}
body {
&#light {
@include theme(
#theme-primary: #fff,
)
}
&#dark {
@include theme(
#theme-primary: #000,
)
}
}
複製代碼
全局引入上面的scss
文件,這樣就能夠直接經過設置<body>
標籤的id
的值爲dark
或light
給--theme-primary
賦予不一樣的顏色值,此時就能直接在須要應用該顏色的元素上進行以下設置:java
.example-class {
color: var(--theme-primary);
}
複製代碼
在個人博客shirmy中點擊月亮/太陽便可看到效果,亦可用一樣的方式定義多套主題色。node
Nuxt
內置了head
屬性配置,head
配置直接修改根目錄下的nuxt.config.js
文件就能夠了,而且還內置vue-meta
插件,所以想要在不一樣頁面改變相應的title
,請看如下作法:ios
// nuxt.config.js
module.exports = {
head: {
// 組件中的 head() 方法返回的字符串會替換 %s
titleTemplate: '%s | shirmy',
}
}
// 文章詳情頁 pages/article/_id.vue
export default {
head() {
return {
title: this.article.title
}
}
}
複製代碼
如此一來,當查看某篇文章詳情的時候就會觸發head()
方法,title
顯示爲article title | shirmy
nginx
只要修改nuxt.config.js
配置便可,而且能夠根據傳入的參數作一些自定義的配置。git
// nuxt.config.js
module.exports = {
router: {
scrollBehavior: function (to, from, savedPosition) {
return { x: 0, y: 0 }
}
},
}
複製代碼
fetch()
是Nuxt
中獨有的方法,它會在組件初始化前被調用,所以沒法經過this
獲取組件對象。好比進入首頁或從首頁切換到歸檔頁,在進入頁面前會先執行fetch()
方法,爲了異步獲取數據,fetch()
方法必須返回Promise,所以能夠直接返回一個Promise
或者使用async await
(async await
其本質就是返回Promise
)。
該方法不會設置組件的數據,若是想要設置組件的數據,或者使用context
上下文,可使用asyncData。
export default {
// 雖然沒法經過 this.$nuxt.$route 獲取路由參數,可是能夠經過 params 來獲取
async fetch({ store, params }) {
await store.dispatch('about/getAuthor', params.id)
await store.dispatch('about/getArticles', {
authorId: params.id,
page: 0
})
}
}
複製代碼
這樣就能確保內容已經渲染好再下載到瀏覽器,若是使用mounted
等生命週期鉤子,則是在頁面下載到瀏覽器後再獲取數據,起不到SSR
服務端渲染的效果。
爲了方便統一管理scss
變量,一般會在目錄中建立variables.scss
和mixin.scss
,可是咱們寫的vue
文件這麼多,若是都要一個個導入豈不是吃力不討好,這時能夠藉助style-resource
:
npm install -S @nuxtjs/style-resources
複製代碼
修改nuxt.config.js
文件:
// nuxt.config.js
module.exports = {
styleResources: {
scss: ['./assets/scss/variables.scss', './assets/scss/mixin.scss']
}
},
複製代碼
圖片懶加載的關鍵是使用IntersectionObserver,IE瀏覽器不兼容,須要使用polyfill
。該WebAPI
用於監聽元素是否出如今頂級文檔視窗中。
經過這個WebAPI
,咱們能夠把<img>
標籤的src
屬性地址先掛在data-src
屬性上,當該元素出如今視窗時就會觸發IntersectionObserver
的的回調方法,此時再給<img>
標籤的src
屬性賦予先前掛在data-src
上的地址
監聽copy
事件,而後經過getSelection()
方法獲取複製的內容,在經過clipboardData
的setData()
方法在複製內容上加上轉載信息:
if (process.env.NODE_ENV === 'production') {
const copyText = ` --------------------- 做者:shirmy 連接:${location.href} 來源:https://www.shirmy.me 商業轉載請聯繫做者得到受權,非商業轉載請註明出處。`
document.addEventListener('copy', e => {
if (!window.getSelection) {
return
}
const content = window.getSelection().toString()
e.clipboardData.setData('text/plain', content + copyText)
e.clipboardData.setData('text/html', content + copyText)
e.preventDefault()
})
}
複製代碼
若是讀者在評論中輸入<script>alert('xss')</script>
,那麼進入文章詳情頁時就會觸發這段代碼,這時候,咱們就須要把script過濾掉,固然XSS還有許多相似的方式,可是萬變不離其宗
在這裏,我藉助了DOMPurify
npm install dompurify -S
複製代碼
DOMPurify.sanitize(html)
複製代碼
首先進入官網Google 統計分析服務,並註冊你的帳號,獲得一個格式如GA_MEASUREMENT_ID
的媒體資源ID
而後直接修改nuxt.config.js
,固然也能夠自定義(gtag.js開發指南):
// nuxt.config.js
module.exports = {
head: {
// 其它配置...
script: [
// 其它配置...
{
async: 'async',
type: 'text/javascript',
// GA_MEASUREMENT_ID 替換爲你剛剛註冊獲得的媒體資源ID
src: 'https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID'
},
{
// Global site tag (gtag.js) - Google Analytics
type: 'text/javascript',
// GA_MEASUREMENT_ID 替換爲你剛剛註冊獲得的媒體資源ID
innerHTML: ` window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'GA_MEASUREMENT_ID'); `
}
]
}
}
複製代碼
參考文檔
咱們知道,菜單欄是分爲多級的,其實它是和router
對應上的。所以咱們能夠把一級每一個一級菜單抽出來,做爲單獨的一個對象,而後導入這些配置項,經過對數據結構的整理和重組,組合成咱們所須要的路由文件。
// article.js
export const articleRouter = {
// ...路由配置(名稱,路徑,是否顯示,圖標,子路由等屬性)
}
// author.js
export const authorRouter = {
// ...路由配置
}
// index.js
let homeRouter = [
{
// ...路由配置
},
articleRouter,
authorRouter
]
// step1: 根據本身定義的配置進行處理,並供菜單欄遍歷使用
// step2: 深度遍歷構建路由
// step3: 插入到 Vue Router 中
// routes.js
const routes = [
{
path: '',
name: 'Home',
redirect: '/about',
component: Home,
children: [
// 這裏就是與菜單相對應的路由數組
...homeRouter
]
},
// 這裏能夠額外配置一些非菜單路由
]
複製代碼
經過這種方式,就能夠更加靈活的給每一個路由添加特定的配置,實現個性化的定製,實現不一樣頁面的解耦。
經過<input type="file">
獲取到圖片後,經過使用URL.createObjectURL()
靜態方法建立DOMString
。而後賦值給Image
對象,再根據該Image
對象進行判斷
fileChange(e) {
const imgFile = e.target.files[0]
// 圖標大小大於 1M
if (imgFile.size > 1024 * 1024 * 1) {
// ...
}
const imgSrc = window.URL.createObjectURL(imgFile)
const image = new Image()
image.src = imgSrc
image.onload = () => {
// 圖片加載後獲取圖片寬度
const w = image.width
// 圖片加載後獲取圖片高度
const h = image.height
// ... 後續處理
}
}
複製代碼
accessToken
是普通的訪問token,一般設置爲一兩個小時,refreshToken
用於驗證用戶是否能夠再獲取一個新的accessToken
,能夠設置爲一個月。accessToken
過時了,在進行操做時,先把這個失敗的請求保存起來,而後再向服務器驗證refreshToken
,若是經過,則頒發一個新的accessToken
,再經過這個新的accessToken
去請求剛剛緩存起來的請求。refreshToken
驗證不經過,則請求失敗,直接調用相關的loginOut()
方法好比咱們要使用lodash
中的throttle
方法,咱們能夠這樣作:
// 只導入 throttle 相關模塊
import throttle from 'lodash/throttle'
複製代碼
這樣能極大減小打包後的體積,這種方式很是有用,經常使用的,包括ECharts也是如此。
targetId === id
來判斷是否選中某個對象,當有多個篩選條件時,咱們能夠經過如下方式把多個相同的邏輯進行以下封裝:selectFilter(id, target) {
// 若是和當前選擇同樣則沒必要再選中了
if (id === this[target]) {
return
}
// 只須要額外傳入和組件data相同的字符串名就不用再寫多個函數了
this[target] = id
this.getArticles()
}
複製代碼
// filter/index.js
export default {
format(value, format) {},
filter(value) {}
}
// filters 中包含多個過濾器
import filters from '@/services/filter'
import Vue from 'vue'
// main.js
// 全局過濾器,不要一個個註冊,全局組件同理
Object.keys(filters).forEach(k => Vue.filter(k, filters[k]))
複製代碼
參考文檔
├── app # 業務代碼
│ ├── api # api
│ │ ├── blog # 提供給博客前端API
│ │ └── v1 # 提供給博客管理系統API
│ ├── dao # 數據庫操做層
│ ├── lib # 工具函數、工具類、常量
│ ├── models # Sequelize model 層
│ └── validators # 參數校驗工具類
├── config # 全局項目配置
├── core # 核心庫
│ ├── db.js # Sequelize 全局配置
│ ├── http-exception.js # 異常處理定義
│ ├── init.js # 項目初始化
│ ├── lin-validator.js # 參數校驗插件
│ ├── multipart.js # 文件上傳處理
│ └── util.js # 核心庫工具函數
├── middleware # 中間件
複製代碼
用Koa2
寫服務端代碼,有一個體驗就是文件導出來導出去,各類路徑,這時咱們可使用別名:
npm install -S nodule-alias
複製代碼
{
// ...
"_moduleAliases": {
"@models": "app/models",
}
}
複製代碼
// app.js
require('module-alias/register')
// article.js
const { Article } = require('@models')
複製代碼
當路由模塊不少時,在app.js
中一個個導入豈不是越寫越長,這時咱們能夠藉助require-directory
工具
const requireDirectory = require('require-directory')
const Router = require('koa-router')
class InitManager {
static initCore(app) {
// 入口
InitManager.app = app
InitManager.initLoadRoutes()
}
static initLoadRoutes() {
// process.cwd() 獲取絕對路徑
const appDirectory = `${process.cwd()}/app/api`
// 使用 require-directory 提供的方法導入自動導入路由文件
requireDirectory(module, appDirectory, {
visit: whenLoadingModule
})
// 註冊全部檢測到的 Koa 路由
function whenLoadingModule(obj) {
if (obj instanceof Router) {
InitManager.app.use(obj.routes())
}
}
}
}
module.exports = InitManager
// app.js
const Koa = require('koa')
const app = new Koa()
InitManager.initCore(app)
複製代碼
實際上官方文檔就已經寫得很清楚了:Node.js SDKV6,無非就是安裝插件,照着文檔搬運代碼。
在這裏要注意的是,若是上傳多個文件,咱們須要放在一個循環裏逐一上傳,而上傳又是異步的,那麼如何驗證全部文件都已經上傳成功,在這裏咱們可使用Promise.all()
方法進行封裝,舉個栗子:
class UpLoader {
async upload(files) {
let promise = []
for (const file of files) {
// ...
promise.push(new Promise((resolve, reject) => {
// 執行上傳邏輯
// resolve() or reject()
}))
}
Promise.all(promises).then(res => {
// ... 所有成功
}).catch(e => {
// ... 有上傳失敗的
})
}
}
複製代碼
// http-exception.js
class HttpException extends Error {
constructor(msg = '服務器異常', errorCode = 10000, code = 400) {
super()
this.msg = msg
this.errorCode = errorCode
this.code = code
}
}
// exception.js
const { HttpException } = require('@exception')
const catchError = async (ctx, next) => {
try {
// 利用洋蔥圈模型的特性,全部請求都會通過這裏
await next()
} catch (error) {
const isHttpException = error instanceof HttpException
const isDev = global.config.environment = 'dev'
if (isDev && !isHttpException) {
throw error
}
// 已知錯誤
if (isHttpException) {
ctx.body = {
msg: error.msg,
errorCode: error.errorCode,
request: `${ctx.method}: ${ctx.path}`
}
ctx.status = error.code
} else {
// 未知錯誤
ctx.body = {
msg: '服務器內部錯誤',
errorCode: 999,
request: `${ctx.method}: ${ctx.path}`
}
ctx.status = 500
}
}
}
module.exports = catchError
// app.js
const catchError = require('./middleware/exception')
app.use(catchError)
複製代碼
權限校驗是經過JWT
實現的,使用JWT
能夠用用戶ID、超時時間、權限級別給用戶生成一個Token
返回到客戶端,客戶端再把這個Token
存儲到cookie
中,步驟以下:
jsonwebtoken
插件accessToken
accessToken
保存到cookie
中,而後之後的每次發送請求都會攜帶這個token
token
是否合法,而且判斷用戶是否有權限訪問該API其它業務代碼及框架的基本用法就很少說了,能夠直接參考smile-blog-koa
參考文檔
這裏以一個前端項目爲例,首先在項目根目錄下建立一個.travis.yml
文件,並寫入如下代碼保存:
language: node_js
cache:
directories:
- node_modules # 緩存 node_modules
node_js: stable # 穩定版本
branches:
only:
- master # 每次 push 或者 pull request 時會觸發持續集成
install:
- npm install # 固然你可使用 yarn
scripts:
- npm test # 執行測試
- npm build # build
複製代碼
這時只要把項目push
到master
分支就會觸發部署,這一步的目的是驗證項目是否經過測試、編譯,模擬生產環境進行自動測試,提早發現錯誤。效果圖大概長這樣:
ssh-keygen
建立,並賦予權限:cd ~/.ssh
sudo chmod 700 ~/.ssh/
sudo chmod 600 ~/.ssh/*
複製代碼
cat id_rsa.pub >> authorized_keys
cat authorized_keys
sudo chmod 600 ~/.ssh/*
複製代碼
id_rsa
和id_rsa.pub
文件,其實就跟你配置GitHub SSH Key
同樣:ssh-keygen -t rsa -C "這裏替換成你的郵箱"
# 獲取祕鑰並複製之
cat id_rsa.pub
複製代碼
瀏覽器打開GitHub:用戶頭像 -> Setting -> SSH and GPG keys -> New SSH Key,添加之
上面的能夠找到Windows、Mac、Linux的Ruby安裝方式,我使用的是Mac,直接使用Homebrew安裝,固然Macb自己就自帶Ruby:
# 安裝 homebrew
ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
# 安裝最新版本 Ruby
# 更新 brew 支持的版本信息
brew update
# 編譯安裝最新版本
brew install ruby
# 檢驗是否安裝成功
ruby --version
複製代碼
安裝ruby是由於travis客戶端使用ruby寫的
# 安裝
gem install travis
複製代碼
進入到要部署的項目根目錄下,用GitHub的帳號密碼登陸Travis客戶端:
travis login --auto
複製代碼
利用服務器私鑰加密生成id_rsa.enc
文件,travis會藉助它來登陸你的服務器,這樣就能夠在你的服務器上進行自動部署的操做了:
travis encrypt-file ~/.ssh/id_rsa --add
複製代碼
執行完這句後,項目根目錄下就會生成一個id_rsa.enc
文件,而且先前在目錄下建立好的.travis.yml
文件會多出這樣一行:
before_install:
- openssl aes-256-cbc -K $encrypted_######_key -iv $encrypted_#######_iv -in id_rsa.enc -out ~\/.ssh/id_rsa -d
複製代碼
踩了不少坑?不要緊,這時已經很是接近成功了
Travis CI自帶了一些生命鉤子,咱們能夠在相應的生命鉤子(Travis CI Job Lifecycle)搞事情,其中after_success
鉤子是執行部署腳本的鉤子。
此時在.travis.yml
上添加部署腳本,若是你不想暴露你在服務器上的部署用戶名和服務器IP,你能夠在travis中配置環境變量
項目部署面板 -> 右側的More options -> Settings -> 找到Environment Variables -> 輸入變量名,變量值,而後在yml文件中用$變量名
來引用
language: node_js
cache:
directories:
- node_modules
node_js: stable
branches:
only:
- master
before_install:
# 注意 這裏我改爲了 ~/.ssh/id_rsa 而不是自動生成的 ~\/.ssh/id_rsa
- openssl aes-256-cbc -K $encrypted_######_key -iv $encrypted_######_iv -in id_rsa.enc -out ~/.ssh/id_rsa -d
# 賦予權限
- chmod 600 ~/.ssh/id_rsa
install:
- npm install
scripts:
- npm test # 執行測試
- npm build # build
# 執行部署腳本
after_success:
# $DEPLOY_USER 環境變量:服務器用戶名
# $DEPLOY_HOST 環境變量:服務器IP
# 項目目錄 你在服務器上的項目目錄
- ssh "$DEPLOY_USER"@"$DEPLOY_HOST" -o StrictHostKeyChecking=no 'cd 項目目錄 && git pull && bash ./script/deploy.sh'
addons:
ssh_known_hosts:
# 你的服務器IP
- "$DEPLOY_HOST"
複製代碼
在根目錄下建立script文件夾放部署腳本
# script/deploy.sh
#!/bin/bash
echo 'npm install'
npm install
echo 'npm run build'
npm run build
echo 'success'
複製代碼
部署成功以後,就會看到上面有個下面這樣的徽章,點擊它,按照你須要的格式複製使用
再來回顧一下流程:
想起大學時本身建了個簡單的網站,當時使用的http,而後打開的時候常常都被http劫持,那是真的迫不得已,因此此次必需要整個https。https是啥?想必你們都知道,不懂的直接超文本安全傳輸協議
在這裏,我使用的是騰訊雲的免費證書:
首先登陸你的服務器,咱們須要建立一個文件夾來放咱們的證書
mkdir /usr/local/nginx/cert
複製代碼
而後使用scp
命令把咱們的證書傳到服務器上
# 進入到放證書的文件夾
cd shirmy.me
# 把 Nginx 下的文件遠程拷貝到服務器上
scp -r ./Nginx/* 你的服務器用戶名@你的服務器IP:/usr/local/nginx/cert
複製代碼
接下來就能夠修改Nginx的配置了,其實騰訊雲提供了很完善的證書安裝指引,裏面有除了Nginx以外的其它服務器配置方式:
若是直接使用文檔中的方式,Nginx會報警告,須要作一些小的修改:
server {
listen 443; #SSL 訪問端口號爲 443
server_name www.shirmy.com; #填寫綁定證書的域名
# ssl on; #啓用 SSL 功能 這行會報警告 去掉便可
ssl_certificate ../cert/1_www.shirmy.me_bundle.crt; #證書文件名稱
ssl_certificate_key ../cert/2_www.shirmy.me.key; #私鑰文件名稱
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #請按照這個協議配置
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; #請按照這個套件配置,配置加密套件,寫法遵循 openssl 標準。
ssl_prefer_server_ciphers on;
location / {
root /var/www/www.domain.com; #網站主頁路徑。此路徑僅供參考,具體請您按照實際目錄操做。
index index.html index.htm;
# 這是爲解決 Vue Router 哈希模式刷新後404的問題 Nginx 找不到文件後會在內部發起一個子請求到根目錄下的 index.html
try_files $uri $uri/ /index.html;
}
}
複製代碼
騰訊雲文檔提供瞭如下的配置方式,可是我用的是另一種配置方式:
# 文檔提供的配置方式
}
server {
listen 80;
server_name www.domain.com; #填寫綁定證書的域名
rewrite ^(.*)$ https://$host$1 permanent; #把http的域名請求轉成https
}
複製代碼
這種方式實際上是利用了<meta>
標籤中的的http-equiv
屬性,與之對應的值是content
,咱們須要新建一個index.html
文件,複製並修改如下代碼:
<html>
<!-- 自動刷新並指向新頁面,0 是指0秒後刷新(當即刷新) -->
<meta http-equiv="refresh" content="0;url=https://www.shirmy.me/">
</html>
複製代碼
這樣當咱們訪問http://www.shirmy.me
時就會從新刷新到https://www.shirmy.me
,而後再修改nginx
配置以下:
server {
listen 80; # 監聽默認端口
server_name www.shirmy.me; # 域名
location / {
root www/http.shirmy.me/; # 剛剛的 index.html 所在目錄
index index.html index.htm;
}
}
複製代碼
最後,重啓咱們的Nginx服務器:
cd /usr/local/nginx/sbin
# 平滑重啓
./nginx -s reload
# 非平滑重啓
./nginx -s stop && ./nginx
複製代碼
大功告成,配置了HTTPS的網站,要保證網站的連接都是安全的,包括API請求都必須使用HTTPS
https//api.shirmy.me:3000/v1/articles
,如何去掉端口呢?# 負載均衡就是靠下面這個來實現
# blogapi 替換成你喜歡的名字
upstream blogapi {
server http://127.0.0.1:3000;
# server 你也能夠選擇配置多個IP
}
server {
# 同上面同樣的 HTTPS 配置
listen 443 ssl;
server_name api.shirmy.me;
ssl_certificate ../cert/1_api.shirmy.me_bundle.crt;
ssl_certificate_key ../cert/2_api.shirmy.me.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
# 反向代理配置
location / {
# $host 表明轉發服務器
proxy_set_header Host $host;
proxy_redirect off;
# 記錄真實IP
proxy_set_header X-Real-IP $remote_addr;
# 存儲請求鏈路上各代理IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 鏈接超時時間
proxy_connect_timeout 60;
# nginx接收upstream server數據超時時間
proxy_read_timeout 600;
# nginx發送數據至upstream server超時時間
proxy_send_timeout 600;
# 反向代理到上面定義好的 upstream blogapi 下的服務器上
proxy_pass http://blogapi;
}
}
複製代碼
如此一來,就實現了反向代理和負載均衡,此外,咱們應該讓用戶第一次訪問該服務器後,之後再訪問也是訪問該服務器,避免屢次創建http鏈接,那麼咱們能夠這樣修改:
upstream blogapi {
# 避免每次被請求到多臺服務器上 知足用戶保持訪問同一臺服務器 又能實現負載均衡
ip_hash;
server http://127.0.0.1:3000;
# server 你也能夠選擇配置多個服務器IP
}
複製代碼
最後記得重啓/usr/local/nginx/sbin/nginx -s reload
除了主頁shirmy.me以外,咱們一般還要有一個管理後臺:admin.shirmy.me,由於用的是免費證書,因此咱們也只好爲子域名申請一個SSL證書,而且以一樣的方式配置。
咱們又總不能用端口shirmy.me:5000這樣子訪問吧,其實只要這樣作:
server {
listen 80;
# admin.shirmy.me
server_name admin.shirmy.me;
location / {
# 直接看上面 HTTP 跳轉到 HTTPS 的配置
root www/http.admin.shirmy.me/;
index index.html index.htm;
}
}
複製代碼
最後記得重啓/usr/local/nginx/sbin/nginx -s reload