Node+Koa2+Mysql 搭建简易博客
亲爱的编程学习爱好者,如果你点开了这篇文章,说明你对《Node+Koa2+Mysql 搭建简易博客》很感兴趣。本篇文章就来给大家详细解析一下,主要介绍一下MySQL、Node.js、javascript、koa.js,希望所有认真读完的童鞋们,都有实质性的提高。
Koa2-blog
2018-1-5 更新教程(新增上传头像、新增分页、样式改版、发布文章和评论支持markdown语法)
现在GitHub的代码结构有变现在GitHub的代码结构有变,接口名也有变动。
Node+Koa2+Mysql 搭建简易博客
预览地址
写在前面
本篇教程一方面是为了自己在学习的过程加深记忆,也是总结的过程。另一方面给对这方面不太了解的同学以借鉴经验。如发现问题还望指正,
如果你觉得这篇文章帮助到了你,那就赏脸给个star吧,https://github.com/wclimb/Koa...
下一篇可能是 Node + express + mongoose 或 zepto源码系列
感谢您的阅读^_^
ps:关于markdown代码缩进问题,看起来不太舒服,但复制到编辑器是正常的哟!
演示效果

开发环境
- nodejs
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}`)
我们使用
// 注册用户 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,就是用户名、密码、头像、注册时间,最后调用
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
<meta charset="UTF-8"><title>注册</title><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>
我们先安装supervisor
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<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><header><div class="user_name"> Hello, 欢迎注册登录^_^ </div> <div class="message">登录成功</div> <div class="user_right"> <div class="has_user"> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq5roGCUgXuytMyero5ko5XFfIfNhNCyr5q5aWO7ZKikx5CBpoNrcqCyzJehlnlw062tfs6B3LKkkrqBZK92gqC0jYqbfaN-a77Qs7GDhp6as3uG3oaVsbOFmIVhu6yKnrSKdW0' rel='nofollow'>GitHub</a> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaeSbIdoyNyzon2rhM--e4aUh6elaZK6m2C8dnmss2uCmn2jpGy0t86kgaybz7Guf9CGurFrkeCbbQ' rel='nofollow'>全部文章</a> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaeSbIdoyNyzon2rhM--e4aUh6elaZK6m2C8dnmss2uCmn2jpGy0t86kgaybz7Guf9CGurFrkeCbbQ' rel='nofollow'>全部文章</a> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaeSbIdoyN2foJiKidTGZ3qdgtDcaYffhWmshnmryGuOqpNrpa7HzJtpl2SN3q6IetuCtr6nnKqjmqurfZ6ua4qcfn2gr7Oqu22CeYjesYiVzZHdrW6HqptkvIZ9Z76zhaqKjY-is9CzbQ' rel='nofollow'>我的文章</a> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaeSbIdoyN2foJiKidTGZ3qdgtDcaYffhWmshnmryGuOqpNrpa7HzJtpl2SN3q6IetuCtr6nnKqjmqurfZ6ua4qcfn2gr7Oqu22CeYjesYiVzZHdrW6HqptkvIZ9Z76zhaqKjY-is9CzbQ' rel='nofollow'>我的文章</a> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycZqTpo-gyaq7on2rhM-tZoPQhqfLsIathWWweYGtso2cmYmzgm20p9FsjoaEmL2uft6St7qjhtN9ZQ' rel='nofollow'>发表文章</a> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycZqTpo-gyaq7on2rhM-tZoPQhqfLsIathWWweYGtso2cmYmzgm20p9FsjoaEmL2uft6St7qjhtN9ZQ' rel='nofollow'>发表文章</a> <span class="signout">登出</span> </div> <div class="none_user has_user"> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq5roGCUgXuytMyero5ko5XFfIfNhNCyr5q5aWO7ZKikx5CBpoNrcqCyzJehlnlw062tfs6B3LKkkrqBZK92gqC0jYqbfaN-a77Qs7GDhp6as3uG3oaVsbOFmIVhu6yKnrSKdW0' rel='nofollow'>GitHub</a> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaeSbIdoyNyzon2rhM--e4aUh6elaZK6m2C8dnmss2uCmn2jpGy0t86kgaybz7Guf9CGurFrkeCbbQ' rel='nofollow'>全部文章</a> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaeSbIdoyNyzon2rhM--e4aUh6elaZK6m2C8dnmss2uCmn2jpGy0t86kgaybz7Guf9CGurFrkeCbbQ' rel='nofollow'>全部文章</a> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaqRkJ2tybunon2rhM-tZoPQhqfLsIathWWweYGtso2cmYmzgm20p9FsjoaEmL2uft6St7qjhtN9ZQ' rel='nofollow'>注册</a> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaqRkJ2tybunon2rhM-tZoPQhqfLsIathWWweYGtso2cmYmzgm20p9FsjoaEmL2uft6St7qjhtN9ZQ' rel='nofollow'>注册</a> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaqRkJ2txrqaon2rhM-tZoPQhqfLsIathWWweYGtso2cmYmzgm20p9FsjoaEmL2uft6St7qjhtN9ZQ' rel='nofollow'>登录</a> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaqRkJ2txrqaon2rhM-tZoPQhqfLsIathWWweYGtso2cmYmzgm20p9FsjoaEmL2uft6St7qjhtN9ZQ' rel='nofollow'>登录</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判断他存不存在,就可以知道用户是否需要登录,如果不存在用户,则只显示
修改views/signup.ejs
<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>
先看我们请求的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,地址是
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位的
- 使用我们之前说的
router.get('/signup',async (ctx,next)=>{ await ctx.render('signup',{ session:ctx.session, }) })
这里就用到了ejs所需的session 我们通过渲染signup.ejs模板,将值ctx.session赋值给session,之后我们就可以在signup.ejs中使用了
ejs的常用标签为:-
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
<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>
修改 index.js 文件 把下面这段代码注释去掉,之前注释是因为我们没有写signin的路由,以免报错,后面还有文章页和登出页的路由,大家记住一下
app.use(require('./routers/signin.js').routes())
现在注册一下来看看效果吧
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加密
我们可以看到登录成功返回的结果是<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>
我们增加了ajax请求,在routers/signin.js里,我们设置如果登录失败就返回false,登录成功返回true
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
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())的注释去掉,现在把注释的路由全部取消注释就对了
然后我们看看
// 发表文章页面 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
<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>
现在看看能不能发表吧
即使我们发表了文章,但是当前我们的posts的页面没有显示,因为还没有获取到数据
我们可以看我们之前写的代码
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后面的代码我们通过
<div class="container"> <ul class="posts"><li> <div class="author"> <span title="<%= res.name %>"><a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaeSbIdoyN2foJiKidTGZ3qdgtDcaYffhWmshnmryKaOqnymbqDHurukgZx40a6hlZSH3Lmyhal9mqurfZ6_gI1gf316aL-30WiOdoDdsmZ7zoXNz22Hupicr6yUnrKzhpx-kIZqvt3RdQ' rel='nofollow'>author: </a></span> <span>评论数:</span> <span>浏览量:</span> </div> <div class="comment_pv"> <span></span> </div> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaeSbIdoyNyepZZ6iJyuiGHQhc2mspK-faXDiYGgsqN5nHqmnWi03LOifauEz757hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Jtt' rel='nofollow'> <div class="title"> <img class="userAvator" src="images/<%=%20res.avator%20%>.png" alt="Node+Koa2+Mysql 搭建简易博客" ></div> <div class="content markdown"> </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 target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaeSbIdoyN2foJiKidTGZ3qdgtDcaYffhWmshnmryIBxqpSBhq3H0KysjoWM3rF3htGSlrVwgrp5p6urfZ6usoabio2KbLKnuKSDhonQsZ52l5HQsbKHupdlsXmFrrNrhap9a45pvt3AooODdKI' rel='nofollow'>author: </a></span> <span>评论数:</span> <span>浏览量:</span> </div> <div class="comment_pv"> <span></span> </div> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaeSbIdoyNyepZZ6iJyuiGHQhc2msJqZfmDFq2Wkv3yNqX18jqW_lrdvfauEz62tf8-St7VthaqCnLGGgp-yo31jiaaGsbS3zW2DeYzfsmZ-3oWVuWqR4IqasYNtcQ' rel='nofollow'> <div class="title"> <img class="userAvator" src="../images/<%=%20posts.avator%20%>.png" alt="Node+Koa2+Mysql 搭建简易博客" ></div> <div class="content markdown"> </div> </a> <div class="edit"> <p><a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ykqOUfayktba7sYF6edvHZ4Pegre6fIHffZqrq36fv42JZH19i6S0t7ijgZx8mL2hft2Ht8tuh62FqrBkfa6ya41hibOTorS0o3U' rel='nofollow'>编辑</a></p> <p><a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq5rqJiUpn-yvpawqJd6iJu-e4fXkru2pZCYfqbEiWGgx6eJn3qmsWi03LtxfoaA3Mehh96Ct7p8gd99mqurfp-_jYlkfX2LpLS3uKOBnHyYvaF-3Ye3y26HrYWqsGR9rrJrjWGJs5OitLSjdQ' rel='nofollow'> 删除</a> </span> </div> </div> </div> <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\" alt="Node+Koa2+Mysql 搭建简易博客" ><span>'+ val.name +'<div class="cmt_detail">'+ val.content + '<span class=\"cmt_time\">'+ val.moment +'<span class=\"cmt_name\">'; if (val.name == userName) { _comment += '<a class=\"delete_comment\" href=\"javascript:delete_comment('+ val.id +');\"> 删除' } _comment += '' }) $('.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>
现在点击单篇文章试试,进入单篇文章页面,但是编辑、删除、评论都还没有做,点击无效,我们先不做,先实现每个用户自己发表的文章列表,我们之前在 get '/posts' 里面说先忽略if (ctx.request.querystring) {}里面的代码,这里是做了一个处理,假如用户点击了某个用户,该用户发表了几篇文章,我们需要只显示该用户发表的文章,那么进入的url应该是 /posts?author=xxx ,这个处理在posts.ejs 就已经加上了,就在文章的左下角,作者:xxx就是一个链接。我们通过判断用户来查找文章,继而有了
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
<div class="container"> <ul class="posts"><li> <div class="author"> <span title="<%= res.name %>"><a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaeSbIdoyN2foJiKidTGZ3qdgtDcaYffhWmshnmryKaOqnymbqDHurukgZx40a6hlZSH3Lmyhal9mqurfZ6_gI1gf316aL-30WiOdoDdsmZ7zoXNz22Hupicr6yUnrKzhpx-kIZqvt3RdQ' rel='nofollow'>author: </a></span> <span>评论数:</span> <span>浏览量:</span> </div> <div class="comment_pv"> <span></span> </div> <a target='_blank' href='https://www.17golang.com/gourl/?redirect=MDAwMDAwMDAwML57hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Juaq6t9nq6ycaeSbIdoyNyepZZ6iJyuiGHQhc2mspK-faXDiYGgsqN5nHqmnWi03LOifauEz757hpSHp6VpkrqbYLx2eayza4KafaOkbLS3zqSBrJvPsa5_0Ia6sWuR4Jtt' rel='nofollow'> <div class="title"> <img class="userAvator" src="images/<%=%20res.avator%20%>.png" alt="Node+Koa2+Mysql 搭建简易博客" ></div> <div class="content markdown"> </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 +''+ '<span>评论数:'+ val.comments +''+ '<span>浏览量:'+ val.pv +''+ ''+ '<div class=\"comment_pv\">'+ '<span>'+ val.moment +''+ ''+ '<a href=\"/posts/'+ val.id +'\">'+ '<div class=\"title\">'+ '<img class="userAvator" src="images/' + val.avator + '.png" alt="Node+Koa2+Mysql 搭建简易博客" >' + val.title + ''+ '<div class=\"content\">'+ val.content + ''+ ''+ '</script>
'
)
})
}else{
alert('分页不存在')
}
}
})
})
编辑文章、删除文章、评论、删除评论
评论
修改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
<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"></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>
现在试试编辑文字然后修改提交吧
结语
至此一个简单的blog就已经制作好了,其他扩展功能相信你已经会了吧!如果出现问题,还望积极提问哈,我会尽快处理的
所有的代码都在 https://github.com/wclimb/Koa... 里面,如果觉得不错就star一下吧。有问题可以提问哟
下一篇可能是 Node + express + mongoose 或 zepto源码系列
感谢您的阅读^_^到这里,我们也就讲完了《Node+Koa2+Mysql 搭建简易博客》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于mysql的知识点!
-

- 上一篇
- 【MySQL】Mysql误操作后使用binlog2sql快速回滚

- 下一篇
- 【踩坑记录】记一次MySQL主从复制延迟的坑
-
- 数据库 · MySQL | 2天前 |
- MySQL设置中文界面,超简单教程来了!
- 332浏览 收藏
-
- 数据库 · MySQL | 2天前 | mysql 索引提示
- MySQL进阶必看!FORCE/USE/IGNOREINDEX用法大揭秘
- 182浏览 收藏
-
- 数据库 · MySQL | 2天前 |
- 手把手教你写MySQL存储过程,小白也能轻松上手
- 163浏览 收藏
-
- 数据库 · MySQL | 2天前 | mysql group by
- MySQL分组查询优化:GROUPBY原理+索引优化超全解析
- 324浏览 收藏
-
- 数据库 · MySQL | 2天前 |
- MySQL设置中文语言,轻松拥有中文界面
- 211浏览 收藏
-
- 数据库 · MySQL | 2天前 |
- MySQL建库语句从入门到精通:创建数据库+设置字符集&排序规则(附实例)
- 176浏览 收藏
-
- 数据库 · MySQL | 2天前 |
- 从零开始学MySQL数据库操作,小白轻松变大神!
- 496浏览 收藏
-
- 数据库 · MySQL | 2天前 |
- MySQL插入日期到时间字段,轻松搞定日期格式
- 484浏览 收藏
-
- 数据库 · MySQL | 2天前 | mysql 数据压缩
- MySQL怎么实现高效压缩存储?表压缩+列式存储详细解读
- 272浏览 收藏
-
- 数据库 · MySQL | 2天前 | mysql JOIN优化
- MySQL优化JOIN操作:七大技巧教你提升关联查询速度
- 106浏览 收藏
-
- 数据库 · MySQL | 2天前 |
- MySQL出现中文乱码?超详细解决方案一次性搞定
- 211浏览 收藏
-
- 数据库 · MySQL | 2天前 |
- MySQL主从复制这样配!搞懂这些参数,replication稳了~
- 131浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 茅茅虫AIGC检测
- 茅茅虫AIGC检测,湖南茅茅虫科技有限公司倾力打造,运用NLP技术精准识别AI生成文本,提供论文、专著等学术文本的AIGC检测服务。支持多种格式,生成可视化报告,保障您的学术诚信和内容质量。
- 26次使用
-
- 赛林匹克平台(Challympics)
- 探索赛林匹克平台Challympics,一个聚焦人工智能、算力算法、量子计算等前沿技术的赛事聚合平台。连接产学研用,助力科技创新与产业升级。
- 51次使用
-
- 笔格AIPPT
- SEO 笔格AIPPT是135编辑器推出的AI智能PPT制作平台,依托DeepSeek大模型,实现智能大纲生成、一键PPT生成、AI文字优化、图像生成等功能。免费试用,提升PPT制作效率,适用于商务演示、教育培训等多种场景。
- 59次使用
-
- 稿定PPT
- 告别PPT制作难题!稿定PPT提供海量模板、AI智能生成、在线协作,助您轻松制作专业演示文稿。职场办公、教育学习、企业服务全覆盖,降本增效,释放创意!
- 55次使用
-
- Suno苏诺中文版
- 探索Suno苏诺中文版,一款颠覆传统音乐创作的AI平台。无需专业技能,轻松创作个性化音乐。智能词曲生成、风格迁移、海量音效,释放您的音乐灵感!
- 61次使用
-
- golang MySQL实现对数据库表存储获取操作示例
- 2022-12-22 499浏览
-
- 搞一个自娱自乐的博客(二) 架构搭建
- 2023-02-16 244浏览
-
- B-Tree、B+Tree以及B-link Tree
- 2023-01-19 235浏览
-
- mysql面试题
- 2023-01-17 157浏览
-
- MySQL数据表简单查询
- 2023-01-10 101浏览