Quantcast
Channel: CNode:Node.js专业中文社区
Viewing all 14821 articles
Browse latest View live

【北京】人人车诚招前端工程师

$
0
0

初中级前端工程师● 工作内容 ○ 负责与产品、后端协作,高效完成业务系统的前端开发 ○ 负责开发通用组件库、性能优化相关工作 ● 要求 ○ 精通JavaScript、CSS、HTML等前端相关技术 ○ 熟练掌握Vue、ES6、Webpack等 ○ 学习能力强,有较强的分析问题、解决问题能力 ○ 良好的沟通及人际技巧,与团队成员高效协作,快速融入团队 ○ 对前端技术有持续的热情,个性乐观开朗,逻辑性强,善于和各种背景的人合作 ○ 工作经验1年以上 ● 加分项 ○ 熟悉Nodejs,并有实际产出 ○ 有React Native开发经验 ○ 有技术博客或GitHub账号并持续更新 ○ 热衷学习与自我修炼,喜欢阅读技术文档和书籍 ○ 熟练掌握React、Vue及相关框架和技术,理解框架设计原理 ● 职位诱惑 ○ 晋升空间 ○ 团队氛围好 ○ 发展前景 ○ 技术大牛 高级前端工程师● 工作内容 ○ 负责与产品、后端协作,高效完成业务系统的前端开发 ○ 指导初中级工程师 ○ 负责开发通用组件库、性能优化相关工作 ○ 负责建设前端系统架构 ○ 项目重构 ● 要求 ○ 精通JavaScript、CSS、HTML等前端相关技术 ○ 熟练掌握Vue、ES6、Webpack等 ○ 有一定的前端架构能力 ○ 具备页面性能优化能力 ○ 对前端技术有持续的热情,个性乐观开朗,逻辑性强,善于和各种背景的人合作 ○ 3年以上前端开发经验 ● 加分项 ○ 有React Native开发经验 ○ 有技术博客或GitHub账号并持续更新 ○ 热衷学习与自我修炼,喜欢阅读技术文档和书籍 ○ 熟练掌握React、Vue及相关框架和技术,理解框架设计原理 ○ 有NodeJS开发经验,并有实际产出 ● 职位诱惑 ○ 晋升空间 ○ 团队氛围好 ○ 发展前景 ○ 技术大牛

备注薪资:具体看能力 简历投递:wangnan@renrenche.com


Egg.js 如何优雅的校验参数?

$
0
0

在开发过程中我们很容易在控制器内写出下面的代码:

// app/controller/topics.js
const Controller = require('egg').Controller;

// 定义创建接口的请求参数规则
const createRule = {
  ...
};

class TopicController extends Controller {
  async create() {
     ...
  }
}
module.exports = TopicController;

在接口较少的时候尚能条理清晰。一旦接口多起来,接口逻辑就会被行数众多的参数校验掩盖。

const createRule = {
  ...
}
const selectRule = {
  ...
}
const destroyRule = {
  ...
}
const updateRule = {
  ...
}
...

我们应当如何解耦呢,或者有无合适的轮子? 我尝试过使用装饰器,将参数校验内容注入方法,让校验与方法体更近,但是仍然有些朋克风。

【菜鸟求助】请教下为什么npm会突然无法使用,npm -v时提示下图错误,node -v可以显示

请问有Egg.js好的开源项目吗

$
0
0

大佬们,请问有Egg.js好的开源项目吗?

[杭州/成都] 蚂蚁金服2019届前端工程师实习生校园招聘开启啦!

$
0
0

如果你关注企业级应用设计或前端,一定听过蚂蚁设计语言 Ant Design,它在过去 14 个月在 GitHub 的 star 数从 4k 飙升至 2w+。

如果你关注数据可视化,或许会知道 G2这款技术产品,刚一开源就得到世界级计算机科学家 Leland Wilkinson的肯定。其背后便是蚂蚁可视化 AntV体系。

为企业级框架和应用而生的 Node.js 框架 egg.js、轻量级前端应用框架 dva 这些开源技术统统都出自蚂蚁金服体验科技。

首届蚂蚁金服体验科技大会》刚刚结束不久,大家是否还意犹未尽呢?

蚂蚁金服体验科技的背后是国内乃至全球最优秀的前端+设计团队,大牛频出,更有网红,包括但不限于

[
  {name: '玉伯', weibo: '玉伯也叫黑侠'},
  {name: '苏千', github: 'fengmk2'},
  {name: '民工叔', weibo: '民工精髓V'},
  {name: '偏右', github: 'afc163'},
  {name: '死马', github: 'dead-horse'},
  {name: '阮一峰', blog: '阮一峰的网络日志'}
  {name: '御术', weibo: 'Kener-林峰'}
]

蚂蚁体验科技本年度的春季实习校招已经开始,主要面向 2019 年毕业生,欢迎大家投递简历到 suqian.yf@antfin.com邮箱地址,任何疑问,都可以直接回复咨询。

技术方向涵盖:前端,数据可视化,数据图形,Node.js 服务端,cli 工程自动化工具,IDE 开发工具等等,Web 领域的前沿技术也非常欢迎你来挑战!

nodejs项目如何做监控预警

$
0
0

比如线上的nodejs服务挂掉时邮件提醒

【杭州】 2018 阿里云-天池平台 寻找前端技术专家

$
0
0

平台介绍

平台地址 : https://tianchi.aliyun.com/

由阿里云-天池团队主持,联合阿里集团内的智能团队(飞天一部、达摩院、iDst、蚂蚁金服、阿里妈妈、阿里小蜜等), 打造AI分享、学习、比赛、合作一体的 AI技术社区。

目前 天池大赛已经吸引了国内外的众多选手参赛,赛事也非常密集(围绕AI领域的难题)。之前推出的AI主题的一些直播教程,也非常受欢迎。

此外,国际站正在筹备中,未来将是个全球的平台(对的,你会和外籍同事一起共事)。

岗位: 前端技术专家

目前平台正在升级阶段,重构升级 以及 国际化,非常好机会,我们寻找这样的你:

  • 扎实的前端基础,具备架构能力
  • 丰富的项目经验,有技术团队管理经验
  • 独挡一面,有技术热情,工作认真,勇于挑战难题
  • 有移动端经验,熟悉微信、微博等社交媒体开发的 +加分
  • 有后端经验,熟悉node.js/java +加分
  • 有开源项目、或参与的开源项目 +加分

