第4章 开发博客项目之接口
系统架构设计的四层抽象:
第一层: www.js [开启 Server]
第二层:app.js [通信设置层]
第三层:router.js [业务逻辑层]
第四层:controller.js [数据层]
4-5 搭建开发环境
mkdir blog-node
cd blog-node
npm init -y
npm i cross-env -D
npm i nodemon -D
1.基本设置代码
blog-node
——app.js
const serverHandle = (req, res) => {
// 设置返回格式 JSON
res.setHeader('Content-type', 'application/json');
const resData = {
name: '双越',
site: 'imooc',
env: process.env.NODE_ENV
};
res.end(JSON.stringify(resData));
};
module.exports = serverHandle;
2.配置环境
blog-node
——package.json
{
"name": "blog-node",
"version": "1.0.0",
"description": "",
"main": "bin/www.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "cross-env NODE_ENV=dev nodemon ./bin/www.js",
"prd": "cross-env NODE_ENV=production nodemon ./bin/www.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"cross-env": "^6.0.3",
"nodemon": "^2.0.1"
}
}
3.创建 Server 的代码
blog-node
——bin
————www.js
const http = require('http');
const PORT = 8000;
const serverHandle = require('../app');
const server = http.createServer(serverHandle);
server.listen(PORT);
npm run dev
4-6 初始化路由
1.拆分路由处理
blog-node
——app.js
const serverHandle = (req, res) => {
// 设置返回格式 JSON
res.setHeader('Content-type', 'application/json');
};
module.exports = serverHandle;
//process.env.NODE_ENV
2.实现5个接口
blog-node
——src
————router
——————blog.js
const handleBlogRouter = (req, res) => {
const method = req.method;
// 获取博客列表
if(method === 'GET' && req.path === '/api/blog/list') {
return {
msg: '这是获取博客列表的接口'
}
}
// 获取博客详情
if(method === 'GET' && req.path === '/api/blog/detail') {
return {
msg: '这是获取博客详情的接口'
}
}
// 新建一篇博客
if(method === 'POST' && req.path === '/api/blog/new') {
return {
msg: '这是新建博客的接口'
}
}
// 更新一篇博客
if(method === 'POST' && req.path === '/api/blog/update') {
return {
msg: '这是更新博客的接口'
}
}
// 删除一篇博客
if(method === 'POST' && req.path === '/api/blog/del') {
return {
msg: '这是删除博客的接口'
}
}
};
module.exports = handleBlogRouter;
3.实现1个接口
blog-node
——src
————router
——————user.js
const handleUserRouter = (req, res) => {
const method = req.method;
// 登录
if(method === 'POST' && req.path === '/api/user/login') {
return {
msg: '这是登录的接口'
}
}
};
module.exports = handleUserRouter;
4.在 app.js 里调用路由
blog-node
——app.js
const handleBlogRouter = require('./src/router/blog');
const handleUserRouter = require('./src/router/user');
const serverHandle = (req, res) => {
// 设置返回格式 JSON
res.setHeader('Content-type', 'application/json');
// 获取 path
const url = req.url;
req.path = url.split('?')[0];
// 处理 blog 路由
const blogData = handleBlogRouter(req, res);
if (blogData) {
res.end(
JSON.stringify(blogData)
)
return
}
// 处理 user 路由
const userData = handleUserRouter(req, res);
if (userData) {
res.end(
JSON.stringify(userData)
)
return
}
// 未命中路由,返回 404
res.writeHead(404, {"Content-type": "text/plain"});
res.write("404 Not Found\n");
res.end();
};
module.exports = serverHandle;
// process.env.NODE_ENV
4-7 开发路由(博客列表路由)
1.开发数据模型
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
}
2.解析 query
blog-node
——app.js
const querystring = require('querystring');
const serverHandle = (req, res) => {
// 设置返回格式 JSON
// ...
// 获取 path
// ...
// 解析 query
req.query = querystring.parse(url.split('?')[1]);
// 处理 blog 路由
// ...
// 处理 user 路由
// ...
// 未命中路由,返回 404
// ...
};
module.exports = serverHandle;
// process.env.NODE_ENV
3.返回格式正确的假数据
controller 只关心数据
blog-node
——src
————controller
——————blog.js
const getList = (author, keyword) => {
return [
{
id: 1,
title: '标题A',
content: '内容A',
createTime: 1546610491112,
author: 'zhangsan'
},
{
id: 2,
title: '标题B',
content: '内容B',
createTime: 1546610524373,
author: 'lisi'
},
]
};
module.exports = {
getList
};
4.在路由里面引用 controller
blog-node
——src
————router
——————blog.js
const { getList } = require('../controller/blog');
const { SuccessModel, ErrorModel} = require('../model/resModel');
const handleBlogRouter = (req, res) => {
const method = req.method;
// 获取博客列表
if(method === 'GET' && req.path === '/api/blog/list') {
const author = req.query.author || '';
const keyword = req.query.keyword || '';
const listData = getList(author, keyword);
return new SuccessModel(listData);
}
// 获取博客详情
// ...
// 新建一篇博客
// ...
// 更新一篇博客
// ...
// 删除一篇博客
// ...
};
module.exports = handleBlogRouter;
4-8 开发路由(博客详情路由)
1.
blog-node
——src
————controller
——————blog.js
const getList = (author, keyword) => {
// ...
};
const getDetail = (id) => {
return {
id: 1,
title: '标题A',
content: '内容A',
createTime: 1546610491112,
author: 'zhangsan'
}
};
module.exports = {
getList,
getDetail
};
2.
blog-node
——src
————router
——————blog.js
const { getList, getDetail } = require('../controller/blog');
const handleBlogRouter = (req, res) => {
const method = req.method;
// 获取博客列表
// ...
// 获取博客详情
if(method === 'GET' && req.path === '/api/blog/detail') {
const id = req.query.id;
const data = getDetail(id);
return new SuccessModel(data);
}
// 新建一篇博客
// ...
// 更新一篇博客
// ...
// 删除一篇博客
// ...
};
module.exports = handleBlogRouter;
3.用 promise 处理Post 接口
files
——a.json
{
"next": "b.json",
"msg": "this is a"
}
——b.json
{
"next": "c.json",
"msg": "this is b"
}
——c.json
{
"next": null,
"msg": "this is c"
}
1.回调地狱
用 callback 方式获取一个文件的内容
const fs = require('fs')
const path = require('path')
function getFileContent(fileName, callback) {
const fullFileName = path.resolve(__dirname, 'files', fileName)
fs.readFile(fullFileName, (err, data) => {
if (err) {
console.error(err)
return
}
callback(
JSON.parse(data.toString())
)
})
}
测试 callback-hell
getFileContent('a.json', aData => {
console.log('a data', aData)
getFileContent(aData.next, bData => {
console.log('b data', bData)
getFileContent(bData.next, cData => {
console.log('c data', cData)
})
})
})
2.介绍Promise,解决回调地狱
用 promise 方式获取文件内容
function getFileContent(fileName) {
const promise = new Promise((resolve, reject) => {
const fullFileName = path.resolve(__dirname, 'files', fileName)
fs.readFile(fullFileName, (err, data) => {
if (err) {
reject(err)
return
}
resolve(
JSON.parse(data.toString())
)
})
})
return promise
}
getFileContent('a.json').then(aData => {
console.log('a data', aData)
return getFileContent(aData.next)
}).then(bData => {
console.log('b data', bData)
return getFileContent(bData.next)
}).then(cData => {
console.log('c data', cData)
})
3.介绍 async await,用同步的方式写异步
用 async await 方式获取文件内容
async function readFileData() {
// 同步写法
try {
const aData = await getFileContent('a.json')
console.log('a data', aData)
const bData = await getFileContent(aData.next)
console.log('b data', bData)
const cData = await getFileContent(bData.next)
console.log('c data', cData)
} catch (err) {
console.error(err)
}
}
readFileData()
// async await 要点:
// 1. await 后面可以追加 promise 对象,获取 resolve 的值
// 2. await 必须包裹在 async 函数里面
// 3. async 函数执行返回的也是一个 promise 对象
// 4. try-catch 截获 promise 中 reject 的值
4-9 开发路由(处理 POSTData)
1.使用 promise 处理 post data
将路由处理的相关函数放到获取到 post 数据之后执行
blog-node
——app.js
// 用于处理 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
// ...
// 获取 path
// ...
// 解析 query
// ...
// 处理 post data
getPostData(req).then(postData => {
req.body = postData;
// 处理 blog 路由
// ...
// 处理 user 路由
// ...
// 未命中路由,返回 404
// ...
});
};
module.exports = serverHandle;
// process.env.NODE_ENV
4-10 开发路由(新建和更新、删除博客路由)
1.
blog-node
——src
————controller
——————blog.js
// ...
const newBlog = (blogData = {}) => {
// blogData 是一个博客对象,包含 title content 属性
console.log('newBlog', id, blogData);
return {
id: 3
}
};
const updateBlog = (id, blogData = {}) => {
// id 就是要更新博客的 id
// blogData 是一个博客对象,包含 title content 属性
console.log('updateBlog', id, blogData);
return true;
};
const delBlog = (id) => {
// id 就是要更新博客的 id
console.log('delBlog', id);
return true;
};
module.exports = {
getList,
getDetail,
newBlog,
updateBlog,
delBlog
};
2.
blog-node
——src
————router
——————blog.js
const { getList, getDetail, newBlog, updateBlog, delBlog } = require('../controller/blog');
const handleBlogRouter = (req, res) => {
const method = req.method;
const id = req.query.id;
// 获取博客列表
// ...
// 获取博客详情
// ...
// 新建一篇博客
if(method === 'POST' && req.path === '/api/blog/new') {
const data = newBlog(req.body);
return new SuccessModel(data);
}
// 更新一篇博客
if(method === 'POST' && req.path === '/api/blog/update') {
const result = updateBlog(id, req.body);
if (result) {
return new SuccessModel();
} else {
return new ErrorModel('更新博客失败');
}
}
// 删除一篇博客
if(method === 'POST' && req.path === '/api/blog/del') {
const result = delBlog(id)
if (result) {
return new SuccessModel()
} else {
return new ErrorModel('删除博客失败')
}
}
};
module.exports = handleBlogRouter;
4-11 开发路由(登录路由)
1.
blog-node
——src
————controller
——————user.js
const login = (username, password) => {
if (username === 'zhangsan' && password === '123') {
return true;
}
return false;
}
module.exports = {
login
};
2.
blog-node
——src
————router
——————user.js
const { login } = require('../controller/user');
const { SuccessModel, ErrorModel} = require('../model/resModel');
const handleUserRouter = (req, res) => {
const method = req.method;
// 登录
if (method === 'POST' && req.path === '/api/user/login') {
const { username, password } = req.body;
const result = login(username, password);
if (result) {
return new SuccessModel();
}
return new ErrorModel('登录失败');
}
}
module.exports = handleUserRouter;
4-12 补充:路由和API
第5章 开发博客项目之数据存储
nodejs web 博客.jpeg nodejs web 博客.jpeguse myblog;
show tables;
select * from blogs;
select * from users;
insert into users (username, `password`, realname) values ('zhangsan', '123', '张三');
insert into users (username, `password`, realname) values ('lisi', '456', '李四');
insert into blogs (title,content,createtime,author) values ('标题A','内容A',1575539061028,'zhangsan');
insert into blogs (title,content,createtime,author) values ('标题B','内容B',1575539103536,'lisi');
insert into blogs (title,content,createtime,author) values ('标题C','内容C',1575539160333,'zhangsan');
5-5 nodejs 链接 mysql 做成工具
npm i mysql --save --registry=https://registry.npm.taobao.org
npm run dev
1.根据环境连接不同的数据库
blog-node
——src
————conf
——————db.js
const env = process.env.NODE_ENV;
let MYSQL_CONF;
if (env === 'dev') {
MYSQL_CONF = {
host: 'localhost',
user: 'root',
password: 'root',
port: '3306',
database: 'myblog'
}
}
if (env === 'production') {
MYSQL_CONF = {
host: 'localhost',
user: 'root',
password: 'root',
port: '3306',
database: 'myblog'
}
}
module.exports = {
MYSQL_CONF
};
2.数据库操作的封装
blog-node
——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)
}else {
resolve(result)
}
})
});
return promise;
};
module.exports = {
exec
};
5-6 API对接mysql(博客列表)
1.连接数据库替换掉假数据
blog-node
——src
————controller
——————blog.js
const { exec } = require('../db/mysql');
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;`;
return exec(sql);
}
2.
blog-node
——src
————router
——————blog.js
// 获取博客列表
if (method === 'GET' && req.path === '/api/blog/list') {
//...
// const listData = getList(author, keyword);
// return new SuccessModel(listData);
const result = getList(author, keyword);
return result.then(listData => {
return new SuccessModel(listData);
});
}
3.
blog-node
——app.js
// 处理 blog 路由
// const blogData = handleBlogRouter(req, res);
// if (blogData) {
// res.end(
// JSON.stringify(blogData)
// );
// return
// }
const blogResult = handleBlogRouter(req, res);
if (blogResult) {
blogResult.then(blogData => {
res.end(
JSON.stringify(blogData)
);
});
return
}
5-7&5-8 API对接mysql(博客详情,增、删、改、查)
1.连接数据库替换掉假数据
blog-node
——src
————controller
——————blog.js
// 获取博客详情
const getDetail = (id) => {
let sql = `select * from blogs where id='${id}'`;
return exec(sql).then(rows => {
return rows[0];
});
};
// 新增一篇博客
const newBlog = (blogData = {}) => {
const { title, content, author } = blogData;
const createtime = Date.now();
let 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 = {}) => {
const { title, content } = blogData;
let 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) => {
let sql = `delete from blogs where id='${id}' and author='${author}'`;
return exec(sql).then(delData => {
if (delData.affectedRows > 0) {
return true;
}
return false;
})
};
2.
blog-node
——src
————router
——————blog.js
// 获取博客详情
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' && path === '/api/blog/new') {
// const data = newBlog(req.body);
// return new SuccessModel(data);
req.body.author = 'zhangsan'; // 假数据, 待开发登陆时再改成真实数据
const result = newBlog(req.body);
return result.then(data => {
return new SuccessModel(data);
});
}
// 更新一篇博客
if(method === 'POST' && req.path === '/api/blog/update') {
const result = updateBlog(id, req.body);
return result.then(val => {
if (val) {
return new SuccessModel();
} else {
return ErrorModel('更新博客失败');
}
})
}
// 删除一篇博客
if(method === 'POST' && req.path === '/api/blog/del') {
const author = 'zhangsan';// 假数据, 待开发登陆时再改成真实数据
const result = delBlog(id, author);
return result.then(val => {
if (val) {
return new SuccessModel();
} else {
return new ErrorModel('删除博客失败');
}
})
}
5-9 API对接mysql(登录)
1.
blog-node
——src
————controller
——————user.js
const { exec } = require('../db/mysql');
const login = (username, password) => {
const sql = `select username, realname from users where username='${username}' and password='${password}';`;
return exec(sql).then(rows => {
return rows[0] || {};
})
}
module.exports = {
login
};
2.
blog-node
——src
————controller
——————user.js
const { login } = require('../controller/user');
const { SuccessModel, ErrorModel} = require('../model/resModel');
const handleUserRouter = (req, res) => {
const method = req.method;
// 登录
if(method === 'POST' && req.path === '/api/user/login') {
const { username, password } = req.body;
const result = login(username, password);
return result.then(data => {
if (data.username) {
return new SuccessModel();
} else {
return new ErrorModel('登录失败');
}
});
}
}
module.exports = handleUserRouter;
3.
blog-node
——app.js
// 处理 user 路由
// const userData = handleUserRouter(req, res)
// if (userData) {
// res.end(
// JSON.stringify(userData)
// )
// return
// }
const userResult = handleUserRouter(req, res);
if (userResult) {
userResult.then(userData => {
res.end(
JSON.stringify(userData)
);
});
return
}
网友评论