笔记目录
前言
本章的学习内容:node.js模块发开发,
1. 学生数据修改接口
接口地址:/api/student/update
请求方式:post
参数:id(必须)
name / nickname / gender / age / score / className / avatar(可以为空,不写的参数就是之前的旧数据)
分析:先设置设置中间件,拿到post的参数,1.判断id是否传递,不存在返回400 提示客户端输入id,2.通过id去数据库找数据,如果返回值的长度是0,提示id不存在,3.当查到信息定义一个对象去接收 results[0](把旧数据存起来),用或的短路运算设置属性的默认值为旧值,4.把数据通过updat set SQL语句更新到数据库 。
const express = require('express');
const app = express();
const mysql = require('mysql');
const connection = mysql.createConnection({
host: '127.0.0.1',
user: 'root',
password: 'root',
database: 'student'
})
app.use(express.urlencoded({extended:false}));
app.post('/api/student/update',(req,res)=>{
const newStu = req.body;
if (!newStu.id) return res.status(400).send({ code: 400, msg: 'id必须要传递' });
connection.query(`select * from student where id=${newStu.id}`,(error,results)=>{
if(error) return res.status(500).send({code: 500, msg: '服务器内部错误', error});
if (results.length == 0) return res.status(400).send({ code: 400, msg: 'id不存在' });
//id存
const oldStu = results[0]; //oldStu是数据库中原来的数据在
console.log(results[0]);
newStu.name = newStu.name || oldStu.name;
newStu.nickname = newStu.nickname || oldStu.nickname;
newStu.age = newStu.age || oldStu.age;
newStu.gender = newStu.gender || oldStu.gender;
newStu.score = newStu.score || oldStu.score;
newStu.className = newStu.className || oldStu.className;
newStu.avatar = newStu.avatar || oldStu.avatar;
connection.query(`update student set name='${newStu.name}',nickname='${newStu.nickname}',
age=${newStu.age}, gender='${newStu.gender}', score=${newStu.score},
className='${newStu.className}', avatar='${newStu.avatar}' where id=${newStu.id}`,(error,results)=>{
if(error) return res.status(500).send({code: 500, msg: '服务器内部错误', error});
res.status(200).send({
code: 200,
msg: '修改成功'
})
})
});
}).listen(80,()=>console.log('服务开启成功'));
2. 模块化开发
node中模块类型的划分:
- 内置模块(node自身提供,不需要引用,直接引用 fs path http…)
- 第三方模块 (由社区或个人提供,需要npm安装引入 express)
- 自定义模块 (自己定义的模块 文件模块 一个文件也可以被认为是1个模块)
模块化开发的好处:
- 每个模块都是一个独立的作用域,在这个而文件中定义的变量、函数、对象都是私有的,对其他文件不可见,不会出现命名问题。
- 利于团队开发
- 利于后期维护
- …
2.1 require函数
require函数是一个同步函数(会按顺序加载引入模块)
require函数的参数:
模块的名字 内置模块 第3方模块.
文件模块的路径
- 如果文件模块是一个json文件. 会将json文件中的数据转换为js对象
- 如果文件模块是一个.js文件. 会将这个.js文件中的代码执行一遍.
如果require函数的参数是1个文件模块,那么这个文件模块的后缀名可以省略(建议别省略)
使用require函数加载1个文件模块,实际上是将这个文件模块中的代码执行一遍
当require函数的参数是1个模块名称的时候 如何加载
- 判断这个模块是不是一个内置模块. 如果是内置模块 直接加载.
如果不是内置模块,就认为这是1个第3方模块.- 先在当前文件夹下去查找是否有 node_modules这个文件夹.
如果有,就进去找是否有这个第3方模块.- 就会去上一级目录查找 是否有 node_modules这个文件夹.
- 再去上1级目录. 直到找到根目录,如果都没有 就报错.
第3方模块找到之后,如何加载
- 先判断文件夹下是否有package.json.
如果没有,就直接执行 index.js index.json index.node- 如果有package.json文件
查找main属性: 如果有指定入口文件 就去执行入口文件.
如果没有main属性: index.js index.json index.node
Node.js的模块化遵守的CommonJS的模块化规范
(现在用的比较多的ES6的模块化)
2.2 module.exports
在文件模块中,有个全局变量叫做module.exports ,它的值,决定了加载这个文件模块的时候,向外界暴露的数据
使用require函数加载1个文件模块,会得到1个返回值,这个返回值就是这个文件模块中module.exports的值
文件模块中的module.exports它的默认值是1个空对象 { }
所以,在一个文件模块中 如果我们没有修改module.exports的值。,
那么我们使用require函数加载它 得到的返回值就是1个空对象.
文件模块中还有1个全局的变量: exports,使用它也能向外界暴露数据
文件模块向外界暴露数据,暴露的是module.exports,默认情况下,exports和module.exports指向的是同1个对象,如果修改了module.exports指向的对象. 这个时候通过exports就没有用了
简单理解就是在一个js文件内封装自定义模块,要通过module.exports暴露出去(默认值是空对象),然后那个地方要引用组件就require(‘组件的js文件地址’)引入组件。
3. 学生信息接口的模块化开发
前面写的这个路由接口,是所有操作都写在一个页面里的,接下来可以通过模块化开发对其进行优化。
3.1 定义一个配置项模块
用于存放msql连接参数,分页接口的pageSize参数
module.exports = {
port: 80, //代表web服务运行的端口
dbConfig: { //mysql模块的连接配置信息
host: '127.0.0.1',
user: 'root',
password: 'root',
database: 'dbstudy'
},
pageSize: 5 //每一页的数据量
}
主页 index.js
//1. 导入模块
const express = require('express');
const config = require('./config.js');
//2. 创建应用
const app = express();
//注册中间件 - 解析post传递的数据.
app.use(express.urlencoded({extended: false}));
//返回值就是文件模块暴露出来的路由对象,
//这个路由对象上就已经注册好了路由了.
const stuRouter = require('./router/stuRouter.js');
//将返回的路由对象,挂载到app
// 所有的URL都到stuRouter中进行路由匹配.
app.use('/api/student', stuRouter); // -> (req, res) => { }
//4. 开启监听
app.listen(config.port, () => console.log('服务启动成功'));
3.2 定义路由模块
之前是所有的配置路由都写在起始文件index.js内,定义路由模块,把所有的路由放在自定义模块中,在index.js页面用app.use()方法挂载这些路由接口。
- stuRoter模块
//这个模块: 路由模块
//作用: 注册路由. 只需要做注册路由.
/*
express五大对象
1. express
2. application
3. request
4. response
5. router对象: 专门用来做路由注册,并且只能做路由注册.
我们发现,这个路由模块并不纯粹 因为它除了做路由匹配,还做的事情是逻辑的处理.
handler 处理
*/
const express = require('express');
const stuHandler = require('../handler/stuHandler.js');
//调用express的Router方法,就能得到这个router对象.
//这个时候,就可以使用这个router对象来进行路由的注册.
const router = express.Router();
//使用router注册路由的方法和我们之前讲的app注册路由的方法一样.
// /api/student/api/student/list
router.get('/list', stuHandler.list)
router.get('/query', stuHandler.query);
router.post('/add', stuHandler.add);
router.get('/delete', stuHandler.delete);
router.post('/update', stuHandler.update);
module.exports = router;
//当前这个文件模块执行完毕之后,router身上就已经注册好了路由
// 并且将其暴露出去了.
3.3 路由处理方法模块
每个接口都有不同的需求和处理,把这些处理方法封装成一个处理方法模块
- stuHandler模块
//逻辑处理模块.
const stuDb = require('../databse/stuDb.js');
module.exports.list = (req, res) => {
//0. 将客户端通过URL传递到服务端的q page参数取出来.
let { q, page } = req.query;
// 判断1下,page是否有传递 如果page没有传值 那么默认值就设置为1
page = page || 1; //2
// 定义1个变量 表示页容量.
const pageSize = 5;
//如果q有值,就意味着sql语句的后面要加1个where条件.
//1. 从数据库中将学生表中的所有数据查询出来返回.
//1.2 执行sql语句
// q=花&page=1
// 当前接口
// q page参数
// 都是可选的.
// q 没有值,查询所有 有值就查询满足条件的.
/*
1. 如果q参数没有值: select * from student limit (page-1)*size, size
2. 如果q参数有值: select * from student where name like '%q%' or nickname like '%q%' limit (page-1)*size, size
1. 查询所有的数据并分页.
2. 根据关键字查询数据并分页.
*/
if (q) {
//如果q有值.
stuDb.getAllStudentByPageAndQ(page, q, results => {
res.send({
code: 200,
count: results.length,
data: results
})
}, error => {
return res.status(500).send({ code: 500, msg: '服务器内部错误', error });
});
} else {
//q没有值,要去查询所有的学生
stuDb.getAllStudentByPage(page, results => {
res.send({
code: 200,
count: results.length,
data: results
})
}, error => {
return res.status(500).send({ code: 500, msg: '服务器内部错误', error });
});
}
}
module.exports.query = (req, res) => {
//1. 从客户端的URL中取出传递的id参数
const { id } = req.query; //5
if (!id) return res.status(400).send({ code: 400, msg: '参数错误' });
//2. 去数据库中查询出id为传入的id的记录.
stuDb.getStudentById(id, results => {
//查询成功
//判断查询到的结果是否有数据
if (results.length == 0) return res.status(404).send({ code: 404, msg: 'id不存在' });
res.send({ code: 200, data: results[0] });
}, error => {
res.status(500).send({ code: 500, msg: '服务器内部错误', error });
})
}
module.exports.add = (req, res) => {
//1. 先从post请求中取出传递的数据
const stu = req.body;
// 对数据进行一些校验.
// name nickname gender这三个数据是必须要传递,否则就报400
if (!stu.name || !stu.nickname || !stu.gender) return res.status(400).send({ code: 400, msg: '参数错误' });
//3个必传的数据都有了.
//判断1下其它的参数是否有传递,如果有传递,就是它传递的值, 如果没有传递我们就设置1个默认值.
stu.age = stu.age || 18;
stu.score = stu.score || 60;
stu.className = stu.className || '深圳黑马前端就业班第71期';
stu.avatar = stu.avatar || 'default_avatar.png';
//调用数据模块的方法新增数据
stuDb.add(stu, results => {
res.status(201).send({
code: 201,
msg: '新增成功',
insertId: results.insertId
});
}, error => {
res.status(500).send({ code: 500, msg: '服务器内部错误', error });
})
}
//当URL是 /delete 的时候,对应的逻辑处理就是交给这个方法。
module.exports.delete = (req, res) => {
//1. 取出客户端通过URL传递到服务端的id
const { id } = req.query;
//2. 判断id是否有传递
if (!id) return res.status(400).send({ code: 400, msg: '参数错误' });
//3. 调用数据模块的方法 删除学员
stuDb.deleteStudentById(id, results => {
//成功的时候执行
//1. 判断1下受影响的行数
if (results.affectedRows == 0)
return res.status(404).send({ code: 404, msg: 'id不存在' });
res.send({ code: 200, msg: '删除成功' });
}, error => {
res.status(500).send({ code: 500, msg: '服务器内部错误', error });
});
}
module.exports.update = (req, res) => {
//1. 取出客户端通过post传递到服务端的数据.
const newStu = req.body;
//2. 判断id有没有传递
if (!newStu.id) return res.status(400).send({ code: 400, msg: 'id必须要传递' });
//3. 再判断哪些数据传递了 传了就使用新值 没有传就使用原来的值.
//3.1 先去数据库中 查询出本身的数据
/// 根据id去数据库中查询数学生信息
stuDb.getStudentById(newStu.id, results => {
//查询成功
if (results.length == 0) return res.status(400).send({ code: 400, msg: 'id不存在' });
//id存
const oldStu = results[0]; //oldStu是数据库中原来的数据在
newStu.name = newStu.name || oldStu.name;
newStu.nickname = newStu.nickname || oldStu.nickname;
newStu.age = newStu.age || oldStu.age;
newStu.gender = newStu.gender || oldStu.gender;
newStu.score = newStu.score || oldStu.score;
newStu.className = newStu.className || oldStu.className;
newStu.avatar = newStu.avatar || oldStu.avatar;
//更新数据
stuDb.updateStudentById(newStu, results => {
res.send({
code: 200,
msg: '修改成功'
});
}, error => {
res.status(500).send({ code: 500, msg: '服务器内部错误', error });
})
}, error => {
res.status(500).send({ code: 500, msg: '服务器内部错误', error });
})
}
3.4 mysql模块
每个路由处理方法,都会使用mysql操作数据库数据,可以把这部分代码封装成一个mysql请求数据库模块
-stuDb.js模块
//逻辑处理模块.
const stuDb = require('../databse/stuDb.js');
module.exports.list = (req, res) => {
//0. 将客户端通过URL传递到服务端的q page参数取出来.
let { q, page } = req.query;
// 判断1下,page是否有传递 如果page没有传值 那么默认值就设置为1
page = page || 1; //2
// 定义1个变量 表示页容量.
const pageSize = 5;
//如果q有值,就意味着sql语句的后面要加1个where条件.
//1. 从数据库中将学生表中的所有数据查询出来返回.
//1.2 执行sql语句
// q=花&page=1
// 当前接口
// q page参数
// 都是可选的.
// q 没有值,查询所有 有值就查询满足条件的.
/*
1. 如果q参数没有值: select * from student limit (page-1)*size, size
2. 如果q参数有值: select * from student where name like '%q%' or nickname like '%q%' limit (page-1)*size, size
1. 查询所有的数据并分页.
2. 根据关键字查询数据并分页.
*/
if (q) {
//如果q有值.
stuDb.getAllStudentByPageAndQ(page, q, results => {
res.send({
code: 200,
count: results.length,
data: results
})
}, error => {
return res.status(500).send({ code: 500, msg: '服务器内部错误', error });
});
} else {
//q没有值,要去查询所有的学生
stuDb.getAllStudentByPage(page, results => {
res.send({
code: 200,
count: results.length,
data: results
})
}, error => {
return res.status(500).send({ code: 500, msg: '服务器内部错误', error });
});
}
}
module.exports.query = (req, res) => {
//1. 从客户端的URL中取出传递的id参数
const { id } = req.query; //5
if (!id) return res.status(400).send({ code: 400, msg: '参数错误' });
//2. 去数据库中查询出id为传入的id的记录.
stuDb.getStudentById(id, results => {
//查询成功
//判断查询到的结果是否有数据
if (results.length == 0) return res.status(404).send({ code: 404, msg: 'id不存在' });
res.send({ code: 200, data: results[0] });
}, error => {
res.status(500).send({ code: 500, msg: '服务器内部错误', error });
})
}
module.exports.add = (req, res) => {
//1. 先从post请求中取出传递的数据
const stu = req.body;
// 对数据进行一些校验.
// name nickname gender这三个数据是必须要传递,否则就报400
if (!stu.name || !stu.nickname || !stu.gender) return res.status(400).send({ code: 400, msg: '参数错误' });
//3个必传的数据都有了.
//判断1下其它的参数是否有传递,如果有传递,就是它传递的值, 如果没有传递我们就设置1个默认值.
stu.age = stu.age || 18;
stu.score = stu.score || 60;
stu.className = stu.className || '深圳黑马前端就业班第71期';
stu.avatar = stu.avatar || 'default_avatar.png';
//调用数据模块的方法新增数据
stuDb.add(stu, results => {
res.status(201).send({
code: 201,
msg: '新增成功',
insertId: results.insertId
});
}, error => {
res.status(500).send({ code: 500, msg: '服务器内部错误', error });
})
}
//当URL是 /delete 的时候,对应的逻辑处理就是交给这个方法。
module.exports.delete = (req, res) => {
//1. 取出客户端通过URL传递到服务端的id
const { id } = req.query;
//2. 判断id是否有传递
if (!id) return res.status(400).send({ code: 400, msg: '参数错误' });
//3. 调用数据模块的方法 删除学员
stuDb.deleteStudentById(id, results => {
//成功的时候执行
//1. 判断1下受影响的行数
if (results.affectedRows == 0)
return res.status(404).send({ code: 404, msg: 'id不存在' });
res.send({ code: 200, msg: '删除成功' });
}, error => {
res.status(500).send({ code: 500, msg: '服务器内部错误', error });
});
}
module.exports.update = (req, res) => {
//1. 取出客户端通过post传递到服务端的数据.
const newStu = req.body;
//2. 判断id有没有传递
if (!newStu.id) return res.status(400).send({ code: 400, msg: 'id必须要传递' });
//3. 再判断哪些数据传递了 传了就使用新值 没有传就使用原来的值.
//3.1 先去数据库中 查询出本身的数据
/// 根据id去数据库中查询数学生信息
stuDb.getStudentById(newStu.id, results => {
//查询成功
if (results.length == 0) return res.status(400).send({ code: 400, msg: 'id不存在' });
//id存
const oldStu = results[0]; //oldStu是数据库中原来的数据在
newStu.name = newStu.name || oldStu.name;
newStu.nickname = newStu.nickname || oldStu.nickname;
newStu.age = newStu.age || oldStu.age;
newStu.gender = newStu.gender || oldStu.gender;
newStu.score = newStu.score || oldStu.score;
newStu.className = newStu.className || oldStu.className;
newStu.avatar = newStu.avatar || oldStu.avatar;
//更新数据
stuDb.updateStudentById(newStu, results => {
res.send({
code: 200,
msg: '修改成功'
});
}, error => {
res.status(500).send({ code: 500, msg: '服务器内部错误', error });
})
}, error => {
res.status(500).send({ code: 500, msg: '服务器内部错误', error });
})
}
4. 问题:请求数据库异步操作
问题描述:之前所有的操作写在一个页面的时候,请求数据库connection.query(sql语句,(error,results)=>{})如果请求失败直接return error,成功也可以直接调用results(成功执行sql语句的结果)。但是封装模块化之后,因为请求数据库是一个异步操作,error和results如果直接return的话是接收不到的,但是我们逻辑处理模块又需要sql请求返回的数据。
解决方法:通过回调函数获得SQL返回参数error和results
SQL模块代码:
module.exports.getAllStudentByPageAndQ = (page,q, successCallback, errorCallback) => {
//1.生成sql语句
const sqlCommand = `select * from student where name like '%${q}%' or nickname like '%${q}%' limit ${(page-1)*config.pageSize}, ${config.pageSize}`;
//2. 执行sql语句.
connection.query(sqlCommand, (error, results) => {
if(error){
errorCallback(error);
}else{
successCallback(results);
}
});
}
定义两个回调函数参数 successCallback,errorCallback分别用来存 errorCallback(error)错误信息,successCallback(results)成功返回数据
路由逻辑模块代码:
stuDb.getAllStudentByPageAndQ(page, q, results => {
res.send({
code: 200,
count: results.length,
data: results
})
}, error => {
return res.status(500).send({ code: 500, msg: '服务器内部错误', error });
});
这里results 就是接受的成功执行SQL后的数据,error 就是错误信息
上面用来箭头函数的简写(results)=>{} results参数接受
successCallback(results) results参数保存
总结
今日份学习内容:模块化开发(自定义模块),异步操作返回数据的处理,接口路由练习,node.js中操作mysql请求练习。