欢迎投递简历: jianxun.zxl@alibaba-inc.com, 邮件标题请备注: 天池平台-简历-$name

*** 注意:阿里集团的职位之间是互斥的,一份简历尽可能先投递到最中意的职位 ***

Node 循环依赖之源码解析

$
0
0

讲一讲 Node 循环依赖的问题,以官网上的例子结合 Node 源码来分析为什么循环依赖不会导致死循环,以及循环依赖可能造成的问题。

案例

官网上给出的例子是这样的:

a.js:

console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');

b.js:

console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');

main.js:

console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main, a.done=%j, b.done=%j', a.done, b.done);

官网的解释是: main.js先加载 a.js,然后 a.js中会加载 b.js,但是在 b.js中又加载了 a.js。这个时候为了防止无限循环,会将a.js未完成的 exports对象返回给 b.js模块,接着 b.js完成加载,并且它的 exports对象被提供给 a.js模块。

由此可以看出,之所以不会发生依赖的死循环,是因为模块能够导出未完成的 exports对象。那么问题来了,为什么模块没有执行完,却能导出对象呢?

下面通过分析模块源码 lib/module.js来解答这个问题。要注意的是,核心模块和文件模块(用户编写的模块)的加载是不同的,本文只讨论文件模块的加载。为了便于理解,会对源码进行简化。

Module 构造函数

在 Node 中,每个模块在被 require导入的时候都会创建一个模块实例,即 Module实例,并且 Node 会缓存每个模块的实例,以便在下次 require该模块的时候可以直接从缓存中返回。

模块实例有一个 exports属性,初始化为空对象。当我们在文件模块中通过 module.exportsexports来导出的时候,其实就是在给模块实例的 exports添加属性或者直接重写它。

// Module 构造函数
function Module(id, parent) {
  this.id = id;
  this.exports = {};
  this.parent = parent;
  updateChildren(parent, this, false);
  this.filename = null;
  this.loaded = false;
  this.children = [];
}

require 方法

require方法定义在 Module的原型链上,被每个模块实例共享。

Module.prototype.require = function(id) {
  return Module._load(id, this, false);
};

require内部调用 Module._load方法,下面是简化后的 _load方法。

Module._load = function(request, parent, isMain) {
  // 获取模块文件的绝对路径
  var filename = Module._resolveFilename(request, parent, isMain);

  // 如果有缓存,直接返回缓存中的模块实例的 exports 属性
  var cachedModule = Module._cache[filename];
  if (cachedModule) {
    return cachedModule.exports;
  }

  // 如果是核心模块,使用 NativeModule.require 方法加载
  if (NativeModule.nonInternalExists(filename)) {
    return NativeModule.require(filename);
  }

  // 创建模块实例,并存入缓存
  var module = new Module(filename, parent);
  Module._cache[filename] = module;

  // 加载模块
  module.load(filename);

  return module.exports;
};

上面的代码中,以模块的绝对路径作为模块id,优先从缓存中获取模块实例的 exports属性。如果模块实例不在缓存中,则创建模块实例并存入缓存,最后根据模块id调用 module.load加载该模块。

加载模块

模块的加载通过 module.load方法完成,该方法根据模块的绝对路径确定文件扩展名,不同的文件扩展名采用不同的加载方法。

Module.prototype.load = function(filename) {
  this.filename = filename;
  this.paths = Module._nodeModulePaths(path.dirname(filename));

  var extension = path.extname(filename) || '.js';
  if (!Module._extensions[extension]) extension = '.js';
  Module._extensions[extension](this, filename);
  this.loaded = true;
};

.js扩展名为例,处理方法如下:

Module._extensions['.js'] = function(module, filename) {
  var content = fs.readFileSync(filename, 'utf8');
  module._compile(internalModule.stripBOM(content), filename);
};

module._compile方法对模块文件进行编译执行。

Module.prototype._compile = function(content, filename) {
  // 将模块代码包装在一个函数中
  var wrapper = Module.wrap(content);

  // 在当前全局上下文中编译包装好的模块代码,并返回一个可执行的函数
  var compiledWrapper = vm.runInThisContext(wrapper, { ... });

  // 获取模块目录的路径
  var dirname = path.dirname(filename);

  // 扩展 require 方法,如添加 require.resolve、require.cache 等属性
  var require = internalModule.makeRequireFunction(this);

  // 传入模块实例的 exports 属性、require 方法、模块实例自身、完整文件路径
  // 和文件目录来执行该函数。
  var result = compiledWrapper.call(this.exports, this.exports, require, this,
                                  filename, dirname);
  return result;
};
Module.wrap = function(script) {
  return Module.wrapper[0] + script + Module.wrapper[1];
};

Module.wrapper = [
  '(function (exports, require, module, __filename, __dirname) { ',
  '\n});'
];

vm.runInThisContext这个方法会在 V8 虚拟机环境中编译代码,并在当前全局的上下文中运行代码并返回结果。在全局上下文运行的好处是在模块中我们可以使用一些全局变量,如:processconsole等。具体参考:vm 的官方文档

上面的代码使用 Module.wrap将模块代码包装在函数中,这样就避免了作用域被污染,接着通过执行 vm.runInThisContext返回一个可执行的函数,最后传入当前模块实例的 exports属性、模块实例的 this,以及require方法、完整文件路径和文件目录来执行该函数。

由此也可以看出,module.exportsexports并不是全局的,而是在执行模块代码的包装函数时传入的参数(当前模块实例的 exports)。这也解释了为什么在文件模块中重写 exports会无法导出,因为它只能改变函数形参的引用,而无法实际影响到当前模块实例的 exports属性。

循环依赖

在分析完模块的整个加载过程后,回到上面那个问题:为什么模块没有执行完,却能导出对象呢?关键就在于,在加载模块时,如果模块没有缓存,会先创建模块实例,然后存入缓存,再编译执行模块代码。

以官网的例子来说:

  1. a.js先加载,所以先缓存 a.js的模块实例,然后编译执行 a.js
  2. 在执行 a.js的过程中,先导出 exports.done = false,此时 a.js模块实例的 exports属性值为 { done: false }。接着加载 b.js
  3. b.js在执行过程中发现需要加载 a.js,此时由于 a.js模块已经被缓存,所以直接获取到缓存中的 a.js模块实例的 exports属性,值为 { done: false },然后继续执行。
  4. b.js执行完毕返回,a.js继续执行。

