I started my own personal blog

Has always wanted to write a blog of their own, recently a little time, spent a few days to masturbate a prototype has been, the follow-up to improve the content, optimization function, there are a lot of places have not come and do processing, continue to optimize. My ability is limited, and some places are not well handled. I hope you can give me correction. In the future, my blog will also be synchronized to this website.

Github address Address of the website

Front-end page documentation

On the page, vue-CLI is directly used to generate the project, install the corresponding dependency packages, and run the project. The dependency packages installed in the project include the following:

  • Vuex (Data Management)
  • Element – the UI (UI)
  • Axios (Interface request)
  • Sass (CSS Preprocessor)

Using the step

// Clone project gitclone[email protected]: dragonnahs/new_websit_blog. Git / / directory into the front pagecdBaozi_blog // install depends on NPM install // run project NPM run dev // package project NPM run buildCopy the code

Here is not a step by step about the implementation, the main place roughly said, how to achieve, very simple, can first clone down to run a try, rely on the background interface, so try to run the background first. Here are some technical points in writing code.

Markdown format parsing and highlighting

The libraries you need to use are marked and hight.js, which are used in the blog details page,

// tempalte(v-highlight) <div class="markdown" v-html="compiledMarkdown"V-highlight ></div> // script // introduce and initializelet marked = require('marked')
let hljs = require('highlight.js')
import 'highlight.js/styles/default.css'
marked.setOptions({
    renderer: new marked.Renderer(),
    gfm: true,
    tables: true,
    breaks: false,
    pedantic: false,
    sanitize: false,
    smartLists: true,
    smartypants: false,
    highlight: function (code, lang) {
        if (lang && hljs.getLanguage(lang)) {    
            return hljs.highlight(lang, code, true).value;
        } else {
            returnhljs.highlightAuto(code).value; }}})export default{
    data () {
        return{blogInfo: {}}}, computed: {// Parse codecompiledMarkdown() {return marked(this.blogInfo.content || ' ', {
                sanitize: true})}} // We can see the contents of the page after parsing, but there is no highlighting style, the reason is that vue-router will remove the hight event when switching routes, the solution is to define a custom instruction to add the corresponding class name to the code, implement code highlighting. // main.jslet hljs = require('highlight.js'// Custom code highlighting directive vue.directive ('highlight'.function (el) {
  let blocks = el.querySelectorAll('pre code'); Blocks. ForEach ((block)=>{hljs.highlightblock (block)})})Copy the code

Login authentication

Since the blog management background is involved, there must be a login verification problem, the use of token to verify permissions.

  • The front page

BeforeEach method is used to judge each route and verify whether the route requires login permission. If login permission is required, the user information is obtained through the local token and the current route redirection is continued. If not, jump to the login page to log in. The token is returned by the background during login. The background sets the expiration time and saves the token to the local session after login

// router/index.js

router.beforeEach((to, from, next) = > {
    // Match routes, whether login authentication is required (defined when registering routes)
  if(to.matched.some(record= > record.meta.requireAuthor)){
    store.dispatch('getUser').then(data= > {
      console.log(data)
      if(data && data.length > 0){
        next()
      }else{
        next('/login')
      }
    }).catch(err= > {
      next('/login')
    })

  }
  next()
})

Copy the code

The comment function

Started to use most is news soufangwang comments, later to write their own reviews, the main reason is the use of news soufangwang need to load a lot of external file, may have a look inside the network load a lot of things, and I have no too high requirements of the function of the comments, so their literally wrote a simple function of comments, but still want to keep a record of how to realize the news soufangwang introduction.

This is a separate component

<template>
    <div id="SOHUCS" sid="Replace this with a statement to configure the SourceID"></div>
