我的第一个node项目,用koa+sequelize来开发接口

·  阅读 2211
我的第一个node项目,用koa+sequelize来开发接口

一直都很想尝试用node来写点东西,学习了一番之后依葫芦画瓢用koa框架加上sequelize ORM从零开始用MVC模式编写了个简单的后台项目,故在此做一下记录。

一、构建项目

1、创建文件夹,初始化项目

mkdir node-koa-demo # 创建项目
cd node-koa-demo # 进入目录
cnpm init -y # 生成package.json
cnpm install koa koa-body koa-router koa-static koa2-cors path -S # 安装koa插件
touch app.js # 生成入口文件
复制代码

2、定义项目启动命令

// package.json
{
  "name": "node-koa-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node app.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "koa": "^2.11.0",
    "koa-body": "^4.1.1",
    "koa-router": "^7.4.0",
    "koa-static": "^5.0.0",
    "koa2-cors": "^2.0.6",
    "path": "^0.12.7"
  }
}

复制代码

3、定义入口文件

// app.js
const Koa = require('koa')
const app = new Koa()
const config = require('./config')
const path = require('path')
const koaBody = require('koa-body')({ // // 解析body的中间件
  multipart: true, // 支持文件上传
  encoding:'gzip',
  formLimit: '5mb', // 限制表单请求体的大小
  jsonLimit: '5mb', // JSON 数据体的大小限制
  textLimit: '5mb', // 限制 text body 的大小
  formidable:{
    uploadDir: path.join(__dirname, '/public/upload'), // 设置文件上传目录
    keepExtensions: true,    // 保持文件的后缀
    maxFieldsSize: 200 * 1024 * 1024, // 设置上传文件大小最大限制,默认2M
    onFileBegin: (name, file) => { // 文件上传前的设置
      console.log(`name: ${name}`)
      console.log(file)
    }
  }
})
const static = require('koa-static')

// 解析body的中间件
app.use(koaBody)
app.use(static(path.join(__dirname)))

app.listen(config.service.port, () => {
  console.log('server is running')
})

复制代码

4、定义配置文件

1)定义本地配置文件env文件:

.env文件:

// .env
SERVE_PORT=[项目端口]
SERVE_ENVIROMENT=[项目所处环境]
复制代码

安装dotenv插件用来在项目中引用env文件

cnpm install dotenv -S # 用来引入.env配置环境变量
复制代码

入口文件引入插件:

const dotenv = require('dotenv') // 引入配置文件
dotenv.config()
复制代码
2)定义项目中的配置文件config.js:

config.js:

module.exports = {
  service: {
    port: process.env['SERVE_PORT'],
    enviroment: process.env['SERVE_ENVIROMENT'] || 'dev'
  }
}
复制代码

入口文件引入config.js:

const config = require('./utils/config')
global.config = config
复制代码

启动项目: cnpm run start

5、项目热更新

安装插件

cnpm install nodemon -S
复制代码
添加命令:
// package.json
"start:dev": "nodemon node app.js"
复制代码

二、开发接口

1、sequelize连接mysql

1)安装相关依赖
cnpm install mysql2 sequelize -S
复制代码
2)创建数据库配置文件夹db
mkdir db
touch db/index.js
复制代码
3)定义数据库连接配置,并引入
// .env
DB_DATABASE=[数据库名称]
DB_USER=[数据库用户名]
DB_PSW=[数据库连接密码]
DB_HOST=[数据库端口]
复制代码
4)定义sequelize文件

// db/index.js
const Sequelize = require('sequelize')

const sequelize = new Sequelize(
  process.env['DB_DATABASE'],
  process.env['DB_USER'],
  process.env['DB_PSW'], 
  {
    host: process.env['DB_HOST'], // 数据库地址
    dialect: 'mysql', // 数据库类型
    dialectOptions: { // 字符集
      charset:'utf8mb4',
      collate:'utf8mb4_unicode_ci',
      supportBigNumbers: true,
      bigNumberStrings: true
    },
    pool: {
      max: 5, // 连接池最大链接数量
      min: 0, // 最小连接数量
      idle: 10000 // 如果一个线程10秒内没有被使用的花,就释放连接池
    },
    timezone: '+08:00', // 东八时区
    logging: (log) => {
      console.log('dbLog: ', log)
      return false
    } // 执行过程会打印一些sql的log,设为false就不会显示
  }
)

module.exports = sequelize
复制代码
5)定义model
mkdir model
touch model/User.js
复制代码
const Sequelize = require('sequelize')
const sequelize = require('../db')

