koa面向对象式编程

koa面向对象式编程

最近由于在用egg.js,所以十分羡慕它里面面向对象的逻辑,

自己参照着简单封装了一个面向对象的koa2-node的 写法

封装思想,将一些基础的方法(list, insert, update, delete)封装至一个父类(基类),子类继承它便可以获得对应的方法

  • route层

    定义了一堆页面路由

    • 用户请求对应的路由

    • 进入对应的server.action方法

    • 文件目录

    • admin/index.js

    const model = require('../model') // 获取基本路由
    // 模块导出
    module.exports = {
      ...model,
    }
    ​
    • model/index.js

    ​
    // enum http function: post、get
    const methods = require('../methods')
    ​
    module.exports = {
      // search
      'list': { method: methods.get },
      // add
      'insert': { method: methods.post },
      // update
      'update': { method: methods.post },
      // delete
      'delete': { method: methods.post },
    }
    • method.js

    module.exports = {
      get: 'get',
      post: 'post',
    }
    
     
    • index.js
    // 使用koa-router middleware
    const router = require('koa-router')()
    ​
    // 获取对应的service层
    const services = {
      admin: require('../services/AdminService'),
    }
    // 配置所有的routes文件
    const routes = (config => {
        return config.reduce((copy, name) => {
        const obj = require(`./${name}`) // 获取对应个单个文件  admin.js etc..
        const newArr = Object.keys(obj).reduce((total, each) => {
          // 配置path,method etc..
          // {  path: '/api/admin/list', method: post, action: list, server: 'admin' }
          let item = { path: `/api/${name.toLowerCase()}/${each}`, method: obj[each].method, action: each, service: name }
          total.push(item)
          return total
        }, [])
        copy = copy.concat(newArr)
          return copy
        }, [])
    })(Object.keys(services))
    ​
    // 配置最终的路由,形式为
    // router.get(url, service.action)
    ​
    routes.forEach(item => {
      const { method, path, service: serviceName, action } = item
      const service = services[serviceName]
      router[method](path, service[action])
    })
    module.exports = router
     
  • service层

    对请求参数的操作层

    对返回数据的加工层

    • 拿到request.body里面的参数

    • 调用对应的controller

    • 拿到返回的数据,加工返回

    • 文件目录

    • BaseService.js

      const { pojo,filterUnderLine,} = require('../helper') // 获取辅助类里面的一些方法
      const { success, failed, successWithCode } = pojo // 获取消息集里的一些辅助方法
      ​
      // 需要绑定this的方法
      const funcs = [
        'list',
        'insert',
        'update',
        'delete',
      ]
      class BaseService {
        constructor() {
          this.controller = null;
          // 循环遍历绑定this
          funcs.forEach(item => {
            this[item] = this[item].bind(this)
          })
      ​
        }
      ​
        // 查询方法
        async list(ctx) {
          // controller返回的是一个对象,success(成功为true, 失败为false), data(成功则有此数据), err(失败则有此对象)
          const { success: flag, data, err } = await this.controller.list()
          if (flag) {
            // success 为pojo消息集的成功返回
            ctx.body = success(
              data.map(item => 
                //  筛选下划线属性,返回驼峰
                filterUnderLine(item)
              )
            )
          } else {
            // failed 为pojo消息集的失败返回,下同
            ctx.body = failed(err)
          }
        }
        // 插入方法
        async insert(ctx) {
          const { row } = ctx.request.body
          const { success, err } = await this.controller.insert(row)
          if (success) {
            // successWithCode 为没有数据返回时的成功返回
            ctx.body = successWithCode('添加成功') // 没有数据则返回
          } else {
            // 同上
            ctx.body = failed(err)
          }
        }
        // 更新方法
        // 同上
        async update(ctx) {
          const { row } = ctx.request.body
          const { success, err } = await this.controller.update(row)
          if (success) {
            ctx.body = successWithCode('添加成功')
          } else {
            ctx.body = failed(err)
          }
        }
        // 删除方法
        // 同上
        async delete(ctx) {
          const { row } = ctx.request.body
          const { success, err } = await this.controller.delete(row)
          if (success) {
            ctx.body = successWithCode('添加成功')
          } else {
            ctx.body = failed(err)
          }
        }
      }
      ​
      module.exports = BaseService
    • AdminService.js

      // 导入基类
      const BaseService = require('./BaseService')
      // 导入对应的controller
      const Controller = require('../controller/AdminController')
      // 生成一次controller
      const AdminController = new Controller()
      class AdminService extends BaseService {
        constructor() {
          super()
          // 绑定对应的controller
          this.controller = AdminController;
        }
      }
      module.exports = new AdminService()
      ​
      
  • controller层

    对数据库的操作层

    • 接收service层传来的参数

    • 对参数进行处理

    • 执行sql语句,返回数据库结果

    • 文件目录

    • BaseController.js

      const pool = require('../lib/mysql') // 导入封装好的mysql库
      const { query } = pool // 导入query方法
      const { STATUS } = require('../enum') // 导入枚举类型STATUS
      const { NtNUpdate,filterCamel, } = require('../helper') // 导入helper内相关方法
      ​
      class BaseController {
        constructor() {
          this.table = ''
        }
        // 查询表内所有数据(非删除)
        async list() {
          const sql = `select * from ${this.table} where status != ?`
          return await query(sql, [STATUS.DELED])
        }
        // 插入新数据
        async insert(row) {
          const {
            keys,
            vals
          } = filterCamel(row) // 将驼峰命令转换为下划线
          const names = keys.join(',') // 对应的参数
          const questions = keys.map(item => '?').join(',') // 对应的参数占位符
          // 补全sql语句 insert into table (x, xx) values(x, xx)
          const sql = `insert into ${this.table}(${names},create_time,status) values(${questions},now(),?)`
          return await query(sql, [...vals, STATUS.NORMAL])
        }
      ​
        async update(row) {
          const {
            id,
          } = row; // 获取数据内的id
          // 删除id
          delete row.id
          // 启始sql
          let _sql = `update ${this.table} set `
          const {
            sql,
            args
          } = NtNUpdate(row, _sql)// 获取对象内非空值,加工sql 语句  update table set name=?, val= ?
          // 补全sql语句 update table set name = ?, val = ? where id = ?
          _sql = sql + 'where id = ?'
          return await query(_sql, [...args, id])
        }
      ​
        async delete(row) {
          const {
            id
          } = row // 获取数据内的id
          // 补全sql update table set status = ? where id = ?
          const sql = `update ${this.table} set status = ? where id = ?`
          return await query(sql, [STATUS.DELED, id])
        }
      ​
        excute(sql, vals) {
          // 执行方法
          return await = query(sql, vals)
        }
        // log 方法
        log({func, err}) {
          console.log(`excute function[${func}] occured error : ${err.message || err}`)
        }
      ​
      }
      ​
      module.exports = BaseController
    • AdminController

      const BaseController = require('./BaseController') // 获得基类
      ​
      class AdminController extends BaseController {
        constructor() {
          super();
          this.table = 'tour_admin' // 赋值table
        }
      }
      module.exports = AdminController
  • helper类

    • helper/index.js

      const pojo = require('./pojo')
      ​
      /**
       * 
       * @param {Object} params  参数对象
       * @param {String} sql sql语句
       * @description 根据参数对象去改变sql语句,最后返回对应的sql语句
       * @return 返回处理后的sql语句
       */
      const update = (params, sql) =>  {
        let keys = Object.keys(params)
        let arr = []
        keys.forEach((key) => {
          if (key) {
            sql = sql + `${key} = ? ,`
            arr.push(params[key])
          }
        })
        sql = sql.substring(0, sql.length - 1)
        return {
          args: arr,
          sql,
        }
      }
      /**
       * 
       * @param {String} val  原下划线值
       * @param {String} char 要替换的字符
       * @description 根据原key去替换下划线后转为驼峰
       * @return 返回处理后的key
       */
      const replaceUnderLine = (val, char = '_') => {
        const arr = val.split('')
        const index = arr.indexOf(char)
        arr.splice(index, 2, arr[index+1].toUpperCase())
        val = arr.join('')
        return val
      }
      ​
      /**
       * 
       * @param {String} val  原下划线值
       * @description 下划线转驼峰
       * @return 返回处理后的key
       */
      const underline2Camel = (val) => {
        return val.replace(/\_(\w)/g, (all, letter) => {
          return letter.toUpperCase()
        })
      }
      ​
      /**
       * 
       * @param {String} val  原key
       * @param {String} char  要替换的字符
       * @description 驼峰转下划线
       * @return 返回处理后的key
       */
      const camel2UnderLine = (val, char = '_') => {
        return val.replace(/([A-Z])/g,`${char}$1`).toLowerCase();
      }
      ​
      /**
       * 
       * @param {Object} obj  原对象
       * @param {String} char  要替换的字符
       * @description 对象驼峰转下划线
       * @return 返回处理后的keys 和 对应的vals { aboutExample: 'example' }, ['about_example'], ['example']
       * @return { Array } keys 
       * @return { Array } vals 
       */
      const fileterCamel = (obj, char = '_') => {
        const keys = Object.keys(obj)
        return keys.reduce((init, item) => {
          const str = item
          if (~item.indexOf(char)) {
            str = camel2UnderLine(item)
          }
          init.keys.push(str)
          init.vals.push(obj[item])
          return init
        }, {
          keys: [],
          vals: [],
        })
      }
      ​
      /**
       * 
       * @param {Object} obj  原对象
       * @param {String} char  要替换的字符
       * @description 对象下划线转驼峰
       * @return 返回处理后的对象 { about_example: 'example' } { aboutExample: 'example' }
       * @return { Object } obj  
       */
      const  filterUnderLine = (obj, char = '_') => {
        const arr =  Object.keys(obj).filter(item => ~item.indexOf(char))
        arr.forEach(item => {
          const val = obj[item]
          const key = underline2Camel(item)
          obj[key] = val
          delete obj[item]
        })
        return obj
      }
      ​
      module.exports = {
        NtNUpdate: update,
        filterUnderLine,
        replaceUnderLine,
        replaceCamel: fileterCamel,
        pojo,
      }
      ​
    • helper/pojo.js

      // 成功返回
      const success = (result) => {
        return {
          retCode: 200,
          retValue: result
        }
      }
      // 成功没数据返回
      const successWithCode = msg => {
        return {
          retCode: 200,
          msg,
        }
      }
      ​
      // 失败返回
      const failed = (error) => {
        console.log(error)
        return {
          retCode: 500,
          msg: error.message || error || '服务器异常'
        }
      }
      ​
      ​
      ​
      module.exports = {
        success,
        failed,
        successWithCode,
      }
      ​
  • enum枚举

    • enum/index.js

      const status = require('./status') // 获取状态
      const type = require('./type')// 获取类型
      ​
      // 导出
      module.exports = {
        STATUS: status,
        TYPES: type,
      }
      ​
    • enum/status.js

      module.exports = {
        DELED: 404, // 删除状态
        NORMAL: 0, // 普通状态
      }
    • enum/type.js

      module.exports = {
        MANAGER: 999, // 管理员
        USER: 0, // 用户
      }
      
       
  • lib/mysql.js
  • const mysql = require('mysql');
    const config = require('../config/default.js')
    ​
    // 连接池
    const pool  = mysql.createPool({ 
      host     : config.database.HOST, // 数据库连接端口
      user     : config.database.USERNAME, // 数据库用户名
      password : config.database.PASSWORD, // 数据库密码
      database : config.database.DATABASE // 数据库名称
    });
    ​
    /**
     * 
     * @param sql 接收的sql语句
     * @param {Array} values sql语句参数
     * @return { Object } { success: boolean, err || data  }
     */
    const query = function( sql, values ) {
      return new Promise(( resolve, reject ) => {
        pool.getConnection(function(err, connection) {
          if (err) {
            resolve( {
              success: false,
              err,
            } )
          } else {
            connection.query(sql, values, ( err, rows) => {
              if ( err ) {
                resolve({
                  success: false,
                  err,
                } )
              } else {
                resolve( {
                  success: true,
                  data: rows,
                } )
              }
              connection.release()
            })
          }
        })
      })
    }
    module.exports={
      query,
    }
  • config

  • config/default.js
    
    const config = {
      // 启动端口
      port: 1200,
      // 数据库配置
      database: {
        DATABASE: 'xxx',
        USERNAME: 'root',
        PASSWORD: 'xxx',
        PORT: 'xxx',
        HOST: 'xxx'
      }
    }
    ​
    module.exports = config

  • src/index.js

      

