likes
comments
collection
share

node-express+mysql开发后端服务

作者站长头像
站长
· 阅读数 58

1.前言

最近自己动手开发项目,后端启用node+express开发,前端使用react+antd+hook开发,node虽然是单线程,但是开发小型项目简单明了,前端工作者自己就可以搭建服务。

2开发环境

node服务搭建相对简单,只需要本机上装有node环境即可 运行以下命令

npm init

node服务需要一个入口文件,我这里命名为serve.js,启动就是node serve.js,由于我不喜欢这么操作,我想跟前端运行项目一样操作,那么可以在package.json配置一下,由于我想做代码的热更新,这里使用了nodemon,只需要安装一下即可

node-express+mysql开发后端服务

3.具体开发

3.1文件目录

node-express+mysql开发后端服务

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
评论
请登录