这种循环依赖导致的问题很明显:

  1. b.js在执行过程中获取到的 a.js的导出可能是不完整的。
  2. 如果 a.js在加载 b.js后重写了 module.exportsb.js中获取到的 a.js的导出还是维持着旧的引用。

具体的解决方案可以参考 Maples7 的博客:Node.js 中的模块循环依赖及其解决

References


基于 Node.js 实现压缩和解压缩

$
0
0

压缩格式

zip 和 gzip 是两种我们最常见到的压缩格式,当然,gzip 在 Windows 下很少有人接触。 tar 是一种归档格式,它默认不会压缩,需要结合 gzip 来将最终的 tar 文件以 gzip 格式压缩成为一个 tar.gz 文件,通常我们会缩写为 tgz。

为什么没有提到 rar?因为它是专利保护的算法,你可以免费获得解压工具,而压缩工具是需要付费的。所以我们一般应用场景下,很少会提供 rar 压缩文件。

本文将分别介绍 gzip,tar,tgz 和 zip 的压缩和解压缩在 Node.js 下如何实现。

未压缩文件库

本文所使用的未压缩文件库来自于 urllib,需要先 clone 它下来到指定目录。

git clone https://github.com/node-modules/urllib.git nodejs-compressing-demo

gzip

在 Linux 的世界,每个工具的职责会很纯粹,非常单一,如 gzip,它只会对文件进行压缩,至于文件夹如何打包压缩,跟它没关系,那是 tar 要去负责的事情。

gzip 命令行压缩一个文件

例如我们要将 nodejs-compressing-demo/lib/urllib.js文件进行 gzip 压缩,会得到一个 urllib.js.gz文件,源文件会被删除。

$ ls -l nodejs-compressing-demo/lib/urllib.js 
-rw-r--r--  1 a  a  31318 Feb 12 11:27 nodejs-compressing-demo/lib/urllib.js

$ gzip nodejs-compressing-demo/lib/urllib.js

$ ls -l nodejs-compressing-demo/lib/urllib.js.gz 
-rw-r--r--  1 a  a  8909 Feb 12 11:27 nodejs-compressing-demo/lib/urllib.js.gz

# 还原压缩文件
$ gunzip nodejs-compressing-demo/lib/urllib.js.gz

文件大小从 31318 字节减少到 8909 字节,超过 3.5 倍的压缩效果。

还可以通过 pipe 方式,结合 cat命令,将文件压缩并保存为任意文件:

$ ls -l nodejs-compressing-demo/README.md
-rw-r--r--  1 a  a  13747 Feb 12 11:27 nodejs-compressing-demo/README.md

$ cat nodejs-compressing-demo/README.md | gzip > README.md.gz

$ ls -l README.md.gz 
-rw-r--r--  1 a  a  4903 Feb 12 11:50 README.md.gz

Node.js 实现 gzip

当然,我们不会真的从零开始实现一个 gzip 算法和工具,在 Node.js 的世界,早已有人为你准备好这些基础库,我们只需要开箱即用。 本文将会使用 compressing模块,实现所有压缩和解压缩代码。

为什么会选择 compressing?因为它有足够充分的代码质量和单元测试保证,处于活跃的维护状态,API 非常友好,而且还支持流式接口。

Promise 接口

const compressing = require('compressing');

// 选择 gzip 格式,然后调用 compressFile 方法
compressing.gzip.compressFile('nodejs-compressing-demo/lib/urllib.js', 'nodejs-compressing-demo/lib/urllib.js.gz')
  .then(() => {
    console.log('success');
  })
  .catch(err => {
    console.error(err);
  });

// 解压缩是反响过程,接口都统一为 uncompress
compressing.gzip.uncompress('nodejs-compressing-demo/lib/urllib.js.gz', 'nodejs-compressing-demo/lib/urllib.js2')
  .then(() => {
    console.log('success');
  })
  .catch(err => {
    console.error(err);
  });

结合 async/await 的编程模型,代码写起来就是一个普通的异步 io 操作。

const compressing = require('compressing');

async function main() {
  try {
    await compressing.gzip.compressFile('nodejs-compressing-demo/lib/urllib.js', 
      'nodejs-compressing-demo/lib/urllib.js.gz');
    console.log('success');
  } catch (err) {
    console.error(err);
  }

  // 解压缩
  try {
    await compressing.gzip.uncompress('nodejs-compressing-demo/lib/urllib.js.gz', 
      'nodejs-compressing-demo/lib/urllib.js2');
    console.log('success');
  } catch (err) {
    console.error(err);
  }
}

main();

Stream 接口

需要特别注意的是,使用 Stream 模式编程,需要处理每个 stream 的 error事件,并且要手动销毁所有 stream

fs.createReadStream('nodejs-compressing-demo/lib/urllib.js')
  .on('error', handleError)
  .pipe(new compressing.gzip.FileStream()) // It's a transform stream
  .on('error', handleError)
  .pipe(fs.createWriteStream('nodejs-compressing-demo/lib/urllib.js.gz2'))
  .on('error', handleError);

// 解压缩,就是 pipe 的方向倒转过来
fs.createReadStream('nodejs-compressing-demo/lib/urllib.js.gz2')
  .on('error', handleError)
  .pipe(new compressing.gzip.UncompressStream()) // It's a transform stream
  .on('error', handleError)
  .pipe(fs.createWriteStream('nodejs-compressing-demo/lib/urllib.js3'))
  .on('error', handleError);

根据官方的 Backpressuring in Streams推荐,我们应该使用 pump模块来配合 Stream 模式编程,由 pump 来完成这些 Stream 的清理工作。

const pump = require('pump');

const source = fs.createReadStream('nodejs-compressing-demo/lib/urllib.js');
const target = fs.createWriteStream('nodejs-compressing-demo/lib/urllib.js.gz2');

pump(source, new compressing.gzip.FileStream(), target, err => {
  if (err) {
    console.error(err);
  } else {
    console.log('success');
  }
});

// 解压缩
pump(fs.createReadStream('nodejs-compressing-demo/lib/urllib.js.gz2'), 
    new compressing.gzip.FileStream(), 
    fs.createWriteStream('nodejs-compressing-demo/lib/urllib.js3'), 
    err => {
  if (err) {
    console.error(err);
  } else {
    console.log('success');
  }
});