</template>
<script>
export default {
    mounted () {
        window.changyan = undefined;
        window.cyan = undefined;
        this.loadScript('https://changyan.sohu.com/upload/changyan.js',()=>{
            window.changyan.api.config({
                appid: '# # #'// Change your app's appid here, conf:'# # # #'// Change this to your app conf. }); })}, methods: {loadScript(url, callback){// loadScriptlet script = document.createElement('script');
            if(script. ReadyState) {/ / IE browser script. The onreadystatechange =function () {
                    if (script.readyState === 'loaded' || script.readyState === 'complete') { script.onreadystatechange = null; callback(); }}}else{// other browsers script.onload =function () {
                    callback();
                }
            }
            script.src = url;
            document.getElementsByTagName('head')[0].appendChild(script);
        }
    }
}
</script>


Copy the code

You can then introduce components where needed

Remove the log when the package goes online

/ / build/webpack. Prod. Conf. Js to modify the file new UglifyJsPlugin ({uglifyOptions: {compress: {warnings:false,
        drop_debugger: true// Add drop_console:true// add pure_funcs: ['console.log'}, // add},sourceMap: config.build.productionSourceMap,
    parallel: true
}),

Copy the code

Using History mode

By default, vue-router uses hash routing mode, and there is always a hashtag in the address bar. In this case, some wechat sharing functions will remove the hashtag, so using history mode is more appropriate

// router/index.js
const router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      component: resolve => require(['.. /views/out.vue'], resolve),}}) // Add 'mode' to route initialization:'history'Make sure that the 'assetsPublicPath' in config/index.js is in the root directory before going online. Otherwise, you will get an error, build: {// Templatefor index.html
    index: path.resolve(__dirname, '.. /pcBlog/index.html'),

    // Paths
    assetsRoot: path.resolve(__dirname, '.. /pcBlog'),
    assetsSubDirectory: 'static',
    assetsPublicPath: '/'} // After this, run 'NPM run build' and put the generated package on the serverhistoryModel / / find the server ` / etc/nginx/conf. D/blog. Conf ` files, configuration as follows, must be specified to pack directory, server {listen 80; server_name blog.baozinews.cn;# to the root directory
        root   /usr/share/nginx/new_websit_blog/baozi_blog/pcBlog;
        # official specified configuration
        location / {
                 try_files $uri $uri/ /index.html;
        }
        Interface proxyLocation /blog/v1/ {proxy_pass http://127.0.0.1:3006; } location ~* ^.+\.(css|js|ico|gif|jpg|jpeg|png)$ { log_not_found off;# disable log
     access_log off;
     # 7 days cache time
     expires 7d;
     # source server
     #proxy_pass http://localhost:8888;
     # specify the cache area set above
     proxy_cache imgcache;
     # Cache expiration managementproxy_cache_valid 200 302 1d; proxy_cache_valid 404 10m; proxy_cache_valid any 1h; proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; }}Copy the code

At this point, the vue-router’s history mode configuration is complete, and access refresh can be used normally.

Process of going online

The on-line configuration is recorded here, including both front-end and back-end, involving direct package on-line, node deployment, and accelerated processing of CDN

1. First, the front end page

First, NPM run build generates the packaged file. Due to the modification of the configuration, HERE I generate the pcBlog file, put the file on the server, configure the corresponding Nginx

// /etc/nginx/conf.d/blog.conf server { listen 80; server_name blog.baozinews.cn; // Secondary domain root /usr/share/nginx/new_websit_blog/baozi_blog/pcBlog; // Directory file // This is the vue-router official specified configuration forhistoryMode configuration location / {try_files$uri $uri/ /index.html; } location /blog/v1/ {proxy_pass http://127.0.0.1:3006; // Proxy address, back-end node port number}}Copy the code

Because the history mode is used, the domain name needs to be specified in the root directory of the project when the package goes online, and the above nginx configuration must be added, otherwise the access to the secondary page will report an error.

Then is the CDN acceleration, the use of seven cattle CDN acceleration, upload pictures is also the use of seven cattle cloud storage, the key is free. The specific use is to bind the CDN accelerated domain name first. The second level domain name I use here is Dragon.baozinews.cn (the second level domain name is generated on www.dnspod.cn), and the final accelerated access domain name is this one. The CNAME parameter needs to be configured. Just follow the tips or search the Internet. After the configuration is complete, you can access the accelerated domain name, but you need to configure the back source domain name of the accelerated domain name on seven Niuyun, which refers to the domain name that your project really visits, is the secondary domain name configured in nginx. From here, you can access the accelerated secondary domain directly, along with the directory you have configured nginx to point to.

2. Start the backend

Pm2 start SRC /app.js –name ‘pm2 start SRC /app.js’ –name’ pm2 start SRC /app.js –name ‘pm2 start SRC /app.js’ Pm2 ls Displays all startup items. Pm2 restart ID Indicates the restart item. Pm2 log ID Indicates the log of the item

  1. How to update code after CDN acceleration is configured

After the project is deployed, CDN acceleration is completed, new functions are added in the code, and deployed to the server after completion, but access to the accelerated domain name is still the old page, which needs to be handled in the qiniu CDN acceleration function, select the “refresh prefetch” module, and then select the refresh directory, In which to fill in the acceleration of http://dragon.baozinews.cn/ domain name, and then submit, refresh the page again, is that you just submit the latest page.

The back-end code

Main Technology Stack

  • node
  • vue
  • mongoose

The main reason why mongodb is used for storage in the background is that it is somewhat familiar. Those who are familiar with mysql can also directly use mysql to store data. If you are not familiar with mongodb, you can check the following links for a little familiarity with the API and refer to the links

  • The back-end code

// jwt.js creates and validates tokens
// This is the simplest method to use. For more rigorous methods, see NPM
const jwt = require('jsonwebtoken')

let secret = 'jwttoken'


class Jwt {
    constructor(data,token){
        // Data is the data sent from the front end
        this.data = data
        this.token = token
    }
    / / token is generated
    createToken(){
        let token = jwt.sign(this.data, secret, {
            expiresIn: '1h'.// Valid time
            issuer: 'baozi' 
        })
        return token
    }
    / / authentication token
    verifyToken(){
        try{
            let result = jwt.verify(this.token, secret)
            return result
        }catch(err){
            return null}}}module.exports = Jwt

// user.js

// Create a token when logging in to the interface
let token = new jwtClass({username: req.username}).createToken()

// Verify the token when obtaining user information
let resultToken = new jwtClass(' ',token).verifyToken()

Copy the code

User Password Encryption

Bcrypt is used to encrypt and verify the password, when registering users to encrypt the password, when logging in to verify whether the password is correct.

// Register static async registerUser(CTX){let req = ctx.request.body
    let result = await UserModel.findOne({'username': req.username})
    if(result){
        ctx.body = new ErrorResModel('Username already exists')
        return} // Password encryptionlet password = req.password
    // hashGenerate one by encrypting the passwordhashValue, where password is the input password, 10 is the number of Hash rounds, support asynchronous processing, and put the generatedhashThe value is saved to the database as the passwordlet hashPassword = await bcrypt.hash(password,10)

    let newUser = new UserModel({
        username: req.username,
        password: hashPassword,
        power: req.username == 'admin' ? 10 : 1,
        modifyAt: req.modifyAt || ' ',
        email: req.email || ' ',
        telphone: req.telphone || ' ',
        realName: req.realName || ' '
    })
    await newUser.save()
    let token = new jwtClass({username: req.username}).createToken()
    ctx.body = new SuccessResModel({
        token: token,
        userinfo: {username: req.username}
    },'Registration successful'Static async loginUser(CTX){let req = ctx.request.body

    let result = await UserModel.findOne({'username': req.username}) // Check whether the entered password is the same as the password stored in the databasefalseInconsistent, returntrueconsistentlet flag = await bcrypt.compare(req.password,result.password)
    if(! Ctx. body = new ErrorResModel('Incorrect account password')
        return
    }
    if(result){
        let token = new jwtClass({username: req.username}).createToken()
        ctx.body = new SuccessResModel({
            token: token,
            userinfo: result
        }, 'Login successful')
        return
    }
    ctx.body = new ErrorResModel('Incorrect account password')}Copy the code

Related data query problems of Mongoose

Since the blog table involves the association of the tag and user table, the tag is an array type, the initial processing mode, in the local also got the correct list, after publishing to the server, but there is a problem, first record the solution, the follow-up to see if there is a more optimized processing mode.

// database/model/blog.js
const blogSchema = new Schema({
    title: {
        type: String,
        required: true
    },
    createAt: {
        type: Date,
        default: Date.now()
    },
    content: {
        type: String,
        required: true
    },
    author: Schema.Types.ObjectId,
    tags: [{
        type: Schema.Types.ObjectId,
        ref: 'Tags'}], // tags are array format, associated tags isShow: {type: Boolean,
        default: true
    },
    modeifyAt: {
        type: Date,
        default: Date.now()
    },
    clickNum: {
        type: Number,
        default: 0
    },
    imgUrl: String,
    power: {
        type: Number,
        default: 0
    },
    isTop: {
        type: Boolean,
        default: false}}) // controller/blog.js gets the list of blogs BlogModel. Aggregate ([{// single association$lookup: {
            from: 'users'.localField: 'author',
            foreignField: '_id',
            as: 'userinfo'}}, {// A single association$lookup: {
            from: 'tags'.localField: 'tags',
            foreignField: '_id',
            as: 'tagList'}}, {$match: {
            $or: [
                {title: {$regex: searchKey,$options: '$i'}},]},}, {$sort: {createAt: -1}},
    {$skip: (pageNum-1)*pageSize},
    {$limit: pageSize},
])


