node項目實戰-用node-koa2-mysql-bootstrap搭建一個前端論壇

媒介

在進修了koa2和express並寫了一些demo后,盤算本身寫一個項目練練手,由因而在校生,沒什麼好的項目做,即以開闢一個前端論壇為目的,功用需求參照一下一些社區制定,主要有:

  1. 登錄註冊
  2. 個人信息保護、頭像等基礎信息
  3. 發表文章,富文本編輯器採納wangEditor插件,編輯、刪除文章,文章分類等
  4. 文章批評、文章珍藏、點贊等
  5. 支撐文章分頁、批評分頁加載
  6. 關注取關用戶
  7. 資本(文件)上傳分享、下載、檢察
  8. 進修資本引薦…..

作者個人日記
but。。。。由於種種緣由,如今僅完成了部份功用,資本分享還沒寫
項目運轉結果:http://120.77.211.212/home

項目手藝棧運用:node-koa2-ejs-bootstrap3—jquery, github地點:https://github.com/Jay214/myb…,假如以為對你有協助或許還看得下去,迎接star~~勉勵勉勵我這前端渣渣輝。

開闢環境

node: v8.3.0

koa: ^2.4.1

mysql: 5.7.1

npm: 5.3.0及以上

怎樣運轉項目

將項目clone至當地 git clone git@github.com:Jay214/myblog-koa2.git
裝置模塊中間件 npm install
裝置mysql
mysql版本引薦運用5.7以下的,5.7的有個bug,圖形化界面引薦運用navicat for mysql
運轉可以裝置supervisor(npm install supervisor 項目運轉東西,開啟后即處於監聽形式,修正文件后保留即可,無需再啟動項目) node index 或npm supervisor index
localhost:8080/home 端口號可自行修正
若發明項目有存在什麼bug或有比較好的發起迎接多多發起,qq:2752402930。

準備工作

由於koa2是基於es6的promise和es7的await/async語法,所以假如對es6/es7不懂的話請先過一遍文檔,背景搭建數據庫是癥結,所以請先裝置好mysql,mysql發起裝置5.7版本以下的,由於5.7.0版本有個bug,須要變動設置文件,詳細若你們裝置的時刻便知道了。

裝置node環境,運用node -v檢察node版本,node須要較新版本可以支撐es6的promise和es7的await/async語法,如今node版本都邑自帶npm的,所以不須要再去裝置npm。

項目構造

《node項目實戰-用node-koa2-mysql-bootstrap搭建一個前端論壇》

1.config寄存默許文件(數據庫銜接設置)
2.lib寄存數據庫文件
3.middlewares寄存推斷上岸註冊與否中間件
4.public寄存靜態文件,js,援用bootstrap框架等文件
5.routers寄存路由文件
6.views寄存模板文件
7.index是順序主文件,定義接口,數據庫接口,援用模塊等
8.package.json項目的設置文件,包含項目名,作者,依靠,模塊等

項目用vscode開闢的,用起來很愜意,還沒嘗試過的小夥伴趕忙去試一下吧。
項目初始化:cd myblog1 -> npm init 此時已豎立好了package.json文件了。

由於koa2是輕量級的框架,玲瓏精幹,所以我們為了增進我們的開闢效力和方便性,我們須要裝置一些koa2的模塊中間件:

npm install i koa koa-bodyparser koa-mysql-session koa-router koa-session-minimal koa-static koa-views md5 moment mysql ejs koa-static-cache --save-dev

各模塊用途

koa node框架
koa-bodyparser 表單剖析中間件
koa-mysql-session、koa-session-minimal 處置懲罰數據庫的中間件
koa-router 路由中間件
koa-static 靜態資本加載中間件
ejs 模板引擎
md5 暗碼加密
moment 時候中間件
mysql 數據庫
koa-views 模板顯現中間件
koa-static-cache 文件緩存

項目基礎框架搭建

設置數據庫銜接
在config文件夾新建default.js :

const config = {   
 //啟動端口    
port: 8080,
    //數據庫設置   
 database: {      
  DATABASE: 'nodesql',        
  USERNAME: 'root',       
 PASSWORD: '123456',      
  PORT: '3306',        
HOST: 'localhost'  
  }
}
module.exports = config;  

