node.js接口开发(三)

307 阅读7分钟

上一章博客里开发的就当练习了,接下去用express脚手架来开发的才是我最后在项目中用到的。

一、创建脚手架

  1. 首先全局安装express,在命令行中输入npm install express-generator -g
  2. cd到需要创建改项目的父文件,然后输入命令express 项目名称
  3. 创建成功后出现如下目录

4. 配置环境,还是先安装依赖cross-env和nodenom 输入命令npm install nodemon --save -dev npm install cross-env --save -dev
5. 在package.json文件内的script字段加入"dev": "cross-env NODE_ENV=dev nodemon ./bin/www"
这样运行npm run dev 就可以把node跑起来,通过nodemon保存后可以直接查看效果,通过cross-env可以区分出环境

1.www

根据package.json可以知道入口文件在bin文件夹下的www文件,先看代码

#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('news-express:server');
var http = require('http');

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '8000');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

这个文件里主要配置端口号,抛出一些借口错误,并且创建一个http服务,根据代码var app = require('../app');var server = http.createServer(app);得知http创建的服务来自app.js文件。

2、app.js

  • 在之前的app.js里面漏掉了非常重要的一步,我也是直接在和react项目开始联调的时候才发现的--跨域。react项目和node的项目要同时开启必定不在同一个端口号上,比如这个node的端口号是8000,而我react项目的端口号是3000,受到浏览器同源规则的限制,3000端口号发起的请求,8000端口号的服务是不会允许接收的
  • 解决方案有两种,一种是用Nginx代理,把前后端代理到同一个端口下,但我的电脑不是服务器,配置Nginx感觉完全没有必要,那就用另一种方法,在app.js里面加入几行代码
app.all('*', function (req, res, next) {    
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Content-type");
  res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
  res.header("X-Powered-By", ' 3.2.1');
  res.header("Content-Type", "application/json;charset=utf-8");
  next();
});
  • 这段代码简单来说就是允许接受put,post,get,delete,options类型,来自所有网址都可以的请求
  • 其他的关于接收参数,还有一些关于cookie,路由解析等等的配置已经写好了,自己需要补充路由对应的功能是什么,首先导入对应的功能
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var newsRouter = require('./routes/news');

然后就是每个路由对应的功能了,我在这里引入了三个文件,随便看其中一个,其他的只是业务功能上不同,大致还是一样的

3.routes/news.js

const express = require('express');
const router = express.Router();
const { getAllNews, insertNews, getOneNew, updataNews, deleteNews } = require("../controller/news")
const { SuccessModel, ErrorModel } = require("../model/resModel")

// 获取新闻分页信息
router.post('/getNewsPage', (req, res ,next) => {
    const result = getAllNews();
    let lists=[],newArry=[],hasmore,total = 0
    const resultData = result.then(data => {
        if (data) {    
            let {current, pageSize} = req.body.data
            lists = data
            pageSize=parseInt(pageSize)
            current=(parseInt(current) - 1)*pageSize
            //.slice(start,end):start必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推
            newArry=lists.slice(current,current+pageSize)
            hasmore=current+pageSize > lists.length ? false : true
            total = lists.length
            // console.log(newArry)
            return new SuccessModel(newArry)
        }
        return new ErrorModel('异常错误')
    })
    resultData.then(data => {
        res.send({
            list: data,
            hasmore,
            total
        })
    })
})

// 添加新闻
router.post('/insertNews',(req,res,next) => {
    const { title, content, createby } = req.body.data
    const result = insertNews(title, content, createby);
    const resultData = result.then(data => {
        if (data) {
            return new SuccessModel(data)
        }
        return new ErrorModel('异常错误')
    })
    resultData.then(data => {
        res.json(data)
    })
});

// 得到单个新闻信息
router.post('/getOneNew',(req,res,next) => {
    const { id } = req.body.data
    const result = getOneNew(id);
    const resultData = result.then(data => {
        if (data) {
            return new SuccessModel(data)
        }
        return new ErrorModel('异常错误')
    })
    resultData.then(data => {
        res.json(data)
    })
});

// 修改新闻信息
router.post('/updataNews',(req,res,next) => {
    const { id, title, content } = req.body.data
    const result = updataNews(id, title, content);
    const resultData = result.then(data => {
        if (data) {
            return new SuccessModel(data)
        }
        return new ErrorModel('异常错误')
    })
    resultData.then(data => {
        res.json(data)
    })
});

// 删除新闻信息
router.post('/deleteNews',(req,res,next) => {
    const { id } = req.body.data
    const result = deleteNews(id);
    const resultData = result.then(data => {
        if (data) {
            return new SuccessModel(data)
        }
        return new ErrorModel('异常错误')
    })
    resultData.then(data => {
        res.json(data)
    })
});
module.exports = router;
  • 这里的业务逻辑也不复杂,按照注释对应的功能,就是对新闻的增删改查加上一个分页查询
  • 在这里可以看到许多和之前自己写的,不用脚手架工具类似的地方,比如说引入的SuccessModel、ErrorModel文件,用来返回真确和错误的状态
  • 同样的,这个地方用来接受前端传过来的参数,并且根据数据库查出来的数据做处理,至于数据库怎么查,sql语句该怎么写,在文件头部引入了controller文件夹下对应的js文件,router文件夹下有多少个js文件,就对应有几个controller下的js文件

