智慧微服務的設計與開發(node.js)
設計目標
基於koa2、關係資料庫(暫時只支援mysql)建立的智慧微服務快速開發框架,將同時支援graphql與rest標準,使用typescript語言編寫,力求安全、高效。
相關開源專案(gels -- 凝膠),希冀該專案能成為聯結設計、開發,前端、後端的“強力膠水”,成為微服務快速開發的有力框架。
專案地址:ofollow,noindex" target="_blank">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 usersWHERE username = ?and age = ?
- page, 分頁引數,第幾頁
- size, 分頁引數,每頁行數
-
sort, 查詢結果排序引數
查詢示例:/rs/users?page=1&size=10&sort=age desc 生成sql:SELECT * FROM usersORDER BY age desc LIMIT 0,10
-
search, 模糊查詢切換引數,不提供時為精確匹配
查詢示例:/rs/users?username=i&password=1&search 生成sql:SELECT * FROM usersWHERE username like ?and password like ?
-
ins, 資料庫表單欄位in查詢,一欄位對多個值,例:
查詢示例:/rs/users?ins=["age",11,22,26] 生成sql:SELECT * FROM usersWHERE age in ( ? )
-
ors, 資料庫表多欄位精確查詢,or連線,多個欄位對多個值,支援null值查詢,例:
查詢示例:/rs/users?ors=["age",1,"age",22,"password",null] 生成sql:SELECT * FROM usersWHERE( age = ?or age = ?or password is null )
-
lks, 資料庫表多欄位模糊查詢,or連線,多個欄位對多個值,支援null值查詢,例:
查詢示例:/rs/users?lks=["username","i","password",null] 生成sql:SELECT * FROM usersWHERE( username like ?or password is null)
-
count, 資料庫查詢函式count,行統計,例:
查詢示例:/rs/users?count=["1","total"]&fields=["username"] 生成sql:SELECT username,count(1) as totalFROM users
-
sum, 資料庫查詢函式sum,欄位求和,例:
查詢示例:/rs/users?sum=["age","ageSum"]&fields=["username"] 生成sql:SELECT username,sum(age) as ageSumFROM users
-
group, 資料庫分組函式group,例:
查詢示例:/rs/users?group=age&count=["*","total"]&fields=["age"] 生成sql:SELECT age,count(*) as totalFROM usersGROUP BY age
不等操作符查詢支援
支援的不等操作符有:>, >=, <, <=, <>, =;逗號符為分隔符,一個欄位支援一或二個操作。
特殊處:使用"="可以使某個欄位跳過search影響,讓模糊匹配與精確匹配同時出現在一個查詢語句中
-
一個欄位一個操作,示例:
查詢示例:/rs/users?age=>,10 生成sql:SELECT * FROM usersWHERE age> ?
-
一個欄位二個操作,示例:
查詢示例:/rs/users?age=>,10,<=,35 生成sql:SELECT * FROM usersWHERE age> ? and age<= ?
-
使用"="去除欄位的search影響,示例:
查詢示例:/rs/users?age==,22&username=i&search 生成sql:SELECT * FROM usersWHERE 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