然後在lib文件夾新建mysql.js:

var mysql = require('mysql');
var config = require('../config/default.js')
//豎立數據庫銜接池
var pool = mysql.createPool({
    host: config.database.HOST,
    user: config.database.USERNAME,
    password: config.database.PASSWORD,
    database: config.database.DATABASE
});
let query = function(sql, values) {    
return new Promise((resolve, reject)=>{
        pool.getConnection(function (err,connection) {
            if(err){      reject(err);
                }else{                
connection.query(sql,values,(err,rows)=>{ 
                   if(err){ 
                       reject(err);
                    }else{
                        resolve(rows); 
                   }                   
 connection.release(); //為每個要求都豎立一個connection運用完后挪用connection.release(); 直接開釋資本。 
                                         //query用來操縱數據庫表
                })
            } 
            }) 
   })}

這裏豎立了一個數據庫銜接池和封裝了一個操縱數據庫表的函數,假如關於數據庫銜接有不懂的話請自行百度。

豎立進口文件

在主目錄新建index.js,即項目進口文件:

const koa = require("koa");   //node框架
const path = require("path");  
const bodyParser = require("koa-bodyparser"); //表單剖析中間件
const ejs = require("ejs");   //模板引擎
const session = require("koa-session-minimal");   //處置懲罰數據庫的中間件
const MysqlStore = require("koa-mysql-session");  //處置懲罰數據庫的中間件
const router = require("koa-router");     //路由中間件
const config = require('./config/default.js');    //引入默許文件
const views = require("koa-views");   //模板顯現中間件
const koaStatic = require("koa-static");  //靜態資本加載中間件
const staticCache = require('koa-static-cache')
const app = new koa();

//session存儲設置,將session存儲至數據庫
const sessionMysqlConfig = {
    user: config.database.USERNAME,
    password: config.database.PASSWORD,
    database: config.database.DATABASE,
    host: config.database.HOST,
}

//設置session中間件
app.use(session({
    key: 'USER_SID',
    store: new MysqlStore(sessionMysqlConfig)
}))

//設置靜態資本加載中間件
app.use(koaStatic(
    path.join(__dirname , './public')
))

//設置服務端模板襯着引擎中間件
app.use(views(path.join(__dirname, './views'),{
    extension: 'ejs'
}))

//運用表單剖析中間件
app.use(bodyParser({
    "formLimit":"5mb",
    "jsonLimit":"5mb",
    "textLimit":"5mb"
}));

//運用新建的路由文件
//登錄
app.use(require('./routers/signin.js').routes())
//註冊
app.use(require('./routers/signup.js').routes())
//退出登錄
app.use(require('./routers/signout.js').routes())
//首頁
app.use(require('./routers/home.js').routes())
//個人主頁
app.use(require('./routers/personal').routes())
//文章頁
app.use(require('./routers/articles').routes())
//資本分享
app.use(require('./routers/share').routes())
//個人日記
app.use(require('./routers/selfNote').routes())
//監聽在8080端口
app.listen(8080) 

console.log(`listening on port ${config.port}`)

上面代碼都有解釋,我就不逐一說清楚明了,由於資本分享和個人日記還沒寫,所以臨時一致share…替換。

接下來向mysql.js增加數據庫操縱語句,建表、增編削查。。。

var users = `create table if not exists users(
    id INT(200) NOT NULL AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    pass VARCHAR(40) NOT NULL,
    avator VARCHAR(100) DEFAULT 'default.jpg', 
    job VARCHAR(40),
    company VARCHAR(40),
    introdu VARCHAR(255),
    userhome VARCHAR(100),
    github VARCHAR(100),
    PRIMARY KEY (id)
);`

var posts = `create table if not exists posts(
    id INT(200) NOT NULL AUTO_INCREMENT,
        name VARCHAR(100) NOT NULL,
        title VARCHAR(100) NOT NULL,
        content TEXT NOT NULL,
        uid INT(200) NOT NULL,
        moment VARCHAR(40) NOT NULL,
        comments VARCHAR(255) NOT NULL DEFAULT '0',
        pv VARCHAR(40) NOT NULL DEFAULT '0',
        likes INT(200) NOT NULL DEFAULT '0',
        type VARCHAR(20) NOT NULL,
        avator VARCHAR(100),
        collection INT(200) NOT NULL DEFAULT '0', 
        PRIMARY KEY (id) ,
        FOREIGN KEY (uid) REFERENCES users(id)
        ON DELETE CASCADE

);`

