Node+Koa2+Mysql 搭建簡易博客

Koa2-blog

2018-1-5 更新教程(新增上傳頭像、新增分頁、樣式改版、發佈文章和評論支持markdown語法)
如今GitHub的代碼結構有變如今GitHub的代碼結構有變,接口名也有變更。

Node+Koa2+Mysql 搭建簡易博客javascript

預覽地址

http://blog.wclimb.sitecss

寫在前面

本篇教程一方面是爲了本身在學習的過程加深記憶,也是總結的過程。另外一方面給對這方面不太瞭解的同窗以借鑑經驗。如發現問題還望指正,
若是你以爲這篇文章幫助到了你,那就賞臉給個star吧,https://github.com/wclimb/Koa...
下一篇多是 Node + express + mongoose 或 zepto源碼系列
感謝您的閱讀^_^
ps:關於markdown代碼縮進問題,看起來不太舒服,但複製到編輯器是正常的喲!html

演示效果

img

開發環境

  • nodejs v8.1.0
  • koa v2.3.0
  • mysql 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;

img

啓用建立的數據庫github

$ use nodesql;

查看數據庫中的表

$ show tables;

顯示Empty set (0.00 sec),由於咱們尚未建表,稍後會用代碼建表
註釋:
這是後面建表以後的狀態
img

目錄結構

img

  • 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

各模塊用處

  1. koa node框架
  2. koa-bodyparser 表單解析中間件
  3. koa-mysql-sessionkoa-session-minimal 處理數據庫的中間件
  4. koa-router 路由中間件
  5. koa-static 靜態資源加載中間件
  6. ejs 模板引擎
  7. md5 密碼加密
  8. moment 時間中間件
  9. mysql 數據庫
  10. markdown-it markdown語法
  11. koa-views 模板呈現中間件
  12. chai mocha 測試使用
  13. koa-static-cache 文件緩存

在文件夾裏面新建所需文件

img

首先配置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

<!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
  • 咱們使用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爲 <strong>hello</strong>的時候 <%= code %> 會顯示<strong>hello</strong>
<%- code %>則顯示加粗的hello

實現登陸頁面

img

修改 /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

img

咱們怎麼查看咱們註冊好的帳號和密碼呢?打開mysql控制檯

$ select * from users;

這樣剛剛咱們註冊的用戶信息都出現了
img

若是你以爲這篇文章幫助到了你,那就賞臉給個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頁面

所有文章

img
修改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 {
                '<': '&lt;',
                '"': '&quot;',
                '>': '&gt;',
                "'": '&#39;'
            }[target]
        }),
        newTitle = title.replace(/[<">']/g, (target) => {
            return {
                '<': '&lt;',
                '"': '&quot;',
                '>': '&gt;',
                "'": '&#39;'
            }[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
img

<%- 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'
        })  
})

單篇文章頁面

img
可是我須要點擊單篇文章的時候,顯示一篇文章怎麼辦呢?

修改 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 {
                '<': '&lt;',
                '"': '&quot;',
                '>': '&gt;',
                "'": '&#39;'
            }[target]
        }),
        newContent = content.replace(/[<">']/g, (target) => {
            return {
                '<': '&lt;',
                '"': '&quot;',
                '>': '&gt;',
                "'": '&#39;'
            }[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源碼系列感謝您的閱讀^_^

相關文章
相關標籤/搜索