4.controller/news.js

const { exec } = require('../db/mysql')


// 获取所有新闻
const getAllNews = () => {
    let sql = `SELECT newscontent.*,admins.username AS create_username FROM newscontent
    LEFT JOIN admins ON newscontent.createby = admins.id`
    return exec(sql).then(row => {
        return row || []
    })
}

// 添加新闻
const insertNews = (title, content, createby) => {
    let nowTime = new Date();
    let timeData = nowTime.getFullYear()+"-" + (nowTime.getMonth()+1) + "-" + nowTime.getDate() + " " + nowTime.getHours()+ ":" + nowTime.getMinutes() + ":" + nowTime.getSeconds();
    let sql = `INSERT INTO newscontent(NAME,content, createtime,createby) VALUES('${title}','${content}','${timeData}','${createby}')`
    return exec(sql).then(row => {
        return row || []
    })
}

// 查询单个新闻
const getOneNew = (id) => {
    let sql = `SELECT * FROM newscontent WHERE id = '${id}'`
    return exec(sql).then(row => {
        return row[0] || []
    })
}

// 修改新闻
const updataNews = (id, title, content) => {
    let sql = `UPDATE newscontent SET NAME='${title}', content='${content}' WHERE id='${id}'`
    return exec(sql).then(row => {
        return row || []
    })
}

// 删除新闻
const deleteNews = (id) => {
    let sql = `DELETE FROM newscontent WHERE id = '${id}'`
    return exec(sql).then(row => {
        return row || []
    })
}
module.exports = {
    getAllNews,
    insertNews,
    getOneNew,
    updataNews,
    deleteNews
}
  • 这里要做的事情就很简单了,他会得到一些参数,根据他得到的参数,其实也就是查询条件,对数据库做相应对处理,并返回对应对数据
  • 数据返回到route下对应对js后,根据业务来判断返回对状态是正确的还是错误的,这个状态在model文件夹下的resModel.js文件下

5.model/resModel.js

class BeseModel {
    constructor (date, message) {
        if (typeof data === 'string') {
            this.message = data
            data = null
            message = null
        }
        if (date) {
            this.data = date
        }
        if (message) {
            this.message = message
        }
    }
}

// 返回成功状态
class SuccessModel extends BeseModel {
    constructor(data, message) {
        super(data, message);
        this.errno = 0
    }
}

// 返回失败状态
class ErrorModel extends BeseModel {
    constructor(data, message) {
        super(data, message);
        this.errno = -1
    }
}

module.exports = {
    SuccessModel,
    ErrorModel
}
  • 和之前一样,前端会根据errno这个字段来判断是返回正确的数据还是异常的数据从而来作出相应的网页动作
  • 接下来就剩数据库还未连接了,在controller/news.js文件中引入了db/mysql文件

6.db/mysql.js

const mysql = require('mysql');
const express = require('express');
const router = express.Router();
const { MYSQL_CONF } = require("../conf/db")

const con =  mysql.createConnection(MYSQL_CONF)

// 开始连接
con.connect()

//执行sql的函数
function exec(sql) {
    const promise =  new Promise((resolve, reject) => {
        con.query(sql, (err, result) => {
            if (err) {
                reject(err);
                return
            }
            resolve(result)
        })
    })
    return promise
}

module.exports = { exec }
  • 这里接受一个sql语句,并且返回一个promise,方便后面接收数据
  • 这里创建了一个mysql连接
const { MYSQL_CONF } = require("../conf/db")

const con =  mysql.createConnection(MYSQL_CONF)

配置文件在conf/db文件中

7.conf/db

const env = process.env.NODE_ENV

//配置
let MYSQL_CONF

if (env === 'dev') {
    MYSQL_CONF = {
        host: "localhost",
        user: "root",
        password: "7777",
        port: "3306",
        database: "news"
    }
}

if (env === 'production') {
    MYSQL_CONF = {
        host: "localhost",
        user: "root",
        password: "7777",
        port: "3306",
        database: "news"
    }
}

module.exports = { MYSQL_CONF }
  • 这里要做的也简单,根据不同环境连接不同的数据库,至于环境在package.json中定义

8.小结

  • 其实用来express脚手架,最大的差别还是在定义路由的写法上,不用想之前一样写这么多if,其他对功能逻辑的拆分还是差不多的,现在可以根据网站需要的功能开发接口了,下一步要做的就是前端接受参数并且把这是数据渲染到页面上。
  • 这次项目用的是react框架,在后面会对react开发页面做一个总结