Stream 接口的优势

Stream 接口看起来比 Promise 接口复杂多了,为何还会有这种应用场景呢? 其实在 HTTP 服务领域,Stream 模型会有更大的优势,因为 HTTP 请求本身就是一个 Request Stream,如要将一个上传文件以 gzip 压缩返回,使用 Stream 接口不需要将上传文件保存到本地磁盘,而是直接消费这个文件流。

使用 egg 文件上传的示例代码,我们稍微改造一下,就能实现 gzip 压缩然后返回。

const pump = require('pump');

class UploadFormController extends Controller {
  // ... other codes

  async upload() {
    const stream = await this.ctx.getFileStream();
    // 直接将压缩流赋值给 ctx.body,实现边压缩边返回的流式响应
    this.ctx.body = pump(stream, new compressing.gzip.FileStream());
  }
}

tar | gzip > tgz

gzip 章节可以提前知道,tar 是负责对文件夹进行打包的。 例如要对 nodejs-compressing-demo整个文件夹打包成一个文件发送给别人,可以通过 tar 命令完成。

$ tar -c -f nodejs-compressing-demo.tar nodejs-compressing-demo/

$ ls -l nodejs-compressing-demo.tar
-rw-r--r--  1 a  a  206336 Feb 12 14:01 nodejs-compressing-demo.tar

如大家所见,tar 打包出来的文件一般都比较大,因为它是未压缩的,大小跟实际文件夹总大小接近。所以我们都会在打包同时进行压缩。

$ tar -c -z -f nodejs-compressing-demo.tgz nodejs-compressing-demo/

$ ls -l nodejs-compressing-demo.tgz
-rw-r--r--  1 a  a  39808 Feb 12 14:07 nodejs-compressing-demo.tgz

tar 和 tgz 超过 5 倍大小的差异,可以大大减少网络传输带宽。

Node.js 实现 tgz

Promise 接口

先使用 compressing.tar.compressDir(sourceDir, targetFile)将一个文件夹打包到一个 tar 文件,然后使用上文的 gzip 压缩方式,将 tar 文件压缩为 tgz 文件。

const compressing = require('compressing');

compressing.tar.compressDir('nodejs-compressing-demo', 'nodejs-compressing-demo.tar')
  .then(() => {
    return compressing.gzip.compressFile('nodejs-compressing-demo.tar', 
      'nodejs-compressing-demo.tgz');
  });
  .then(() => {
    console.log('success');
  })
  .catch(err => {
    console.error(err);
  });

// 解压缩
compressing.gzip.uncompress('nodejs-compressing-demo.tgz', 'nodejs-compressing-demo.tar')
  .then(() => {
    return compressing.tar.uncompress('nodejs-compressing-demo.tar', 
      'nodejs-compressing-demo2');
  });
  .then(() => {
    console.log('success');
  })
  .catch(err => {
    console.error(err);
  });

结合 async/await 的编程模型,代码写起来会更加容易阅读:

const compressing = require('compressing');

async function main() {
  try {
    await compressing.tar.compressDir('nodejs-compressing-demo', 
      'nodejs-compressing-demo.tar');
    await compressing.gzip.compressFile('nodejs-compressing-demo.tar', 
      'nodejs-compressing-demo.tgz');
    console.log('success');
  } catch (err) {
    console.error(err);
  }
  
  // 解压缩
  try {
    await compressing.gzip.uncompress('nodejs-compressing-demo.tgz', 
      'nodejs-compressing-demo.tar');
    await compressing.tar.uncompress('nodejs-compressing-demo.tar', 
      'nodejs-compressing-demo2');
    console.log('success');
  } catch (err) {
    console.error(err);
  }
}

main();

Stream 接口

通过 compressing.tar.Stream类,可以动态添加任意文件、文件夹到一个 tar stream 对象中,非常灵活。

const tarStream = new compressing.tar.Stream();
// dir
tarStream.addEntry('dir/path/to/compress');
// file
tarStream.addEntry('file/path/to/compress');
// buffer
tarStream.addEntry(buffer);
// stream
tarStream.addEntry(stream);

const destStream = fs.createWriteStream('path/to/destination.tgz');
pump(tarStream, new compressing.gzip.FileStream(), destStream, err => {
  if (err) {
    console.error(err);
  } else {
    console.log('success');
  }
});

zip

zip 其实可以看作是 tar + gzip 的「商业化」结合,它让使用者不需要区分是压缩文件还是压缩文件夹,反正用我 zip 就对了。

使用 zip 命令行工具压缩一个文件夹的例子:

$ zip -r nodejs-compressing-demo.zip nodejs-compressing-demo/
  adding: nodejs-compressing-demo/ (stored 0%)
  adding: nodejs-compressing-demo/test/ (stored 0%)
  ...
  adding: nodejs-compressing-demo/.travis.yml (deflated 36%)

$ ls -l nodejs-compressing-demo.*
-rw-r--r--  1 a  a  206336 Feb 12 14:06 nodejs-compressing-demo.tar
-rw-r--r--  1 a  a   39808 Feb 12 14:07 nodejs-compressing-demo.tgz
-rw-r--r--  1 a  a   55484 Feb 12 14:34 nodejs-compressing-demo.zip

通过 tgz 和 zip 文件大小对比,可以看出默认的压缩参数下,gzip 的效果会比 zip 好。

Node.js 实现 zip

实现代码跟 tar 类似,只不过默认是压缩的,不需要再添加 gzip 的过程。

const compressing = require('compressing');

compressing.zip.compressDir('nodejs-compressing-demo', 'nodejs-compressing-demo.zip')
  .then(() => {
    console.log('success');
  })
  .catch(err => {
    console.error(err);
  });

// 解压缩
compressing.zip.uncompress('nodejs-compressing-demo.zip', 'nodejs-compressing-demo3')
  .then(() => {
    console.log('success');
  })
  .catch(err => {
    console.error(err);
  });

总结

基于 Node.js 实现的压缩和解压缩是否比想象中简单?感谢 npm 这个巨人,让我们编程也能拥有命令行工具那样简单的体验。 无论是 Promise 接口,还是 Stream 接口,都有它最合适的场景,你会选择了吗?

到此,你拥有的压缩和解压缩能力,你能够做什么样的服务和功能呢?

