智能微服務的設計與開發(node.js)

設計目標

基於koa二、關係數據庫(暫時只支持mysql)創建的智能微服務快速開發框架,將同時支持graphql與rest標準,使用typescript語言編寫,力求安全、高效。 前端

相關開源項目(gels -- 凝膠),希冀該項目能成爲聯結設計、開發,前端、後端的「強力膠水」,成爲微服務快速開發的有力框架。
項目地址:https://github.com/zhoutk/gelsjava

設計思路

中小型企業,更多的是注重快速開發、功能迭代。關係數據庫爲咱們提供了不少有用的支持,我試圖把數據庫設計與程序開發有機的結合起來,讓前端送到後端的json對象自動映射成爲標準的SQL查詢語句。個人這種ORM方式,服務端不須要寫一行代碼,只需完成關係數據庫的設計,就能爲前端提供標準服務接口。
我設計了一套數據庫訪問標準接口,在實踐中已經獲得很好的運用。我已經在es6, typescript, java, python & go中實現;下一步是對數據庫支持的擴展,準備支持流行的關係數據庫(Mssql, sqlite3, prostgres等),有選擇支持一些nosql,好比:mongo。node

數據庫接口設計

  • 事務元素接口,sql參數用於手動書寫sql語句,id會做爲最後一個參數被送入參數數組。python

    export default interface TransElement {
        table: string;
        method: string;
        params: object | Array<any>;
        sql?: string;
        id?: string | number;
    }
  • 數據庫操做接口,包括基本CURD,兩個執行手寫sql接口,一個批量插入與更新二合一接口,一個事務操做接口。實踐證實,下面八個接口,在絕大部分狀況下已經足夠。mysql

    export default interface IDao {
        select(tablename: string, params: object, fields?: Array<string>): Promise<any>;
        insert(tablename: string, params: object): Promise<any>;
        update(tablename: string, params: object, id: string|number): Promise<any>;
        delete(tablename: string, id: string|number): Promise<any>;
        querySql(sql: string, values: Array<any>, params: object, fields?: Array<string>): Promise<any>;
        execSql(sql: string, values: Array<any>): Promise<any>;
        insertBatch(tablename: string, elements: Array<any>): Promise<any>;
        transGo(elements: Array<TransElement>, isAsync?: boolean): Promise<any>;
    }
  • BaseDao,爲業務層提供標準數據庫訪問的基類,是自動提供標準rest微服務的關鍵git

    import IDao from './idao'
    let dialect = G.CONFIGS.db_dialect                    //依賴注入
    let Dao = require(`./${dialect}Dao`).default    
    
    export default class BaseDao {
        private table: string
        static dao: IDao                               //以組合的模式,解耦業務層與數據庫訪問層
        constructor(table?: string) {
            this.table = table || ''
            if (!BaseDao.dao) {
                BaseDao.dao = new Dao()
            }
        }
        async retrieve(params = {}, fields = [], session = {userid: ''}): Promise<any> {
            let rs
            try {
                rs = await BaseDao.dao.select(this.table, params, fields)
            } catch (err) {
                err.message = `data query fail: ${err.message}`
                return err
            }
            return rs
        }
        async create(params = {}, fields = [], session = {userid: ''}): Promise<any> {
            let rs
            try {
                rs = await BaseDao.dao.insert(this.table, params)
            } catch (err) {
                err.message = `data insert fail: ${err.message}`
                return err
            }
            let { affectedRows } = rs
            return G.jsResponse(200, 'data insert success.', { affectedRows, id: rs.insertId })
        }
        async update(params, fields = [], session = { userid: '' }): Promise<any> {
            params = params || {}
            const { id, ...restParams } = params
            let rs
            try {
                rs = await BaseDao.dao.update(this.table, restParams, id)
            } catch (err) {
                err.message = `data update fail: ${err.message}`
                return err
            }
            let { affectedRows } = rs
            return G.jsResponse(200, 'data update success.', { affectedRows, id })
        }
        async delete(params = {}, fields = [], session = {userid: ''}): Promise<any> {
            let id = params['id']
            let rs
            try {
                rs = await BaseDao.dao.delete(this.table, id)
            } catch (err) {
                err.message = `data delete fail: ${err.message}`
                return err
            }
            let {affectedRows} = rs
            return G.jsResponse(200, 'data delete success.', { affectedRows, id })
        }
    }

默認路由

  • /op/:command,只支持POST請求,不鑑權,提供登陸等特定服務支持es6

    • login,登陸接口;輸入參數{username, password};登陸成功返回參數:{status:200, token}
  • /rs/:table[/:id],支持四種restful請求,GET, POST, PUT, DELELTE,除GET外,其它請求檢測是否受權