var comment= `create table if not exists comment(
 id INT(200) NOT NULL AUTO_INCREMENT,
 name VARCHAR(100) NOT NULL,
 content TEXT NOT NULL,
 moment VARCHAR(40) NOT NULL,
 postid INT(200) NOT NULL,
 avator VARCHAR(100),
 PRIMARY KEY ( id ),
 FOREIGN KEY (postid) REFERENCES posts(id)
 ON DELETE CASCADE
);`

var likes = `create table if not exists likes(
    id INT(200) NOT NULL AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    postid INT(200) NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (postid) REFERENCES posts(id)
    ON DELETE CASCADE
);`
 var collection = `create table if not exists collection(
     id INT(200) NOT NULL AUTO_INCREMENT,
     uid VARCHAR(100) NOT NULL,
     postid INT(200) NOT NULL,
     PRIMARY KEY (id),
     FOREIGN KEY (postid) REFERENCES posts(id) 
     ON DELETE CASCADE
 );`
 var follow = `create table if not exists follow(
         id INT(200) NOT NULL AUTO_INCREMENT,
         uid INT(200) NOT NULL,  
         fwid INT(200) NOT NULL DEFAULT '0',
         PRIMARY KEY (id),
         FOREIGN KEY (uid) REFERENCES users(id)
         ON DELETE CASCADE
     )
     `

let createTable = function(sql){
    return query(sql, []);
}

//建表
createTable(users);
createTable(posts);
createTable(comment);
createTable(likes);
createTable(collection);
createTable(follow);
//createTable(follower);
//註冊用戶
let insertData = function(value){
    let _sql = "insert into users(name,pass) values(?,?);"
    return query(_sql,value);
}
//更新頭像
let updateUserImg = function(value){
    let _sql = "update users set avator=? where id=?"
    return query(_sql,value);
}
//更新用戶信息
let updateUser = function(value){
    let _sql = "update users set name=?,job=?,company=?,introdu=?,userhome=?,github=? where id=?"
    return query(_sql,value);
}
//發表文章
let insertPost = function(value){
    let _sql = "insert into posts(name,title,content,uid,moment,type,avator) values(?,?,?,?,?,?,?);"
    return query(_sql,value);
}

//更新文章批評數
let updatePostComment = function(value){
    let _sql = "update posts set comments=? where id=?"
    return query(_sql,value);
}
.......

統共六張表:用戶表、文章表、文章批評表、文章珍藏表、文章點贊表、用戶關注表。

這裏援用了外鍵,然則如今的開闢不引薦運用外鍵了,所以你們可以自行修正,這裡在項目第一次啟動時會湧現數據庫豎立失利(由於外鍵緣由),只需重新啟動就ok了,假如對mysql還不相識的,這裏附送人人一個傳送門:mysql入門視頻教程 暗碼:c2q7 。

前端頁面開闢

