这个开发的想法是这样来的,大概两个月前,腾讯云的工做人员打电话给我,说个人域名没有解析到腾讯云的服务器上,并且页脚也没有备案号。我当时就震惊了,竟然会打电话给我,然而个人大学时代买的服务器已通过期了...因而为了拯救个人域名,拯救我申请了好久的备案号,决意要全栈打造一个属于本身的博客系统。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