Vue、Node全栈项目~面向小白的博客系统~
 
   
     个人博客系统 
    
 
 前言
「www.llongjie.top」
这是一套包含前后端代码的个人博客系统,欢迎各位提出建议,本来打算用nuxt来书写,但是想学了react之后用next来写,后面会用reactSSR来重构!
博客该有的功能都有,可以写文章,可以评论,可以留言,甚至可以玩游戏(虽然还没完善),等等让你来发现。
这是一个小型的全栈项目,主要是检验并记录一下自己的学习成果
运用的技术Vue、Vue-Cli、Element-Ui、后端NodeJs、数据库MySql等各种技术
看到这里了,请你们不要白嫖,点个star(O(∩_∩)O)
前台页面
-  
   
页面设计 借鉴开源项目 「白茶」  
「支持PC端和移动端,响应式页面,更多页面细节让你去发掘」
1. 主页
2. 文章列表
3. 听雨
4. Demo
5. 关于我
6. 留言板
页面详情看这里
后台页面
-  
   
主页  -  
   
发布文章  -  
   
关于我  -  
   
评论列表  -  
   
设置  
其他的例如文章列表等我后面会别补上,「主要是想让你们研究透代码,自己去写,因为真的很简单(手动狗头)」
页面详情看这里
后端相关
-  
   
数据库操作使用 sequelize  -  
   
后端框架使用 koa  
「sequelize」和「koa」在此项目中的使用有机会我会写成对应的文章出来
❝数据库表结构
❞
「这个博客里本来打算实现文章分类功能,后面感觉不需要,就没用到这个表了」
-  
   
用户表 ID 用户名 用户账号 用户密码 生日 头像 创建时间  -  
   
文章表 ID 用户ID 文章标题 文章图片 文章music 文章内容 分类ID(外键) 文章摘要 发布时间 浏览人数 喜欢人数 评论数(通过文章留言板去查) 分类ID  -  
   
文章留言表 ID 留言文章ID 留言用户ID 留言内容 留言时间  -  
   
分类表 ID 分类名称  -  
   
留言表 ID 留言用户ID 留言内容  -  
   
用户是否喜欢文章表 ID 用户ID 文章ID (通过多对多关系生产)  
❝表关系
❞
-  
   
一个分类有多个文章(一对多)  -  
   
一篇文章可以有多个评论(一对多)  -  
   
一个用户可以评论多篇文章(一对多)  -  
   
一个用户可以留言多次(一对多)  -  
   
一篇文章可以被多个用户喜欢,一个用户可以喜欢多个文章(多对多)  
代码目录介绍及运行介绍
后端代码采用三层架构模式
❝models 是表现层
services 是业务逻辑层
routes 是数据访问层
❞
-  
   
「backstage」 是后台管理系统  -  
   
「client」 是前端  
「拉取代码后先在根目录和client文件夹里面和backstage都执行 npm install安装依赖」
「之后首先要在根目录创建public和encrypt文件夹」
「public文件夹里面要创建upload文件夹,上传文件需要,打包前端代码也是打包到这个目录」
「encrypt里面要创建dbEncrypt.js和ossEncrypt.js」
可随时在models/tables/db.js里面切换
本地开发就用localDbInfo里面的,注意要在db.js切换
——————————数据库请自行安装————————————
dbEncrypt.js
    module.exports = {
    localDbInfo: {
        dbName: 'XXX', // 数据库名称
        userName: 'XXX', // 数据库用户名
        password: 'XXX', // 数据库密码
        host: {
            host: 'localhost',
            dialect: 'mysql'
        }
    },
    aliDbInfo: { // 服务器上的数据库信息,同上
        dbName: 'XXX',
        userName: 'XXX',
        password: 'XXX',
        host: {
            host: 'XXX',
            dialect: 'mysql'
        }
    }
}
 
 如果没有服务器,本地开发的话就不需要这个了
我写了两个上传文件的接口,/upload和/ossUpload,没有服务器就切换成/upload就行了
ossEncrypt.js
    module.exports = {
        region: 'XXX', // OSS region
        accessKeyId: 'XXXXXX', OSS accessKeyId
        accessKeySecret: 'XXXXXX', OSS accessKeySecret
        bucket: 'XXXX', // OSS bucket名
    }
 
 接下来就可以愉快的玩耍了