希望本文对你有用❤️

原文

原始文章来自 egg.js 团队的 Node.js 经验分享:Node.js 专栏

蚂蚁金服实习生招聘

蚂蚁金服2019届实习生校园招聘已经开启,非常欢迎大家加入我们!详情请查看 https://cnodejs.org/topic/5a9627e32580af301494a8f8招聘帖子。

【北京】北京盛安德科技发展有限公司诚聘Node.JS全栈开发良将(薪资:25K-35K)

$
0
0

我们是谁,我们做什么 北京盛安德科技发展有限公司(以下简称“盛安德”)成立于2001年,是国际领先的软件开发服务提供商,专注于为客户开发“快速解决问题、创造真正价值“的软件。 我们的愿景和目标是“成为最适合程序员工作和发展的公司”。成立16年以来,我们一直致力于企业管理理念的创新,以提供自由宽松、开放合作的环境让程序员有机会去成长和改变。 “自由创新、开放合作”的企业文化由此培养了越来越多的程序员成长为独立自主,有创造力的敏捷开发工程师。他们能够站在客户的立场上,立足于客户的业务模式和信息化目标,与客户共同面对问题,协同创新,开发出能快速解决客户问题,给客户带来真正价值的软件。这也是盛安德的核心优势, 在全球1000+的客户中赢得了良好的口碑。 盛安德秉承“为客户创造真正价值”的服务宗旨,根据客户(企业)要解决的问题以及差异化的业务模型,针对不同客户量身提供由2~7个敏捷开发工程师组成的敏捷小团队提供的软件研发+服务,帮助中小企业以最小的信息化投入,获得最为直接的收益。而在这个过程中,程序员在帮助客户解决问题、创造价值的同时,自身的价值也在不断提升,盛安德也因此离自己的愿景和目标越来越近。

我们拥有  1000+建立合作的客户  50+正在合作超过两年的客户  30+客户分布的国家,包括中国、美国、英国、澳大利亚、瑞士、德国、芬兰、荷兰、比利时等  400+员工在全球21个办公地点为客户提供服务  40+员工从一线工程师成长为负责交付和管理的角色  0从未和客户产生过知识产权相关的纠纷

我们的价值观和传统 • 诚实,不说谎 – 这是每一个盛安德员工坚守的第一准则。 • 不做自有知识产权的软件产品 – 做纯粹的技术服务公司, 从根本上保护客户的知识产权。 • 坚持公平的商业规则 – 我们一直坚持不提供回扣等有违商业公平原则的行为, 并且时刻保持审视自身,给客户创造真正的价值。 • 敏捷 – 从2006年我们开始在整个公司倡导并实践敏捷理念,借助敏捷拉近工程师和市场的距离。 • 精简运营团队规模 – 只有这样才能把最好的资源提供给一线员工。 • 投资于员工– 为员工提供多样化的培训和发展平台。 这些价值观和传统始终指导着我们的业务、机制、品牌、员工发展等每一个业务环节。

我们的网站: 英文网站:http://www.shinetechchina.com; 中文网站:http://www.shinetechchina.com.cn

** 我们的福利:**  我们是开放的管理文化,不介意您之前的工作经历和学历背景,只要您爱生活,爱交流,就可以加入我们;  加入我们,您不必担心迟到,我们实行弹性办公,无需打卡,上下班悠闲自得;  加入我们,休假宽松,第一年5天年假,第二年10天年假,比国家规定多出好多呦;  加入我们,您可享用营养的健康水果,休息室里有不限时不限量的茶点及饮料,还有香浓的咖啡;  加入我们,五险一金齐全,并增加午餐补助、笔记本补助;  加入我们,可享受每年一次的健康体检,并组织其他形式丰富的运动或者活动;  加入我们,您可在图书室借阅各类热门图书,在工作之余让身心得到放松;  加入我们,您可参加公司定期举行的敏捷或者技术方面的培训,时刻给自己的技术充电;

岗位职责及任职要求: 岗位职责: 1,负责项目前端、后端的开发,完成整体业务模块的研发工作,具备快速解决问题的能力; 2,参与系统需求分析与设计,负责完成核心代码编写和单元测试( Jest、Mocha) ,并撰写相关技术文档; 3,熟悉 Html5、CSS3,能够根据 Zeplin 设计编写结构良好的响应式布局 Html、CSS 及动效; 4,扎实的 ES6 功底 ,能够快速设计、研发具备可扩展性的 RESTful 接口; 5,参与性能效率优化,所开发的应用需能满足千万级的访问量;

任职要求: 1,具备持续学习能力,对各种新技术、编程语言及框架有学习热情,开发过开源框架者优先; 2,熟悉主流前端框架及其生态链(Vue/React/Angular 以及对应的路由和状态管理方案) ,熟练使用各种工具包 ; 3,丰富的 Node.js 后端开发经验,良好的算法/数据结构基础,精通 Express/Koa/PM2/Bluebird/Mongoose 等框架及工具; 4,熟悉各种常用数据库及缓存产品中的一种或多种,如: MongoDB 、PostgreSQL、 Mysql 、Redis 、Memcache 等; 5,对 CSS/Javascript 性能优化、解决多浏览器兼容性问题具备一定的经验; 6,熟练掌握 Logstash、Elasticsearch、Kibana 者优先; 7,良好的编程习惯,追求极致的代码质量, 能按时、独立、高质量地完成工作 ; 8,英文读写熟练、口语熟练者优先;

薪资标准: 25K-35K(根据个人能力确定)

工作地址: 北京市西城区新德街甲20号中影器材大厦705

联系方式: 邮箱:wangy@shinetechsoftware.com 电话:010-62370842 联系人:王女士

nodejs开发接口自动化测试03-用户登录问题

$
0
0

本教程使用的到技术栈如下: 操作系统:windows10, 编程语言:node.js 8.9.4 https://nodejs.org/en/代码编辑器: CukeTest http://cuketest.com/用到的http库 got :https://www.npmjs.com/package/got被测API:https://developer.github.com/v3/#authentication上篇文章链接:https://cnodejs.org/topic/5a7bff368d6e16e56bb8066c

认证方式

一般情况下,我们api的用户验证方式为如下三种:

  • 用户名/密码
  • API Key / Tokens
  • OAuth

本次代码样例中使用 用户名/密码方式. 操作如下:

新建用例文件

