摘要:搭建簡易博客預覽地址寫在前面本篇教程一方面是為了自己在學習的過程加深記憶,也是總結的過程。
Koa2-blog
2018-1-5 更新教程(新增上傳頭像、新增分頁、樣式改版、發布文章和評論支持markdown語法)
現在GitHub的代碼結構有變現在GitHub的代碼結構有變,接口名也有變動。
Node+Koa2+Mysql 搭建簡易博客
預覽地址http://blog.wclimb.site
寫在前面本篇教程一方面是為了自己在學習的過程加深記憶,也是總結的過程。另一方面給對這方面不太了解的同學以借鑒經驗。如發現問題還望指正,
如果你覺得這篇文章幫助到了你,那就賞臉給個star吧,https://github.com/wclimb/Koa...
下一篇可能是 Node + express + mongoose 或 zepto源碼系列
感謝您的閱讀^_^
ps:關于markdown代碼縮進問題,看起來不太舒服,但復制到編輯器是正常的喲!
nodejs v8.1.0
koa v2.3.0
mysql v5.7.0
準備工作文中用到了promise、async await等語法,所以你需要一點es6的語法,傳送門當然是阮老師的教程了 http://es6.ruanyifeng.com/
如果你已經配置好node和mysql可以跳過
經常會有人問報錯的問題,運行出錯大部分是因為不支持async,升級node版本可解決
$ node -v 查看你的node版本,如果過低則去nodejs官網下載替換之前的版本
下載mysql,并設置好用戶名和密碼,默認可以為用戶名:root,密碼:123456
進入到 bin 目錄下 比如 cd C:Program FilesMySQLMySQL Server 5.7in
然后開啟mysql
$ mysql -u root -p
輸入密碼之后創建database(數據庫),nodesql是我們創建的數據庫
$ create database nodesql;
記住sql語句后面一定要跟;符號,接下來看看我們創建好的數據庫列表
$ show databases;
啟用創建的數據庫
$ use nodesql;
查看數據庫中的表
$ show tables;
顯示Empty set (0.00 sec),因為我們還沒有建表,稍后會用代碼建表
注釋:
這是后面建表之后的狀態
config 存放默認文件
lib 存放操作數據庫文件
middlewares 存放判斷登錄與否文件
public 存放樣式和頭像文件
routes 存放路由文件
views 存放模板文件
index 程序主文件
package.json 包括項目名、作者、依賴等等
首先我們創建koa2-blog文件夾,然后cd koa2-blog
接著使用 npm init 來創建package.json
接著安裝包,安裝之前我們使用cnpm安裝
$ npm install -g cnpm --registry=https://registry.npm.taobao.org
$ cnpm i koa koa-bodyparser koa-mysql-session koa-router koa-session-minimal koa-static koa-views md5 moment mysql ejs markdown-it chai mocha koa-static-cache --save-dev
各模塊用處
koa node框架
koa-bodyparser 表單解析中間件
koa-mysql-session、koa-session-minimal 處理數據庫的中間件
koa-router 路由中間件
koa-static 靜態資源加載中間件
ejs 模板引擎
md5 密碼加密
moment 時間中間件
mysql 數據庫
markdown-it markdown語法
koa-views 模板呈現中間件
chai mocha 測試使用
koa-static-cache 文件緩存
在文件夾里面新建所需文件
首先配置config我們新建default.js文件
const config = { // 啟動端口 port: 3000, // 數據庫配置 database: { DATABASE: "nodesql", USERNAME: "root", PASSWORD: "123456", PORT: "3306", HOST: "localhost" } } module.exports = config
這是我們所需的一些字段,包括端口和數據庫連接所需,最后我們把它exports暴露出去,以便可以在別的地方使用
配置index.js文件index.js
const Koa = require("koa"); 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 config = require("./config/default.js"); const router=require("koa-router") const views = require("koa-views") // const koaStatic = require("koa-static") const staticCache = require("koa-static-cache") const app = new Koa() // 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(staticCache(path.join(__dirname, "./public"), { dynamic: true }, { maxAge: 365 * 24 * 60 * 60 })) app.use(staticCache(path.join(__dirname, "./images"), { dynamic: true }, { maxAge: 365 * 24 * 60 * 60 })) // 配置服務端模板渲染引擎中間件 app.use(views(path.join(__dirname, "./views"), { extension: "ejs" })) app.use(bodyParser({ formLimit: "1mb" })) // 路由(我們先注釋三個,等后面添加好了再取消注釋,因為我們還沒有定義路由,稍后會先實現注冊) //app.use(require("./routers/signin.js").routes()) app.use(require("./routers/signup.js").routes()) //app.use(require("./routers/posts.js").routes()) //app.use(require("./routers/signout.js").routes()) app.listen(3000) console.log(`listening on port ${config.port}`)
我們使用koa-session-minimal`koa-mysql-session`來進行數據庫的操作
使用koa-static配置靜態資源,目錄設置為public
使用ejs模板引擎
使用koa-bodyparser來解析提交的表單信息
使用koa-router做路由
使用koa-static-cache來緩存文件
之前我們配置了default.js,我們就可以在這里使用了
首先引入進來 var config = require("./config/default.js");
然后在數據庫的操作的時候,如config.database.USERNAME,得到的就是root。
如果你覺得這篇文章幫助到了你,那就賞臉給個star吧,https://github.com/wclimb/Koa...
配置lib的mysql.js文件關于數據庫的使用這里介紹一下,首先我們建立了數據庫的連接池,以便后面的操作都可以使用到,我們創建了一個函數query,通過返回promise的方式以便可以方便用.then()來獲取數據庫返回的數據,然后我們定義了三個表的字段,通過createTable來創建我們后面所需的三個表,包括posts(存儲文章),users(存儲用戶),comment(存儲評論),create table if not exists users()表示如果users表不存在則創建該表,避免每次重復建表報錯的情況。后面我們定義了一系列的方法,最后把他們exports暴露出去。
這里只介紹注冊用戶insertData,后續的可以自行查看,都差不多
// 注冊用戶 let insertData = function( value ) { let _sql = "insert into users set name=?,pass=?,avator=?,moment=?;" return query( _sql, value ) }
我們寫了一個_sql的sql語句,意思是插入到users的表中(在這之前我們已經建立了users表)然后要插入的數據分別是name、pass、avator、moment,就是用戶名、密碼、頭像、注冊時間,最后調用query函數把sql語句傳進去(之前的寫法是"insert into users(name,pass) values(?,?);",換成現在得更容易理解)
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() }) } }) }) } // let query = function( sql, values ) { // pool.getConnection(function(err, connection) { // // 使用連接 // connection.query( sql,values, function(err, rows) { // // 使用連接執行查詢 // console.log(rows) // connection.release(); // //連接不再使用,返回到連接池 // }); // }); // } let users = `create table if not exists users( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL, pass VARCHAR(100) NOT NULL, avator VARCHAR(100) NOT NULL, moment VARCHAR(100) NOT NULL, PRIMARY KEY ( id ) );` let posts = `create table if not exists posts( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL, title TEXT(0) NOT NULL, content TEXT(0) NOT NULL, md TEXT(0) NOT NULL, uid VARCHAR(40) NOT NULL, moment VARCHAR(100) NOT NULL, comments VARCHAR(200) NOT NULL DEFAULT "0", pv VARCHAR(40) NOT NULL DEFAULT "0", avator VARCHAR(100) NOT NULL, PRIMARY KEY ( id ) );` let comment = `create table if not exists comment( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL, content TEXT(0) NOT NULL, moment VARCHAR(40) NOT NULL, postid VARCHAR(40) NOT NULL, avator VARCHAR(100) NOT NULL, PRIMARY KEY ( id ) );` let createTable = function( sql ) { return query( sql, [] ) } // 建表 createTable(users) createTable(posts) createTable(comment) // 注冊用戶 let insertData = function( value ) { let _sql = "insert into users set name=?,pass=?,avator=?,moment=?;" return query( _sql, value ) } // 刪除用戶 let deleteUserData = function( name ) { let _sql = `delete from users where name="${name}";` return query( _sql ) } // 查找用戶 let findUserData = function( name ) { let _sql = `select * from users where name="${name}";` return query( _sql ) } // 發表文章 let insertPost = function( value ) { let _sql = "insert into posts set name=?,title=?,content=?,md=?,uid=?,moment=?,avator=?;" return query( _sql, value ) } // 更新文章評論數 let updatePostComment = function( value ) { let _sql = "update posts set comments=? where id=?" return query( _sql, value ) } // 更新瀏覽數 let updatePostPv = function( value ) { let _sql = "update posts set pv=? where id=?" return query( _sql, value ) } // 發表評論 let insertComment = function( value ) { let _sql = "insert into comment set name=?,content=?,moment=?,postid=?,avator=?;" return query( _sql, value ) } // 通過名字查找用戶 let findDataByName = function ( name ) { let _sql = `select * from users where name="${name}";` return query( _sql) } // 通過文章的名字查找用戶 let findDataByUser = function ( name ) { let _sql = `select * from posts where name="${name}";` return query( _sql) } // 通過文章id查找 let findDataById = function ( id ) { let _sql = `select * from posts where id="${id}";` return query( _sql) } // 通過評論id查找 let findCommentById = function ( id ) { let _sql = `select * FROM comment where postid="${id}";` return query( _sql) } // 查詢所有文章 let findAllPost = function () { let _sql = ` select * FROM posts;` return query( _sql) } // 查詢分頁文章 let findPostByPage = function (page) { let _sql = ` select * FROM posts limit ${(page-1)*10},10;` return query( _sql) } // 查詢個人分頁文章 let findPostByUserPage = function (name,page) { let _sql = ` select * FROM posts where name="${name}" order by id desc limit ${(page-1)*10},10 ;` return query( _sql) } // 更新修改文章 let updatePost = function(values){ let _sql = `update posts set title=?,content=?,md=? where id=?` return query(_sql,values) } // 刪除文章 let deletePost = function(id){ let _sql = `delete from posts where id = ${id}` return query(_sql) } // 刪除評論 let deleteComment = function(id){ let _sql = `delete from comment where id=${id}` return query(_sql) } // 刪除所有評論 let deleteAllPostComment = function(id){ let _sql = `delete from comment where postid=${id}` return query(_sql) } // 查找評論數 let findCommentLength = function(id){ let _sql = `select content from comment where postid in (select id from posts where id=${id})` return query(_sql) } // 滾動無限加載數據 let findPageById = function(page){ let _sql = `select * from posts limit ${(page-1)*5},5;` return query(_sql) } // 評論分頁 let findCommentByPage = function(page,postId){ let _sql = `select * from comment where postid=${postId} order by id desc limit ${(page-1)*10},10;` return query(_sql) } module.exports = { query, createTable, insertData, deleteUserData, findUserData, findDataByName, insertPost, findAllPost, findPostByPage, findPostByUserPage, findDataByUser, findDataById, insertComment, findCommentById, updatePost, deletePost, deleteComment, findCommentLength, updatePostComment, deleteAllPostComment, updatePostPv, findPageById, findCommentByPage }
下面是我們建的表
users ? | posts ? | comment | |
---|---|---|---|
? id ? | ? id ? | ? id ? | |
? name ? | ? name ? | ? name ? | |
? pass ? | ? title ? | ? content ? | |
?avator? ? | content ? ? | ? moment ? | |
? moment? ? | md ? ? | ? postid? | |
? ?- ? | uid ? ? | ? avator ? | |
? ?- ? | moment ? ? | ? -? | |
? ? -? | comments ? ? | ? -? | ? ? |
? ? -? | pv ? ? | ? - ? | ? ? |
? ? -? | ?avator? ? ? | ? -? | ? |
id主鍵遞增
name: 用戶名
pass:密碼
avator:頭像
title:文章標題
content:文章內容和評論
md:markdown語法
uid:發表文章的用戶id
moment:創建時間
comments:文章評論數
pv:文章瀏覽數
postid:文章id
現在感覺有點枯燥,那我們先來實現一下注冊吧
實現注冊頁面routers/singup.js
const router = require("koa-router")(); const userModel = require("../lib/mysql.js"); const md5 = require("md5") const checkNotLogin = require("../middlewares/check.js").checkNotLogin const checkLogin = require("../middlewares/check.js").checkLogin const moment = require("moment"); const fs = require("fs") // 注冊頁面 router.get("/signup", async(ctx, next) => { await checkNotLogin(ctx) await ctx.render("signup", { session: ctx.session, }) }) module.exports = router
使用get方式得到"/signup"頁面,然后渲染signup模板,這里我們還沒有在寫signup.ejs
views/signup.ejs
注冊
我們先安裝supervisor
$ cnpm i supervisor -g
supervisor的作用是會監聽文件的變化,而我們修改文件之后不必去重啟程序
supervisor --harmony index
現在訪問 localhost:3000/signup 看看效果吧。注意數據庫一定要是開啟的狀態,不能關閉
完善注冊功能首先我們來完善一下樣式吧,稍微美化一下
public/index.css
body, header, ul, li, p, div, html, span, h3, a, blockquote { margin: 0; padding: 0; color: #333; } body { margin-bottom: 20px; } ul,li{ list-style-type: none; } a { text-decoration: none; } header { width: 60%; margin: 20px auto; } header:after{ content: ""; clear: both; display: table; } header .user_right{ float: right } header .user_right .active{ color: #5FB878; background: #fff; border: 1px solid #5FB878; box-shadow: 0 5px 5px #ccc; } header .user_name { float: left } .user_name { font-size: 20px; } .has_user a, .has_user span, .none_user a { padding: 5px 15px; background: #5FB878; border-radius: 15px; color: #fff; cursor: pointer; border: 1px solid #fff; transition: all 0.3s; } .has_user a:hover,.has_user span:hover{ color: #5FB878; background: #fff; border: 1px solid #5FB878; box-shadow: 0 5px 5px #ccc; } .posts{ border-radius: 4px; border: 1px solid #ffffd; } .posts > li{ padding: 10px; position: relative; padding-bottom: 40px; } .posts .comment_pv{ position: absolute; bottom: 5px; right: 10px; } .posts .author{ position: absolute; left: 10px; bottom: 5px; } .posts .author span{ margin-right: 5px; } .posts > li:hover { background: #f2f2f2; } .posts > li:hover pre{ border: 1px solid #666; } .posts > li:hover .content{ border-top: 1px solid #fff; border-bottom: 1px solid #fff; } .posts > li + li{ border-top: 1px solid #ffffd; } .posts li .title span{ position: absolute; left: 10px; top: 10px; color: #5FB878; font-size: 14px; } .posts li .title{ margin-left: 40px; font-size: 20px; color: #222; } .posts .userAvator{ position: absolute; left: 3px; top: 3px; width: 40px; height: 40px; border-radius: 5px; } .posts .content{ border-top: 1px solid #f2f2f2; border-bottom: 1px solid #f2f2f2; margin: 10px 0 0 0 ; padding: 10px 0; margin-left: 40px; } .userMsg img{ width: 40px; height: 40px; border-radius: 5px; margin-right: 10px; vertical-align: middle; display: inline-block; } .userMsg span{ font-size: 18px; color:#333; position: relative; top: 2px; } .posts li img{ max-width: 100%; } .spost .comment_pv{ position: absolute; top: 10px; } .spost .edit { position: absolute; right: 20px; bottom: 5px; } .spost .edit p { display: inline-block; margin-left: 10px; } .comment_wrap { width: 60%; margin: 20px auto; } .submit { display: block; width: 100px; height: 40px; line-height: 40px; text-align: center; border-radius: 4px; background: #5FB878; cursor: pointer; color: #fff; float: left; margin-top: 20px ; border:1px solid #fff; } .submit:hover{ background: #fff; color: #5FB878; border:1px solid #5FB878; } .comment_list{ border: 1px solid #ffffd; border-radius: 4px; } .cmt_lists:hover{ background: #f2f2f2; } .cmt_lists + .cmt_lists{ border-top: 1px solid #ffffd; } .cmt_content { padding: 10px; position: relative; border-radius: 4px; word-break: break-all; } .cmt_detail{ margin-left: 48px; } .cmt_content img{ max-width: 100%; } /* .cmt_content:after { content: "#content"; position: absolute; top: 5px; right: 5px; color: #aaa; font-size: 13px; } */ .cmt_name { position: absolute; right: 8px; bottom: 5px; color: #333; } .cmt_name a { margin-left: 5px; color: #1E9FFF; } .cmt_time{ position: absolute; font-size: 12px; right: 5px; top: 5px; color: #aaa } .form { margin: 0 auto; width: 50%; margin-top: 20px; } textarea { width: 100%; height: 150px; padding:10px 0 0 10px; font-size: 20px; border-radius: 4px; border: 1px solid #d7dde4; -webkit-appearance: none; resize: none; } textarea#spContent{ width: 98%; } .tips { margin: 20px 0; color: #ec5051; text-align: center; } .container { width: 60%; margin: 0 auto; } .form img.preview { width:100px; height:100px; border-radius: 50%; display: none; margin-top:10px; } input { display: block; width: 100%; height: 35px; font-size: 18px; padding: 6px 7px; border-radius: 4px; border: 1px solid #d7dde4; -webkit-appearance: none; } input:focus,textarea:focus{ outline: 0; box-shadow: 0 0 0 2px rgba(51,153,255,.2); border-color: #5cadff; } input:hover,input:active,textarea:hover,textarea:active{ border-color: #5cadff; } .create label { display: block; margin: 10px 0; } .comment_wrap form { width: 100%; margin-bottom: 85px; } .delete_comment, .delete_post { cursor: pointer; } .delete_comment:hover, .delete_post:hover, a:hover { color: #ec5051; } .disabled{ user-select: none; cursor: not-allowed !important; } .error{ color: #ec5051; } .success{ color: #1E9FFF; } .container{ width: 60%; margin:0 auto; } .message{ position: fixed; top: -100%; left: 50%; transform: translateX(-50%); padding: 10px 20px; background: rgba(0, 0, 0, 0.7); color: #fff; border-bottom-left-radius: 15px; border-bottom-right-radius: 15px; z-index: 99999; } .markdown pre{ display: block; overflow-x: auto; padding: 0.5em; background: #F0F0F0; border-radius: 3px; border: 1px solid #fff; } .markdown blockquote{ padding: 0 1em; color: #6a737d; border-left: 0.25em solid #dfe2e5; margin: 10px 0; } .markdown ul li{ list-style: circle; margin-top: 5px; }
我們再把模板引擎的header和footer獨立出來
/views/header.ejs
順便引入index.css和jq
koa2-blog <% if(session.user){ %> Hello,<%= session.user %> <% } %> <% if(!session.user){ %> 歡迎注冊登錄^_^ <% } %>
首先我們看到用到了session.user,這個值從哪來呢?請看下面的代碼
// 注冊頁面 router.get("/signup", async(ctx, next) => { await checkNotLogin(ctx) await ctx.render("signup", { session: ctx.session, }) })
我們可以看到我們向模板傳了一個session值,session:ctx.session,這個值存取的就是用戶的信息,包括用戶名、登錄之后的id等,session一般是你關閉瀏覽器就過期了,等于下次打開瀏覽器的時候就得重新登錄了,用if判斷他存不存在,就可以知道用戶是否需要登錄,如果不存在用戶,則只顯示全部文章 注冊 登錄 ,如果session.user存在則有登出的按鈕。
在上面我們會看到我用了另外一個if判斷,判斷type類型,這樣做的目的是比如我們登錄注冊頁面,注冊頁面的導航會高亮,其實就是添加了class:active;
之后我們每個ejs文件的頭部會這樣寫<%- include("header",{type:"signup"}) %> 登錄頁面則是<%- include("header",{type:"signin"}) %>
/views/footer.ejs
修改views/signup.ejs
<%- include("header",{type:"signup"}) %><% include footer %>
先看我們請求的url地址,是"/signup",為什么是這個呢?我們看下面這段代碼(后面有完整的)
router.post("/signup", async(ctx, next) => { //console.log(ctx.request.body) let user = { name: ctx.request.body.name, pass: ctx.request.body.password, repeatpass: ctx.request.body.repeatpass, avator: ctx.request.body.avator } .... }
我們的請求方式是post,地址是/signup所以走了這段代碼,之后會獲取我們輸入的信息,通過ctx.request.body拿到
這里重點就在于ajax提交了,提交之后換回數據 1 2 3,然后分別做正確錯誤處理,把信息寫在error和success中
修改routers/signup.js
const router = require("koa-router")(); const userModel = require("../lib/mysql.js"); const md5 = require("md5") const checkNotLogin = require("../middlewares/check.js").checkNotLogin const checkLogin = require("../middlewares/check.js").checkLogin const moment = require("moment"); const fs = require("fs") // 注冊頁面 router.get("/signup", async(ctx, next) => { await checkNotLogin(ctx) await ctx.render("signup", { session: ctx.session, }) }) // post 注冊 router.post("/signup", async(ctx, next) => { //console.log(ctx.request.body) let user = { name: ctx.request.body.name, pass: ctx.request.body.password, repeatpass: ctx.request.body.repeatpass, avator: ctx.request.body.avator } await userModel.findDataByName(user.name) .then(async (result) => { console.log(result) if (result.length) { try { throw Error("用戶已經存在") } catch (error) { //處理err console.log(error) } // 用戶存在 ctx.body = { data: 1 };; } else if (user.pass !== user.repeatpass || user.pass === "") { ctx.body = { data: 2 }; } else { // ctx.session.user=ctx.request.body.name let base64Data = user.avator.replace(/^data:image/w+;base64,/, ""); let dataBuffer = new Buffer(base64Data, "base64"); let getName = Number(Math.random().toString().substr(3)).toString(36) + Date.now() await fs.writeFile("./public/images/" + getName + ".png", dataBuffer, err => { if (err) throw err; console.log("頭像上傳成功") }); await userModel.insertData([user.name, md5(user.pass), getName, moment().format("YYYY-MM-DD HH:mm:ss")]) .then(res=>{ console.log("注冊成功",res) //注冊成功 ctx.body = { data: 3 }; }) } }) }) module.exports = router
我們使用md5實現密碼加密,長度是32位的
使用我們之前說的bodyParse來解析提交的數據,通過ctx.request.body得到
我們引入了數據庫的操作 findDataByName和insertData,因為之前我們在/lib/mysql.js中已經把他們寫好,并暴露出來了。意思是先從數據庫里面查找注冊的用戶名,如果找到了證明該用戶名已經被注冊過了,如果沒有找到則使用insertData增加到數據庫中
ctx.body 是我們通過ajax提交之后給頁面返回的數據,比如提交ajax成功之后msg.data=1的時候就代表用戶存在,msg.data出現在后面的signup.ejs模板ajax請求中
上傳頭像之前要新建好文件夾,我們ajax發送的是base64的格式,然后使用fs.writeFile來生成圖片
我們使用ajax來提交數據,方便來做成功錯誤的處理
模板引擎ejs我們使用的是ejs,語法可以見ejs
之前我們寫了這么一段代碼
router.get("/signup",async (ctx,next)=>{ await ctx.render("signup",{ session:ctx.session, }) })
這里就用到了ejs所需的session 我們通過渲染signup.ejs模板,將值ctx.session賦值給session,之后我們就可以在signup.ejs中使用了
ejs的常用標簽為:
<% code %>:運行 JavaScript 代碼,不輸出
<%= code %>:顯示轉義后的 HTML內容
<%- code %>:顯示原始 HTML 內容
<%= code %>和<%- code %>的區別在于,<%= code %>不管你寫什么都會原樣輸出,比如code為 hello的時候 <%= code %> 會顯示hello
而<%- code %>則顯示加粗的hello
修改 /routers/signin.js
const router = require("koa-router")(); const userModel = require("../lib/mysql.js") const md5 = require("md5") const checkNotLogin = require("../middlewares/check.js").checkNotLogin const checkLogin = require("../middlewares/check.js").checkLogin router.get("/signin", async(ctx, next) => { await checkNotLogin(ctx) await ctx.render("signin", { session: ctx.session, }) }) module.exports=router
修改 /views/signin.ejs
<%- include("header",{type:"signin"}) %><% include footer %>
修改 index.js 文件 把下面這段代碼注釋去掉,之前注釋是因為我們沒有寫signin的路由,以免報錯,后面還有文章頁和登出頁的路由,大家記住一下
app.use(require("./routers/signin.js").routes())
現在注冊一下來看看效果吧
$ supervisor --harmony index
我們怎么查看我們注冊好的賬號和密碼呢?打開mysql控制臺
$ select * from users;
這樣剛剛我們注冊的用戶信息都出現了
如果你覺得這篇文章幫助到了你,那就賞臉給個star吧,https://github.com/wclimb/Koa...
登錄頁面修改signin
routers/signin.js
const router = require("koa-router")(); const userModel = require("../lib/mysql.js") const md5 = require("md5") const checkNotLogin = require("../middlewares/check.js").checkNotLogin const checkLogin = require("../middlewares/check.js").checkLogin router.get("/signin", async(ctx, next) => { await checkNotLogin(ctx) await ctx.render("signin", { session: ctx.session, }) }) router.post("/signin", async(ctx, next) => { console.log(ctx.request.body) let name = ctx.request.body.name; let pass = ctx.request.body.password; await userModel.findDataByName(name) .then(result => { let res = result if (name === res[0]["name"] && md5(pass) === res[0]["pass"]) { ctx.body = true ctx.session.user = res[0]["name"] ctx.session.id = res[0]["id"] console.log("ctx.session.id", ctx.session.id) console.log("session", ctx.session) console.log("登錄成功") }else{ ctx.body = false console.log("用戶名或密碼錯誤!") } }).catch(err => { console.log(err) }) }) module.exports = router
我們進行登錄操作,判斷登錄的用戶名和密碼是否有誤,使用md5加密
我們可以看到登錄成功返回的結果是result 結果是這樣的一個json數組:id:4 name:rrr pass:...
[ RowDataPacket { id: 4, name: "rrr", pass: "44f437ced647ec3f40fa0841041871cd" } ]
修改views/signin.ejs
signin.ejs
<%- include("header",{type:"signin"}) %><% include footer %>
我們增加了ajax請求,在routers/signin.js里,我們設置如果登錄失敗就返回false,登錄成功返回true
ctx.body = false ctx.body = true
那我們登錄成功后要做跳轉,可以看到window.location.href="/posts"跳轉到posts頁面
全部文章
修改routers/posts.js
posts.js
const router = require("koa-router")(); const userModel = require("../lib/mysql.js") const moment = require("moment") const checkNotLogin = require("../middlewares/check.js").checkNotLogin const checkLogin = require("../middlewares/check.js").checkLogin; const md = require("markdown-it")(); // 重置到文章頁 router.get("/", async(ctx, next) => { ctx.redirect("/posts") }) // 文章頁 router.get("/posts", async(ctx, next) => { let res, postsLength, name = decodeURIComponent(ctx.request.querystring.split("=")[1]); if (ctx.request.querystring) { console.log("ctx.request.querystring", name) await userModel.findDataByUser(name) .then(result => { postsLength = result.length }) await userModel.findPostByUserPage(name,1) .then(result => { res = result }) await ctx.render("selfPosts", { session: ctx.session, posts: res, postsPageLength:Math.ceil(postsLength / 10), }) } else { await userModel.findPostByPage(1) .then(result => { //console.log(result) res = result }) await userModel.findAllPost() .then(result=>{ postsLength = result.length }) await ctx.render("posts", { session: ctx.session, posts: res, postsLength: postsLength, postsPageLength: Math.ceil(postsLength / 10), }) } }) // 首頁分頁,每次輸出10條 router.post("/posts/page", async(ctx, next) => { let page = ctx.request.body.page; await userModel.findPostByPage(page) .then(result=>{ //console.log(result) ctx.body = result }).catch(()=>{ ctx.body = "error" }) }) // 個人文章分頁,每次輸出10條 router.post("/posts/self/page", async(ctx, next) => { let data = ctx.request.body await userModel.findPostByUserPage(data.name,data.page) .then(result=>{ //console.log(result) ctx.body = result }).catch(()=>{ ctx.body = "error" }) }) module.exports = router
修改 index.js
app.use(require("./routers/posts.js").routes())的注釋去掉
修改 views/posts.ejs
<%- include("header",{type:"posts"}) %> posts <% include footer %>
現在看看登錄成功之后的頁面吧
接下來我們實現登出頁面
登出頁面修改 router/signout.js
signout.js
const router = require("koa-router")(); router.get("/signout", async(ctx, next) => { ctx.session = null; console.log("登出成功") ctx.body = true }) module.exports = router
把session設置為null即可
修改 index.js
app.use(require("./routers/posts.js").routes())的注釋去掉,現在把注釋的路由全部取消注釋就對了
然后我們看看 views/header.ejs
我們點擊登出后的ajax 的提交,成功之后回到posts頁面
發表文章修改router/posts
在后面增加
// 發表文章頁面 router.get("/create", async(ctx, next) => { await ctx.render("create", { session: ctx.session, }) }) // post 發表文章 router.post("/create", async(ctx, next) => { let title = ctx.request.body.title, content = ctx.request.body.content, id = ctx.session.id, name = ctx.session.user, time = moment().format("YYYY-MM-DD HH:mm:ss"), avator, // 現在使用markdown不需要多帶帶轉義 newContent = content.replace(/[<">"]/g, (target) => { return { "<": "<", """: """, ">": ">", """: "'" }[target] }), newTitle = title.replace(/[<">"]/g, (target) => { return { "<": "<", """: """, ">": ">", """: "'" }[target] }); //console.log([name, newTitle, content, id, time]) await userModel.findUserData(ctx.session.user) .then(res => { console.log(res[0]["avator"]) avator = res[0]["avator"] }) await userModel.insertPost([name, newTitle, md.render(content), content, id, time,avator]) .then(() => { ctx.body = true }).catch(() => { ctx.body = false }) })
修改 views/create.ejs
create.ejs
<%- include("header",{type:"create"}) %><% include footer %>
現在看看能不能發表吧
即使我們發表了文章,但是當前我們的posts的頁面沒有顯示,因為還沒有獲取到數據
我們可以看我們之前寫的代碼 router.get("/posts", async(ctx, next) => {}路由
if (ctx.request.querystring) { ... }else { await userModel.findPostByPage(1) .then(result => { //console.log(result) res = result }) await userModel.findAllPost() .then(result=>{ postsLength = result.length }) await ctx.render("posts", { session: ctx.session, posts: res, postsLength: postsLength, postsPageLength: Math.ceil(postsLength / 10), }) }
if前面這部分我們先不用管,后面會說。只需要看else后面的代碼我們通過userModel.findPostByPage(1)來獲取第一頁的數據,然后查找所有文章的數量,最后除以10拿到首頁文章的頁數,把數據postsPageLength的值傳給模板posts.ejs。這樣就可以渲染出來了
修改 views/posts.ejs
posts.ejs
<%- include("header",{type:"all"}) %><% include footer %><% posts.forEach(function(res){ %>
- <% }) %>
<%= res.moment %><%= res.title %><%- res.content %>
現在看看posts頁面有沒有文章出現了
我們看到是所第一頁的文章數據,初始化的稍后我們是用服務端渲染的數據,使用了分頁,每頁顯示10條數據,然后通過請求頁數。
下面是服務端請求拿到的第一頁的數據
await userModel.findPostByUserPage(name,1) .then(result => { res = result })
要拿到別的頁面數據的話要向服務器發送post請求,如下
// 首頁分頁,每次輸出10條 router.post("/posts/page", async(ctx, next) => { let page = ctx.request.body.page; await userModel.findPostByPage(page) .then(result=>{ //console.log(result) ctx.body = result }).catch(()=>{ ctx.body = "error" }) })單篇文章頁面
但是我需要點擊單篇文章的時候,顯示一篇文章怎么辦呢?
修改 routers/posts.js
在posts.js后面增加
// 單篇文章頁 router.get("/posts/:postId", async(ctx, next) => { let comment_res, res, pageOne, res_pv; await userModel.findDataById(ctx.params.postId) .then(result => { //console.log(result ) res = result res_pv = parseInt(result[0]["pv"]) res_pv += 1 // console.log(res_pv) }) await userModel.updatePostPv([res_pv, ctx.params.postId]) await userModel.findCommentByPage(1,ctx.params.postId) .then(result => { pageOne = result //console.log("comment", comment_res) }) await userModel.findCommentById(ctx.params.postId) .then(result => { comment_res = result //console.log("comment", comment_res) }) await ctx.render("sPost", { session: ctx.session, posts: res[0], commentLenght: comment_res.length, commentPageLenght: Math.ceil(comment_res.length/10), pageOne:pageOne }) })
現在的設計是,我們點進去出現的url是 /posts/1 這類的 1代表該篇文章的id,我們在數據庫建表的時候就處理了,讓id為主鍵,然后遞增
我們通過userModel.findDataById 文章的id來查找數據庫
我們通過userModel.findCommentById 文章的id來查找文章的評論,因為單篇文章里面有評論的功能
最后通過sPost.ejs模板渲染單篇文章
修改 views/sPost.ejs
sPost.ejs
<%- include("header",{type:""}) %>
<%= posts.moment %><%= posts.title %><%- posts.content %><% if(session.user){ %> <% } else{ %>登錄之后才可以評論喲
<% } %> <% if (commentPageLenght > 0) { %><% pageOne.forEach(function(res){ %><% } else{ %><% }) %>.png" alt=""><%= res["name"] %><%- res["content"] %><%= res["moment"] %> <% if(session && session.user === res["name"]){ %> );"> 刪除 <% } %>還沒有評論,趕快去評論吧!
<% } %>