中間件

  • globalError,全局錯誤處理中間件
  • router,路由中間件
  • logger,日誌,集成log4js,輸出系統日誌
  • session,使用jsonwebtoken,實現鑑權;同時,爲經過的鑑權的用戶生成對應的sessiongithub

    • 用戶登陸成功後獲得的token,在之後的ajax調用時,須要在header頭中加入token key

restful_api

數據表庫設計完成後,會自動提供以下形式的標準restful api,多表關係可用關係數據庫的視圖來完成。web

  • [GET] /rs/users[?key=value&...], 列表查詢,支持各類智能查詢
  • [GET] /rs/users/{id}, 單條查詢
  • [POST] /rs/users, 新增記錄
  • [PUT] /rs/users/{id}, 修改記錄
  • [DELETE] /rs/users/{id}, 刪除記錄

智能查詢

查詢保留字:fields, page, size, sort, search, lks, ins, ors, count, sum, group
  • fields, 定義查詢結果字段,支持數組和逗號分隔字符串兩種形式ajax

    查詢示例:  /rs/users?username=white&age=22&fields=["username","age"]
    生成sql:   SELECT username,age FROM users  WHERE username = ?  and age = ?
  • page, 分頁參數,第幾頁
  • size, 分頁參數,每頁行數
  • sort, 查詢結果排序參數

    查詢示例:  /rs/users?page=1&size=10&sort=age desc
    生成sql:   SELECT * FROM users  ORDER BY age desc LIMIT 0,10
  • search, 模糊查詢切換參數,不提供時爲精確匹配

    查詢示例:  /rs/users?username=i&password=1&search
    生成sql:   SELECT * FROM users  WHERE username like ?  and password like ?
  • ins, 數據庫表單字段in查詢,一字段對多個值,例:

    查詢示例:  /rs/users?ins=["age",11,22,26]
    生成sql:   SELECT * FROM users  WHERE age in ( ? )
  • ors, 數據庫表多字段精確查詢,or鏈接,多個字段對多個值,支持null值查詢,例:

    查詢示例:  /rs/users?ors=["age",1,"age",22,"password",null]
    生成sql:   SELECT * FROM users  WHERE  ( age = ?  or age = ?  or password is null )
  • lks, 數據庫表多字段模糊查詢,or鏈接,多個字段對多個值,支持null值查詢,例:

    查詢示例:  /rs/users?lks=["username","i","password",null]
    生成sql:   SELECT * FROM users  WHERE  ( username like ?  or password is null  )
  • count, 數據庫查詢函數count,行統計,例:

    查詢示例:  /rs/users?count=["1","total"]&fields=["username"]
    生成sql:   SELECT username,count(1) as total  FROM users
  • sum, 數據庫查詢函數sum,字段求和,例:

    查詢示例:  /rs/users?sum=["age","ageSum"]&fields=["username"]
    生成sql:   SELECT username,sum(age) as ageSum  FROM users
  • group, 數據庫分組函數group,例:

    查詢示例:  /rs/users?group=age&count=["*","total"]&fields=["age"]
    生成sql:   SELECT age,count(*) as total  FROM users  GROUP BY age
不等操做符查詢支持

支持的不等操做符有:>, >=, <, <=, <>, =;逗號符爲分隔符,一個字段支持一或二個操做。
特殊處:使用"="可使某個字段跳過search影響,讓模糊匹配與精確匹配同時出如今一個查詢語句中

  • 一個字段一個操做,示例:

    查詢示例:  /rs/users?age=>,10
    生成sql:   SELECT * FROM users  WHERE age> ?
  • 一個字段二個操做,示例:

    查詢示例:  /rs/users?age=>,10,<=,35
    生成sql:   SELECT * FROM users  WHERE age> ? and age<= ?
  • 使用"="去除字段的search影響,示例:

    查詢示例:  /rs/users?age==,22&username=i&search
    生成sql:   SELECT * FROM users  WHERE age= ?  and username like ?