在CukeTest中新建feature文件,定义测试用例

# language: zh-CN
功能: 查看github仓库信息
应该包含github仓库中所有信息

  场景: 查看github仓库信息
    假如认证用户登录
    当查看我github仓库信息
    那么github仓库中应该包含"abc"

CukeTest 中可视化界面如下: image.png

打开definitions.js 文件生成对应的代码: defintions.js

var { Given, When, Then } = require('cucumber')
var got = require('got')
var assert = require('assert')
var { userinfo, username } = require('../../config')

let base_url = 'https://api.github.com'

Given(/^认证用户登录$/, async function () {
    let response = await got.get(base_url, userinfo)
    return assert.equal(response.statusCode, 200);
});
async function listMyRepos() {
    let response = await got.get(base_url + '/user/repos', userinfo)
    return response;
}

When(/^查看我github仓库信息$/, async function () {
    await listMyRepos();
});

async function myReposIncludes(repo) {
    let response = await listMyRepos();
    let res = response.body;
    let arr = [];
    for (let v of res) {
        arr.push(v.name)
    }
    return assert.ok(arr.indexOf(repo) > -1);
}

Then(/^github仓库中应该包含"([^"]*)"$/, async function (repo) {
    return await myReposIncludes(repo);
});

config.js

exports.userinfo = {
    json:true,
    auth:'yourGItHubUserName:YourPasswd'
}

exports.username = 'yourGItHubUserName'

点击运行项目,即可自动运行项目代码,运行完毕后自动生成测试报告。 image.png

所有代码已经上传到github: https://github.com/imzengyang/API-testing

运行方式

  • 安装依赖 使用CukeTest打开我们项目代码,点击更新依赖包配置按钮,下载项目依赖项。 image.png

  • 调试代码 修改 config.js 文件,更改为你自己的GitHub用户名和密码 在feature文件中,点击每个操作步骤后面的绿色按钮可以自动定位到具体自动化代码的实现位置。可以进行代码修改 image.png

同理: 在js文件内函数上右键–定位步骤描述,也能从代码位置跳转到对应操作步骤上面。 image.png

总结

使用BDD的方式做自动化测试,希望能够给团队的测试人员更多一种选择。 使用BDD的方式,能够让团队中的更多同事参与进来。

《Node.js 调试指南》开源书籍发布

$
0
0

test.js 文件利用 child_process 执行 node test.js 命令会有什么效果?

$
0
0

test.js

const { spawn } = require('child_process');
const ls = spawn('node', ['test.js']);

ls.stdout.on('data', (data) => {
	console.log(data.toString());		
});

ls.stderr.on('data', (data) => {
	console.log(data.toString());		
})

ls.on('close', (code) => {
	console.log("Exit Code: "+code);		
})

Create-react-app proxy跨域设置问题,在线求解

$
0
0

在开发环境下,在package.json中直接设置"proxy":“https://news-at.zhihu.com”,在项目中通过:

fetch('/api/4/news/latest').then(res=>{
	return res.json();
}).then(data=>{
	console.log(data);
})

是可以成功获取数据的, 但是在package.json如下设置:

"proxy": {
		"/api": {
				"target": "https://news-at.zhihu.com",
				"secure":false
		}
}

就会报错,如下: DingTalk20180228155632.png在线等大神回复

[北京 - 朝外soho] 互联网金融公司理财范诚聘前端工程师,初级/资深,都欢迎 【技术氛围浓烈】

$
0
0

简介

我们是互联网金融公司 - 理财范 www.licaifan.com

我们知道,在过去的两年里,这个行业已经被搞得有点“乌烟瘴气”了,xx融投,x租宝,x晋,x鹿…

但是,我们却一直走的很稳,因为,我们真的只想把这个事情做好,做大。

招聘岗位

1. 高级前端工程师 (15k - 25k)
  • 任职要求

    • 深刻理解web标准,对前端性能、可访问性、可维护性等相关知识有实际的了解和实践经验;
    • 掌握iOS、android、微信等移动平台web特性,熟悉移动端的性能优化以及兼容性问题;
    • 了解HTTP协议;
    • 熟练掌握HTML(5)、CSS(3)、JavaScript(ES6+)等;
    • 熟练使用至少一种JS框架;
    • 熟悉React或者Vue等并有项目经验,掌握其原理,能独立开发常用组件;
    • 熟练使用各种调试、抓包工具,能独立分析、解决和归纳问题;
    • 熟悉前端自动化和工程化;
    • 熟悉NodeJS开发,熟练使用Git;
    • 了解任意一种后台语言(python, ruby, php etc…);
    • 有相应开源项目加分
2. 前端工程师 (8k - 15k)
  • 任职要求

    • 熟练掌握三剑客
    • 至少熟练使用一种js库(例如jQuery 等等)
    • 至少熟练使用一种css库(例如bootstrap 等等)
    • 有多人协同工作经验,了解git基本操作
    • 拥有强烈的求知欲以及对新技术的探索欲
    • 自评学习能力优秀

福利待遇

  1. rmbp + dell u2414 标配
  2. 双休
  3. 加班时间少
  4. 下午茶标配(来了注意锻炼,容易长胖)
  5. 技术氛围浓烈

福利待遇不想多谈,在技术部中,我们的主要任务一直是:

给每一位有才华的伙伴提供最好的发展空间!

给每一位有才华的伙伴提供最好的发展空间!!

给每一位有才华的伙伴提供最好的发展空间!!!

所以你想要什么,要做什么,只要有规划,有时间,我们都会尽量的满足要求,让你能够心无旁骛的发挥最大潜力

后话

虽然公司已经C轮了,但是我们的技术团队依然偏小,这里有你非常广阔的发挥空间。

除了工作上的事情,我们也特别鼓励你启动并开发一些开源项目,例如Verynginx, Qbox, Haishoku就是团队成员在工作之余开发出来的,非常期待我们能够一起成长,做一些牛X的事情。

虽然很遗憾我们没能一起开始创业,但是希望有才华的你能够现在加入我们,一起进行下一场“阶段性战役” – 上市。

联系我们

有意向加入我们,或者一起聊聊天的,可以直接发送简历至 tech(at)licaifan.com, 技术部直招。


微信小程序调用CNodejs社区API服务器配置问题: 该域名包含非法字符,只支持英文大小写字母、数字及“ - ”

