前端从头搭建我的博客

项目介绍

  几乎每一个程序员都有一个博客梦。抱着学习先后端技术的心态花一个月左右的时间来完成这个我的项目。 这是一个使用vue2作前端框架,koa2作后端,mongodb数据库,搭建的单页面应用。网站的功能展现我本身的技术分享,和后台编辑书写博客,支持markdown语法。本次项目大量借鉴了BUPT-HJM大神的博客,从ui到代码细节,借鉴了不少。真的很感谢开源的大神。代码地址github我的网站地址javascript

项目搭建

  由于前端选型使用vue,想用vue-cli一路平推省心省力来着,可是为了更加深刻学习webpack,决定手动从头写起。 关于webpack的实现细节我会单独写一篇博客,由于内容实在太多,在这里只说明实现了哪些功能。css

开发环境

  如今前端开发仍是很看重开发体验的,我以前在上上家公司前端开发时,开发环境一塌糊涂,觉得业内都是这样(后来才知道是前端组长的不负责任)。上家公司的开发环境真的很舒服,不用过多的考虑兼容性,代码风格强制统一,热更新什么的包罗万象。 因此webpack构建的时候就加进去了热更新,使用postcss超前使用css新特性和自动前缀。 须要特别说明的是webpack热更新依赖的仍是node,咱们都知道webpack-dev-server这个插件是用了express框架做为热更新服务的,可是考虑到本次开发时的后台也是node,只不过用的是koa2,那么彻底能够在开发过程当中只启动一个node服务,来知足前端和后端的node需求。因此我没有使用webpack-dev-server这个webpack插件,而是本身实现。具体代码会在webpack那篇博客中细讲。html

生产环境

  生产环境的构建中,也是很常规的一些实现。例如代码压缩,css加前缀,资源文件的路径处理,图片压缩。开启多线程的打包功能,这里吐槽一句 刚开始打包时间是7秒左右,按照网上的打包优化建议一顿花里胡哨的折腾,结果打包时间涨到9秒了。。。 使用了 webpack引觉得傲的很重要的功能,将vue文件拆分红不一样的Chunk,配合vue-router按需引入前端

// vue-router
 const HelloWorld = () => import('../components/HelloWorld.vue');
复制代码

打包的时候把js拆分,按需引入,好处在于js不至于体积过大。须要说明一下这种语法是es7的新特性,配置babel的时候坑不少。vue

总结

  webpack更新换代太快了,不少时候没有中文文档,只能只知其一;不知其二的查github, 可是随着webpack愈来愈成熟稳定,语法也固定下来,惟一须要咱们注意的地方是眼花缭乱的插件,根据本身的须要合理添加。java