「确保数据库信息填写正确之后 在根目录输入在 npm run sync 同步数据库,提示同步完成即可」「然后输入npm start 开启服务,这样本地服务就跑起来了」「接下来就跟平时开发一样跑前端就行了,进入client或者backstage 输入 npm run dev 进入开发模式」
部署
首先你得有一个自己的服务器,我用的是阿里云
这里推荐一个我用的服务器工具xShell和xFtp
然后点击右边的「免费授权页面」填写相应资料即可下载
-  
   
首先需要安装「nodeJs」,百度安装方式很多,请自选其一,安装完成后node -v,能打印出版本代表安装完成, 安装完成后设置为淘宝镜像:「npm config set registry https://registry.npm.taobao.org」
❝
选装[「nvm」(node管理工具), 「git」]
❞ -  
   
「nginx」 安装可以看这里,或者百度教程很多
 -  
   
「mysql」 百度一下,你就知道~~
 -  
   
「navicat」(数据库操作工具), 可以使用「navicat注册机」破解,教程点此
 -  
   
「打开navicat连接上服务器的数据库,连接成功即可」
 -  
   
「pm2」(PM2是node进程管理工具,可以利用它来简化很多node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等,而且使用非常简单)
❝
安装命令:npm install pm2@latest -g
❞❝
pm2 -v打印版本即安装成功
❞ -  
   
打开服务器和阿里云上相应的端口(3306,5008,80)
 -  
   
