之前的图书管理功能的数据是存放在json文件中的,通过读取json文件的内容渲染到页面上,每次读取都要遍历整个文件,当数据量大时很不方便,把数据存放在数据库中才是正确的做法。
1.操作数据库的基本功能
在Mysql中新建一个数据库book,新建一张book的表用来存放图书的数据信息,将id值设为自增。
利用数据库自增功能有一个问题:在执行删除操作后,再添加数据时,id会出现间隔现象,如下图:
数据库搭建好后,创建一个项目测试一下数据库操作的一些基本功能:
新建一个文件夹mydb
准备一个入口文件:index.js
初始化该项目:npm init -y
安装需要的依赖的包:
npm install mysqljs/mysql
①插入数据
/** * 插入数据 */ //加载数据库驱动 const mysql = require('mysql'); //创建数据库链接 const connection = mysql.createConnection({ host: 'localhost', //数据库所在的服务器的域名或者IP地址 user: 'root', //登录数据库的账号 password: 'password', database: 'book' //数据库的名称book,注意这里不是链接的名称,我创建的链接的名称为mybook }); //执行连接操作 connection.connect(); //使用mysql第三方包简化了insert操作,?用来填充数据,只需要提供一个对象 let sql = 'insert into book set ?' //用来插入的数据 let data = { name: '明朝那些事', author: '当年明月', category: '文学', description: '明朝的历史' } //操作数据库 connection.query(sql,data,(err,results,fields) => { if(err) return; //console.log(results); if(results.affectedRows == 1){ console.log('数据插入成功'); } }); //关闭数据库 connection.end();
②更新数据
/** * 更新数据 */ //加载数据库驱动 const mysql = require('mysql'); //创建数据库链接 const connection = mysql.createConnection({ host: 'localhost', //数据库所在的服务器的域名或者IP地址 user: 'root', //登录数据库的账号 password: 'password', database: 'book' //数据库的名称book,注意这里不是链接的名称,我创建的链接的名称为mybook }); //执行连接操作 connection.connect(); let sql = 'update book set name=?,author=?,category=?,description=? where id=?' //用来更新的数据 let data = ['浪潮之巅','吴军','计算机','IT巨头的兴衰史',6]; //操作数据库 connection.query(sql,data,(err,results,fields) => { if(err) return; //console.log(results); if(results.affectedRows == 1){ console.log('数据更新成功'); } }); //关闭数据库 connection.end();
③删除数据
/** * 删除数据 */ //加载数据库驱动 const mysql = require('mysql'); //创建数据库链接 const connection = mysql.createConnection({ host: 'localhost', //数据库所在的服务器的域名或者IP地址 user: 'root', //登录数据库的账号 password: 'password', database: 'book' //数据库的名称book,注意这里不是链接的名称,我创建的链接的名称为mybook }); //执行连接操作 connection.connect(); let sql = 'delete from book where id=?' //用来删除的数据 let data = [6]; //操作数据库 connection.query(sql,data,(err,results,fields) => { if(err) return; //console.log(results); if(results.affectedRows == 1){ console.log('数据删除成功'); } }); //关闭数据库 connection.end();
④查询数据
/** * 操作数据库基本步骤 */ //加载数据库驱动 const mysql = require('mysql'); //创建数据库链接 const connection = mysql.createConnection({ host: 'localhost', //数据库所在的服务器的域名或者IP地址 user: 'root', //登录数据库的账号 password: 'password', database: 'book' //数据库的名称book,注意这里不是链接的名称,我创建的链接的名称为mybook }); //执行连接操作 connection.connect(); let sql = 'select * from book' let data = null; //操作数据库 connection.query('select 1+1 as solution',(err,results,fields) => { if(err) return; console.log(results[0]); }); //关闭数据库 connection.end();
上述的增删改查的代码有很多重复性,可以将以上功能封装为一个通用的api文件db.js:
/** * 封装操作数据库的通用API */ const mysql = require('mysql'); exports.base = (sql,data,callback) => { //创建数据库链接 const connection = mysql.createConnection({ host: 'localhost', //数据库所在的服务器的域名或者IP地址 user: 'root', //登录数据库的账号 password: 'password', database: 'book' //数据库的名称book,注意这里不是链接的名称,我创建的链接的名称为mybook }); //执行连接操作 connection.connect(); //操作数据库(数据库操作也是异步的,异步不同通过返回值来处理,只能通过回调函数) connection.query(sql,data,(err,results,fields) => { if(err) throw err; callback(results); }); //关闭数据库 connection.end(); }
测试该api:
/** * 测试通用API */ const db = require('./db.js'); //插入操作 let sql = 'insert into book set ?'; let data = { name: '笑傲江湖', author: '金庸', category: '文学', description: '武侠小说' }; db.base(sql,data,(result) => { console.log(result); }); //更新操作 let sql = 'update book set name=?,author=?,category=?,description=? where id=?'; let data = ['天龙八部','金庸','文学','武侠小说',5]; db.base(sql,data,(result) => { console.log(result); }); //删除操作 let sql = 'delete from book where id=?' let data = [7]; db.base(sql,data,(result) => { console.log(result); }); //查询操作 let sql = 'select * from book where id = ?'; let data = [8]; db.base(sql,data,(result) => { console.log(result); });
2.通过一个小的登陆验证功能来测试前后端+数据库一起的功能:
后台逻辑代码login.js:
/** * 登陆验证(前端+后端+数据库) */ const express = require('express'); const bodyParser = require('body-parser'); const db = require('./db.js'); const app = express(); //启动bodyParser这个API来处理参数,处理表单提交的 app.use(bodyParser.urlencoded({extended:false})); app.use(express.static('./public')); app.post('/check',(req,res) => { let param = req.body; console.log(param); let sql = 'select count(*) as total from user where username=? and password=?'; let data = [param.username,param.password]; db.base(sql,data,(result) => { console.log(result); if(result[0].total == 1){ res.send('login success!'); }else{ res.send('login failure!'); } }); }); app.listen(3000,() => { console.log('running...'); });
前端页面代码login.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>登录</title> </head> <body> <form action="http://localhost:3000/check" method="POST"> 用户名:<input type="text" name="username"><br> 密码:<input type="password" name="password"><br> <input type="submit" value="登录"> </form> </body> </html>
3.重构图书管理功能,使数据从数据库中获取
将操作数据库的通用api文件db.js拷贝到mybook项目下,还需要安装mysql的api
(注意:由于desc字段在数据库中是关键字,不能使用,所以把该字段名称改为description,之前的.art文件里用到的desc字段名称全部要改过来,否则执行过程中会报错)
只需修改service.js业务模块中的功能:
/** * 业务模块 */ const data = require('./data.json'); const path = require('path'); const fs = require('fs'); const db = require('./db.js'); /* //自动生成图书编号(自增) let maxBookCode = () => { let arr = []; data.forEach(item => { arr.push(item.id); }); return Math.max.apply(null,arr); } //把内存数据写入到文件 let writeDataFile = (res) => { //需要把内存中的数据写入文件 //JSON.stringify(data)仅传data一个参数的话,data.json文件是压缩形式的 fs.writeFile(path.join(__dirname,'data.json'),JSON.stringify(data,null,4),(err) => { if(err){ res.send('server err'); } //文件写入成功之后重新跳转到主页面 res.redirect('/'); }); } */ //渲染主页面 exports.showIndex = (req,res) => { let sql = 'select * from book'; //从数据库中获取内容 db.base(sql,null,(result) => { //数据库获取的内容只能通过回调函数来得到 res.render('index',{list:result}); }) //res.render('index',{list:data}); } //跳转到添加图书的页面 exports.toAddBook = (req,res) => { //render将会根据views中的模板文件进行渲染,渲染的是空对象{} res.render('addBook',{}); } //添加图书保存数据 exports.addBook = (req,res) => { //获取表单数据 let info = req.body; let book = {}; for (const key in info) { book[key] = info[key]; } let sql = 'insert into book set ?' db.base(sql,book,(result) => { if(result.affectedRows == 1){ //添加成功后跳转到主页面 res.redirect('/'); } }); /* book.id = maxBookCode()+1; data.push(book); //需要把内存中的数据写入文件 writeDataFile(res); */ } //跳转到编辑页面 exports.toEditBook = (req,res) => { let id = req.query.id; let sql = 'select * from book where id=?'; let data = [id]; db.base(sql,data,(result)=>{ //注意:必须使用result[0]获取数据才能渲染到页面上,因为得到的resul结果集是一个数组,不是对象,渲染不了 res.render('editBook',result[0]); }); /* let book = null; data.forEach((item)=>{ if(id == item.id){ book = item; //break; //forEach循环中不能有break,用return终结即可 return; } }); //render将会根据views中的模板文件进行渲染,渲染的是对应图书的完整信息 res.render('editBook',book); */ } //编辑图书更新数据: //1.先查询出对应的数据并渲染到页面上 //2.然后再提交表单,再重新保存,写入文件 //编辑的时候要告诉服务器编辑的是哪条数据 exports.editBook = (req,res) => { //报错:为什么总是跳转到addBook的页面? 问题已解决:editBook.art页面的action路径错写成'/addBook',改成'/editBook'就可以正常运行 //获取表单的数据 let info = req.body; let sql = 'update book set name=?,author=?,category=?,description=? where id=?'; let data = [info.name,info.author,info.category,info.description,info.id]; db.base(sql,data,(result) => { if(result.affectedRows == 1){ //更新成功后跳转到主页面 res.redirect('/'); } else{ res.send('edit failure'); } }); /* //覆盖原有的数据 data.forEach((item)=>{ if(info.id == item.id){ for (const key in info) { item[key] = info[key]; } return; } }); //需要把内存中的数据写入文件 writeDataFile(res); */ } //删除图书信息 exports.deleteBook = (req,res) => { //先获取到传过来的id let id = req.query.id; let sql = 'delete from book where id=?'; let data = [id]; db.base(sql,data,(result) => { if(result.affectedRows == 1){ //删除成功后跳转到主页面 res.redirect('/'); } }); /* data.forEach((item,index)=>{ if(item.id == id){ //删除数组的一项数据 data.splice(index,1); } return; }); //需要把内存中的数据写入文件 writeDataFile(res); */ }