項目基礎構造搭建好后,就可以舉行前端頁面的編寫了。用node開闢web時我們平常會合營模板引擎,這個項目我採納的是ejs,除了ejs以外較為經常使用的另有jade,然則jade相對ejs來講的話代碼構造不夠清楚。關於ejs語法,這裏做個簡樸的引見:
header.ejs

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Myblog</title>
        <link rel="stylesheet" href="/css/bootstrap.min.css">
        <link rel="stylesheet" href="/css/index.css">
        <script src="/js/jquery-3.2.1.min.js" type="text/javascript"></script>
        <script src="/js/bootstrap.min.js" type="text/javascript"></script>
      



  • nav.ejs

     </head>
       <body>
       <header class="nav-head">
           <div class="nav container">
               <ul>
                   <li><a href="/home">首頁</a></li>
                   <li> <a href="/share">資本分享</a></li>
                   <li> <a href="/share">引薦</a></li>
                   <li> <a href="/share">個人日記</a></li>
                   <li><a href="/about">關於作者</a></li>
                   <li><input type="text" placeholder="搜刮" class="input-sm search"></li>
                 
                   <% if(session.user){ %>
                       <li>
                           <img src="/images/<%= session.avator %>" alt="" class="img-circle img-title">
                          
                           <ul class="menu">
                               <li class="personal menulist"><a href="/personal/<%= session.user %>">主頁</a></li>
                            <!--   <li class="collection menulist"><a href="#">珍藏集</a></li>
                            -->
                               <li class="menulist"><a href="/articles">寫文章</a></li>
                               <li class="out"><a href="/signout">登出</a></li>
                           </ul>
                       </li>
                       <script>
                             var imgTitle = document.getElementsByClassName('img-title')[0],
              
                             menu = document.getElementsByClassName('menu')[0];
                             imgTitle.onclick = function (event) {
                             showTap();
                             event.stopPropagation();
                               }
                               
                               document.body.addEventListener('click',function (event) { 
                                   menu.style.display = 'none';
                                  // event.stopPropagation();
                                },true)
         
                                 function showTap(){
                                    if(menu.style.display == 'block'){
                                        menu.style.display = 'none';
                                    }else {
                                        menu.style.display = 'block';
                                    }
                               }
                               //退出登錄
                               var signOut = document.getElementsByClassName('out')[0];
                           /*    signOut.onclick = function(){
                                   ajax('get','/signout',null);
                                   xhr.onreadystatechange = function () {
                                 if(xhr.readyState==4&&xhr.status>=200&&xhr.status<300){
                                    let  text = xhr.responseText;    //服務器返回的對象
                               if(text){
                                   window.location.reload = 'localhost:8080/home';
                                    }
                                    
                                }
                            }
    
                               }*/
                       </script>
                       <% }else{ %>
                           <li class="login">
                                   <a class="loginup" href="javascript:;"><span class="glyphicon glyphicon-user"></span> 註冊 | 登錄</a>
                              
                               </li>
                               <% } %>
                 
               </ul>
    
            </div>
       </header>
       <script>
         
       
           var searchInput = document.getElementsByClassName('search')[0];
           searchInput.onfocus = function () {
               this.style.width = "300px";
           }
           searchInput.onblur = function () {
               this.style.width = "180px";
           }
           
       </script>
    
    
    
    
    
  • login.ejs
<div class="sign">
    <a href="javascript:;" title="封閉" class="login-close close">×</a>
    <div class="sign-title">
        <h1>用戶註冊</h1>
        <h3>來吧騷年們!</h3>
    </div>
    <form class="form signup" role="form">

            <div class="form-group">
                <input type="text" name="username" placeholder="賬號不少於兩個字符" class="form-control">
            </div>
        <div class="form-group">
            <input type="password" name="pass" class="pass form-control" placeholder="暗碼">
        </div>
        <div class="form-group">
            <input type="password" name="repeatpass" id="repeat" placeholder="反覆暗碼" class="form-control">
        </div>
            <div class="form-group">
            <input type="button" value="註冊" class="btn btn-primary login-up">
            </div>

    </form>
    <form class="form signin" role="form">
       <div class="form-group">
           <input type="text" name="username" placeholder="請輸入用戶名" class="form-control">
       </div>
       <div class="form-group">
           <input type="password" name="pass" class="pass form-control" placeholder="請輸入暗碼">
       </div>
      <div class="form-group">
          <input type="button" value="登錄" class="btn btn-primary login-in">
      </div>
    </form>
    <div class="form-tips">
        <span>已有賬號?</span>
        <a href="javascript:;" class="register">登錄</a>
    </div>
