从零搭建博客之框架搭建

从零搭建博客之框架搭建

2019年08月09日 阅读:73 字数:2248 阅读时长:5 分钟

从一开始的WordPress,再到现在自个用Node写博客,我为啥这么喜欢折腾呐,也许是因为闲的慌吧

前言

一开始使用WordPress,觉得有些臃肿,PHP会的不多改起来有点麻烦。后面换成Hexo,支持Markdown静态优秀,不过动态功能像评论、文件管理得自己实现,不如自个写一个。

业余时间快速开发,计划主体功能2-3个月内完成,实际上因为拖延症陆陆续续做了大半年。

1、技术选型

1.1、Node.js框架

Express、Koa 都是非常优秀的框架,简单且扩展性强,非常适合做个人项目,但框架本身缺少约定,基础设施需要寻找或自己搭建。而我的初衷是快速搭建,所以我把目光转向阿里巴巴团队的Egg.js和360团队的Thinkjs,两者中文文档详细容易扩展,社区资源丰富。

Egg.js 选择了 Koa 作为其基础框架,在它的模型基础上,进一步对它进行了一些增强。

Thinkjs,框架底层也是基于 Koa 2.x 实现,相对于Egg.js来说,相对于Egg.js它处于更上层,封装了很多常用的功能,符合快速搭建一个博客的初衷,所以我选用了Thinkjs。

2 年前入坑 thinkjs ,现在有没有必要改换 eggjs ?

更新于2021-10-08:18年选用Thinkjs的时候,它和Egg.js差不多,其实两个都能满足博客应用的需求,虽然它不更新了,但是已经相对稳定,所以暂时不会闲着更换成Egg.js,后续的新项目再考虑Egg.js

1.2、后端技术

  • Node.js
  • Mysql:由于博客的数据间存在一定的关系,且数据量并不大,所以选用Mysql而不是MongoDB,而且还有个原因就是Thinkjs对Mysql更友好。
  • file-cache:我的网站流量并不大,所以简单使用文件做缓存管理,后续更换redis也很简单
  • session-cookies:内容管理系统就只有我一个用户登录,所以简单使用传统的session-cookies,而不是jwt,且如果用jwt图片验证码服务端存储还得用另外的
  • RESTful:内容管理系统api使用RESTful规范
  • Nunjucks:模板引擎,主要用于后端渲染sitemap、xml

1.3、前端技术

  • Vue2、Vue-router、Vuex
  • Nuxt.js:Vue服务端渲染
  • Element-ui:组件库
  • Eslint:JS代码规范控制
  • Stylelint:CSS代码规范控制
  • Axios:网络请求

2、功能与特性

2.1、内容管理

  • SPA单页应用:内容管理系统不需要SEO,首页渲染时间也没有要求
  • 栏目管理:文章分类,单页面如关于我们页面
  • 文章管理:编写、修改文章
  • 文件管理:文件上传,预览与选择,图片自动裁剪格式化
  • 友链管理
  • 评论管理:原本打算使用Valine、Waline、gittalk这样的第三方评论系统,但是不好管理,所以不如自己实现
  • 系统配置
  • 用户管理:单用户,所以没有权限控制

2.2、博客前台

  • SSR同构渲染:需要考虑搜索引擎的收录,所以需要SEO优化
  • 状态数据管理:菜单、系统配置公共数据
  • 响应式:element-ui的栅格布局
  • 网络请求:全局拦截器
  • 暗黑模式:浅色、暗黑模式切换

3、项目结构

因为前后端都是使用javascript开发,前台(nuxt-ssr模式)、后台(nuxt-spa模式)都是用element-ui+vue,所以考虑放到同一个项目,使用一个node_modules。

c0adb3bab85224b2.png