$
0
0

Screen Shot 2018-02-28 at 17.42.00 1.png问题:该域名包含非法字符,只支持英文大小写字母、数字及“ - ” 配置服务器的时候遇到了这个问题,请问怎么处理呢?

求助:npm 报错 Error: EEXIST: file already exists, mkdir

$
0
0

npm报错w4ctech.png

使用npm 安装 gitbook 报错

Error: EEXIST: file already exists, mkdir ‘C:\ProgramFiles\nodejs\node_modules\npm\gitbook’

安装 该方法解决后。 再次重装gitbook 问题并没有解决。

【上海】追书小说,16-30k 招 Node.js 后端开发工程师

$
0
0

Node.js 后端开发工程师

职位要求

相对来说,我们更看重人品以及基础知识,语言其实也不是问题,以下要求从高到底排列

  1. 有责任心,能为自己的工作和成果负责;
  2. 有上进心,学习能力以及沟通能力强,能挑战自我不断追求卓越;
  3. 基础扎实,熟悉常用数据结构和算法,能够独立设计系统中功能的架构;
  4. 熟悉 TCP、UDP、HTTP 协议以及 Restful API,熟悉前后端的常见知识要点;
  5. 了解常用重构方法,了解设计模式原则;
  6. 熟练掌握 Git 版本控制工具,了解 git flow 以及 github flow;
  7. 对 MongoDB / MySQL / Redis 有实操经验,了解 DB 设计和优化;
  8. 有 2 年以上的后端开发经验;
  9. 精通 JavaScript & Node.js;
  10. 熟悉各种 Node.js 基础&常用模块;
  11. 熟悉单元测试,了解常用测试框架 mocha 以及 ava;

加分项

  1. 在博客或 Github上 有技术沉淀;
  2. 熟悉 Docker 以及 k8s 等集群调度工具,有实际经验;
  3. 熟悉 ansible,了解常用模块;
  4. 熟悉 TypeScript、Golang;

你将得到

技术环境

  1. 体量:1 亿用户以及千万级日活并发场景下的业务架构挑战(是的,全靠 Node.js 支撑);
  2. 技术:最新的技术应用,大数据、k8s、ELK、React、Vue 等(是的,全是最新的);
  3. 开发:Node.js 全栈、Golang 以及 DevOps;

公司环境

  1. 福利:免费水果零食饮料;节日礼物;每年设有年度长途旅行、不定期 outing;
  2. 薪酬:行业高薪、绩效奖金、年终奖金等;
  3. 办公:开放式办公,MBPR 标配;
  4. 自由:带薪假期,灵活调休;

其它

  • 公司主页:https://www.zhuishushenqi.com/
  • 工作地点:上海闵行泓毅大厦B座(9号线合川路,也欢迎各位来做客)
  • 简历请投:xuzp # 1391.com
    • 邮件标题:【CNode】你的名字 + 求职职位 + 期望薪资
    • 邮件内容:请自由发挥,告诉我们你为何如此优秀

另外,再啰嗦一句,投简历之前,可先参考下这个简历模板 https://github.com/geekcompany/ResumeSample

在egg中,validate、jwt、验证或token无效就直接抛出422或401,有什么方法能拦截这些错误消息,然后整理后抛出200,附带一些错误信息。

$
0
0

在egg中,validate、jwt、验证或token无效就直接抛出422或401,有什么方法能拦截这些错误消息,然后整理后抛出200,附带一些错误信息。

require的探究

$
0
0

require()是nodejs中使用频率最高的函数,但是对它的认识一直都是一知半解,最近看了一下的源码才知道其主要工作流程,现在做一下总结。先说结论:

  • native模块(也叫做核心模块,即位于lib目录下的js文件)的require()是位于bootstrap_node.js中的NativeModule.require()
  • 而用户自己编写的程序中的require()则是位于lib/module.js中的Module._load()

其实后者在某些情况下回依赖前者,两者并不是完全独立,本文基于node 9.5.0进行讨论。

一个nodejs程序的启动过程

先写一个测试程序test.js如下:

"use strict";
console.log("nodejs");

然后使用node --inspect-brk test.js启动,则程序进入调试模式并停在第一行,在chrome的调试界面中我们可以清楚看到程序的调用栈。启动过程中node使用了哪些函数,这些函数的所在位置都可以在chrome的调试面板中清楚看到。

仔细观察我们得知node依次运行bootstrap_node.jsmodule.js里面的若干函数进行初始化工作,然后才运行用户的代码。 node_app_stack.jpg

bootstrap_node.js

native模块中的require定义在bootstrap_node.js中,bootstrap_node.js是node启动过程中在js层面的第一个文件,它执行了很多初始化工作(我们这里不一一细说,有兴趣可以参考《通过源码解析 Node.js 启动时第一个执行的 js 文件:bootstrap_node.js》 )。
bootstrap_node.js的结构很简单,里面只有一个立即运行的匿名函数,这个匿名函数运行了startup()来真正执行初始化。

'use strict';
(function(process) {
 //...省略其它代码
  startup();
});

值得注意的是这个匿名函数有一个process变量,这个变量非常重要,它上面有很多属性都是C++那边暴露过来的,是JS和C++交互的一个重要渠道,process是从node.cc中传递过来的。具体过程可以参考《node源码粗读(4):process对象底层实现》 ,因为我们这里专注js层面的分析,C++层面我们不详细讨论。

native模块中的require

我们以加载lib/module.js为例,探讨一下native模块中require()是怎么来的(注意,此时用户自己编写的模块中的require尚未初始化)。
通过查找我们发现bootstrap_node.js是通过const Module = NativeModule.require('module')来加载module模块,NativeModule.require()的核心代码如下:

NativeModule.require = function(id) {
    if (id === 'native_module') {//返回NativeModule自己
      return NativeModule;
    }

    const cached = NativeModule.getCached(id);
    if (cached && (cached.loaded || cached.loading)) {//如果已经缓存则返回缓存的模块
      return cached.exports;
    }

    if (!NativeModule.exists(id)) {//如果系统中并不存在这个id代表的native模块则抛出异常
      const err = new Error(`No such built-in module: ${id}`);
      err.code = 'ERR_UNKNOWN_BUILTIN_MODULE';
      err.name = 'Error [ERR_UNKNOWN_BUILTIN_MODULE]';
      throw err;
    }
    //否则编译并缓存该模块
    moduleLoadList.push(`NativeModule ${id}`);
    const nativeModule = new NativeModule(id);//初始化一个新的模块对象
    nativeModule.cache();//缓存模块
    nativeModule.compile();//编译模块
    return nativeModule.exports;
}