Copy the code

When running locally, there is no problem. The tagList can be obtained normally, and then deployed to the server, the tagList display is always empty, but tag id has been stored in the tag. I thought the reason is that the mongodb version is too low. 3.2.0 was used at that time, but it still didn’t work after the update. Finally, it was solved in another way. The following is the solution

// controller/blog.js
let resultTemp = await BlogModel. Aggregate ([{// single association$lookup: {
            from: 'users'.localField: 'author',
            foreignField: '_id',
            as: 'userinfo'}}, {$match: {
            $or: [
                {title: {$regex: searchKey,$options: '$i'}},]},}, {$sort: {createAt: -1}},
    {$skip: (pageNum-1)*pageSize},
    {$limit: pageSize},]) // Array type associationlet result = await (function() {returnNew Promise(resovlve => {// here the tags under the association get the list of tags, so in blogModel tags must {type: mongoose.Schema.Types.ObjectId,ref:'Tags'}
        BlogModel.populate(resultTemp, 'tags'.function(err,res){
            resovlve(res)
        })
    })
})()


Copy the code

At present, it can run normally on the server, and the scheme provided on the Internet is the first one, but IT is not clear why it cannot run normally on the service. We will continue to search for the scheme and update it in the document after the solution is solved.

Setting timeout

// public/utils/time-out.js

async functiontimeOut(ctx, next) { var tmr = null; const timeout = 5000; Race ([new Promise()) {// Set the timeout time.function (resolve, reject) {
            tmr = setTimeout(function () {
                var e = new Error('Request timeout');
                e.status = 408;
                reject(e);
            }, timeout);
        }),
        new Promise(functionResolve, reject) {// Use a closure to execute the following middleware (async)function() { await next(); clearTimeout(tmr); resolve(); }) (); }) ]) } module.exports = timeOut // app.js app.use(TimeOut)Copy the code

First wrote here, function also is not very perfect, there are some problems, I hope you to correct me, now want to come or give priority to in order to improve the function, the subsequent will continue to optimize code, function is currently planning to personal data, comments list management, and the list of users management, there are some graphics log, log on this side of the back-end didn’t add, At present, the pM2 log is the standard, and the log will be added later. After improvement, the blog will be updated to the website, and the code will continue to be improved.