</div>
<div class="login-form-mask"></div>
<script>
  //  $(document).ready(function () {
        var $close = $('.login-close');
        var $sign = $('.sign');
        $close.click(function () {
            $sign.css("display","none");
        })

        var $register = $('.register'), //login/loginup切換
            $span = $('.form-tips span'),
            $signup = $('.signup'),
            $signTitle = $('.sign-title h1'),
            $signin = $('.signin');

        $register.click(function () {
            if($span.html() == "已有賬號?"){

                $signin.css('display','block');
                $signup.css('display','none');
                $(this).html('註冊');
                $span.html("沒有賬號?");
                $signTitle.html("迎接登錄");

            }else{

                $signin.css('display','none');
                $signup.css('display','block');
                $(this).html('登錄');
                $span.html("已有賬號?");
                $signTitle.html("迎接註冊");
            }
        })

        var $loginup = $('.loginup');   //點擊登錄/註冊,阻撓事宜冒泡
        $loginup.click(function () {
            $mask.fadeIn(100);
            $sign.slideDown(200);
            return false;
        })

        var $close = $('.login-close'),
            $mask = $('.login-form-mask'),
            $sign = $('.sign');

        $sign.click(function () {
            return false;
        })

        $close.click(function (e) {
           // e.stopPropagation();
            fadeOut();

        })

        $(document).click(function (e) { //點擊恣意位置作廢登錄框
            //e.stopPropagation();
            fadeOut();
        })
        function fadeOut(){
            $mask.fadeOut(100);
            $sign.slideUp(200);
        }
    

    var loginUp = document.getElementsByClassName('login-up')[0],
        loginIn = document.getElementsByClassName('login-in')[0],
        signUp = document.getElementsByClassName('signup')[0],
        signIn = document.getElementsByClassName('signin')[0];

    
    
    loginUp.onclick = function () { //註冊
        var data1 = 'username=' + signUp["username"].value + '&' + 'pass='+ signUp["pass"].value + '&' + 'repeatpass=' + signUp["repeatpass"].value;
        var  reg = /^[\u4E00-\u9FA5]{2,5}$/;
      /*  if(!reg.test(signUp["username"].value)){
            signUp["username"].classList.add("tips");
            signUp['username'].value()
        } */
        ajax('post','/signup',data1,"application/x-www-form-urlencoded");
        xhr.onreadystatechange = function () {
            if(xhr.readyState==4&&xhr.status>=200&&xhr.status<300){
             let  text = JSON.parse(xhr.responseText).code;  
             console.log(text)  //服務器返回的對象
               if(text == 3){
                   fadeOut();
                   alert("註冊勝利")
                   setTimeout(()=>{
                    window.location.reload();
                },1000)
               //    document.getElementsByClassName('login')[0].outerHTML = "<li class='users'><a href='/'>"+signUp["username"].value+ "(=^ ^=)" +"</a></li>"
               }else{
                fadeOut();
                alert("用戶已存在")
               }
               
            }
        }

    }

    loginIn.onclick = function () { //登錄
        var data2 = 'username=' + signIn["username"].value + '&' + 'pass=' + signIn["pass"].value;
        ajax('post','/signin',data2,"application/x-www-form-urlencoded");
        xhr.onreadystatechange = function () {
            if(xhr.readyState==4&&xhr.status>=200&&xhr.status<300){
             let  text = JSON.parse(xhr.responseText).code;    //服務器返回的對象
                console.log(text);
                
                 //  document.getElementsByClassName('login')[0].outerHTML = "<li class='users'><a href='/'>"+signUp["username"].value+ "(=^ ^=)" +"</a></li>"
               if(text===1){
                fadeOut();
                // let imgTitle = document.getElementsByClassName('img-title')[0];
                // imgTitle.setAttribute('src','/images/' + JSON.parse(xhr.responseText).avator)
                setTimeout(()=>{
                    window.location.reload();
                },1000)
               }else if(text === 2){
                   alert('暗碼毛病')
               }else{
                   alert('賬號不存在')
               }
               
            }
        }
      
    }
</script>
  • footer.ejs
   </body>
    </html>

header為頁面頭部構造,nav為頁面導航條,login為登錄、註冊內容、footer為頁面頂部構造。可以看到我在ejs文件里有許多的if else 推斷語句,這是依據session來推斷用戶是不是登錄襯着差別的內容。如今我們須要我們的頁面編寫款式:分別是home.css和index.css

為了加強對原生js的明白,在項目里我用了大批的原生ajax(明顯jquery封裝的ajax比較好哈哈),因而這裏先編寫一個原生ajax要求:

  • ajax.js
