实现一个简单 MySQL ORM 模块
支持类似如下的使用方法(最好包含基本的单元测试):
const orm = new MyORM({
// mysql连接信息
connection: {host: '127.0.0.1', port: 3306, user: 'root', password: '', database: 'test'},
});
orm.table('xxxx').find(query).skip(0).limit(20)
.then(list => console.log('results', list))
.catch(err => console.log(err))
orm.table('xxxxx').update(query, update)
.then(ret => console.log(ret))
.catch(err => console.log(err))
// 另外需要支持基本的 delete, findOne 等方法
实现一个简单的 Redis Session 中间件
支持类似如下的使用方法(最好包含基本的单元测试):
// 初始化中间件
app.use(mySession({
connection: {host: '127.0.0.1', port: 6379}, // Redis连接信息
maxAge: 3600, // session的有效期
sessionId: 'my.sid', // session ID 的cookie名称
}));
// 使用时直接在 req.session 上添加或删除属性即可
使用 net 模块实现一个简单的 HTTP 客户端
支持类似如下的使用方法(最好包含基本的单元测试):
request({
method: 'POST', // 请求方法
url: 'http://xxx.com', // 请求地址
qs: {a: 123, b: 456}, // query查询参数
form: {c: 111, d: 'zxxxxx'}, // post body参数
headers: {
'user-agent': 'SuperID/Node.js', // 请求头
},
})
.then(ret => {
// ret.headers 表示响应头
// ret.statusCode 表示响应代码
// ret.body 表示响应内容(Buffer)
})
.catch(err => console.log(err))
实现一个简单的测试单元框架
支持类似如下的使用方法(最好包含基本的单元测试):
// 同步功能测试
test('测试1', function () {
assert.euqal(1 + 1, 2);
});
// 异步功能测试
test('测试2', function (done) {
setTimeout(function () {
assert.equal(2 + 2, 4);
done();
}, 100);
});
执行测试后返回类似如下的结果:
测试1 - 耗时100ms - 失败
测试2 - 耗时125ms - 通过
参与方式
本帖回复答案,@leizongmin 会给出指点
老雷简介
老雷(雷宗民),一登高级后端工程师,《Node.js实战(双色)》和《Node.js实战(第2版)》作者之一,xss模块作者,5年Node.js使用经验,GitHub: leizongmin
全文完
欢迎关注我的公众号【node全栈】
我目前的能力估计只能做一下 “使用 net 模块实现一个简单的 HTTP 客户端”, 详见 这里 不过有点奇怪的是, 对于某些网站的访问, 似乎不会触发 data 事件. 比方说 http://www.baidu.com/s?wd=qwe&ie=UTF-8&tn=baidulocal, 我对比过我的模块发出的 request header 跟 curl 发出的 request header, 它们是一样的, 但是我的模块就是获取不了响应… 不知道什么原因, 恳请指点.
可能是这个导致的
Transfer-Encoding:chunked
http://blog.csdn.net/yankai0219/article/details/8269922 https://zh.wikipedia.org/wiki/分块传输编码#.E6.A0.BC.E5.BC.8F
@magicdawn 嗯 那是因为 data 事件一直没触发
另外, 我认为跟 response header 里的某个参数关系不大. 因为不论 response header 里有什么参数, 它总得先把这个 header 给我发过来吧, 但我这里的情况相当于 socket connect 之后就直接 close 了.
而且, 访问 weibo.com 的时候, response header 里也是带有 Transfer-Encoding: chunked, 不过 data 事件却有触发.
顺便附上 curl 访问 baidu 的 request header: curl -vvv ‘http://www.baidu.com/s?wd=qwe&ie=UTF-8&tn=baidulocal’
- Trying 115.239.211.112…
- Connected to www.baidu.com (115.239.211.112) port 80 (#0)
GET /s?wd=qwe&ie=UTF-8&tn=baidulocal HTTP/1.1 Host: www.baidu.com User-Agent: curl/7.47.1 Accept: /
HTTP/1.1 200 OK . . .
@leizongmin 唔…你可以试下访问 http://www.baidu.com/s?wd=qwe&ie=UTF-8&tn=baidulocal, 应该是没有跳转的.
但我现在遇到的问题是整个响应我都没有接收到
┌─(~/Downloads)────────────────────────(reverland@reverland-R478-R429:pts/17)─┐
└─(19:52:10)──> nc -lvp 8000 |hexdump -C ──(日, 3月20)─┘
Listening on [0.0.0.0] (family 0, port 8000)
Connection from [127.0.0.1] port 8000 [tcp/*] accepted (family 2, sport 59636)
00000000 47 45 54 20 2f 73 3f 77 64 3d 71 77 65 26 69 65 |GET /s?wd=qwe&ie|
00000010 3d 55 54 46 2d 38 26 74 6e 3d 62 61 69 64 75 6c |=UTF-8&tn=baidul|
00000020 6f 63 61 6c 20 48 54 54 50 2f 31 2e 31 20 0a 48 |ocal HTTP/1.1 .H|
00000030 6f 73 74 3a 20 77 77 77 2e 62 61 69 64 75 2e 63 |ost: www.baidu.c|
00000040 6f 6d 0a 0a 0a 0a |om....|
00000046
┌─(~/Downloads)────────────────────────(reverland@reverland-R478-R429:pts/17)─┐
└─(20:00:52)──> nc -lvp 8000 |hexdump -C ──(日, 3月20)─┘
Listening on [0.0.0.0] (family 0, port 8000)
Connection from [127.0.0.1] port 8000 [tcp/*] accepted (family 2, sport 59650)
00000000 47 45 54 20 2f 73 3f 77 64 3d 71 77 65 26 69 65 |GET /s?wd=qwe&ie|
00000010 3d 55 54 46 2d 38 26 74 6e 3d 62 61 69 64 75 6c |=UTF-8&tn=baidul|
00000020 6f 63 61 6c 20 48 54 54 50 2f 31 2e 31 0a 48 6f |ocal HTTP/1.1.Ho|
00000030 73 74 3a 20 77 77 77 2e 62 61 69 64 75 2e 63 6f |st: www.baidu.co|
00000040 6d 0a 0a |m..|
00000043
@ayiis 我没什么好吐槽的… 你实现的完成度高多了, 错误处理也很完善跟周到, 而且没有像我那样使用 querystring 跟 url 偷懒 Orz…
话说把另外几个题也做了吧, 我也想学习一下~
傻傻的test
'use strict';
global.test = function(desc, fn) {
let start = Date.now();
// 异步
if (fn.length !== 0) {
return fn(report);
}
// 同步
else {
let e;
try {
fn();
}
catch (err) {
e = err;
}
finally {
report(e);
}
}
function report(err) {
let t = Date.now() - start;
let status = err ? '失败' : '通过';
console.log(`${ desc } - 耗时${ t }ms - ${ status }`);
}
};
const assert = require('assert');
// 同步功能测试
test('测试1', function() {
assert.equal(1 + 1, 2);
});
// 异步功能测试
test('测试2', function(done) {
setTimeout(function() {
assert.equal(2 + 2, 4);
done();
}, 100);
});
- 自己实现这些方法,比如parseURL用encodeURIComponent还是escape编码我心里没底,也没测试过
- 我代码里用了很多个Buffer.concat来拼接buffer,效率可能很低
- transfer-encoding:chunked这种情况我写了几段代码试图兼容,但写着写着我发现需要看paper才能写下去就删掉了
- 其实并没有做错误处理…
- 我发现自己应该多去看书ORZ
var mysql = require("mysql");
class MyOrm {
constructor(connect) {
if (typeof connect == "object" && typeof connect.connection == "object") {
this.pool = mysql.createPool(connect.connection);
} else {
throw new Error('please ....');
}
}
table(tableName) {
this.tablename = tableName;
return this;
}
find(q) {
this.opeart = "select * from " + this.tablename + " where 1 =1";
if (typeof q == "object") {
for (var ob in q) {
this.opeart = this.opeart + " and " + ob + "='" + q[ob] + "'";
}
}
return this;
}
skip(s) {
this.opeart = this.opeart + " limit " + s;
this.isLimt = true;
return this;
}
limit(l) {
if (this.isLimt) {
this.opeart = this.opeart + ", " + l;
} else {
this.opeart = this.opeart + "limit " + l;
}
return this;
}
then(callback) {
this.pool.query(this.opeart,function(err,data){
if(err){
this.err = err;
}else {
callback(data);
}
})
return this;
}
catch(callback){
if(this.err){
callback(this.err);}
else callback(null);
}
}
var tt = new MyOrm({
// mysql连接信息
connection: {
host: '',
port: 3306,
user: 'root',
password: '',
database: 'test'
},
});
tt.table('ak_user').find({userName: "nothing"}).skip(0).limit(12).then(data => console.log(data)).catch(err => console.log(err));
做了一部分 ,汗。。总感觉err那一块处理的不是很好
var mysql = require("mysql");
class MyOrm {
constructor(connect) {
if (typeof connect == "object" && typeof connect.connection == "object") {
this.pool = mysql.createPool(connect.connection);
} else {
throw new Error('please ....');
}
}
table(tableName) {
this.tablename = tableName;
return this;
}
find(q) {
this.opeart = "select * from " + this.tablename + " where 1 =1";
if (typeof q == "object") {
for (var ob in q) {
this.opeart = this.opeart + " and " + ob + "='" + q[ob] + "'";
}
}
return this;
}
skip(s) {
this.opeart = this.opeart + " limit " + s;
this.isLimt = true;
return this;
}
limit(l) {
if (this.isLimt) {
this.opeart = this.opeart + ", " + l;
} else {
this.opeart = this.opeart + "limit " + l;
}
return this;
}
then(callback) {
var p;
(function(pool,opeart){
p = new Promise(function(res,rej){
pool.query(opeart,function(err,data){
if(err){
rej(err);
}else {
res(data);
}
})
})
})(this.pool,this.opeart);
return p.then(callback) ;
}
}
var tt = new MyOrm({
// mysql连接信息
connection: {
host: '',
port: 3306,
user: '',
password: '',
database: ''
},
});
tt.table('ak_user').find({userName: "nothing"}).skip(0).limit(12).then(data => console.log(data)).catch(err => console.log(err));
加强版。ok的
@leizongmin nodejs新手,快速写出来的,求指导
var _ = require('lodash');
var mysql = require('mysql');
var Promise = require('bluebird');
/**
* 构造函数
* @param options 选项配置
* @constructor
*/
function MyORM(options){
this.connection = mysql.createConnection(options.connection);
Promise.promisifyAll(this.connection);
}
MyORM.prototype.table = function(name){
return new Query(this,name);
}
MyORM.prototype.execSQL = function(sql){
console.log(sql);
return this.connection.queryAsync(sql);
}
/**
* 构造函数
* @param orm MyORM对象
* @param tableName 表名
* @constructor
*/
function Query(orm,tableName){
if(!tableName) throw new Error('tableName required');
this._orm = orm;
this._tableName = tableName;
}
Query.prototype.find = function(query){
this._op = "SELECT";
this._where = parseParamsObject(query);
return this;
}
Query.prototype.select = function(select){
this._select=select;
return this;
}
Query.prototype.skip = function(skip){
this._skip = skip;
return this;
}
Query.prototype.limit = function(limit){
this._limit = limit;
return this;
}
Query.prototype.findOne = function(query){
this._op = "SELECT_ONE";
this._where = parseParamsObject(query);
return this;
}
Query.prototype.delete = function(query){
this._op = "DELETE";
this._where = parseParamsObject(query);
return this;
}
Query.prototype.update = function(query,update){
this._op = "UPDATE";
this._where = parseParamsObject(query);
this._update = update;
return this;
}
Query.prototype.save = function(save){
this._op = "INSERT";
this._save = save;
return this;
}
/**
* 生成SQL语句并且执行
* @private
*/
Query.prototype._doQuery = function(){
var sql,op = this._op;
if(op === 'SELECT'){
var select = this._select || '*';
sql = 'SELECT '+select+' from '+this._tableName;
if(this._where){
sql += ' where ' + this._where;
}
var limit='';
if(this._skip){
limit += this._skip;
}
if(this._limit){
if(limit) limit += ','
limit += this._limit;
}
if(limit){
sql += ' limit ' + limit;
}
}else if(op === 'UPDATE'){
sql = 'UPDATE ' +this._tableName + ' SET '+ mysql.escape(this._update);
if(this._where){
sql += ' where ' + this._where;
}
if(this._limit){
sql += ' limit '+this._limit;
}
}else if(op === 'DELETE'){
sql = 'DELETE from ' +this._tableName;
if(this._where){
sql += ' where ' + this._where;
}
if(this._limit){
sql += ' limit '+this._limit;
}
}else if(op === 'SELECT_ONE'){
var select = this._select || '*';
sql = 'SELECT '+select+' from '+this._tableName;
if(this._where){
sql += ' where ' + this._where;
}
sql += ' limit 1';
}else if(op === 'INSERT'){
sql = 'INSERT INTO ' +this._tableName + ' SET '+ mysql.escape(this._save);
}
var retVal = this._orm.execSQL(sql);
if(op === 'SELECT_ONE'){
retVal = retVal.then(function(rs){
return rs[0];
});
}
return retVal;
}
Query.prototype.then = function(){
var pro = this._doQuery();
return pro.then.apply(pro,arguments);
}
//简单的测试:
var orm = new MyORM({
// mysql连接信息
connection: {host: '127.0.0.1', port: 3306, user: 'root', password: '', database: 'test'},
});
orm.table('user').find({name:'高端人士',age:1,$or:{height:1,weight:2}})
.skip(1).limit(10).select('name,age')
.then(function(rs){
console.log(rs);
}).catch(function(e){
console.error(e);
})
//sql == > SELECT name,age from user where (name='测试' AND age=1 AND (height=1 OR weight=2)) limit 1,10
/**
* 简单的根据对象参数生成SQL的where条件,$and表示sql的and,$or表示sql的or
* @param obj
* @param parseValue
*/
function parseParamsObject(obj,parseValue){
if(!parseValue){
parseValue = function(key,value){
//如需要处理大于小于等其他操作符的情况,可以自行修改
return key + '=' + mysql.escape(value);
}
}
return parse('$and',obj,parseValue)
}
function joinCriteria(arr,join){
if(!arr || arr.length === 0){
return;
}
return '('+arr.join(' '+join+' ')+')';
}
function parseChild(key,obj,join,parseValue){
var ret = [];
if(_.isArray(obj)){
obj.forEach(function(val){
if(_.isPlainObject(val)){
var v = parseParamsObject(val,parseValue);
if(v != null){
ret.push(v);
}
}
});
}else if(_.isPlainObject(obj)){
Object.keys(obj).forEach(function(key){
var val = obj[key];
var v = parse(key,val,parseValue);
if(v != null){
ret.push(v);
}
});
}
return joinCriteria(ret,join);
}
function parse(key,val,parseValue){
if(key === '$and'){
return parseChild(key,val,'AND',parseValue);
}
if(key === '$or'){
return parseChild(key,val,'OR',parseValue);
}
if(_.isPlainObject(val) && _.includes(Object.keys(val),'$or')){
var vs = val['$or'];
var temp = [];
if(_.isArray(vs)){
vs.forEach(function(ele){
var v = parseValue(key,ele);
if(v != null){
temp.push(v);
}
});
}
return joinCriteria(temp,'OR');
}
return parseValue(key,val);
}