「nginx配置」
为什么是5008,因为我koa监听的是5008端口
❝
这里给出我的nginx配置
❞user root root;
worker_processes auto;
error_log /www/logs/nginx_error.log crit;
pid /www/server/nginx/logs/nginx.pid;
worker_rlimit_nofile 51200;
events
{
use epoll;
worker_connections 51200;
multi_accept on;
}
http
{
include mime.types;
#include luawaf.conf;
# 设置缓存路径并且使用一块最大100M的共享内存,用于硬盘上的文件索引,包括文件名和请求次数,每个文件在1天内若不活跃(无请求)则从硬盘上淘汰,硬盘缓存最大10G,满了则根据LRU算法自动清除缓存。
proxy_cache_path /var/cache/nginx/cache levels=1:2 keys_zone=imgcache:100m inactive=1d max_size=10g;
include proxy.conf;
default_type application/octet-stream;
server_names_hash_bucket_size 512;
client_header_buffer_size 32k;
large_client_header_buffers 4 32k;
client_max_body_size 100m;
sendfile on;
tcp_nopush on;
keepalive_timeout 60;
tcp_nodelay on;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 256k;
fastcgi_intercept_errors on;
#开启和关闭gzip模式
gzip on;
#gizp压缩起点,文件大于1k才进行压缩
gzip_min_length 1k;
# 设置压缩所需要的缓冲区大小,以4k为单位,如果文件为7k则申请2*4k的缓冲区
gzip_buffers 4 16k;
#nginx对于静态文件的处理模块,开启后会寻找以.gz结尾的文件,直接返回,不会占用cpu进行压缩,如果找不到则不进行压缩
gzip_static on;
# 识别http协议的版本,早起浏览器可能不支持gzip自解压,用户会看到乱码
gzip_http_version 1.1;
# gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间
gzip_comp_level 1;
# 进行压缩的文件类型。
gzip_types text/plain application/json application/javascript application/x-javascript text/javascript text/css application/xml image/jpeg image/gif image/png video/mpeg audio/x-pn-realaudio audio/x-midi audio/basic audio/mpeg audio/ogg audio/* video/mp4;
# 启用应答头"Vary: Accept-Encoding"
gzip_vary on;
# nginx做为反向代理时启用,off(关闭所有代理结果的数据的压缩),expired(启用压缩,如果header头中包括"Expires"头信息),no-cache(启用压缩,header头中包含"Cache-Control:no-cache"),no-store(启用压缩,header头中包含"Cache-Control:no-store"),private(启用压缩,header头中包含"Cache-Control:private"),no_last_modefied(启用压缩,header头中不包含"Last-Modified"),no_etag(启用压缩,如果header头中不包含"Etag"头信息),auth(启用压缩,如果header头中包含"Authorization"头信息)
gzip_proxied expired no-cache no-store private auth;
# (IE5.5和IE6 SP1使用msie6参数来禁止gzip压缩 )指定哪些不需要gzip压缩的浏览器(将和User-Agents进行匹配),依赖于PCRE库
gzip_disable "MSIE [1-6]\.";
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server_tokens off;
access_log off;
# 是否启用在on-the-fly方式压缩文件,启用后,将会在响应时对文件进行压缩并返回。
brotli on;
# 启用后将会检查是否存在带有br扩展的预先压缩过的文件。如果值为always,则总是使用压缩过的文件,而不判断浏览器是否支持。
brotli_static always;
# 设置压缩质量等级。取值范围是0到11.
brotli_comp_level 6;
# 设置缓冲的数量和大小。大小默认为一个内存页的大小,也就是4k或者8k。
brotli_buffers 16 8k;
# 设置需要进行压缩的最小响应大小。
brotli_min_length 20;
# 指定对哪些内容编码类型进行压缩。text/html内容总是会被进行压缩
brotli_types text/plain application/json application/javascript application/x-javascript text/javascript text/css application/xml image/jpeg image/gif image/png video/mpeg audio/x-pn-realaudio audio/x-midi audio/basic audio/mpeg audio/ogg audio/* video/mp4;
server {
listen 80;
# 您的域名
server_name xxxxxx.xxx;
location ^~ / {
proxy_cache imgcache;
proxy_cache_key $scheme$proxy_host$uri$is_args$args;
proxy_cache_valid 200 304 302 24h;
proxy_pass http://www.域名:5008;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Nginx-Proxy true;
proxy_redirect off;
rewrite ^.+(?<!js|css|png|map)$ /index.html break;
autoindex on;
index index.htm index.html;
set $fallback_uri /index.html;
if ($http_accept !~ text/html) {
set $fallback_uri /null;
}
try_files $uri $uri/ $fallback_uri = 404;
}
location ^~ /pc {
proxy_pass http://www.域名:5008/back;
index index.htm index.html;
}
}
include /www/server/panel/vhost/nginx/*.conf;
} 
一切准备工作已就绪,可以把项目部署到服务器上了
「执行 npm run build 命令分别打包前台和后台」执行打包后的结构是这样的
接下来把项目(除client和backstage和node_modules以外)放置到服务器的dist文件夹中(然后打该目录执行「npm i」下载依赖)
由于对nginx不熟悉,所以我这里public文件放置得做一些改动,熟悉的可以自行配置(顺便教下我。。)
就是把pc文件夹里面的放置放到同级
皆大欢喜,做到这里就完成了。接下使用 域名就能访问啦
例如我的博客
日志记录
放置到服务器后,出了问题,肯定不像本地开发调试一样方便,所以我们需要「日志记录」,来定位问题
我采用的库是 log4js打不开就得翻墙,我只记录了接口调用记录,需要sql调用记录的可以自行加上 npm i koa-log4
const log4js = require('koa-log4')
const path = require('path')
log4js.configure({
    appenders: {
        api: {
            type: 'dateFile',
            filename: path.resolve(__dirname, 'logs', 'api', 'logging.log'),
            maxLogSize: 1024 * 1024, // 配置文件的最大字节数
            keepFileExt: 3, // 最多保存3天
            layout: {
                type: 'pattern',
                pattern: '%c [%d{yyyy-MM-dd hh:mm:ss}] [%p]:%m%n'
            }
        },
        default: {
            type: 'stdout'
        }
    },
    categories: {
        api: {
            appenders: ['api'],
            level: 'all'
        },
        default: {
            appenders: ['default'],
            level: 'all'
        }
    }
})
process.on("exit", () => {
    log4js.shutdown()
})
const apiLogger = log4js.getLogger("api")
exports.apiLogger = apiLogger
 
 然后创建一个中间件
// apiLoggerMiddleware.js
const { apiLogger } = require('../logger') 
// 处理错误的中间件
module.exports = async (ctx, next) => {
    try {
        await next();
    }
    finally {
        apiLogger.debug(`${ctx.method} ${ctx.path} ${JSON.stringify(ctx.body)}`)
    }
};
//init.js
app.use(require('./apiLoggerMiddleware')) // API请求日志
 
 首页加载速度优化
用vue-cli正常打包后,会生成「多个chunk-hash.js 和 chunk-hash.css」,些许增加访问速度,所以我们需要把对应合为一个
合并chunk-hash.js
在所有异步组件前增加以下代码,目的是把打包的chunk统一
合并chunk-hash.css
vue.config.js
module.exports = {
 configureWebpack: config => {
     // 公共代码抽离
      config.optimization.splitChunks.cacheGroups = {
        vendor: {
          chunks: 'all',
          test: /node_modules/,
          name: 'vendor',
          minChunks: 1,
          maxInitialRequests: 5,
          minSize: 0,
          priority: 100
        },
        common: {
          chunks: 'all',
          test: /[\\/]src[\\/]js[\\/]/,
          name: 'common',
          minChunks: 2,
          maxInitialRequests: 5,
          minSize: 0,
          priority: 60
        },
        styles: {
          name: 'styles',
          test: /\.(le|sa|sc|c)ss$/,
          chunks: 'all',
          reuseExistingChunk: true,
          minChunks: 1,
          enforce: true
        }
      }
    }
}
 
 index.html
「XXX是我的名字」
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="description" content="XXX,一名前端工程师,这是我的个人博客,网站文章随便写,想写啥写啥">
    <meta name="keywords" content="个人博客,XXX,前端,技术,WEB,blog,BLOG,搭建博客,前端技术,VUE博客,XXX的博客">
    <meta name="anthor" content="XXX,[email protected]">
    <meta name="robots" content="博客, 前端, blog, 个人博客, XXX, Yong,XXX的博客,web,VUE,React">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
    <link rel="icon" href="https://qiheizhiya.oss-cn-shenzhen.aliyuncs.com/image/favicon.ico">
    <!-- 使用CDN的CSS文件 -->
    <% for (var i in htmlWebpackPlugin.options.cdn &&
    htmlWebpackPlugin.options.cdn.css) { %>
    <link
            href="<%= htmlWebpackPlugin.options.cdn.css[i] %>"
            rel="stylesheet"
    />
    <% } %>
    <title>漆黑之牙</title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
    <!-- 使用CDN的JS文件 -->
    <% for (var i in htmlWebpackPlugin.options.cdn &&
    htmlWebpackPlugin.options.cdn.js) { %>
    <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
    <% } %>
    <!-- 使用CDN的JS文件 -->
  </body>
</html>
 
 cdn
vue.config.js
const isProduction = process.env.NODE_ENV === 'production';
const cdn = {
  externals: {
    'vue': 'Vue',
    'vuex': 'Vuex',
    'vue-router': 'VueRouter',
    'axios': 'axios',
    "element-ui": "ELEMENT",
  },
  css: [
    'https://lib.baomitu.com/element-ui/2.13.2/theme-chalk/index.css'
  ],
  js: [
    'https://cdn.bootcss.com/vue/2.6.10/vue.min.js',
    'https://cdn.bootcss.com/vue-router/3.1.3/vue-router.min.js',
    'https://cdn.bootcss.com/vuex/3.1.2/vuex.min.js',
    'https://lib.baomitu.com/element-ui/2.13.2/index.js',
    'https://cdn.bootcss.com/axios/0.19.2/axios.min.js'
  ]
}
module.exports = {
 chainWebpack: config => {
    // 注入cdn
    config.plugin('html').tap(args => {
      // 生产环境或本地需要cdn时,才注入cdn
      if (isProduction) { args[0].cdn = cdn }
      return args
    })
  },
  configureWebpack: config => {
   config.externals = cdn.externals
  }
}
 
 前端gzip
npm i -d compression-webpack-plugin
vue.config.js
//也是在configureWebpack中
//gzip压缩
  config.plugins.push(new CompressionPlugin({
    filename: '[path].gz[query]',
    //压缩算法
    algorithm: 'gzip',
    //匹配文件
    test: /\.js$|\.css$|\.html$|\.woff$|\.ttf$|\.eot$|/,
    //压缩超过此大小的文件,以字节为单位
    threshold: 1024,
    minRatio: 0.8,
    //删除原始文件只保留压缩后的文件
    deleteOriginalAssets: isProduction
  }))
 
 「完整配置请自行看client中的vue.config.js」
优化前:1M带宽才首屏加载「10多秒」
优化后:正常情况下首屏加载「1,2秒」
最后
不知不觉写的太多了,技术大佬们最烦的就是看到长篇大论了吧!
项目无模板,纯手写
分享自己的一个全栈简单项目给大家,有什么建议/bug/优化可以提一下,感谢!!。
如果看到这里,就请帮忙点个star吧!!
喜欢技术的也可以加我,一起进步。