var xhr = null;
    function ajax(method,url,data,types) {   //封裝一個ajax要領
      //  var text;
        if(window.XMLHttpRequest){
            xhr = new XMLHttpRequest();
        }else if(window.ActiveXObject){
            xhr = new ActiveXObject("Microsoft.XMLHTTP");
        }else {
            alert('你的瀏覽器不支撐ajax');
            return false;
        }
      
        xhr.onerror = function (err) {
            alert("some err have hapened:",err);
        }
        xhr.open(method,url,true);
        if(method=="post"){
            xhr.setRequestHeader("Content-type",types);
         // xhr.setRequestHeader("Conent-Type",'application/json'"application/x-www-form-urlencoded")
        }
        try{
            setTimeout(()=>{
                xhr.send(data);
        },0);
        }catch(err) {
            alert("some error have hapened in font:",err);
        }
        return xhr;
    }

完成登錄註冊

前端基礎頁面開闢好后,我們就可以寫背景登錄接口了:

註冊:signup.js

var router = require('koa-router')();
var userModel = require('../lib/mysql.js');
var md5 = require('md5')

    // 註冊頁面
  
    // post 註冊
router.post('/signup', async(ctx, next) => {
    console.log(ctx.request.body)
    var user = {
        name: ctx.request.body.username,
        pass: ctx.request.body.pass,
        repeatpass: ctx.request.body.repeatpass
    }
    let flag = 0;
    await userModel.findDataByName(user.name)
        .then(result => {
            console.log(result)
            if (result.length) {
               
                    //處置懲罰err
                    console.log('用戶已存在')
                    ctx.body = {
                        code: 1
                    };               
                
            } else if (user.pass !== user.repeatpass || user.pass == '') {
                ctx.body = {        //應把這個邏輯放到前端
                    code: 2
                };

            } else {
                flag = 1;             
                
            }
        })
    if(flag==1){
        let res = await  userModel.insertData([user.name, md5(user.pass + 'asd&$BH&*') ])
       console.log(res.insertId)
        await  userModel.findDataByName(user.name)
        .then((result)=>{
            
          //  var res = JSON.parse(JSON.stringify(result))
            console.log(result[0]['avator'])
            ctx.session.id = res.insertId;
            ctx.session.user=user.name;
            ctx.session.avator = 'default.jpg';
            ctx.body = {
            code: 3
            };
            console.log('註冊勝利')
         })
    }

})

module.exports = router

暗碼採納md5加密,註冊後為用戶豎立session並將其增加到數據庫,寫完別忘了在末了加上module.exports = router將接口暴露出來。

登錄:signin.js

var router = require('koa-router')();
var userModel = require('../lib/mysql.js')
var md5 = require('md5')

router.post('/signin', async(ctx, next) => {
    console.log(ctx.request.body)
    var name = ctx.request.body.username;
    var pass = ctx.request.body.pass;

    await userModel.findDataByName(name)
        .then(result => {

            var res = JSON.parse(JSON.stringify(result))

            if (name === res[0]['name']&&(md5(pass + 'asd&$BH&*') === res[0]['pass'])) {
                    console.log('登錄勝利')
                    ctx.body = {
                        code: 1,
                    }
    
                    ctx.session.user = res[0]['name']
                    ctx.session.id = res[0]['id']
                    ctx.session.avator = res[0]['avator']   

            }else if(md5(pass + 'asd&$BH&*') != res[0]['pass']){
                ctx.body = {
                    code: 2 //暗碼毛病
                }
            }
        }).catch(err => {
            ctx.body = {
                code: 3 //賬號不存在+
            }
            console.log('用戶名或暗碼毛病!')

        })

})

module.exports = router

退出登錄:signout.js

//運用新建的路由文件
//登錄
app.use(require('./routers/signin.js').routes())
//註冊
app.use(require('./routers/signup.js').routes())
//退出登錄
app.use(require('./routers/signout.js').routes())

登錄註冊完成,由於進修忙碌,內容只能一點一點寫了,後續內容延續更新。。。

    原文作者:Jay2017
    原文地址: https://segmentfault.com/a/1190000014737487
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