const User = sequelize.define('user', {
  id: {
    type: Sequelize.INTEGER,
    allowNull: false, // 设置为false时,会给添加NOT NULL(非空)约束,数据保存时会进行非空验证
    comment: 'ID', // 字段描述(自1.7+后,此描述不再添加到数据库中
    autoIncrement: true, // 是否自增
    primaryKey: true, // 指定是否是主键
    unique: true, // 设置为true时,会为列添加唯一约束
  },
  password: {
    type: Sequelize.STRING(20),
    validate: {}, // 模型每次保存时调用的验证对象。可是validator.js中的验证函数(参见 DAOValidator)、或自定义的验证函数
    allowNull: false, // 设置为false时,会给添加NOT NULL(非空)约束,数据保存时会进行非空验证
    comment: '密码' // 字段描述(自1.7+后,此描述不再添加到数据库中)
  },
  name: {
    type: Sequelize.STRING(20),
    validate: {
      notEmpty: true
    }, // 模型每次保存时调用的验证对象。可是validator.js中的验证函数(参见 DAOValidator)、或自定义的验证函数
    allowNull: false, // 设置为false时,会给添加NOT NULL(非空)约束,数据保存时会进行非空验证
    comment: '用户名称' // 字段描述(自1.7+后,此描述不再添加到数据库中)
  },
  email: {
    type: Sequelize.STRING(20),
    validate: {
      isEmail: true
    }, // 模型每次保存时调用的验证对象。可是validator.js中的验证函数(参见 DAOValidator)、或自定义的验证函数
    allowNull: false, // 设置为false时,会给添加NOT NULL(非空)约束,数据保存时会进行非空验证
    comment: 'email' // 字段描述(自1.7+后,此描述不再添加到数据库中)
  },
  phone: {
    type: Sequelize.STRING(11),
    allowNull: false, // 设置为false时,会给添加NOT NULL(非空)约束,数据保存时会进行非空验证
    comment: '手机号码' // 字段描述(自1.7+后,此描述不再添加到数据库中)
  },
  birth: {
    type: Sequelize.DATE,
    validate: {
      isDate: true
    }, // 模型每次保存时调用的验证对象。可是validator.js中的验证函数(参见 DAOValidator)、或自定义的验证函数
    allowNull: false, // 设置为false时,会给添加NOT NULL(非空)约束,数据保存时会进行非空验证
    defaultValue: new Date(), // 字面默认值, JavaScript函数, 或一个 SQL 函数
    comment: '生日' // 字段描述(自1.7+后,此描述不再添加到数据库中)
  },
  sex: {
    type: Sequelize.INTEGER,
    validate: {
      isInt: true,
      len: 1
    }, // 模型每次保存时调用的验证对象。可是validator.js中的验证函数(参见 DAOValidator)、或自定义的验证函数
    allowNull: false, // 设置为false时,会给添加NOT NULL(非空)约束,数据保存时会进行非空验证
    defaultValue: 0, // 字面默认值, JavaScript函数, 或一个 SQL 函数
    comment: '性别,0-男 1-女' // 字段描述(自1.7+后,此描述不再添加到数据库中)
  },
}, {
  freezeTableName: true, // 设置为true时,sequelize不会改变表名,否则可能会按其规则有所调整
  timestamps: true, // 为模型添加 createdAt  updatedAt 两个时间戳字段
})

//创建表,默认是false,true则是删除原有表,再创建
User.sync({
  force: false,
})

module.exports = User
复制代码
[*自动暴露model(根据需要自行选择是否需要自动暴露所有model)]
// model/index.js

/* 扫描所有的model模型 */
const fs = require('fs')
const files = fs.readFileSync(__dirname + '/model') // 遍历目录
const jsFiles = files.filter(item => {
  return item.endsWith('.js')
}, files)

module.exports = {}
for (const file of jsFiles) {
  console.log(`import model from file ${file}`)
  const name = file.substring(0, file.length - 3)
  module.exports[name] = require(__dirname + '/model/' + file)
}
复制代码

2、定义路由

mkdir router
touch router/index.js
复制代码
// router/index.js
const router = require('koa-router')({
  prefix: '/api'
})

router.get('/', async(ctx, next) => {
  ctx.body = 'Hello World~'
})

module.exports = router
复制代码

在入口文件app.js引入

// 路由中间件
const router = require('./router')
// 开始服务并生成路由
app.use(router.routes()).use(router.allowedMethods()) // 开始服务并生成路由
复制代码

3、自定义中间件

1)创建中间件文件夹
mkdir middleware
复制代码
2)定义一个错误处理中间件
touch middleware/exception.js # 中间件文件
touch utils/http-exception.js # 定义已知异常类
复制代码
3)定义已知异常类继承于Error类

明确已知异常还是未知异常

// utils/http-exception.js
/**
 * 默认的异常
 */
class HttpException extends Error {
  constructor(msg = '错误请求', errorCode = 10000, code = 400) {
    super()
    this.errorCode = errorCode
    this.code = code
    this.msg = msg
  }
}

class ParameterException extends HttpException {
  constructor(msg, errorCode) {
    super()
    this.code = 400
    this.msg = msg || '参数错误'
    this.errorCode = errorCode || 10000
  }
}

class AuthFailed extends HttpException {
  constructor(msg, errorCode) {
    super()
    this.code = 401
    this.mag = msg || '授权失败'
    this.errorCode = errorCode || 10004
  }
}