client 前台
├── admin 内容管理平台
|   ├── api 整合管理所有的api接口
|   ├── layouts 外部容器
|   ├── middleware
│   |   └── permission.js 登录鉴权
|   ├── plugins
│   |   ├── api.js 使上下文能使用api请求
│   |   ├── axios.js axios全局拦截器
│   |   ├── element-ui element-ui按需引入
│   |   └── svg-icon 注册svg icon组件
|   ├── store vuex
|   ├── styles 公共样式
|   ├── utils 工具集
|   ├── views 页面组件
|   └── router.js @nuxtjs/router路由配置
├── common 内容管理平台和博客前台功能模块
├── front 博客前台
|   ├── layouts 外部容器
│   |   ├── app.vue 工具页面使用的外部容器,没有菜单
│   |   └── default 默认容器,有菜单
|   ├── pages nuxt页面
|   ├── plugins
│   |   ├── axios.js axios全局拦截器
│   |   ├── element-ui element-ui按需引入
│   |   └── filters 注册全局过滤器
|   ├── store vuex
│   |   └── index.js nuxtServerInit获取菜单及系统配置
|   ├── vendor 第三方库
│   |   ├── aplayer 音乐播放器
│   |   ├── live2d Live2D Cubism SDK v2看板娘
│   └── └── spine-ts spine骨骼动画
├── config 配置
|   ├── adapter.model.js 数据库配置
│   ├── nuxt.admin.js 内容管理平台nuxt配置
│   └── nuxt.front.js 博客前台nuxt配置
├── logs 日志文件
├── runtime
|   ├── cache 缓存
|   ├── session 会话信息
├── src 后台
|   ├── config 配置
│   |   ├── adapter 数据库、缓存、日志等配置
│   |   ├── middleware 中间件配置
│   |   |   └── nuxt.js nuxt中间件
│   |   ├── extend.js thinkjs扩展配置
│   |   └── router.js thinkjs路由
│   ├── controller 控制器
│   |   ├── admin 内容管理平台api
│   |   └── front 博客前台api
│   ├── logic 校验层
│   ├── model 模型,数据库操作
│   ├── service 服务,可复用,抽离controller逻辑
├── views
│   ├── rss.xml rss模板
│   └── sitemap.xml 网站地图模板
├── www 静态资源,nuxt打包目标目录
│   └── upload 上传目录
├── development.js thinkjs开发环境入口
├── nuxt.config.js
├── package.json
├── production.js thinkjs生产环境入口

4、系统设计

4.1、中间件

nuxt作为node中间件使用Renderer处理和服务所有 SSR 和资源请求,开发环境先Builder然后再渲染,生产环境直接使用nuxt build构建好的资源进行渲染

const { Nuxt, Builder } = require('nuxt');

module.exports = options => {
  const config = require('config/nuxt.front.js'));
  config.dev = options.isDev;

  const nuxt = new Nuxt(config);

  if (options.isDev) {
    new Builder(nuxt).build();
  }

  const middleware = async(ctx, next) => {
    // Default 404
    ctx.status = options.status || 200;
    ctx.req.session = await ctx.session();
    await nuxt.render(ctx.req, ctx.res);

    let err = null;
    return next().catch(e => {
      err = e;
    }).then(() => {
      if (err) {
        return Promise.reject(err);
      }
      // 如果后续执行逻辑有错误,则将错误返回
      return new Promise((resolve, reject) => {
        return { resolve, reject };
      });
    });
  };
  return middleware;
};

front、admin都用nuxt,根据命令里面是否包含--admin,获取对应的配置

nuxt.config.js

const adminConfig = require('./config/nuxt.admin.js')
const frontConfig = require('./config/nuxt.front.js')

const isAdmin = process.argv.includes('--admin')

module.exports = isAdmin ? adminConfig : frontConfig

admin不需要SEO,所以使用nuxt的spa模式

config/nuxt.admin.js

const path = require('path')

const isPro = process.env.NODE_ENV === 'production'
const srcDir = 'client/admin/'

function resolve(dir) {
  return path.join(__dirname, '../', srcDir, dir)
}

