Koa2-blog
2018-1-5 更新教程(新增上传头像、新增分页、样式改版、发布文章和评论支持markdown语法)
现在GitHub的代码结构有变现在GitHub的代码结构有变,接口名也有变动。
Node+Koa2+Mysql 搭建简易博客
预览地址
写在前面
本篇教程一方面是为了自己在学习的过程加深记忆,也是总结的过程。另一方面给对这方面不太了解的同学以借鉴经验。如发现问题还望指正,
如果你觉得这篇文章帮助到了你,那就赏脸给个star吧,https://github.com/wclimb/Koa2-blog
下一篇可能是 Node + express + mongoose 或 zepto源码系列
感谢您的阅读^_^
ps:关于markdown代码缩进问题,看起来不太舒服,但复制到编辑器是正常的哟!
演示效果
开发环境
- nodejs
v8.1.0
- koa
v2.3.0
- mysql
v5.7.0
准备工作
文中用到了promise、async await等语法,所以你需要一点es6的语法,传送门当然是阮老师的教程了 http://es6.ruanyifeng.com/
如果你已经配置好node和mysql可以跳过
经常会有人问报错的问题,运行出错大部分是因为不支持async,升级node版本可解决
$ node -v 查看你的node版本,如果过低则去nodejs官网下载替换之前的版本
下载mysql,并设置好用户名和密码,默认可以为用户名:root,密码:123456
进入到 bin 目录下 比如 cd C:\Program Files\MySQL\MySQL Server 5.7\bin
然后开启mysql
$ mysql -u root -p
输入密码之后创建database
(数据库),nodesql
是我们创建的数据库
$ create database nodesql;
记住sql语句后面一定要跟;
符号,接下来看看我们创建好的数据库列表
$ show databases;
启用创建的数据库
$ use nodesql;
查看数据库中的表
$ show tables;
显示Empty set (0.00 sec)
,因为我们还没有建表,稍后会用代码建表
注释:
这是后面建表之后的状态
目录结构
- config 存放默认文件
- lib 存放操作数据库文件
- middlewares 存放判断登录与否文件
- public 存放样式和头像文件
- routes 存放路由文件
- views 存放模板文件
- index 程序主文件
- package.json 包括项目名、作者、依赖等等
首先我们创建koa2-blog文件夹,然后cd koa2-blog
接着使用 npm init 来创建package.json
接着安装包,安装之前我们使用cnpm安装
$ npm install -g cnpm --registry=https://registry.npm.taobao.org
$ cnpm i koa koa-bodyparser koa-mysql-session koa-router koa-session-minimal koa-static koa-views md5 moment mysql ejs markdown-it chai mocha koa-static-cache --save-dev
各模块用处
koa node
框架koa-bodyparser
表单解析中间件koa-mysql-session
、koa-session-minimal
处理数据库的中间件koa-router
路由中间件koa-static
静态资源加载中间件ejs
模板引擎md5
密码加密moment
时间中间件mysql
数据库markdown-it
markdown语法koa-views
模板呈现中间件chai
mocha
测试使用koa-static-cache
文件缓存
在文件夹里面新建所需文件
首先配置config
我们新建default.js
文件
const config = {
// 启动端口
port: 3000,
// 数据库配置
database: {
DATABASE: 'nodesql',
USERNAME: 'root',
PASSWORD: '123456',
PORT: '3306',
HOST: 'localhost'
}
}
module.exports = config
这是我们所需的一些字段,包括端口和数据库连接所需,最后我们把它exports暴露出去,以便可以在别的地方使用
配置index.js文件
index.js
const Koa = require('koa');
const path = require('path')
const bodyParser = require('koa-bodyparser');
const ejs = require('ejs');
const session = require('koa-session-minimal');
const MysqlStore = require('koa-mysql-session');
const config = require('./config/default.js');
const router=require('koa-router')
const views = require('koa-views')
// const koaStatic = require('koa-static')
const staticCache = require('koa-static-cache')
const app = new Koa()
// session存储配置
const sessionMysqlConfig = {
user: config.database.USERNAME,
password: config.database.PASSWORD,
database: config.database.DATABASE,
host: config.database.HOST,
}
// 配置session中间件
app.use(session({
key: 'USER_SID',
store: new MysqlStore(sessionMysqlConfig)
}))
// 配置静态资源加载中间件
// app.use(koaStatic(
// path.join(__dirname , './public')
// ))
// 缓存
app.use(staticCache(path.join(__dirname, './public'), { dynamic: true }, {
maxAge: 365 * 24 * 60 * 60
}))
app.use(staticCache(path.join(__dirname, './images'), { dynamic: true }, {
maxAge: 365 * 24 * 60 * 60
}))
// 配置服务端模板渲染引擎中间件
app.use(views(path.join(__dirname, './views'), {
extension: 'ejs'
}))
app.use(bodyParser({
formLimit: '1mb'
}))
// 路由(我们先注释三个,等后面添加好了再取消注释,因为我们还没有定义路由,稍后会先实现注册)
//app.use(require('./routers/signin.js').routes())
app.use(require('./routers/signup.js').routes())
//app.use(require('./routers/posts.js').routes())
//app.use(require('./routers/signout.js').routes())
app.listen(3000)
console.log(`listening on port ${config.port}`)
我们使用koa-session-minimal``koa-mysql-session
来进行数据库的操作
使用koa-static
配置静态资源,目录设置为public
使用ejs
模板引擎
使用koa-bodyparser
来解析提交的表单信息
使用koa-router
做路由
使用koa-static-cache
来缓存文件
之前我们配置了default.js,我们就可以在这里使用了
首先引入进来 var config = require(‘./config/default.js’);
然后在数据库的操作的时候,如config.database.USERNAME,得到的就是root。
如果你觉得这篇文章帮助到了你,那就赏脸给个star吧,https://github.com/wclimb/Koa2-blog
配置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;