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

設計目標

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

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

設計思路

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

數據庫接口設計

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

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

    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微服務的關鍵

    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請求,不鑑權,提供登錄等特定服務支持

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

中間件

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

    • 用戶登錄成功後得到的token,在以後的ajax調用時,需要在header頭中加入token key

restful_api

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

  • [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, 定義查詢結果字段,支持數組和逗號分隔字符串兩種形式

    查詢示例:  /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://github.com/zhoutk/blog

相關視頻課程

  1. 運用typescript進行node.js後端開發精要
    這是視頻2的先導課免費入口,數量有限,先到先得
  2. nodejs實戰之智能微服務快速開發框架
    視頻2亮點:

    • 成熟項目經驗,經過總結、提煉、精簡、重構,帶領大家一步步實現一個智能微服務框架,這是一個完整的實用項目從無到有的實現過程。
    • 課程內容與結構經過精心準備,設計合理、節奏緊湊、內容翔實。真實再現了思考、編碼、調試、除 錯的整個開發過程。
    • 購置了新的錄屏軟件和錄音設備,去除了上個視頻中的雜音,清晰度更高,視頻比較長,有三個小時。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章