在學習了koa2和express並寫了一些demo後,打算本身寫一個項目練練手,因爲是在校生,沒什麼好的項目作,即以開發一個前端論壇爲目標,功能需求參照一下一些社區擬定,主要有:javascript
做者我的日記
but。。。。因爲種種緣由,目前僅實現了部分功能,資源分享還沒寫
項目運行效果:http://120.77.211.212/homecss
項目技術棧應用:node-koa2-ejs-bootstrap3—jquery, github地址:https://github.com/Jay214/myb...,若是以爲對你有幫助或者還看得下去,歡迎star~~鼓勵鼓勵我這前端渣渣輝。html
node: v8.3.0前端
koa: ^2.4.1java
mysql: 5.7.1node
npm: 5.3.0及以上mysql
將項目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。jquery
因爲koa2是基於es6的promise和es7的await/async語法,因此若是對es6/es7不懂的話請先過一遍文檔,後臺搭建數據庫是關鍵,因此請先安裝好mysql,mysql建議安裝5.7版本如下的,由於5.7.0版本有個bug,須要更改配置文件,具體若大家安裝的時候便知道了。git
安裝node環境,使用node -v查看node版本,node須要較新版本可以支持es6的promise和es7的await/async語法,如今node版本都會自帶npm的,因此不須要再去安裝npm。es6
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>
<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>
</body> </html>
header爲頁面頭部結構,nav爲頁面導航條,login爲登陸、註冊內容、footer爲頁面頂部結構。能夠看到我在ejs文件裏有不少的if else 判斷語句,這是根據session來判斷用戶是否登陸渲染不一樣的內容。如今咱們須要咱們的頁面編寫樣式:分別是home.css和index.css
爲了加強對原生js的理解,在項目裏我用了大量的原生ajax(顯然jquery封裝的ajax比較好哈哈),所以這裏先編寫一個原生ajax請求:
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())
登陸註冊完成,因爲學習繁忙,內容只能一點一點寫了,後續內容持續更新。。。