var Koa=require('koa');
var path=require('path')
var bodyParser = require('koa-bodyparser');
var session = require('koa-session-minimal');
var MysqlStore = require('koa-mysql-session');
var config = require('./config/default.js');
var koaStatic = require('koa-static')
var app=new Koa()
const routers = require('./routes/index')



// session存储配置
const sessionMysqlConfig= {
  user: config.database.USERNAME,
  password: config.database.PASSWORD,
  database: config.database.DATABASE,
  host: config.database.HOST,
}

// 配置session中间件
app.use(session({
  key: 'USER_SID',
  store: new MysqlStore(sessionMysqlConfig)
}))

// 配置跨域
app.use(async (ctx, next) => {
  ctx.set('Access-Control-Allow-Headers', 'Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With')
  ctx.set('Access-Control-Allow-Origin', 'http://localhost:9000');
  ctx.set('Access-Control-Allow-Methods', 'PUT,DELETE,POST,GET');
  ctx.set('Access-Control-Allow-Credentials', true);
  ctx.set('Access-Control-Max-Age', 3600 * 24);
  await next();
});
// 配置静态资源加载中间件
app.use(koaStatic(
  path.join(__dirname , './public')
))

// 使用表单解析中间件
app.use(bodyParser())

// 使用新建的路由文件
app.use(routers.routes()).use(routers.allowedMethods())

// 监听在1200
app.listen(config.port)

console.log(`listening on port ${config.port}`)

到这里项目就可以完整跑起来了,

最后奉上项目地址

喜欢就Star一下吧~

猜你喜欢

转载自blog.csdn.net/frank_come/article/details/81747985