非常浅显无需多言,关键在于nativeModule.compile()

  NativeModule.prototype.compile = function() {
    /**
    这个方法会从NativeModule._source中取出模块的源码,
    而NativeModule._source是从c++那边传递过来的,它存储着已经加载到内存中的native模块的源码。
    至于C++那边是怎么将native源码传递过来我们不详细说明,关键之处是process.binding()。
    网上有很多相关资料或者参考朴灵的《深入浅出nodejs》的章节2.3.3。
    **/
    let source = NativeModule.getSource(this.id);
    source = NativeModule.wrap(source); //把源码包裹起来生成一个匿名函数的源码
    //省略...
    //根据匿名函数源码生成匿名函数对象
    const fn = runInThisContext(source, {
      filename: this.filename,
      lineOffset: 0,
      displayErrors: true
    }); 
    //生成传递进去的require函数,native模块中的require就在这里!
    const requireFn = this.id.startsWith('internal/deps/') ?
      NativeModule.requireForDeps :
      NativeModule.require;
    //将require传递进去,运行这个函数,module模块就完成了加载  
    fn(this.exports, requireFn, this, internalBinding, process); 
  };

可以看到,native模块最终会被包裹为一个匿名函数然后执行,包裹的内容可以从下面函数得知

  NativeModule.wrap = function(script) {
    return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
  };

  NativeModule.wrapper = [
    '(function (exports, require, module, internalBinding, process) {',
    '\n});'
  ];

因此native模块的源码会被包裹成这样一个函数执行

(function (exports, require, module, internalBinding, process) {
	//native模块的代码
})

第2个参数require就是上面NativeModule.prototype.compile函数中的requireFn对于native模块来说requireFn就是NativeModule.require。即NativeModule.require在加载module模块的时候,将自己作为参数传了进去,这样module模块就可以用相同的方式去加载其它native模块。这意味着其它native模块中的require也是NativeModule.require(被module加载的时候传递进去),从而所有native模块的加载方式都是一致的。

用户模块中的require

bootstrap_node.js中通过以下代码加载用户模块

const Module = NativeModule.require('module');//先加载模块module,它是一个native模块
//省略...
Module.runMain();//然后再运行用户的代码,由此可知用户代码是通过module模块加载的,和native有些不同

再看Module.runMain()

Module.runMain = function() {
  Module._load(process.argv[1], null, true);//process.argv[1]是命令行中传过来,是用户代码的文件名,例如我们这里就是test.js
  /**这里发现一个意外惊喜,原来process.nextTick()的callback在用户代码之后马上执行,是优于eventLoop的。
  这是对我之前写的《不要混淆nodejs和浏览器中的event loop》的一个重要补充
  **/
  process._tickCallback();
};

Module._load()之后的调用关系如下,全部函数都是位于module.js

Module._load()
↓ 
tryModuleLoad()
↓
module.load()
↓
/*
值得一提的是Module._extensions['.js']通过以下代码来读取源码
var content = fs.readFileSync(filename, 'utf8');
因此我们知道用户的模块加载是同步的,并且字符编码只能是utf8
*/
↓
module._compile()//最终运行用户代码的地方

看一下最为关键的module._compile()

Module.prototype._compile = function(content, filename) {
  //将用户的代码包裹为一个匿名函数,这个包裹过程和上面Native.require()中的类似,这里不再重复	
  var wrapper = Module.wrap(content);
  //生成一个匿名函数
  var compiledWrapper = vm.runInThisContext(wrapper, {
    filename: filename,
    lineOffset: 0,
    displayErrors: true
  });
  //注意,这里生成了用户模块中的require!!
  var require = internalModule.makeRequireFunction(this);
  if (inspectorWrapper) {//在调试模式下运行用户代码 
    result = inspectorWrapper(compiledWrapper, this.exports, this.exports,require, this, filename, dirname);
  } else {//在普通模式下运行用户代码
    result = compiledWrapper.call(this.exports, this.exports, require, this,filename, dirname);
  }
};

可见用户代码中的require()internalModule.makeRequireFunction()生成的,它位于lib/internal/module.js

function makeRequireFunction(mod) {
  const Module = mod.constructor;

  function require(path) {
    try {
      exports.requireDepth += 1;
      return mod.require(path);
    } finally {
      exports.requireDepth -= 1;
    }
  }
  //省略...
  return require;
}

mod.require()是什么?我们回溯上去发现modModule.prototype._compile()中的this,因此mod.require()就是Module.prototype.require()

Module.prototype.require = function(path) {
  assert(path, 'missing path');
  assert(typeof path === 'string', 'path must be a string');
  return Module._load(path, this, /* isMain */ false);//最终又变成了Module._load
};

咦?又调用了Module._load(),对照一下上面提及的调用流程,那岂不是无限递归吗?看看Module._load()

Module._load = function(request, parent, isMain) {
  if (isMain && experimentalModules) {//实验模块的加载
  }
  var filename = Module._resolveFilename(request, parent, isMain);
  var cachedModule = Module._cache[filename];
  if (cachedModule) {//如果模块已经被缓存过,则直接返回
    updateChildren(parent, cachedModule, true);
    return cachedModule.exports;
  }
  //如果是已经加载过的非internal的native模块,则用NativeModule.require()加载
  if (NativeModule.nonInternalExists(filename)) {
    debug('load native module %s', request);
    return NativeModule.require(filename);
  }
  //否则从磁盘加载这个模块的源码然后运行之。
  var module = new Module(filename, parent);

  if (isMain) {
    process.mainModule = module;
    module.id = '.';
  }
  Module._cache[filename] = module;
  tryModuleLoad(module, filename);
  return module.exports;
};

由此可知Module._load()在加载第一个用户模块源码然后运行的时候,会间接地将自己作为参数传递进去作为require,这使得第一个用户模块也能用相同的方式去加载它用户模块,这意味着其它用户模块也有着和第一个用户模块一样的require(有点绕,请仔细体会),这样所有用户模块的加载方式都一致了,这和NativeModule.require()是类似的。

Viewing all 14821 articles
Browse latest View live