初始化项目

master
宇天 2017-02-28 18:27:38 +08:00
commit c2d8e2bcea
4 changed files with 808 additions and 0 deletions

7
History.md Normal file
View File

@ -0,0 +1,7 @@
1.0.0 / 2017-02-26
==================
* new project

255
Readme.md Normal file
View File

@ -0,0 +1,255 @@
![module info](https://nodei.co/npm/mysqli.png?downloads=true&downloadRank=true&stars=true)
# mysqli
> 本模块基于node-mysql模块二次封装对基础的增删改查主从库等按js的特点进行了简化并对SQL注入进行安全过滤让没有SQL基础的人也能顺利使用;
> 当然一些复杂的查询以及事务等这些不在我的服务之内而且会用到这些功能的童鞋本身也有一定的SQL基础了; 所以这类童鞋请自行使用各自习惯的SQL模块或手写实现。
## 使用npm安装
```bash
npm install mysqli
```
## 实例化
> 实例化可以传2种格式的配置1是json对象2是数组。
> 只有一个数据库时,默认是主库; 多于1个数据库服务时自动以第1个为主库其他的从库故实例化时`注意顺序`。
```javascript
let Mysqli = require('mysqli')
//传入json
let conn = new Mysqli({
host: '', // IP/域名
post: 3306, //端口, 默认 3306
user: '', //用户名
passwd: '', //密码
charset: '', // 数据库编码,默认 utf8 【可选】
db: '', // 可指定数据库,也可以不指定 【可选】
})
// 传入数组
let conn = new Mysqli([
{
host: 'host1', // IP/域名
post: 3306, //端口, 默认 3306
user: '', //用户名
passwd: '', //密码
charset: '', // 数据库编码,默认 utf8 【可选】
db: '', // 可指定数据库,也可以不指定 【可选】
},
{
host: 'host2', // IP/域名
post: 3306, //端口, 默认 3306
user: '', //用户名
passwd: '', //密码
charset: '', // 数据库编码,默认 utf8 【可选】
db: '', // 可指定数据库,也可以不指定 【可选】
},
])
```
## API方法
### 1. escape(val)
> 这是`node-mysql`的内部方法用于进行SQL安全过滤这里只是做了搬运工把它暴露出来给外部调用而已。
### 2. listDB()
> 顾名思义,该方法即用于列举当前账号权限范围内的所有的数据库名称,返回值是一个数组;
>
> **注:**`该方法配置await指令可得到纯粹的数据否则返回的是一个Promise对象`
```javascript
async function(){
let db = await conn.listDB()
console.log(db)
}
// 不使用await指令时返回的是Promise对象
conn.listDB().then(db => {
console.log(db)
})
```
### 3. useDB(db[, slave])
- db `<String>`
- slave `<Boolean>` 可选
> 该方法用于切换数据库,仅限于同一台机器上的数据库; 在配置中没有指定数据库的情况下,必须先调用该方法才可以进行后续的增删改查等操作。
>
> `db`即为要切换的数据库名; `slave`为是否从库查询,默认主库。
```javascript
async function(){
let docs = await conn.useDB('xx').query(`select * from users limit 10`);
console.log(docs);
}
// 不使用await指令时返回的是Promise对象
conn.useDB('xx')
.query(`select * from users limit 10`)
.then(docs => {
console.log(docs)
})
```
### 4. query(sql[, slave])
- sql `<String>`
- slave `<Boolean>` 可选
> 该方法用于当内置的方法满足不了需求时,可以自行编写`sql语句`执行; 但要注意防止`sql注入`,因为该方法是最基础的方法,模块不对传入的`sql语句`进行任何的安全过滤。
>
> `sql`即为要执行的sql语句; `slave`为是否从库查询,默认主库。
```javascript
async function(){
let docs = await conn.query(`select * from users limit 10`);
console.log(docs);
}
// 不使用await指令时返回的是Promise对象
conn.query(`select * from users limit 10`)
.then(docs => {
console.log(docs)
})
```
### 5. find(conf)
- conf `<Object>`
> 该方法用于查询多条数据。无论结果是多少条,返回的都是`数组格式`; 详细请看下面代码示例:
```javascript
conn.find({
table: '', // 要查询的表
select: ['a', 'b'], //要返回的字段,不传默认返回所有 【可选】
where: [{ //数组格式,可以组成多个条件,默认查询全表 【可选】
join: 'OR', //条件关系 AND, OR
op: '>', //关系符,如 =, >, <, <=, >=
key: 'aa',
val: 23
}],
sort: { //排序, key是要排序的字段,value是排序方式, 1顺序,-1逆序 【可选】
a: 1,
b: -1
},
limit: { // 查询范围,可用于分页 【可选】
start: 0,
size: 10
},
slave: false // 是否从库 【可选】
})
// 其中table这一项还可以联表但是仅限于 'left join'要使用其他的方式联表请自行编写sql语句
// where条件也可以直接使用sql语句要注意防止注入。
conn.find({
table: {
master: 'xx',
unite: [
{
table: 'aa',
on: 'xx.id = aa.xid'
},
//... 可以联多个表, 但都是 left join
]
},
where: `xx.id = 123`
})
```
### 6. findOne(conf)
- conf `<Object>`
> 该方法与上面的`find`方法的使用一致,区别只是该方法只返回一条数据,且为`json格式`。
### 7. count(conf)
- conf `<Object>`
> 该方法与上面的`find`方法的使用一致,不过返回的是条目总数(`<Number>`)
### 8. insert(conf)
- conf `<Object>`
> 该方法与上面的`find`方法的使用类似,手于插入一条数据,具体请看下面代码;
>
> **注:**`该方法一次只能插入一条数据`
```javascript
// 如果主键是自增ID则结果返回的是 刚插入的数据的自增ID
conn.insert({
table: 'xx',
data: {}, //要插入的数据
})
```
### 9. update(conf)
- conf `<Object>`
> 该方法与上面的`find`方法的使用类似,用于更新数据,具体请看下面代码;
> `该方法返回的是被修改的条目数量`
```javascript
// 如果修改成功,则返回被修改的数量
conn.update({
table: 'xx',
data: {}, //要修改的数据
where: `id = 123`
})
```
### 10. remove(conf)
- conf `<Object>`
> 该方法与上面的`find`方法的使用类似,用于删除指定条件的数据; 具体请看下面代码;
> `该方法返回的是被删除的条目数量`
```javascript
// 如果修改成功,返回的是被删除的条目数量
conn.update({
table: 'xx',
data: {}, //要修改的数据
where: `id = 123`
})
```

533
index.js Normal file
View File

@ -0,0 +1,533 @@
/**
* mysql操作类
* @authors yutent (yutent@doui.cc)
* @date 2015-11-24 11:31:55
*
*/
"use strict";
require('es.shim')
let mysql = require('mysql')
/**
* [parseWhere 格式化where条件]
* @param [array] arr [条件数组]
*/
function parseWhere(arr){
if(typeof arr === 'string' && !!arr){
return ' WHERE ' + arr
}else if(Array.isArray(arr) && arr.length > 0){
let where = ''
for(let it of arr){
it.join = it.join || 'AND'
it.op = it.op || '='
if(!/(^\(SELECT\s+.*\)$)|^`/.test(it.val) && !['IN', 'BETWEEN'].includes(it.op)){
it.val = mysql.escape(it.val)
}
where += `${it.join.toUpperCase()} ${it.key} ${it.op} ${it.val} `
}
where = ' WHERE ' + where.trim().replace(/^(AND|OR)/, ' ') + ' '
return where
}else{
return ' '
}
}
class Mysqli{
/**
* [constructor 构造数据库连接]
*/
constructor(conf){
if(!Array.isArray(conf))
conf = [conf]
//是否有从库
this.slave = conf.length > 1
this.conn = null
this.pool = mysql.createPoolCluster()
let idx = 0
while(idx < conf.length){
let cf = conf[idx]
cf.charset = cf.charset || 'utf8'
let name = idx === 0 ? 'MASTER' : ('SLAVE' + idx)
idx++
this.pool.add(name, {
host: cf.host,
port: cf.port,
user: cf.user,
password: cf.passwd,
charset: cf.charset,
collate: cf.charset + ((cf.charset === 'utf8mb4') ? '_unicode_ci' : '_general_ci'),
database: cf.db
})
}
}
//对外的escape方法
escape(val){
return mysql.escape(val)
}
//返回数据库列表
listDB(){
return new Promise((yes, no) => {
this.pool
.getConnection((err, conn) => {
if(err)
return no(`MySQL connect ${err}`)
conn.query('SHOW databases', (err, docs) => {
conn.release()
if(err)
return no('SHOW databases ' + err)
let res = []
for(let it of docs){
res.push(it.Database)
}
yes(res)
})
})
})
}
//选择database
useDB(db, slave){
slave = (slave && this.slave) ? 'SLAVE*' : 'MASTER'
this.conn = (async () => {
return await new Promise((yes, no) => {
this.pool
.getConnection(slave, (err, conn) => {
if(err)
return no(`MySQL connect ${err}`)
conn.query('USE ' + db, (err) => {
if(err)
return no('Select DB ' + err)
yes(conn)
})
})
})
})()
return this
}
/**
* [query sql语句执行]
* @param {[type]} sql [sql语句]
* @param {boolean} slave [是否从库]
*/
query(sql, slave){
slave = (slave && this.slave) ? 'SLAVE*' : 'MASTER'
if(typeof sql !== 'string')
return Promise.reject(`query error, argument sql must be string. ${typeof sql} given`)
return new Promise((yes, no) => {
if(this.conn){
this.conn.then(conn => {
conn.query(sql, (err, res) => {
conn.release()
this.conn = null
if(err)
return no(`Query ${err}; Last exec SQL: ${sql}`)
yes(res)
})
}).catch(no)
}else{
this.pool.getConnection(slave, (err, conn) => {
if(err)
return no(`MySQL connect ${err}`)
conn.query(sql, (err, res) => {
conn.release()
if(err)
return no(`Query ${err}; Last exec SQL: ${sql}`)
yes(res)
})
})
}
})
}
/**
* [find 基础的数据查询, 支持简单的联表查询]
* @param {[type]} conf [要查询的信息]
*
* e.g.
* .find({
* table: '',
* select: ['a', 'b'],
* where: [{ //数组格式,可以组成多个条件,默认查询全表 [可选]
* join: 'OR', //条件关系 AND, OR
* op: '>', //关系符,如 =, >, <, <=, >=
* key: 'aa',
* val: 23
* }],
* sort: { //排序, key是要排序的字段,value是排序方式, 1顺序,-1逆序 [可选]
* a: 1,
* b: -1
* },
* limit: { // 查询范围,可用于分页 [可选]
* start: 0,
* size: 10
* }
* })
*/
find(conf){
conf.slave = (conf.slave && this.slave) ? 'SLAVE*' : 'MASTER'
if(!conf.table)
return Promise.reject('Find Error: empty table')
let fields = '' //-------要返回的字段 ----------------
if(!conf.hasOwnProperty('select') || Object.empty(conf.select))
fields = '*'
else
fields = conf.select.join(',')
let sql = 'SELECT ' + fields
let table = '' //---------要查询的表 -----------------
if(typeof conf.table === 'string'){ //单表
table = ' FROM ' + conf.table
}else{ //联表
table = ' FROM ' + conf.table.master
for(let join of conf.table.unite){
table += ` LEFT JOIN ${join.table} ON ${join.on} `
}
}
sql += table
//查询条件 ---------------------------------------
sql += parseWhere(conf.where)
let sort = '' //排序 ----------------------------------
if(conf.sort && typeof conf.sort === 'object'){
sort = ' ORDER BY '
for(let i in conf.sort){
let c = ''
if(conf.sort[i] === -1)
c = 'DESC'
sort += `${i} ${c},`
}
sort = sort.slice(0, -1)
}
sql += sort
let limit = '' //--------查询范围 ----------
if(conf.hasOwnProperty('limit')){
let start = conf.limit.start || 0
let size = (conf.limit.size && conf.limit.size > 0) ? conf.limit.size : 1
limit = ` LIMIT ${start},${size} `
}
sql += limit
return new Promise((yes, no) => {
if(this.conn){
this.conn.then(conn => {
conn.query(sql, (err, res) => {
conn.release()
this.conn = null
if(err)
return no(`Find ${err}; Last exec SQL: ${sql}`)
yes(res)
})
}).catch(no)
}else{
this.pool.getConnection(conf.slave, (err, conn) => {
if(err)
return no(`MySQL connect ${err}`)
conn.query(sql, (err, res) => {
conn.release()
if(err)
return no(`Find ${err}; Last exec SQL: ${sql}`)
yes(res)
})
})
}
})
}
/**
* [findOne 查找一条记录, 参数同 find]
*/
findOne(conf){
let res = (async () => this.find({
table: conf.table,
select: conf.select || [],
where: conf.where || '',
sort: conf.sort,
slave: conf.slave,
limit: {start: 0, size: 1}
}))()
return new Promise((yes, no) => {
res.then(list => {
yes(list[0] || null)
}, no)
})
}
/**
* [count 计算结果总数, 参数同findOne]
*/
count(conf){
let res = (async () => this.find({
table: conf.table,
select: ['count(*) AS total'],
slave: conf.slave,
where: conf.where || ''
}))()
return new Promise((yes, no) => {
res.then(list => {
yes(list[0] && list[0].total || 0)
}, no)
})
}
/**
* [insert 插入数据,单条]
* @param {[object]} conf [要插入的信息,{table: '', data: {}} ]
*
* eg.
* .insert({
* table: 'test',
* data: {aa: 123, bb: 456}
* }, function(id){...})
*/
insert(conf){
conf.slave = (conf.slave && this.slave) ? 'SLAVE*' : 'MASTER'
if(!conf.table)
return Promise.reject('Insert Error: empty table')
let sql = 'INSERT INTO ' + conf.table + ' ('
let keys = []
let vals = []
for(let i in conf.data){
keys.push(i)
vals.push(mysql.escape(conf.data[i]))
}
sql += `${keys.join(',')}) VALUES (${vals.join(',')})`
return new Promise((yes, no) => {
if(this.conn){
this.conn.then(conn => {
conn.query(sql, (err, res) => {
conn.release()
this.conn = null
if(err)
return no(`Insert ${err}; Last exec SQL: ${sql}`)
yes(res.insertId)
})
}).catch(no)
}else{
this.pool.getConnection(conf.slave, (err, conn) => {
if(err)
return no(`MySQL connect ${err}`)
conn.query(sql, (err, res) => {
conn.release()
if(err)
return no(`Insert ${err}; Last exec SQL: ${sql}`)
yes(res.insertId)
})
})
}
})
}
/**
* [insert 基础的数据修改]
* @param {[object]} conf [要修改的信息, {table: '', where: [], data: {}}]
*
* eg.
* .update({
* table: 'test',
* data: {aa: 123, bb: 456},
* where: [{ //数组格式,可以组成多个条件
* join: 'OR', //条件关系 AND, OR
* op: '>', //关系符,如 =, >, <, <=, >=
* key: 'aa',
* val: 23
* }]
* }, function(nums){...})
*/
update(conf){
conf.slave = (conf.slave && this.slave) ? 'SLAVE*' : 'MASTER'
if(!conf.table)
return Promise.reject('Update Error: empty table')
let sql = 'UPDATE ' + conf.table + ' SET '
let fields = [] //要更新的字段
for(let i in conf.data){
fields.push(i + ' = ' + mysql.escape(conf.data[i]))
}
sql += fields.join(',')
sql += parseWhere(conf.where)
return new Promise((yes, no) => {
if(this.conn){
this.conn.then(conn => {
conn.query(sql, (err, res) => {
conn.release()
this.conn = null
if(err)
return no(`Update ${err}; Last exec SQL: ${sql}`)
yes(res.affectedRows)
})
}).catch(no)
}else{
this.pool.getConnection(conf.slave, (err, conn) => {
if(err)
return no(`MySQL connect ${err}`)
conn.query(sql, (err, res) => {
conn.release()
if(err)
return no(`Update ${err}; Last exec SQL: ${sql}`)
yes(res.affectedRows)
})
})
}
})
}
/**
* [remove 基础的数据删除]
* @param {[type]} conf [要删除的信息, {table: '', where: []}]
*
* eg.
* .update({
* table: 'test',
* where: [{ //数组格式,可以组成多个条件
* join: 'OR', //条件关系 AND, OR
* op: '>', //关系符,如 =, >, <, <=, >=
* key: 'aa',
* val: 23
* }]
* }, function(nums){...})
*/
remove(conf){
conf.slave = (conf.slave && this.slave) ? 'SLAVE*' : 'MASTER'
if(!conf.table)
return Promise.reject('Remove Error: empty table')
let sql = 'DELETE FROM ' + conf.table
if(conf.where)
sql += parseWhere(conf.where)
return new Promise((yes, no) => {
if(this.conn){
this.conn.then(conn => {
conn.query(sql, (err, res) => {
conn.release()
this.conn = null
if(err)
return no(`Remove ${err}; Last exec SQL: ${sql}`)
yes(res.affectedRows)
})
}).catch(no)
}else{
this.pool.getConnection(conf.slave, (err, conn) => {
if(err)
return no(`MySQL connect ${err}`)
conn.query(sql, (err, res) => {
conn.release()
if(err)
return no(`Remove ${err}; Last exec SQL: ${sql}`)
yes(res.affectedRows)
})
})
}
})
}
}
module.exports = Mysqli

13
package.json Normal file
View File

@ -0,0 +1,13 @@
{
"name": "mysqli",
"version": "2.0.0",
"description": "MySQL tool",
"main": "index.js",
"dependencies": {
"es.shim": "^0.0.2",
"mysql": "^2.13.0"
},
"repository": "https://github.com/yutent/mysqli.git",
"author": "yutent",
"license": "MIT"
}