module.exports = {
  alias: {
    '#': resolve('../common')
  },
  axios: {
    proxy: true,
    prefix: '/admin'
  },
  build: {
    babel: {
      plugins: [
        [
          'component',
          {
            'libraryName': 'element-ui',
            'styleLibraryName': 'theme-chalk'
          }
        ]
      ]
    },
    extractCSS: true,
    extend(config, ctx) {
      // set svg-sprite-loader
      const svgRule = config.module.rules.find(rule => rule.test.test('.svg'))
      svgRule.exclude = [resolve('assets/icons/svg')]
      // Includes /icons/svg for svg-sprite-loader
      config.module.rules.push({
        test: /\.svg$/,
        include: [resolve('assets/icons/svg')],
        loader: 'svg-sprite-loader',
        options: {
          symbolId: 'icon-[name]'
        }
      })
    },
    publicPath: '//cdn.timelessq.com/nuxt-admin/' // 只需将www/admin上传cdn
  },
  buildDir: 'www/nuxt-admin',
  buildModules: [
    '@nuxtjs/router'
  ],
  css: [
    '@/styles/index.scss'
  ],
  plugins: [
    '@/plugins/axios',
    '@/plugins/api',
    '@/plugins/element-ui',
    '@/plugins/svg-icon'
  ],
  generate: {
    dir: 'www/nuxt-admin',
    fallback: 'index.html'
  },
  head: {
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: '' }
    ]
  },
  modern: isPro, // 现代模式
  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/proxy'
  ],
  proxy: {
    '/admin': {
      target: 'http://127.0.0.1:8360', // 目标接口域名
      changeOrigin: true // 表示是否跨域
    }
  },
  render: {
    compressor: false // 禁用中间件压缩
    // resourceHints: false  // 添加prefetch并preload链接以加快初始页面加载时间。
  },
  router: {
    middleware: ['permission']
  },
  server: {
    port: 9528
  },
  srcDir,
  ssr: false,
  target: 'static',
  telemetry: false // 关闭收集遥测数据
}

front前台就需要开启SSR

config/nuxt.front.js

const path = require('path')
const CompressionPlugin = require('compression-webpack-plugin')

const isPro = process.env.NODE_ENV === 'production'
const srcDir = 'client/front/'

function resolve(dir) {
  return path.join(__dirname, '../', srcDir, dir)
}

module.exports = {
  alias: {
    '#': resolve('../common')
  },
  buildModules: [
    '@nuxtjs/color-mode',
    '@nuxtjs/style-resources'
  ],
  build: {
    babel: {
      plugins: [
        [
          'component',
          {
            'libraryName': 'element-ui',
            'styleLibraryName': '../packages/theme-chalk/src',
            'ext': '.scss'
          }
        ],
        [
          'prismjs',
          {
            'languages': ['markup', 'css', 'javascript', 'json', 'less', 'scss', 'shell', 'typescript'],
            'plugins': ['show-language', 'highlight-keywords', 'toolbar'],
            'css': false
          }
        ]
      ]
    },
    extractCSS: true,
    plugins: [
      new CompressionPlugin({
        test: /.(js|css|woff|ttf)$/, // 匹配需要压缩的文件后缀 看需求
        threshold: 10240 // 大于10kb的会压缩,默认为0
      })
    ],
    publicPath: '//cdn.timelessq.com/nuxt-front/dist/client' // 只需将www/front上传cdn
  },
  buildDir: 'www/nuxt-front',
  css: [
    '@/styles/global.scss'
  ],
  head: {
    meta: [
      { charset: 'utf-8' },
      { name: 'renderer', content: 'webkit' },
      { name: 'viewport', content: 'width=device-width,initial-scale=1,shrink-to-fit=no' }
    ]
  },
  modern: isPro, // 现代模式
  modules: [
    '@nuxtjs/axios'
  ],
  plugins: [
    '@/plugins/element-ui',
    '@/plugins/axios',
    '@/plugins/filters'
  ],
  render: {
    compressor: false // 禁用中间件压缩
  },
  srcDir,
  styleResources: {
    scss: resolve('/styles/element-variables.scss')
  },
  telemetry: false // 关闭收集遥测数据
}

4.1、文件管理

  • 文件上传本地www/upload目录,开发环境用Thinkjs提供静态资源服务,生产环境www作为网站根目录,使用nginx管理,除静态资源外反向代理到Thinkjs的服务
  • 图片访问时自动裁剪生成指定尺寸、格式的缩略图(后续文章会提到)
  • 生产环境静态资源使用腾讯云对象存储的CDN服务,自动回源拉取文件,不用开发上传腾讯云对象存储功能

推荐阅读

恰饭区

评论区 (0)

0/500

还没有评论,快来抢第一吧