高級操做

  • 新增一條記錄

    • url
    [POST]/rs/users
    • header
    Content-Type: application/json
        token: eyJhbGciOiJIUzI1NiIsInR...
    • 輸入參數
    {
            "username":"bill",
            "password":"abcd",
            "age":46,
            "power": "[\"admin\",\"data\"]"
        }
    • 返回參數
    {
            "affectedRows": 1,
            "id": 7,
            "status": 200,
            "message": "data insert success."
        }
  • execSql執行手寫sql語句,供後端內部調用

    • 使用示例
    await new BaseDao().execSql("update users set username = ?, age = ? where id = ? ", ["gels","99","6"])
    • 返回參數
    {
            "affectedRows": 1,
            "status": 200,
            "message": "data execSql success."
        }
  • insertBatch批量插入與更新二合一接口,供後端內部調用

    • 使用示例
    let params = [
                        {
                            "username":"bill2",
                            "password":"523",
                            "age":4
                        },
                        {
                            "username":"bill3",
                            "password":"4",
                            "age":44
                        },
                        {
                            "username":"bill6",
                            "password":"46",
                            "age":46
                        }
                    ]
        await new BaseDao().insertBatch('users', params)
    • 返回參數
    {
            "affectedRows": 3,
            "status": 200,
            "message": "data batch success."
        }
  • tranGo事務處理接口,供後端內部調用

    • 使用示例
    let trs = [
                    {
                        table: 'users',
                        method: 'Insert',
                        params: {
                            username: 'zhou1',
                            password: '1',
                            age: 1
                        }
                    },
                    {
                        table: 'users',
                        method: 'Insert',
                        params: {
                            username: 'zhou2',
                            password: '2',
                            age: 2
                        }
                    },
                    {
                        table: 'users',
                        method: 'Insert',
                        params: {
                            username: 'zhou3',
                            password: '3',
                            age: 3
                        }
                    }
                ]
        await new BaseDao().transGo(trs, true)          //true,異步執行;false,同步執行
    • 返回參數
    {
            "affectedRows": 3,
            "status": 200,
            "message": "data trans success."
        }

安裝運行

  • 運行數據腳本

    SET NAMES utf8;
    SET FOREIGN_KEY_CHECKS = 0;
    
    -- ----------------------------
    --  Table structure for `users`
    -- ----------------------------
    DROP TABLE IF EXISTS `users`;
    CREATE TABLE `users` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `username` varchar(255) DEFAULT NULL,
    `password` varchar(255) DEFAULT NULL,
    `age` int(11) DEFAULT NULL,
    `power` json DEFAULT NULL,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
    
    -- ----------------------------
    --  Records of `users`
    -- ----------------------------
    BEGIN;
    INSERT INTO `users` VALUES ('1', 'white', '123', '22', null), ('2', 'john', '456i', '25', null), ('3', 'marry', null, '22', null), ('4', 'bill', '123', '11', null), ('5', 'alice', '122', '16', null), ('6', 'zhoutk', '123456', '26', null);
    COMMIT;
    
    SET FOREIGN_KEY_CHECKS = 1;
  • 配置文件示例,./src/config/configs.ts

    export default {
        inits: {
            directory: {
                run: false,
                dirs: ['public/upload', 'public/temp']
            }
        },
        port: 5000,
        db_dialect: 'mysql',
        dbconfig: {
            db_host: 'localhost',
            db_port: 3306,
            db_name: 'strest',
            db_user: 'root',
            db_pass: '123456',
            db_char: 'utf8mb4',
            db_conn: 10,
        },
        jwt: {
            secret: 'zh-tf2Gp4SFU>a4bh_$3#46d0e85W10aGMkE5xKQ',
            expires_max: 36000      //10小時,單位:秒
        },
    }
  • 在終端(Terminal)中依次運行以下命令

    git clone https://github.com/zhoutk/gels
    cd gels
    npm i -g yarn
    yarn global install typescript tslint nodemon
    yarn install
    tsc -w          //或 command + shift + B,選 tsc:監視
    yarn start      //或 node ./dist/index.js

項目結構

├── package.json
├── src                              //源代碼目錄
│   ├── app.ts                       //koa配置及啓動
│   ├── common                       //通用函數或元素目錄
│   │   ├── globUtils.ts             
│   ├── config                       //配置文件目錄
│   │   ├── configs.ts
│   ├── db                           //數據封裝目錄
│   │   ├── baseDao.ts
│   ├── globals.d.ts                 //全局聲明定義文件
│   ├── index.ts                     //運行入口
│   ├── inits                        //啓動初始化配置目錄
│   │   ├── global.ts
│   │   ├── index.ts
│   │   ├── initDirectory.ts
│   ├── middlewares                  //中間件目錄
│   │   ├── globalError.ts
│   │   ├── logger.ts
│   │   ├── router
│   │   └── session.ts
│   └── routers                      //路由配置目錄
│       ├── index.ts
│       └── router_rs.ts
├── tsconfig.json
└── tslint.json

相關資源地址

凝膠(gels)項目: https://github.com/zhoutk/gels
視頻講座資料: https://github.com/zhoutk/sifou
我的博客: https://segmentfault.com/blog...

相關視頻課程

  1. 運用typescript進行node.js後端開發精要
  2. nodejs實戰之智能微服務快速開發框架
    視頻2亮點:

    • 成熟項目經驗,通過總結、提煉、精簡、重構,帶領你們一步步實現一個智能微服務框架,這是一個完整的實用項目從無到有的實現過程。
    • 課程內容與結構通過精心準備,設計合理、節奏緊湊、內容翔實。真實再現了思考、編碼、調試、除 錯的整個開發過程。
    • 購置了新的錄屏軟件和錄音設備,去除了上個視頻中的雜音,清晰度更高,視頻比較長,有三個小時。
相關文章
相關標籤/搜索