实现一个简单 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全栈】
联系我,更多交流
有点高端的。。。。。。,这岂止15k+啊。。
怪不得我没过15K
我目前的能力估计只能做一下 “使用 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
connection: close 之后百度那个还是有问题,可能还是需要哪个 request header
@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 . . .
如果使用HTTP访问百度的时候,百度会要求跳转到HTTPS的,你检查是否有正确处理301或302状态码 @knight42
@leizongmin 唔…你可以试下访问 http://www.baidu.com/s?wd=qwe&ie=UTF-8&tn=baidulocal, 应该是没有跳转的.
但我现在遇到的问题是整个响应我都没有接收到
HTTP/1.1后面多了个空格。hexdump才看到。。。
@reverland 大神啊… 很抱歉给你带来了这么大的麻烦! 万分感谢!!!
@knight42 我是渣渣。。。这也是长姿势了。。
@magicdawn 。。。。。。简直醉了
1是data事件打印的,2是end打印的。。。
@reverland 哈哈~怎么dump的~求教。。。
@magicdawn @reverland 当时真的是手抖… 没想到居然是个空格的问题 Orz
┌─(~/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…
话说把另外几个题也做了吧, 我也想学习一下~
@reverland 在8000起本地server,然后等着dump,然后用问题代码去访问8000是吧?
傻傻的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);
});
@magicdawn 用tcpdump看也行,就是。。。包太多了。。
拿scapy自己做一个专门用来检查HTTP请求的。。。
你们好厉害,看样子窝离15k太远了,还是默默地搬窝的砖。。。
- 自己实现这些方法,比如parseURL用encodeURIComponent还是escape编码我心里没底,也没测试过
- 我代码里用了很多个Buffer.concat来拼接buffer,效率可能很低
- transfer-encoding:chunked这种情况我写了几段代码试图兼容,但写着写着我发现需要看paper才能写下去就删掉了
- 其实并没有做错误处理…
- 我发现自己应该多去看书ORZ
@reverland 这个轮子不好造啊… 我现在的项目正是在用python的sock_raw做这个事情… 求交流
@ayiis scapy啊。。。
@ayiis orz膜
@ayiis 不过说到raw socket,node中到socket真是异类,窝曾经想要用node写个sniffer,然后找到了别人封装到raw socket。。。
对于中级noder来说,题目都恰到好处啊。
mark
配图屌爆了
完成一点,感觉上代码会占大量篇幅 给 github[https://github.com/Tonyce/15kNodeTry]
静待大神指点
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);
}
嘿嘿嘿,我也来一发~~ MySQL orm ← 话说ORM不是要建模么
底层使用Promise封装,每次操作从连接池拿连接对象,开始所有数据库操作(也就是调用.table(‘table_name’)时)每次返回一个新的操作实例 _query 对象,这样就可以实现多个查询并行执行了~~ .目前的问题是当有非常多个操作发起时会生成多个操作实例,感觉上有点占内存。。。 ,每次调用前做了一次检查,每次查询必须首先调用table()
指定表,然后必须调用CRUD操作方法 find();delete();update();findOne()
指定此次的操作 , 最后选择性调用skip(); limit();
方法做结果的筛选。实际测试完全通过。 嘛如有不足还请一定提出指正。
var mysql = require('mysql');
function myOrm(connection){
connection = connection || {};
this._host = connection.host || '127.0.0.1';
this._port = connection.port || 3306;
this._user = connection.user || 'wanlf';
this._password = connection.password || 'wlf112111';
this._database = connection.database || 'VoidMirage';
this._result = null;
this._method = 'query';
//connect
this.pool = mysql.createPool({
host : this._host,
user : this._user,
password : this._password,
database : this._database
});
console.log('initialize successful.database connected...');
}
//use _query class to proxy every query function so as to make every query operation execute individualy.
function _query(table,pool){
this._table = table;
this.pool = pool;
}
_query.prototype.find = function(opt){
console.log('find here')
if(!this._table)
{
throw new Error('can not query data without a table set up.invoke table function first!');
}
this._sql = `select * from ${this._table} where 1=1 `;
this._opt = opt;
return this;
}
myOrm.prototype.table = function(table){
return new _query(table,this.pool)
}
_query.prototype.skip = function(skip_num){
console.log('skip here')
if(!this._sql)
{
throw new Error('can not skip data from empty result,find it first!');
}
this._skip_num = skip_num;
return this;
}
//one of the last function to be called
_query.prototype.limit = function(limit_num){
console.log('limit here')
if(!this._sql)
{
throw new Error('can not skip data from empty result,find it first!');
}
return new Promise((res,rej)=>{
this.pool.getConnection((err,conn)=>{
if(err)
{
rej(err);
}
else
{
if(this._opt)
{
var op = Object.keys(this._opt)
for(var key of op)
{
this._sql += `and ${key}=${this._opt[key]} `
}
}
if(this._skip_num)
{
this._sql += `limit ${this._skip_num} offset ${limit_num} `;
}
console.log('about query for '+this._table)
console.log(this._sql)
conn.query(this._sql,(err,result)=>{
if(err)
{
rej(err);
}
else
{
res(result);
}
_clean.call(this);
conn.release();
});
}
});
});
}
//one of the last function to be called
_query.prototype.exec = function(){
if(!this._table || !this._sql)
{
throw new Error('can not query data before the table and the queryinfo is set up.');
}
return new Promise((res,rej)=>{
this.pool.getConnection((err,conn)=>{
if(err)
{
rej(err);
}
else
{
if(this._upt)
{
var up = Object.keys(this._upt)
for(var key of up)
{
this._sql += ` ${key}='${this._upt[key]}' `;
this._sql +=',';
}
if(this._sql.endsWith(','))
{
this._sql = this._sql.substring(this._sql , this._sql.length - 1);
}
this._sql +='where 1=1'
}
if(this._opt)
{
var op = Object.keys(this._opt)
for(var key of op)
{
this._sql += ` and ${key}='${this._opt[key]}' `
}
}
if(this._skip_num)
{
this._sql += `limit ${this._skip_num}`;
}
if(this._take_num)
{
this._sql +=`offset ${this._take_num}`;
}
console.log(this._sql);
conn.query(this._sql,(err,result)=>{
if(err)
{
rej(err);
}
else
{
res(result);
}
_clean.call(this);
conn.release();
});
}
});
});
}
_query.prototype.take = function(take_num){
this._take_num = take_num;
return this;
}
_query.prototype.delete = function(opt){
if(!this._table)
{
throw new Error('can not delete data from a unknown table! call table function first!')
}
this._method = 'delete';
this._opt = opt;
this._sql = `delete from ${this._table} where 1=1`;
this._skip_num = null;
this._take_num = null;
return this;
}
_query.prototype.update = function(opt,upt){
if(!this._table)
{
throw new Error('can not update data from a unknown table! call table function first!')
}
this._sql = `update ${this._table} set `
this._method = 'update';
this._skip_num = null;
this._take_num = null;
this._opt = opt;
this._upt = upt;
return this;
}
_query.prototype.findOne = function(opt){
if(!this._table)
{
throw new Error('can not find data from a unknown table! call table function first!')
}
this._opt = opt;
this._sql = `select * from ${this._table} where 1=1 `
this._upt = null;
this._skip_num = 1;
this._take_num = null;
return this;
}
function _clean(){
this._table = null;
this._sql = null;
this._opt = null;
this._upt = null;
this._skip_num = null;
this._take_num = null;
}
module.exports.myOrm = myOrm;
如有不足还请指正。@leizongmin
留名关注,真是好问题!
mark From Noder
这应该是10K的面试题
学习一下大牛!