用node快速搭建接口
- 环境
- 实现功能
- 具体实现步奏
- 数据库设计
- 用express创建一个服务器实例
- 创建数据库连接池
- 配置跨域请求和解析前端数据
- 登录接口实现
- 验证token的中间件
- 退出接口
- 获取用户信息接口
- 增删改查功能
- 完整代码
环境
node版本v17.0.0
所用到的依赖
"dependencies": {
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.0",
"md5": "^2.3.0",
"mysql": "^2.18.1"
}
实现功能
登录,退出,获取用户信息,以及登录后对数据库的增删改查操作
完整代码在结尾
具体实现步奏
数据库设计
本案列一共用到了两个表
user表:主要包含用户名,密码和权限(permission)
book表:主要包含书籍名称,作者,出版社,价格等
表的具体内容如下
用express创建一个服务器实例
const express = require('express')
const app = express()
app.listen(3001,()=>{
console.log('serve is running at http://127.0.0.1:3001')
})
创建数据库连接池
let db = mysql.createConnection({
host:'127.0.0.1',
user:'root',
password:'123456',
database:'books',
port:3306
})
db.connect((err)=>{
if(err) throw err;
console.log('连接成功')
})
setInterval(function(){
db.query('select 1')
},5000);
这种写法是最基本的写法,我们还可以通过数据库连接池来实现
// 创建连接池
const pool = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: '123456',
database: 'books',
connectionLimit: 10 // 设置连接池最大连接数
});
// 查询函数
function query(sql, callback) {
// 从连接池中获取一个连接
pool.getConnection((err, connection) => {
if (err) {
callback(err, null);
} else {
// 执行查询
connection.query(sql, (err, results) => {
// 释放连接
connection.release();
callback(err, results);
});
}
});
}
// 定时任务
setInterval(() => {
const sql = 'SELECT 1';
query(sql, (err, results) => {
if (err) {
console.error(err);
} else {
// console.log('查询结果:', results);
}
});
}, 5000);
在上面的代码中两种写法我们都设置了一个定时任务,这段代码是为了保持数据库连接处于活动状态,每隔5秒会向数据库发送一个请求,确保数据库连接不会因为长时间没有交互而被断开,这个技巧被称为“保活”
配置跨域请求和解析前端数据
以下配置表示允许所有的网址和方法请求
app.use((req, res, next) => { //解决跨域问题,能够允许所有网址和方法的跨域处理
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With');
if ('OPTIONS' === req.method) {
res.sendStatus(200);
} else {
next();
}
});
配置解析前端传递的数据用到了body-parser这个包
const bodyParser = require('body-parser');
app.use(bodyParser.json()) //解析json
app.use(bodyParser.urlencoded({ extended: true })); //解析客户端传递过来的参数
登录接口实现
登录接口实现思路
首先接收前端请求的参数,从中解析出用户名和密码,先判断用户名和密码是否存在,不存在直接返回
如果用户名密码都存在,对密码进行md5加密,在进行数据库查询,没有找到返回用户名或密码错误
查询到数据说明,用户名密码正确生成token返回给客户端
// 登录接口
app.post('/login', (req, res) => {
const { username, password } = req.body;
if(!username || !password) res.json({ code:403,message: '用户名或密码不能为空' });
// 进行MD5加密
const md5Pwd = md5(password);
// 查询数据库中是否存在该用户
const sql = `SELECT * FROM user WHERE username='${username}' AND password='${md5Pwd}'`;
query(sql, (err, result) => {
if (err) throw err;
if (result.length === 0) {
res.json({
code: 1,
message: '用户名或密码错误'
});
} else {
// 验证成功,生成token并返回
const payload = {username: username}; // 按照需求设置payload
const secretKey = '147258'; // 按照需求设置密钥
const token = jwt.sign(payload, secretKey, {expiresIn: '1h'}); // 生成token,设置过期时间1小时
// 将token返回给客户端
res.json({
token:token,
code: 0,
message: '登录成功'
});
}
});
});
验证token的中间件
在所有路由之前定义一个中间件来验证token是否存在,以及token是否有效
const verifyToken = (req, res, next) => {
// 获取请信息中的token
const token = req.query.token;
// 如果token不存在,则返回错误信息
if (!token) {
return res.json({ code:401,message: '未提供token' });
}
try {
// 验证token是否有效
const decoded = jwt.verify(token, '147258');
// 将解码后的token信息保存到请求对象中
req.user = decoded;
next();
} catch (err) {
return res.json({ code:403,message: 'token验证失败' });
}
}
退出接口
退出接口需要在token验证有效后才可以请求,具体的操作就是删除token即可
app.post('/user/logout',verifyToken, (req, res) => {
// 删除token
res.clearCookie('token');
res.json({
code: 0,
message: '退出成功'
});
});
获取用户信息接口
从中间件中拿到解析出来的user对象,并拿到里面的username属性
根据该属性判断用户的角色,之后返回给前端,前端可以用来做权限判定
//获取用户信息接口
app.get('/user/getInfo',verifyToken,(req,res)=>{
const username =req.user.username
if(username==='admin'){
res.json({
code:200,
roles:'admin',
introduction: 'I am a super administrator',
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
name: '超级管理员'
})
}else if(username === 'editor'){
res.json({
code:200,
roles:'editor',
introduction: 'I am an editor',
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
name: '编辑'
})
}else{
res.json({
code:201,
roles:'未找到改该角色'
})
}
})
增删改查功能
这些功能都需要登录后才可以操作,请求都需要携带token
// 查询
app.get('/books/getAll',verifyToken,(req, res) => {
query('SELECT * FROM book', (err, results) => {
if (err) throw err;
res.json({code:0,message: '获取数据成功',data:results})
})
})
// 新增
app.post('/books/add',verifyToken,(req, res) => {
const {title, author, publisher, publish_date, price} = req.body
query(`INSERT INTO book (title, author, publisher, publish_date, price) VALUES ('${title}', '${author}', '${publisher}', '${publish_date}', '${price}')`, (err, results) => {
if (err) throw err;
res.json({code:0,message: '新增成功', id: results.insertId})
})
})
// 修改
app.put('/books/put/:id',verifyToken, (req, res) => {
const {title, author, publisher, publish_date, price} = req.body
query(`UPDATE book SET title='${title}', author='${author}', publisher='${publisher}', publish_date='${publish_date}', price='${price}' WHERE id=${req.params.id}`, (err, results) => {
if (err) throw err;
res.json({code:0,message: '修改成功', id: req.params.id})
})
})
// 删除
app.delete('/books/delete/:id',verifyToken, (req, res) => {
query(`DELETE FROM book WHERE id=${req.params.id}`, (err, results) => {
if (err) throw err;
res.json({code:0,message: '删除成功', id: req.params.id})
})
})
这里token我直接就使用params方式传参,因为我测试环境的前端就是这样写的,也可以使用用header来传token,看自己的需求
到目前为止,一个完整的小案列就算完成了!
完整代码
以下是完整的代码,依赖需要自己安装,具体的版本前面有写过
const express = require('express')
const mysql = require('mysql')
const cors = require('cors')
const jwt = require('jsonwebtoken')
const md5 = require('md5')
const bodyParser = require('body-parser');
const app = express()
app.use(bodyParser.json()) //解析json
app.use(bodyParser.urlencoded({ extended: true })); //解析客户端传递过来的参数
app.use((req, res, next) => { //解决跨域问题,能够允许所有网址和方法的跨域处理
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With');
if ('OPTIONS' === req.method) {
res.sendStatus(200);
} else {
next();
}
});
// let db = mysql.createConnection({
// host:'127.0.0.1',
// user:'root',
// password:'123456',
// database:'books',
// port:3306
// })
// db.connect((err)=>{
// if(err) throw err;
// console.log('连接成功')
// })
// setInterval(function(){
// db.query('select 1')
// },5000);
// 创建连接池
const pool = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: '123456',
database: 'books',
connectionLimit: 10 // 设置连接池最大连接数
});
// 查询函数
function query(sql, callback) {
// 从连接池中获取一个连接
pool.getConnection((err, connection) => {
if (err) {
callback(err, null);
} else {
// 执行查询
connection.query(sql, (err, results) => {
// 释放连接
connection.release();
callback(err, results);
});
}
});
}
// 定时任务
setInterval(() => {
const sql = 'SELECT 1';
query(sql, (err, results) => {
if (err) {
console.error(err);
} else {
// console.log('查询结果:', results);
}
});
}, 5000);
//这段代码是为了保持数据库连接处于活动状态,
// 它会每隔5秒钟向数据库发送一个select 1的查询请求,确保连接不会因为长时间没有交互而被断开。
// 这个技巧被称为“保活”,可以让长时间运行的应用程序保持稳定的连接状态。
// 定义一个中间件来验证token,是否需要登录才可以操作
const verifyToken = (req, res, next) => {
// 获取请信息中的token
const token = req.query.token;
// 如果token不存在,则返回错误信息
if (!token) {
return res.json({ code:401,message: '未提供token' });
}
try {
// 验证token是否有效
const decoded = jwt.verify(token, '147258');
// 将解码后的token信息保存到请求对象中
req.user = decoded;
next();
} catch (err) {
return res.json({ code:403,message: 'token验证失败' });
}
}
// 登录接口
app.post('/login', (req, res) => {
const { username, password } = req.body;
if(!username || !password) return res.json({ code:403,message: '用户名或密码不能为空' });
// 进行MD5加密
const md5Pwd = md5(password);
// 查询数据库中是否存在该用户
const sql = `SELECT * FROM user WHERE username='${username}' AND password='${md5Pwd}'`;
query(sql, (err, result) => {
if (err) throw err;
if (result.length === 0) {
res.json({
code: 1,
message: '用户名或密码错误'
});
} else {
// 验证成功,生成token并返回
const payload = {username: username}; // 按照需求设置payload
const secretKey = '147258'; // 按照需求设置密钥
const token = jwt.sign(payload, secretKey, {expiresIn: '1h'}); // 生成token,设置过期时间1小时
// 将token返回给客户端
res.json({
token:token,
code: 0,
message: '登录成功'
});
}
});
});
//获取用户信息接口
app.get('/user/getInfo',verifyToken,(req,res)=>{
const username =req.user.username
if(username==='admin'){
res.json({
code:200,
roles:'admin',
introduction: 'I am a super administrator',
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
name: '超级管理员'
})
}else if(username === 'editor'){
res.json({
code:200,
roles:'editor',
introduction: 'I am an editor',
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
name: '编辑'
})
}else{
res.json({
code:201,
roles:'未找到改该角色'
})
}
})
// 退出接口
app.post('/user/logout',verifyToken, (req, res) => {
// 清除登录状态
// 删除token
res.clearCookie('token');
res.json({
code: 0,
message: '退出成功'
});
});
// 查询
app.get('/books/getAll',verifyToken,(req, res) => {
query('SELECT * FROM book', (err, results) => {
if (err) throw err;
res.json({code:0,message: '获取数据成功',data:results})
})
})
// 新增
app.post('/books/add',verifyToken,(req, res) => {
const {title, author, publisher, publish_date, price} = req.body
query(`INSERT INTO book (title, author, publisher, publish_date, price) VALUES ('${title}', '${author}', '${publisher}', '${publish_date}', '${price}')`, (err, results) => {
if (err) throw err;
res.json({code:0,message: '新增成功', id: results.insertId})
})
})
// 修改
app.put('/books/put/:id',verifyToken, (req, res) => {
const {title, author, publisher, publish_date, price} = req.body
query(`UPDATE book SET title='${title}', author='${author}', publisher='${publisher}', publish_date='${publish_date}', price='${price}' WHERE id=${req.params.id}`, (err, results) => {
if (err) throw err;
res.json({code:0,message: '修改成功', id: req.params.id})
})
})
// 删除
app.delete('/books/delete/:id',verifyToken, (req, res) => {
query(`DELETE FROM book WHERE id=${req.params.id}`, (err, results) => {
if (err) throw err;
res.json({code:0,message: '删除成功', id: req.params.id})
})
})
app.listen(3001,()=>{
console.log('serve is running at http://127.0.0.1:3001')
})