class NotFound extends HttpException {
  constructor(msg, errorCode) {
    super()
    this.code = 404
    this.msg = msg || '未找到该资源'
    this.errorCode = errorCode || 10005
  }
}

class Forbidden extends HttpException {
  constructor(msg, errorCode) {
    super()
    this.code = 403
    this.msg = msg || '禁止访问'
    this.errorCode = errorCode || 10006
  }
}

class Oversize extends HttpException {
  constructor(msg, errorCode) {
    super()
    this.code = 413
    this.msg = msg || '上传文件过大'
    this.errorCode = errorCode || 10007
  }
}

class InternalServerError extends HttpException {
  constructor(msg, errorCode) {
    super()
    this.code = 500
    this.msg = msg || '服务器出错'
    this.errorCode = errorCode || 10008
  }
}

module.exports = {
  HttpException,
  ParameterException,
  AuthFailed,
  NotFound,
  Forbidden,
  Oversize,
  InternalServerError
}
复制代码
4)定义异常处理中间件
// middleware/exception.js
const { HttpException } = require('../utils/http-exception')

// 全局异常监听
const catchError = async(ctx, next) => {
  try {
    await next()
  } catch(error) {
    // 已知异常
    const isHttpException = error instanceof HttpException
    // 开发环境
    const isDev = global.config.service.enviroment === 'dev'

    // 在控制台显示未知异常信息:开发环境下,不是HttpException 抛出异常
    if (isDev && !isHttpException) {
      throw error
    }

    /**
     * 是已知错误,还是未知错误
     * 返回:
     *      msg 错误信息
     *      error_code 错误码
     */
    if (isHttpException) {
      ctx.body = {
        msg: error.msg,
        error_code: error.errorCode
      }
      ctx.response.status = error.code
    } else {
      ctx.body = {
        msg: '未知错误',
        error_code: 9999
      }
      ctx.response.status = 500
    }
  }
}

module.exports = catchError
复制代码
5)入口文件加载引入异常处理中间件
// 加载全局异常
const errors = require('./utils/http-exception')
global.errs = errors

const app = new Koa()
// 全局异常中间件监听、处理,放在所有中间件的最前面
const catchError = require('./middleware/exception')
app.use(catchError)
复制代码

4、定义API统一返回格式

1)resJson.js定义接口返回格式
// utils/resJson.js
const ResultJson =  {
  success: (params) => {
    return {
      data: params.data || null, // 返回的数据
      msg: params.msg || '操作成功', // 返回的提示信息
      code: 1 // 返回的接口调用状态码,0-失败,1-成功
    }
  },
  fail: (params) => {
    return {
      data: params.data || null,
      msg: params.msg || '操作失败',
      code: 0,
      error_code: params.errorCode // 返回接口异常信息码
    }
  }
}

module.exports = ResultJson
复制代码
2)在中间件中使用

修改上文提到的异常处理中间件

/* 错误处理中间件 */
const { HttpException } = require('../utils/http-exception')
const resJson = require('../utils/resJson')

// ...省略上文
    if (isHttpException) {
      ctx.body = resJson.fail(error)
      ctx.response.status = error.code
    } else {
      ctx.body = resJson.fail({
        msg: '未知错误',
        error_code: 9999
      })
      ctx.response.status = 500
    }
// ...省略下文

复制代码

5、[小试牛刀] 编写一个返回所有用户信息的接口

1)创建控制器文件夹,定义User.js,用来对User表进行的操作。
mkdir controller
touch controller/User.js
复制代码
2)获取全部用户列表
const User = require('../model/User.js')
const resJson = require('../utils/resJson')

module.exports = {
  selectAll: async (ctx, next) => {
    await User.findAll({
      raw: true,
      attributes: { // 不返回password字段
        exclude: ['password'] 
      }
    }).then((res) => {
    	// 成功返回
      ctx.body = resJson.success({data: res})
    }).catch((err) => {
    	// 失败,捕获异常并输出
      ctx.body = resJson.fail(err)
    })
  }
}

复制代码
3)声明接口路径
// router/index.js
const router = require('koa-router')({
  prefix: '/api'
})
// User控制器
const User = require('../controller/user')

// 获取全部用户
router.get('/user/list', User.selectAll)

module.exports = router
复制代码
4)访问接口

访问接口地址:http://localhost:3002/api/user/list

访问结果:

{
    "data": [
        {
            "id": 1,
            "name": "cai",
            "email": "cai@qq.com",
            "phone": "13234323453",
            "birth": "2019-12-13T01:23:17.000Z",
            "sex": 1,
            "createdAt": "2019-12-13T01:23:42.000Z",
            "updatedAt": "2019-12-13T01:23:42.000Z"
        }
    ],
    "msg": "操作成功",
    "code": 1
}
复制代码
分类:
阅读
标签:
收藏成功!
已添加到「」, 点击更改