前端细节

  项目开发的时候前端是最熟悉的一部分,vue做为前端框架,对于这种中小型项目来讲很合适。写博客网站也是为了学习,干脆用了vue全家桶。 vue-router,vuex,都在用。 由于个人审美和ui设计实在有限,大量借鉴(抄袭BUPT-HJMnode

首页和博客详情页

  首页包括一个header组件,一个信息展现组件,博客列表页,和分页组件。稍微有点复杂的组件只有信息展现组件和分页组件。 信息展现组件用到了vue的插槽,slot ,默认是自我介绍。关于插槽啰嗦几句:不少初学vue的同窗对于插槽的使用和理解很费解,对于vue中的props都会用,做为父组件给组件传值用,而插槽也是如此,只不过props传入的是data数据,而插槽传入的是html 。   分页组件实际上是和vuex结合一块儿写的。vuex里面的state记录分页的当前页数,点击改变页数触发vuex里面的mutations去加载分页数据。   博客详情页数据的加载来源也是和vuex结合,从state里面获取文章id,去get请求博客细节。博客详情页有两个问题, 1.从后端取出来的数据是markdown原始数据,须要解析。 从网上找了一个很好用的解析插件marked,引入,稍微封装一下就能够用了,配置了一下代码块的样式。若是有兴趣能够去看看代码。 2.利用vue的 this.$nextTick 等dom元素渲染完成后,抽离博客中h1到h6的标签。来组合成菜单栏。react

Array.from(this.$refs.post.querySelectorAll("h1,h2,h3,h4,h5,h6")).forEach(
       (item, index) => {
         item.id = item.localName + "-" + index
         this.category.push({
           tagName: item.localName,
           text: item.innerText,
           href: "#" + item.localName + "-" + index
         })
       }
     );

复制代码

以上是核心代码,也是参考了大神的源码。从新组合成一个菜单栏,这时候就用到了以前信息展现组件的插槽功能。   css直接用了postcss,postcss好处在于它的语法和scss相似,并且丰富的插件可使得使用不少超前的css语法,虽然我用的不多。关于postcss的插件和webpack配置,也不复杂,很常规的一些。细节见代码。原本不打算对移动端博客做兼容的,考虑到你们广泛仍是手机浏览博客频率更高。因此对移动端作下兼容处理,并且postcss对于媒体查询的语法很友好webpack

@custom-media --small-viewport (max-width: 850px);

@media (--small-viewport) {
 .ArticlePage .articleDate{
   margin-left: 0;
   width: 100%;
 }
 .ArticlePage .articleDate .time{
   margin-left: 0;
 }
 .abstract {
   width: 100%;
 }
}
复制代码

以上是对移动端作的媒体查询 ios

以上是移动端和pc端不一样的ui展现,也是大量借鉴了别人的博客

登录页面和博客编辑页面

登录鉴权

  由于这个博客只是单纯本身用,因此很简单的写了一个登录页面,用的是elementUI, 这里涉及到一个前端鉴权的功能,登录成功时,会获取到一个token值有效期24小时。前端拿到token后,写入请求头中,后端某些(例如进去admin页面)请求须要验证token。

// 来自 vuex/mutations.js 做用是将获取到的token存入storage
   	 [types.CREATE_TOKEN]: (state, res) => {
   			state.token = res.token
   			state.userInfo = res.name
   			sessionStorage.setItem('vue-blog-token', res.token)
   			sessionStorage.setItem('vue-blog-userName', res.name)
   		},

   		// 来自 封装的 js/http.js 做用是将token写入headers头部
   	const createToken = ()=>{
   		if(store.state.token){
   		// 全局设定header的token验证,注意Bearer后有个空格
   			axios.defaults.headers.common['Authorization'] = 'Bearer ' + store.state.token
   		}
   	}
   	// axios拦截返回,拦截token过时
   	axios.interceptors.response.use(function (response) {
   		return response
   	}, function (error) {
   		if (error.response.data.error.indexOf('token') !== -1) {
   				store.commit('DELETE_TOKEN')
   		}
   		return Promise.reject(error)
   	});
复制代码

以上是前端处理鉴权的核心代码。

博客编辑功能

博客后台是两部分构成1. 博客列表页,包括公开和没有公开的博客 ,2 markdown 编辑器。

  这里寻找markdown编辑器仍是费了一番周折,开始使用的是 vue-simplemde 开发环境下没有问题,可是打包生成之后报错webpack的babel-loader编译通不过,折腾很久我把这个npm包中涉及到编辑器的vue文件抽出来,vue-simp.vue文件直接引用,反而能够用了,真的是玄学。第二个难点是博客编辑功能的开发,涉及到新建,更新,删除,是否发布,也是bug最多的地方。 博客后台截图

前端总结

  vue和vuex用的更加熟练了,webpack不敢说熟练,可是起码各类资源文件的处理和配置比之前驾轻就熟。多少也锻炼了本身前端封装组件的能力。咱们遇到的问题,别人也会遇到。多用谷歌和github总能解决的。   这里简单聊一下我对前端组件化的一些感想,我总共接触写过三种前端框架,vue,react,小程序。逐渐接受了一种组件化就是容器组件和展现组件。容器组件是指参与项目逻辑和响应用户指令的组件。例如分页组件,内部处理复杂的分页逻辑。展现组件是指只负责渲染的组件,例如博客列表组件,只接受数据,而且渲染。接受到用户指令后,例如父子组件传值或者vuex,redux等状态管理工具来传递给容器组件处理。每每展现组件能够被屡次使用,抽取为公共组件。可是根据项目的不一样,组件抽取的颗粒程度不一样,组件的做用也不一样。

后端细节

  这是我第一次完整的使用koa2,刚开始难免有些无从下手,我仍是相信程序员的学习和进步都是从学习大神代码开始的,参考大神代码,将服务端代码分为如下部分:

入口文件

  由于在koa中使用es6和部分es7新特性,因此在入口文件中加入babel编译

// 来自server/start.js
require('babel-register')({
  presets: [ 'env' ]
})

// Import the rest of our application.
module.exports = require('./index.js')

复制代码

就是很粗浅的配置了一下,因此真正的入口文件是 index.js,    在webpack那讲过开发的时候,先后端使用同一个node服务。判断是否开发环境,而后直接执行函数,将koa做为参数传入,去启动webpack的服务,具体会在关于webpack博客中细讲。

const app = new koa()
const isDeve = process.env.NODE_ENV === 'development'
if (isDeve) {
  require('../build/server')(app)
}
复制代码

require引入的文件是webpack配置文件,这也是webpack和koa结合最核心的代码。至此入口文件中都是很常规的app.use()和链接mongoodb了。   有一个被我忽视的地方就是node怎么指向打包完成后的主页,开发模式中,目标文件夹中是看不到编译后的文件的,实时编译后的文件都保存到了内存中,个人前端代码被webpack 会使用inline mode(内联模式)。这种模式在 bundle 中注入客户端。而在生产环境中须要指明主页。

import serve  from 'koa-static'
const home  = serve(path.join(__dirname+"../../dist/"))
app.use(home)
复制代码

dist文件夹就是打包生成的前端代码,ko2会默认指定index.html为主页。

路由

   用koa写接口koa-router官方推荐的,使用也简单,这里也不过多说明,koa-router和controllers结合一块儿使用,

import * as $ from '../../controllers/articles_controller'
   import verify from '../../middleware/verify'
   export default async(router) => {
   	router.get('/getArticles',$.getAllPublishArticles)
   	router.post('/saveArticle',verify,$.saveArticle)
   	router.get('/articleDetails',$.articleDetails)
   	router.post('/changeArticle',verify,$.changeArticle)
   	router.post('/deletaArticle',verify,$.deletaArticle)
   }
复制代码

简单说明一下以上代码,实际的业务代码所有放在comtrollers下,路由只负责调取不一样的comtroller,verify是鉴权,经过koa中间件,来实现接口是否现须要鉴权才能调用。这样就可使得业务逻辑和路由的剥离,相比我以前写在一块儿要好的多。

controllers和middleware

  后端代码最核心的部分就是controller,包括网站数据的存储,修改,删除,管理员登录。虽然是最核心的代码,却也是最简单。就是增删改查。代码就不放了,感兴趣的大佬能够去github上自行查看github

  关于鉴权的功能,是放在中间件来实现的,同时在中间件还使用了koa-bodyparser,做用获取post提交数据。对于koa来说这个中间件是使用率最高的了。

import jwt from 'jsonwebtoken'
import config from '../serverConfig/index'
export default async(ctx, next) => {
    // console.log(ctx.get('Authorization'))
    const authorization = ctx.get('Authorization')
    if (authorization === '') {
        ctx.throw(401, 'no token detected in http header \'Authorization\'')
    }
    const token = authorization.split(' ')[1]
    let tokenContent
    try {
        tokenContent = await jwt.verify(token, config.jwt.secret)
    } catch (err) {
        if ('TokenExpiredError' === err.name) {
            ctx.throw(401, 'token expired,请及时本地保存数据!')
        }
        ctx.throw(401, 'invalid token')
    }
    console.log('鉴权成功')
    await next()
};

复制代码

以上代码是写在中间件的鉴权代码,用的是jwt,使用也很简单,功能就是检查header是否携带token,和token是否过时。关于koa中间件的高阶使用和源码之类的请原谅个人弱鸡,之后会慢慢补上来的。

mongodb

  单独讲一下mongodb吧,关于服务器部署mongodb的文章一抓一大把这里也再也不说明,代码中mongodb的操做使用的也是常规的mongoose,我做为非科班出身的前端程序员,数据库设计实在是短板,只能参考别人的设计。本网站的数据库设计分两个collection,一个是博客的collection,另外一个是用户的collection。

  在业务逻辑代码中,须要频繁的查询修改。好在mongoose的语法也足够简单。

export async function articleDetails(ctx) {
  const articleID = ctx.query.articleID
  const articleDetail = await Article.findOne({
    _id: articleID
  }).catch(err => {
    ctx.throw(500, ERRMESG)
  });
  ctx.body = {
    success: true,
    articleDetail,
  };
}
复制代码

上面代码中用到的findOne是mongoose查询语句的一种,参数是查询条件。其余的查询语句遇到了去官网查一下就行。

后端总结

   对于koa的学习算是入门了,可是远远没有达到一个合格的标准,不是装逼,而是写的越多,发现本身越弱。中间件只会简单使用,koa对比express的优势在哪也说不出。要学的东西还不少。

上线过程

  网站的服务器是从阿里云购买,域名sxywzg是我和女友姓名的缩写,(喂狗粮支线任务达成)。我购买服务器日期是十月末,买了没几天阿里云推送告诉我服务器双十一打折。。。在服务器上安装node,数据库,配置乱七八的东西不细说了。网上也有不少教程,重点说下nginx配置。

upstream vue{
  server 127.0.0.1:3000;
}

server {
  listen  80;
  server_name www.sxywzg.cn;
  location / {
    proxy_set_header X-real-ip $remote_addr;
    proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-Nginx-Proxy true;
    proxy_pass http://vue;
    proxy_redirect off;
  }
}
复制代码

网站端口都是80,将端口3000转发到80上,server_name就是网站域名。就这么简单的配置我折腾很久,仍是在同事的帮助下整出来。nginx的安装也很简单,自行搜索教程。

pm2

  pm2我以前只是简单的让node服务在服务器跑起来的工具而已,其实远不止那么简单。个人代码是放在github上面的,每次更新代码都会提交到上面,pm2能够帮助我从git仓库拉取代码,而且重启服务。

{
  "apps": [
    {
      "name": "vue-blog",
      "script": "server/start.js",
      "error_file": "server/logs/app-err.log",
      "out_file": "server/logs/app-out.log",
      "env_dev": {
        "NODE_ENV": "development"
      },
      "env_production": {
        "NODE_ENV": "production"
      }
    }
  ],
  "deploy": {
    "production": {
		// 我服务器用户名
      "user": "zhigang",
			///服务器地址
      "host": ["47.95.***.***"],
      "port": "22",
			//代码分支
      "ref": "origin/master",
			//代码仓库
      "repo": "git@github.com:463755120/vue-blog.git",
			//服务器存放代码地址
      "path": "/home/zhigang/vue-blog",
      "ssh_options": "StrictHostKeyChecking=no",
			// 每次执行的命令
      "post-deploy":"cnpm i && npm run build && pm2 start pm2.json --env production",
      "env": {
        "NODE_ENV": "production"
      }
    }
  }
}
复制代码

以上是pm2.json配置,做用是一键上线代码,不用咱们手动把代码在服务器中拉下来,而后跑命令。pm2帮你作这些累活。刚开始用这个功能,惊为天人,还有这么善解人意的工具,可是不知道是个人缘由仍是pm2的缺陷,项目第一次上线时pm2执行cnpm i下载的包不完整,我只能在服务器里手动 下载,之后就好使了。因此我须要更新网站时,把代码提交到git仓库,执行一下 npm run pm2 省时省力。

写在最后

  写博客网站的缘由有两个,第一个是为了检验本身能不能写一个完整的而且能上线的项目,第二个是为之后方便本身写博客。若是不总结本身的技术,用不了多久就会忘得差很少。上家公司的前端组长给我说忘了你是前端开发,你只是解决问题的程序员。我深觉得然,公司能够把咱们划分为前端开发,后端开发。可是我以为从内心须要知道程序员是解决问题的人,不能由于是前端开发就只局限于本身的一亩三分地。不要知足在别人配置好的环境中开发。了解一些项目架构和上线流程,服务器配置不是坏事。2018年立刻过去,今年也承受了我这个年纪不应承受的裁人之苦。明年还要学基本的算法,设计模式,typescript和react。之后的博客也会不按期的增长个人学习笔记。但愿大佬们多提意见,联系个人方式很简单,首页有个人知乎帐号,发私信便可。

相关文章
相关标签/搜索