2018-1-5 更新教程(新增上傳頭像、新增分頁、樣式改版、發佈文章和評論支持markdown語法)
如今GitHub的代碼結構有變如今GitHub的代碼結構有變,接口名也有變更。
Node+Koa2+Mysql 搭建簡易博客javascript
本篇教程一方面是爲了本身在學習的過程加深記憶,也是總結的過程。另外一方面給對這方面不太瞭解的同窗以借鑑經驗。如發現問題還望指正,
若是你以爲這篇文章幫助到了你,那就賞臉給個star吧,https://github.com/wclimb/Koa...
下一篇多是 Node + express + mongoose 或 zepto源碼系列
感謝您的閱讀^_^
ps:關於markdown代碼縮進問題,看起來不太舒服,但複製到編輯器是正常的喲!html
v8.1.0
v2.3.0
v5.7.0
文中用到了promise、async await等語法,因此你須要一點es6的語法,傳送門固然是阮老師的教程了 http://es6.ruanyifeng.com/ java
若是你已經配置好node和mysql能夠跳過node
常常會有人問報錯的問題,運行出錯大部分是由於不支持async,升級node版本可解決
$ node -v 查看你的node版本,若是太低則去nodejs官網下載替換以前的版本
下載mysql,並設置好用戶名和密碼,默承認覺得用戶名:root,密碼:123456mysql
進入到 bin 目錄下 好比 cd C:\Program Files\MySQL\MySQL Server 5.7\bin
而後開啓mysqljquery
$ mysql -u root -p
輸入密碼以後建立database
(數據庫),nodesql
是咱們建立的數據庫git
$ create database nodesql;
記住sql語句後面必定要跟;
符號,接下來看看咱們建立好的數據庫列表es6
$ show databases;
啓用建立的數據庫github
$ use nodesql;
查看數據庫中的表
$ show tables;
顯示Empty set (0.00 sec)
,由於咱們尚未建表,稍後會用代碼建表
註釋:
這是後面建表以後的狀態
首先咱們建立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
文件緩存在文件夾裏面新建所需文件
咱們新建default.js
文件
const config = { // 啓動端口 port: 3000, // 數據庫配置 database: { DATABASE: 'nodesql', USERNAME: 'root', PASSWORD: '123456', PORT: '3306', HOST: 'localhost' } } module.exports = config
這是咱們所需的一些字段,包括端口和數據庫鏈接所需,最後咱們把它exports暴露出去,以即可以在別的地方使用
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...
關於數據庫的使用這裏介紹一下,首先咱們創建了數據庫的鏈接池,以便後面的操做均可以使用到,咱們建立了一個函數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 | - |
如今感受有點枯燥,那咱們先來實現一下注冊吧
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
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>註冊</title> </head> <body> <div class="container"> <form class="form create" method="post"> <div> <label>用戶名:</label> <input placeholder="請輸入用戶名" type="text" name="name"> </div> <div> <label>密碼:</label> <input placeholder="請輸入密碼" class="password" type="password" name="password"> </div> <div> <label>重複密碼:</label> <input placeholder="請確認密碼" class="repeatpass" type="password" name="repeatpass"> </div> <div> <label>上傳頭像:</label> <input type="file" name="avator" id="avator"> <input type="hidden" id="avatorVal"> <img class="preview" alt="預覽頭像"> </div> <div class="submit">註冊</div> </form> </div> </body> </html>
咱們先安裝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 #ddd; } .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 #ddd; } .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 #ddd; border-radius: 4px; } .cmt_lists:hover{ background: #f2f2f2; } .cmt_lists + .cmt_lists{ border-top: 1px solid #ddd; } .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
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>koa2-blog</title> <link rel="icon" href="http://www.wclimb.site/images/avatar.png"> <link rel="stylesheet" href="/index.css"> <script src="http://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script> <script> function fade(txt){ $('.message').text(txt) $('.message').animate({ top:0 }) setTimeout(function(){ $('.message').animate({ top: '-100%' }) },1500) } $(function(){ $('.signout').click(()=>{ $.ajax({ url: "/signout", type: "GET", cache: false, dataType: 'json', success: function (msg) { if (msg) { fade('登出成功') setTimeout(()=>{ window.location.href = "/posts" },1500) } }, error: function () { alert('異常'); } }) }) }) </script> </head> <body> <header> <div class="user_name"> <% if(session.user){ %> Hello,<%= session.user %> <% } %> <% if(!session.user){ %> 歡迎註冊登陸^_^ <% } %> </div> <div class="message">登陸成功</div> <div class="user_right"> <% if(session.user){ %> <div class="has_user"> <a target="__blank" href="https://github.com/wclimb/Koa2-blog">GitHub</a> <% if(type == 'all'){ %> <a class="active" href="/posts">所有文章</a> <% }else{ %> <a href="/posts">所有文章</a> <% }%> <% if(type == 'my'){ %> <a class="active" href="/posts?author=<%= session.user %>">個人文章</a> <% }else{ %> <a href="/posts?author=<%= session.user %>">個人文章</a> <% }%> <% if(type == 'create'){ %> <a class="active" href="/create">發表文章</a> <% }else{ %> <a href="/create">發表文章</a> <% }%> <span class="signout">登出</span> </div> <% } %> <% if(!session.user){ %> <div class="none_user has_user"> <a target="__blank" href="https://github.com/wclimb/Koa2-blog">GitHub</a> <% if(type == 'all'){ %> <a class="active" href="/posts">所有文章</a> <% }else{ %> <a href="/posts">所有文章</a> <% }%> <% if(type == 'signup'){ %> <a class="active" href="/signup">註冊</a> <% }else{ %> <a href="/signup">註冊</a> <% }%> <% if(type == 'signin'){ %> <a class="active" href="/signin">登陸</a> <% }else{ %> <a href="/signin">登陸</a> <% }%> </div> <% } %> </div> </header>
首先咱們看到用到了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
</body> </html>
修改views/signup.ejs
<%- include("header",{type:'signup'}) %> <div class="container"> <form class="form create" method="post"> <div> <label>用戶名:</label> <input placeholder="請輸入用戶名" type="text" name="name"> </div> <div> <label>密碼:</label> <input placeholder="請輸入密碼" class="password" type="password" name="password"> </div> <div> <label>重複密碼:</label> <input placeholder="請確認密碼" class="repeatpass" type="password" name="repeatpass"> </div> <div> <label>上傳頭像:</label> <input type="file" name="avator" id="avator"> <input type="hidden" id="avatorVal"> <img class="preview" alt="預覽頭像"> </div> <div class="submit">註冊</div> </form> </div> <script> $(window).keyup(function (e) { //console.log(e.keyCode) if (e.keyCode == 13) { $('.submit').click() } }) $('#avator').change(function(){ if (this.files.length != 0) { var file = this.files[0], reader = new FileReader(); if (!reader) { this.value = ''; return; }; console.log(file.size) if (file.size >= 1024 * 1024 / 2) { fade("請上傳小於512kb的圖片!") return } reader.onload = function (e) { this.value = ''; $('form .preview').attr('src', e.target.result) $('form .preview').fadeIn() $('#avatorVal').val(e.target.result) }; reader.readAsDataURL(file); }; }) $('.submit').click(()=>{ // console.log($('.form').serialize()) if ($('input[name=name]').val().trim() == '') { fade('請輸入用戶名!') }else if($('input[name=name]').val().match(/[<'">]/g)){ fade('請輸入合法字符!') }else if($('#avatorVal').val() == ''){ fade('請上傳頭像!') }else{ $.ajax({ url: "/signup", data: { name: $('input[name=name]').val(), password: $('input[name=password]').val(), repeatpass: $('input[name=repeatpass]').val(), avator: $('#avatorVal').val(), }, type: "POST", cache: false, dataType: 'json', success: function (msg) { if (msg.data == 1) { $('input').val('') fade('用戶名存在') } else if (msg.data == 2){ fade('請輸入重複的密碼') } else if(msg.data == 3){ fade('註冊成功') setTimeout(()=>{ window.location.href = "/signin" },1000) } }, error: function () { alert('異常'); } }) } }) </script> <% 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
bodyParse
來解析提交的數據,經過ctx.request.body
獲得msg.data=1
的時候就表明用戶存在,msg.data
出如今後面的signup.ejs
模板ajax請求中咱們使用ajax來提交數據,方便來作成功錯誤的處理
咱們使用的是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爲 <strong>hello</strong>
的時候 <%= code %>
會顯示<strong>hello</strong>
而<%- 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'}) %> <div class="container"> <form class="form create" method="post "> <div> <label>用戶名:</label> <input placeholder="用戶名" type="text" name="name"> </div> <div> <label>密碼:</label> <input placeholder="密碼" type="password" name="password"> </div> <div class="submit">登陸</div> </form> </div> <% 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'}) %> <div class="container"> <form class="form create" method="post "> <div> <label>用戶名:</label> <input placeholder="用戶名" type="text" name="name"> </div> <div> <label>密碼:</label> <input placeholder="密碼" type="password" name="password"> </div> <div class="submit">登陸</div> </form> </div> <script> $(window).keyup(function(e){ //console.log(e.keyCode) if (e.keyCode == 13) { $('.submit').click() } }) $('.submit').click(()=>{ if ($('input[name=name]').val().trim() == '' || $('input[name=password]').val().trim() == '' ) { fade('請輸入用戶名或密碼') }else{ console.log($('.form').serialize()) $.ajax({ url: "/signin", data: $('.form').serialize(), type: "POST", cache: false, dataType: 'json', success: function (msg) { if (!msg) { $('input').val('') fade('用戶名或密碼錯誤') } else{ fade('登陸成功') setTimeout(()=>{ window.location.href = "/posts" },1500) } }, error: function () { alert('異常'); } }) } }) </script> <% 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'}) %> <div class="container"> <form style="width:100%" method="post" class="form create"> <div> <label>標題:</label> <input placeholder="請輸入標題" type="text" name="title"> </div> <div> <label>內容:</label> <textarea placeholder="請輸入內容" name="content" id="" cols="42" rows="10"></textarea> </div> <div class="submit">發表</div> </form> </div> <script> $('.submit').click(()=>{ if ($('input[name=title]').val().trim() == '') { fade('請輸入標題') }else if ($('textarea').val().trim() == '') { fade('請輸入內容') }else{ $.ajax({ url: "/create", data: $('.form').serialize(), type: "POST", cache: false, dataType: 'json', success: function (msg) { if (msg) { fade('發表成功') setTimeout(()=>{ window.location.href="/posts" },1000) }else{ fade('發表失敗') } }, error: function () { alert('異常'); } }) } }) </script> <% 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'}) %> <div class="container"> <ul class="posts"> <% posts.forEach(function(res){ %> <li> <div class="author"> <span title="<%= res.name %>"><a href="/posts?author=<%= res.name %> ">author: <%= res.name %></a></span> <span>評論數:<%= res.comments %></span> <span>瀏覽量:<%= res.pv %></span> </div> <div class="comment_pv"> <span><%= res.moment %></span> </div> <a href="/posts/<%= res.id %>"> <div class="title"> <img class="userAvator" src="images/<%= res.avator %>.png"> <%= res.title %> </div> <div class="content markdown"> <%- res.content %> </div> </a> </li> <% }) %> </ul> <div style="margin-top: 30px" class="pagination" id="page"></div> </div> <script src="http://www.wclimb.site/pagination/pagination.js"></script> <script> pagination({ selector: '#page', totalPage: <%= postsPageLength %>, currentPage: 1, prev: '上一頁', next: '下一頁', first: true, last: true, showTotalPage: true, count: 2//當前頁先後顯示的數量 },function(val){ // 當前頁 $.ajax({ url: "posts/page", type: 'POST', data:{ page: val }, cache: false, success: function (msg) { console.log(msg) if (msg != 'error') { $('.posts').html(' ') $.each(msg,function(i,val){ //console.log(val.content) $('.posts').append( '<li>'+ '<div class=\"author\">'+ '<span title=\"'+ val.name +'\"><a href=\"/posts?author='+ val.name +' \">author: '+ val.name +'</a></span>'+ '<span>評論數:'+ val.comments +'</span>'+ '<span>瀏覽量:'+ val.pv +'</span>'+ '</div>'+ '<div class=\"comment_pv\">'+ '<span>'+ val.moment +'</span>'+ '</div>'+ '<a href=\"/posts/'+ val.id +'\">'+ '<div class=\"title\">'+ '<img class="userAvator" src="images/'+ val.avator +'.png">'+ val.title + '</div>'+ '<div class=\"content\">'+ val.content + '</div>'+ '</a>'+ '</li>' ) }) }else{ alert('分頁不存在') } } }) }) </script> <% include footer %>
如今看看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:''}) %> <div class="container"> <ul class="posts spost"> <li> <div class="author"> <span title="<%= posts.name %>"><a href="/posts?author=<%= posts.name %> ">author: <%= posts.name %></a></span> <span>評論數:<%= posts.comments %></span> <span>瀏覽量:<%= posts.pv %></span> </div> <div class="comment_pv"> <span><%= posts.moment %></span> </div> <a href="/posts/<%= posts.id %>"> <div class="title"> <img class="userAvator" src="../images/<%= posts.avator %>.png"> <%= posts.title %> </div> <div class="content markdown"> <%- posts.content %> </div> </a> <div class="edit"> <% if(session && session.user === posts.name ){ %> <p><a href="<%= posts['id'] %>/edit">編輯</a></p> <p><a class="delete_post">刪除</a></p> <% } %> </div> </li> </ul> </div> <div class="comment_wrap"> <% if(session.user){ %> <form class="form" method="post" action="/<%= posts.id %>"> <textarea id="spContent" name="content" cols="82"></textarea> <div class="submit">發表留言</div> </form> <% } else{ %> <p class="tips">登陸以後才能夠評論喲</p> <% } %> <% if (commentPageLenght > 0) { %> <div class="comment_list markdown"> <% pageOne.forEach(function(res){ %> <div class="cmt_lists"> <div class="cmt_content"> <div class="userMsg"> <img src="../images/<%= res['avator'] %>.png" alt=""><span><%= res['name'] %></span> </div> <div class="cmt_detail"> <%- res['content'] %> </div> <span class="cmt_time"><%= res['moment'] %></span> <span class="cmt_name"> <% if(session && session.user === res['name']){ %> <a class="delete_comment" href="javascript:delete_comment(<%= res['id'] %>);"> 刪除</a> <% } %> </span> </div> </div> <% }) %> </div> <% } else{ %> <p class="tips">尚未評論,趕快去評論吧!</p> <% } %> <div style="margin-top: 30px" class="pagination" id="page"></div> </div> <script src="http://www.wclimb.site/pagination/pagination.js"></script> <script> var userName = "<%- session.user %>" pagination({ selector: '#page', totalPage: <%= commentPageLenght %>, currentPage: 1, prev: '上一頁', next: '下一頁', first: true, last: true, showTotalPage:true, count: 2//當前頁前面顯示的數量 },function(val){ // 當前頁 var _comment = '' $.ajax({ url: "<%= posts.id %>/commentPage", type: 'POST', data:{ page: val }, cache: false, success: function (msg) { //console.log(msg) _comment = '' if (msg != 'error') { $('.comment_list').html(' ') $.each(msg,function(i,val){ //console.log(val.content) _comment += '<div class=\"cmt_lists\"><div class=\"cmt_content\"><div class=\"userMsg\"><img src = \"../images/'+ val.avator +'.png\" ><span>'+ val.name +'</span></div ><div class="cmt_detail">'+ val.content + '</div><span class=\"cmt_time\">'+ val.moment +'</span><span class=\"cmt_name\">'; if (val.name == userName) { _comment += '<a class=\"delete_comment\" href=\"javascript:delete_comment('+ val.id +');\"> 刪除</a>' } _comment += '</span></div></div>' }) $('.comment_list').append(_comment) }else{ alert('分頁不存在') } } }) }) // 刪除文章 $('.delete_post').click(() => { $.ajax({ url: "<%= posts.id %>/remove", type: 'POST', cache: false, success: function (msg) { if (msg.data == 1) { fade('刪除文章成功') setTimeout(() => { window.location.href = "/posts" }, 1000) } else if (msg.data == 2) { fade('刪除文章失敗'); setTimeout(() => { window.location.reload() }, 1000) } } }) }) // 評論 var isAllow = true $('.submit').click(function(){ if (!isAllow) return isAllow = false if ($('textarea').val().trim() == '') { fade('請輸入評論!') }else{ $.ajax({ url: '/' + location.pathname.split('/')[2], data:$('.form').serialize(), type: "POST", cache: false, dataType: 'json', success: function (msg) { if (msg) { fade('發表留言成功') setTimeout(()=>{ isAllow = true window.location.reload() },1500) } }, error: function () { alert('異常'); } }) } }) // 刪除評論 function delete_comment(id) { $.ajax({ url: "<%= posts.id %>/comment/" + id + "/remove", type: 'POST', cache: false, success: function (msg) { if (msg.data == 1) { fade('刪除留言成功') setTimeout(() => { window.location.reload() }, 1000) } else if (msg.data == 2) { fade('刪除留言失敗'); setTimeout(() => { window.location.reload() }, 1500) } }, error: function () { alert('異常') } }) } </script> <% include footer %>
如今點擊單篇文章試試,進入單篇文章頁面,可是編輯、刪除、評論都尚未作,點擊無效,咱們先不作,先實現每一個用戶本身發表的文章列表,咱們以前在 get '/posts' 裏面說先忽略if (ctx.request.querystring) {}裏面的代碼,這裏是作了一個處理,假如用戶點擊了某個用戶,該用戶發表了幾篇文章,咱們須要只顯示該用戶發表的文章,那麼進入的url應該是 /posts?author=xxx ,這個處理在posts.ejs 就已經加上了,就在文章的左下角,做者:xxx就是一個連接。咱們經過判斷用戶來查找文章,繼而有了ctx.request.querystring
獲取到的是:author=xxx
注:這裏咱們處理了,經過判斷 session.user === res['name']
若是不是該用戶發的文章,不能編輯和刪除,評論也是。這裏面也能夠注意一下包裹的<a href=""></a>
標籤
還記得以前在 get '/post' 裏面的代碼嗎?
下面的代碼就是以前說先不處理的代碼片斷,不過這個不用再次添加,以前已經添加好了,這段代碼處理我的發佈的文章列表,咱們是經過selfPosts.ejs模板來渲染的,樣式和所有文章列表同樣,可是牽扯到分頁的問題,
分頁請求的是不一樣的接口地址
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), }) }
修改 selfPost.ejs
<%- include("header",{type:'my'}) %> <div class="container"> <ul class="posts"> <% posts.forEach(function(res){ %> <li> <div class="author"> <span title="<%= res.name %>"><a href="/posts?author=<%= res.name %> ">author: <%= res.name %></a></span> <span>評論數:<%= res.comments %></span> <span>瀏覽量:<%= res.pv %></span> </div> <div class="comment_pv"> <span><%= res.moment %></span> </div> <a href="/posts/<%= res.id %>"> <div class="title"> <img class="userAvator" src="images/<%= res.avator %>.png"> <%= res.title %> </div> <div class="content markdown"> <%- res.content %> </div> </a> </li> <% }) %> </ul> <div style="margin-top: 30px" class="pagination" id="page"></div> </div> <script src="http://www.wclimb.site/pagination/pagination.js"></script> <script> pagination({ selector: '#page', totalPage: <%= postsPageLength %>, currentPage: 1, prev: '上一頁', next: '下一頁', first: true, last: true, showTotalPage: true, count: 2//當前頁先後顯示的數量 },function(val){ // 當前頁 $.ajax({ url: "posts/self/page", type: 'POST', data:{ page: val, name: location.search.slice(1).split('=')[1] }, cache: false, success: function (msg) { //console.log(msg) if (msg != 'error') { $('.posts').html(' ') $.each(msg,function(i,val){ //console.log(val.content) $('.posts').append( '<li>'+ '<div class=\"author\">'+ '<span title=\"'+ val.name +'\"><a href=\"/posts?author='+ val.name +' \">author: '+ val.name +'</a></span>'+ '<span>評論數:'+ val.comments +'</span>'+ '<span>瀏覽量:'+ val.pv +'</span>'+ '</div>'+ '<div class=\"comment_pv\">'+ '<span>'+ val.moment +'</span>'+ '</div>'+ '<a href=\"/posts/'+ val.id +'\">'+ '<div class=\"title\">'+ '<img class="userAvator" src="images/' + val.avator + '.png">' + val.title + '</div>'+ '<div class=\"content\">'+ val.content + '</div>'+ '</a>'+ '</li>' ) }) }else{ alert('分頁不存在') } } }) }) </script> <% include footer %>
評論
修改routers/posts.js
在post.js 後面增長
// 發表評論 router.post('/:postId', async(ctx, next) => { let name = ctx.session.user, content = ctx.request.body.content, postId = ctx.params.postId, res_comments, time = moment().format('YYYY-MM-DD HH:mm:ss'), avator; await userModel.findUserData(ctx.session.user) .then(res => { console.log(res[0]['avator']) avator = res[0]['avator'] }) await userModel.insertComment([name, md.render(content),time, postId,avator]) await userModel.findDataById(postId) .then(result => { res_comments = parseInt(result[0]['comments']) res_comments += 1 }) await userModel.updatePostComment([res_comments, postId]) .then(() => { ctx.body = true }).catch(() => { ctx.body = false }) }) // 評論分頁 router.post('/posts/:postId/commentPage', async function(ctx){ let postId = ctx.params.postId, page = ctx.request.body.page; await userModel.findCommentByPage(page,postId) .then(res=>{ ctx.body = res }).catch(()=>{ ctx.body = 'error' }) })
如今試試發表評論的功能吧,之因此這樣簡單,由於咱們以前就在sPost.ejs作了好幾個ajax的處理,刪除文章和評論也是如此
評論咱們也作了分頁,因此後面會有一個評論的分頁的接口,咱們已經在sPost.ejs裏面寫好了ajax請求
刪除評論
修改routers/posts.js
繼續在post.js 後面增長
// 刪除評論 router.post('/posts/:postId/comment/:commentId/remove', async(ctx, next) => { let postId = ctx.params.postId, commentId = ctx.params.commentId, res_comments; await userModel.findDataById(postId) .then(result => { res_comments = parseInt(result[0]['comments']) //console.log('res', res_comments) res_comments -= 1 //console.log(res_comments) }) await userModel.updatePostComment([res_comments, postId]) await userModel.deleteComment(commentId) .then(() => { ctx.body = { data: 1 } }).catch(() => { ctx.body = { data: 2 } }) })
如今試試刪除評論的功能吧
刪除文章
只有本身發表的文字刪除的文字纔會顯示出來,才能被刪除,
修改routers/posts.js
繼續在post.js 後面增長
// 刪除單篇文章 router.post('/posts/:postId/remove', async(ctx, next) => { let postId = ctx.params.postId await userModel.deleteAllPostComment(postId) await userModel.deletePost(postId) .then(() => { ctx.body = { data: 1 } }).catch(() => { ctx.body = { data: 2 } }) })
如今試試刪除文章的功能吧
編輯文章
修改routers/posts.js
繼續在post.js 後面增長
// 編輯單篇文章頁面 router.get('/posts/:postId/edit', async(ctx, next) => { let name = ctx.session.user, postId = ctx.params.postId, res; await userModel.findDataById(postId) .then(result => { res = result[0] //console.log('修改文章', res) }) await ctx.render('edit', { session: ctx.session, postsContent: res.md, postsTitle: res.title }) }) // post 編輯單篇文章 router.post('/posts/:postId/edit', async(ctx, next) => { let title = ctx.request.body.title, content = ctx.request.body.content, id = ctx.session.id, postId = ctx.params.postId, // 如今使用markdown不須要單獨轉義 newTitle = title.replace(/[<">']/g, (target) => { return { '<': '<', '"': '"', '>': '>', "'": ''' }[target] }), newContent = content.replace(/[<">']/g, (target) => { return { '<': '<', '"': '"', '>': '>', "'": ''' }[target] }); await userModel.updatePost([newTitle, md.render(content), content, postId]) .then(() => { ctx.body = true }).catch(() => { ctx.body = false }) })
修改views/edit.js
<%- include("header",{type:''}) %> <div class="container"> <form style="width:100%" class="form create" method="post"> <div> <label>標題:</label> <input placeholder="標題" type="text" name="title" value="<%- postsTitle %>"> </div> <div> <label>內容:</label> <textarea name="content" id="" cols="42" rows="10"><%= postsContent %></textarea> </div> <div class="submit">修改</div> </form> </div> <script> $('.submit').click(()=>{ $.ajax({ url: '', data: $('.form').serialize(), type: "POST", cache: false, dataType: 'json', success: function (msg) { if (msg) { fade('修改爲功') setTimeout(()=>{ window.location.href="/posts" },1000) } }, error: function () { alert('異常'); } }) }) </script> <% include footer %>
如今試試編輯文字而後修改提交吧
至此一個簡單的blog就已經制做好了,其餘擴展功能相信你已經會了吧!若是出現問題,還望積極提問哈,我會盡快處理的
全部的代碼都在 https://github.com/wclimb/Koa... 裏面,若是以爲不錯就star一下吧。有問題能夠提問喲下一篇多是 Node + express + mongoose 或 zepto源碼系列感謝您的閱讀^_^