node-express+mysql开发后端服务
1.前言
最近自己动手开发项目,后端启用node+express开发,前端使用react+antd+hook开发,node虽然是单线程,但是开发小型项目简单明了,前端工作者自己就可以搭建服务。
2开发环境
node服务搭建相对简单,只需要本机上装有node环境即可 运行以下命令
npm init
node服务需要一个入口文件,我这里命名为serve.js,启动就是node serve.js,由于我不喜欢这么操作,我想跟前端运行项目一样操作,那么可以在package.json配置一下,由于我想做代码的热更新,这里使用了nodemon,只需要安装一下即可
3.具体开发
3.1文件目录
3.2入口文件编写
const express = require('express')
const bodyParser = require("body-parser")
const app = express()
const regionRoute = require("./router/region.js")
const edgeRoute = require("./router/edg.js")
const equipRoute = require('./router/equip.js')
const uploadRoute=require('./router/upload.js')
const log4jsMiddleware = require('./logs/log4.js')
const cors = require('cors')
//日志输出
log4jsMiddleware.use(app)
// 解决跨域问题
app.use(cors())
// 解析静态资源的中间件
app.use(express.static('public'))
//解析post的json数据
app.use(bodyParser.urlencoded({
extended: false
}))
//配置表单提交数据
app.use(bodyParser.json()) //配置json数据
//路由调用
app.use('/region', regionRoute)
app.use('/edgDevice', edgeRoute)
app.use('/equip', equipRoute)
app.use('/file',uploadRoute)
// 启动服务器
app.listen(23408, () => {
console.log('服务器已启动,监听端口 23408')
})
3.3sql文件
这里有个问题,如果不设置时区那么数据库取出的时间跟本地时间相差一个小时
const mysql = require('mysql');
// 创建与数据库的连接,数据库连接池
const connection = mysql.createPool({
host: 'xxx.xx.xx.xx', //数据库地址
port: 'xxxx', //端口号
user: 'xxx', //用户名
password: 'xxx', //密码
database: 'load_identification', //数据库名称
timezone: "08:00", //设置时区
connectionLimit: 10, // 连接数量限制
});
module.exports=connection
3.4封装模糊查询分页查询函数
这里封装sql,用这种?传递参数的好处是避免了有些sql注入
function searchDevices(keyword, regionId, pageSize, pageNo, callback) {
const offset = (pageNo - 1) * pageSize;
const countQuery = `
SELECT COUNT(*) as totalCount FROM edge_device
WHERE name LIKE ?
AND region_id = ?
`;
const countParams = [`%${keyword}%`, regionId];
connection.query(countQuery, countParams, (error, countResult) => {
if (error) {
callback(error, null, null);
return;
}
const totalCount = countResult[0].totalCount;
const query = `
SELECT * FROM edge_device
WHERE name LIKE ?
AND region_id = ?
ORDER BY name
LIMIT ? OFFSET ?
`;
const params = [`%${keyword}%`, regionId, pageSize, offset];
connection.query(query, params, (error, results) => {
if (error) {
callback(error, null, null);
} else {
callback(null, results, totalCount);
}
});
});
}
使用
//分页查询
router.post('/query/devLists', (req, res) => {
const data = req.body
const {
edgeId,
pageSize,
pageNo,
endName
} = data
connection.getConnection((err, connection) => {
if (err) {
// 处理数据库连接错误
errorProcess(500, res, '数据库连接错误')
} else {
searchDeviceEnd(endName, edgeId, pageSize, pageNo, (error, results, total) => {
if (error) {
errorProcess(500, res, '数据库连接错误')
} else {
let obj = {
electricityMeterList: results,
totalNum: total
}
res.json(obj)
}
// 关闭数据库连接
connection.release()
})
}
})
})
3.5封装多个sql并行查询函数
使用promise.all方法
const connection = require('../sql.js')
// 执行查询并返回Promise对象
function executeQuery(sql) {
return new Promise((resolve, reject) => {
connection.query(sql, (error, results) => {
if (error) {
reject(error);
} else {
resolve(results);
}
});
});
}
module.exports=executeQuery;
具体使用
router.post('/query/currentTime', (req, res) => {
const data = req.body
let endId = '489190910754'
connection.getConnection((err, connection) => {
if (err) {
// 处理数据库连接错误
errorProcess(500, res, '数据库连接错误');
} else {
Promise.all([
executeQuery(` SELECT * FROM data_collection
WHERE DATE(collect_time) = '2022-07-20' AND collect_obj_id=${endId}
AND TIME(collect_time) < TIME(DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i')) - INTERVAL 15 MINUTE;`),
executeQuery(`SELECT *
FROM load_analysis_result
WHERE DATE(current_run_time) = '2022-07-20'
AND TIME(current_run_time) < TIME(DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i')) - INTERVAL 15 MINUTE;`),
executeQuery(`SELECT id,name FROM appliance WHERE end_id=${endId};`),
executeQuery(`SELECT * FROM end_device WHERE id=${endId};`),
]).then((results) => {
// 将查询结果组合到一个数组中
let resStatus = results.every(item => item && Array.isArray(item) && item.length != 0);
if (resStatus) {
let obj = {}
obj.totalPowerList = generateTotalPowerChart(results[0])
obj.powerList = generatePowerChart(results[1], results[2])
obj.eleMsg = results[3][0]
res.json(obj)
} else {
let obj = {
totalPowerList: {},
powerList: {},
eleMsg: {}
}
res.json(obj)
}
}).catch((err) => {
errorProcess(500, res, '数据库连接错误')
}).finally(() => {
// 关闭数据库连接
connection.release();
});
}
});
});
3.6封装上传接口
上传这里需要借助multer中间件 配置multer
const upload = multer({
dest: 'files/uploads/',
fileFilter: (req, file, cb) => {
const extname = path.extname(file.originalname);
if (extname !== '.zip') {
return cb(new Error('需要上传的是zip文件'));
}
//文件名解码
file.originalname = Buffer.from(file.originalname, "latin1").toString(
"utf8"
);
cb(null, true);
},
});
因为我这里没有搭建文件服务器,就把文件放在本地文件夹目录。
具体封装的写入函数
const fs = require('fs');
function writeFiles(file, path, resolve, reject) {
const targetDirectory = 'files/destination/';
const targetPath = path.join(targetDirectory, file.originalname);
fs.mkdirSync(targetDirectory, {
recursive: true
});
fs.createReadStream(file.path)
.pipe(fs.createWriteStream(targetPath))
.on('finish', () => {
resolve && resolve(file.originalname)
})
.on('error', (err) => {
console.error('Error saving file:', err);
reject && reject(err)
});
}
module.exports = {
writeFiles
}
具体使用
//upload.single名称要与formdata中file保持一致
//上传文件
router.post('/upload', upload.single('file'), (req, res) => {
if (!req.file) {
return res.status(400).send('No file uploaded.');
}
const file = req.file;
writeFiles(file, path, (filename) => {
connection.getConnection((err, connection) => {
if (err) {
// 处理数据库连接错误
console.error('查询出错:', err);
errorProcess(500, res, '数据库连接错误')
} else {
//这里将文件数据存入数据库
const query = `INSERT INTO file set ?`;
const params = {
fileId: uuid.v4(),
filePath: file.path,
fileName: file.originalname,
fileSize: file.size
}
// 执行数据库查询操作
connection.query(query, params, (error, results) => {
connection.release(); // 释放数据库连接
if (error) {
// 处理数据库查询错误
errorProcess(500, res, '数据库连接错误')
return;
} else {
if (results.affectedRows > 0) {
res.send({
msg: '文件上传成功',
fileName: filename
});
} else {
console.log('添加失败');
}
}
});
}
});
}, () => {
res.status(500).send('文件上传失败');
})
});
前端代码示例
uploadFile() {
if (this.fileList.length === 0) {
this.$message.warning("请上传文件");
} else {
let formData = new FormData();
formData.append("file", this.fileList[0]);
//调用上传接口
const url = "http://localhost:23408/file/upload";
this.$axios.post(url, formData).then(res => {
console.log(res)
if(res.status==200){
this.$message.success('上传成功')
this.devVisible=false
}
});
}
},
4.部署
1.需要改动sql.js文件,换成自己的主机ip,端口和数据库名称
const mysql = require('mysql');
// 创建与数据库的连接,数据库连接池
const connection = mysql.createPool({
host: 'xxx.xxx.xx', //数据库地址
port: 'xxxx', //端口号
user: 'xx', //用户名
password: 'xx', //密码
database: 'xxxxxx', //数据库名称
timezone: "08:00", //设置时区
connectionLimit: 10, // 连接数量限制
});
module.exports=connection
2.服务器上装node环境,安装Node.js:在服务器上安装Node.js,您可以从Node.js官方网站下载适用于您服务器操作系统的安装程序,并按照说明进行安装,接下来装包,npm install
3.创建环境变量配置文件:在服务器上的应用程序目录中创建一个名为 .env
的文件,用于存储环境变量。您可以使用以下格式添加环境变量键值对:
4.使用pm2统一管理npm,保证服务持续启动,创建pm2配置文件:在应用程序目录中创建一个名为 ecosystem.config.js
的文件,用于配置pm2。
pm2配置文件
详细参考:blog.csdn.net/zz00008888/…
5.安装pm2:在服务器上运行 npm install -g pm2
命令,全局安装pm2。
6.启动应用程序:在应用程序目录中,使用以下命令启动您的应用程序并使用pm2进行管理:
7.配置自动启动:运行 pm2 startup
命令,按照输出的指示进行操作,以将pm2设置为在服务器启动时自动启动。
8.监控和日志记录:使用pm2的相关命令来监控应用程序的状态和资源使用情况,以及查看日志输出。例如:
- 使用
pm2 list
命令查看当前正在运行的应用程序列表。 - 使用
pm2 monit
命令监控应用程序的状态和资源使用情况。 - 使用
pm2 logs
命令查看应用程序的日志输出。
通过按照这些步骤,您可以在服务器上部署Node.js服务,并使用pm2进行持续运行和管理。根据您的应用程序需求,您可以进一步调整和优化pm2的配置选项。详细的pm2配置说明可以参考pm2官方文档。
6.结尾
本文适用于用node部署简单的后端服务,node可以写crud,包括常见的一些功能,但不涉及复杂的服务,毕竟单线程,有好处也有坏处。正常部署,公司会有一套完整的部署模式,我这里的部署方式是自己部署在自己的云服务器上
转载自:https://juejin.cn/post/7254794609638129721