注:本文是对双越老师的express学习总结,无任何贩卖及收费行为,需要视频课程的请去慕课网自行购买Node.js 从零开发 web server 博客项目-慕课网实战 (imooc.com)
1.不使用框架
1.1 初始环境搭建
- 创建文件夹,npm init初始化npm环境
- 建立bin文件夹,写入www.js文件,即初次执行的文件
- 建立app.js
- npm i nodemon cross-env --save-dev
- 在scripts中加入 dev:cross-env NODE_ENV=dev nodemon ./bin/www.js,此时就可以通过npm run dev来运行了,而且是热启动
const http = require("http");
const serverHandle = require("./app");
const PORT = 8888;
const serve = http.createServer(serverHandle);
serve.listen(PORT);
- app.js
const serverHandle = (req, res) => {
//设置返回格式JSON
res.setHeader("content-type", "application/json");
const resData = {
name: "双越",
};
res.end(JSON.stringify(resData));
};
module.exports = serverHandle;
为什么要把www和app拆分开呢,这是为了做个抽离,app主要是业务配置,www.js 是和server的技术有关系的,是一个基本的功能
1.2 开发接口之初始化路由
由于Node的 http 模块只对HTTP报文的头部进行了解析,然后触发 request 事件。如果请求中还带有内容部分(如 POST 请求,它具有报头和内容),内容部分需要用户自行接收和解析。
所以我们需要在app.js中对query参数及body参数进行处理
// 用于处理 post data
const getPostData = (req) => {
const promise = new Promise((resolve, reject) => {
if (req.method !== 'POST') {
resolve({})
return
}
if (req.headers['content-type'] !== 'application/json') {
resolve({})
return
}
let postData = ''
req.on('data', chunk => {
postData += chunk.toString()
})
req.on('end', () => {
if (!postData) {
resolve({})
return
}
resolve(
JSON.parse(postData)
)
})
})
return promise
}
const serverHandle = (req, res) => {
// 设置返回格式 JSON
res.setHeader('Content-type', 'application/json')
// 获取 path
const url = req.url
req.path = url.split('?')[0]
// 解析 query
req.query = querystring.parse(url.split('?')[1])
//解析body
req.body = postData
}
至此,我们就解析完成了query参数和body参数,此时我们开始写增删改查,写之前我们要把路由和路由处理函数进行拆分
1) 新建src文件夹,新建src/router文件夹,src/router/blog.js,主要是写路由请求;新建controller文件夹,该文件夹中的函数主要是对路由进行处理
2) 写增删改查之前,我们先对返回的函数进行封装,新建src/model/resModel.js
class BaseModel {
constructor(data, message) {
if (typeof data === 'string') {
this.message = data
data = null
message = null
}
if (data) {
this.data = data
}
if (message) {
this.message = message
}
}
}
class SuccessModel extends BaseModel {
constructor(data, message) {
super(data, message)
this.errno = 0
}
}
class ErrorModel extends BaseModel {
constructor(data, message) {
super(data, message)
this.errno = -1
}
}
module.exports = {
SuccessModel,
ErrorModel
}
至此,我们就可以正式的写增删改查了
const handleBlogRouter = (req, res) => {
const method = req.method // GET POST
const id = req.query.id
// 获取博客列表
if (method === 'GET' && req.path === '/api/blog/list') {
let author = req.query.author || ''
const keyword = req.query.keyword || ''
// const listData = getList(author, keyword)
// return new SuccessModel(listData)
if (req.query.isadmin) {
// 管理员界面
const loginCheckResult = loginCheck(req)
if (loginCheckResult) {
// 未登录
return loginCheckResult
}
// 强制查询自己的博客
author = req.session.username
}
const result = getList(author, keyword)
return result.then(listData => {
return new SuccessModel(listData)
})
}
// 获取博客详情
if (method === 'GET' && req.path === '/api/blog/detail') {
// const data = getDetail(id)
// return new SuccessModel(data)
const result = getDetail(id)
return result.then(data => {
return new SuccessModel(data)
})
}
// 新建一篇博客
if (method === 'POST' && req.path === '/api/blog/new') {
// const data = newBlog(req.body)
// return new SuccessModel(data)
const loginCheckResult = loginCheck(req)
if (loginCheckResult) {
// 未登录
return loginCheckResult
}
req.body.author = req.session.username
const result = newBlog(req.body)
return result.then(data => {
return new SuccessModel(data)
})
}
// 更新一篇博客
if (method === 'POST' && req.path === '/api/blog/update') {
const loginCheckResult = loginCheck(req)
if (loginCheckResult) {
// 未登录
return loginCheckResult
}
const result = updateBlog(id, req.body)
return result.then(val => {
if (val) {
return new SuccessModel()
} else {
return new ErrorModel('更新博客失败')
}
})
}
// 删除一篇博客
if (method === 'POST' && req.path === '/api/blog/del') {
const loginCheckResult = loginCheck(req)
if (loginCheckResult) {
// 未登录
return loginCheckResult
}
const author = req.session.username
const result = delBlog(id, author)
return result.then(val => {
if (val) {
return new SuccessModel()
} else {
return new ErrorModel('删除博客失败')
}
})
}
}
module.exports = handleBlogRouter
1.3 接入mysql
新建src/conf/db.js
const env = process.env.NODE_ENV // 环境参数
// 配置
let MYSQL_CONF
let REDIS_CONF
if (env === 'dev') {
// mysql
MYSQL_CONF = {
host: 'localhost',
user: 'root',
password: 'Mysql_2019',
port: '3306',
database: 'myblog'
}
// redis
REDIS_CONF = {
port: 6379,
host: '127.0.0.1'
}
}
if (env === 'production') {
// mysql
MYSQL_CONF = {
host: 'localhost',
user: 'root',
password: 'Mysql_2019',
port: '3306',
database: 'myblog'
}
// redis
REDIS_CONF = {
port: 6379,
host: '127.0.0.1'
}
}
module.exports = {
MYSQL_CONF,
REDIS_CONF
}
新建src/db/mysql.js
const mysql = require('mysql')
const { MYSQL_CONF } = require('../conf/db')
// 创建链接对象
const con = mysql.createConnection(MYSQL_CONF)
// 开始链接
con.connect()
// 统一执行 sql 的函数
function exec(sql) {
const promise = new Promise((resolve, reject) => {
con.query(sql, (err, result) => {
if (err) {
reject(err)
return
}
resolve(result)
})
})
return promise
}
module.exports = {
exec,
escape: mysql.escape
}
以下是博客的增删改查
const getList = (author, keyword) => {
let sql = `select * from blogs where 1=1 `
if (author) {
sql += `and author='${author}' `
}
if (keyword) {
sql += `and title like '%${keyword}%' `
}
sql += `order by createtime desc;`
// 返回 promise
return exec(sql)
}
const getDetail = (id) => {
const sql = `select * from blogs where id='${id}'`
return exec(sql).then(rows => {
return rows[0]
})
}
const newBlog = (blogData = {}) => {
// blogData 是一个博客对象,包含 title content author 属性
const title = xss(blogData.title)
// console.log('title is', title)
const content = xss(blogData.content)
const author = blogData.author
const createTime = Date.now()
const sql = `
insert into blogs (title, content, createtime, author)
values ('${title}', '${content}', ${createTime}, '${author}');
`
return exec(sql).then(insertData => {
// console.log('insertData is ', insertData)
return {
id: insertData.insertId
}
})
}
const updateBlog = (id, blogData = {}) => {
// id 就是要更新博客的 id
// blogData 是一个博客对象,包含 title content 属性
const title = xss(blogData.title)
const content = xss(blogData.content)
const sql = `
update blogs set title='${title}', content='${content}' where id=${id}
`
return exec(sql).then(updateData => {
// console.log('updateData is ', updateData)
if (updateData.affectedRows > 0) {
return true
}
return false
})
}
const delBlog = (id, author) => {
// id 就是要删除博客的 id
const sql = `delete from blogs where id='${id}' and author='${author}';`
return exec(sql).then(delData => {
// console.log('delData is ', delData)
if (delData.affectedRows > 0) {
return true
}
return false
})
}
1.4 登录模块
- 什么是cookie,cookie就是存储在浏览器的一段字符串,最大5kb,且跨域不共享
- 每次发起http请求,就会将请求域的cookie一起发给server
- server可以修改cookie并返回给浏览器
- redis是一个key-value的数据库,速度更快,一般可以把缓存的数据存储在redis中,redis适用于读写比较快,但是不稳定,价格高;mysql稳定,读写慢,价格低
cookie的介绍
js可以操作cookie,浏览器查看cookie,server也可以操作cookie
// 解析 cookie
req.cookie = {}
const cookieStr = req.headers.cookie || '' // k1=v1;k2=v2;k3=v3
cookieStr.split(';').forEach(item => {
if (!item) {
return
}
const arr = item.split('=')
const key = arr[0].trim()
const val = arr[1].trim()
req.cookie[key] = val
})
当解析了cookie之后,我们就可以做简单的登录验证了
比如:如果cookie中有username,我们就证明他有登录权限
// 登录验证的测试
if (method === 'GET' && req.path === '/api/user/login-test') {
if (req.session.username) {
return Promise.resolve(
new SuccessModel({
})
)
}
return Promise.resolve(
new ErrorModel('尚未登录')
)
}
后端如何操作cookie呢?基本步骤如下:
- 后端先校验账号密码是否正确,如果正确,则可以res.setHeader('Set-Cookie',
username=${xxx}) - 此时前端可以在响应头中拿到cookie,然后可以存在本地并且设置cookie,下次再访问就可以携带cookie了,这就有点像token的处理
session的介绍
上边用cookie来进行验证,会存在问题,会暴露字段,我们可以在cookie存储userid,server端对应username
redis的介绍
首先安装redis
const redis = require('redis')
!(async function () {
// 创建客户端
const redisClient = redis.createClient(6379, '127.0.0.1')
// 连接
await redisClient.connect()
.then(() => console.log('redis connect success!'))
.catch(console.error)
// set
await redisClient.set('myname', 'zhangsan123')
// get
const myname = await redisClient.get('myname')
console.log('myname', myname)
// 退出
redisClient.quit()
})()
使用redis
新建src/db/redis.js
const redis = require('redis')
const { REDIS_CONF } = require('../conf/db.js')
// 创建客户端
const redisClient = redis.createClient(REDIS_CONF.port, REDIS_CONF.host)
// 连接数据库,启动之后立刻执行
!(async function () {
await redisClient.connect()
.then(() => console.log('redis connect success!'))
.catch(console.error)
})()
// set
async function set(key, val) {
let objVal
if (typeof val === 'object') {
objVal = JSON.stringify(val)
} else {
objVal = val
}
await redisClient.set(key, objVal)
}
// get
async function get(key) {
try {
let val = await redisClient.get(key)
if (val == null) return val
try {
val = JSON.parse(val) // 尝试转换为 JS 对象
} catch (err) { }
return val
} catch (err) {
throw err
}
}
module.exports = { set, get }