Quantcast
Channel: CNode:Node.js专业中文社区

【广州天河】章和技术(网络安全)node.js全栈开发工程师【薪酬11-20K】

0
0

【广州 天河软件园】章和技术(网络安全行业)node.js后端/全栈开发工程师【薪酬:11-20K】 有兴趣的小伙伴,请发简历到:haiyan.huang@jumho.cn

工作内容: 1.负责web服务端,内部系统后端业务程序开发 2.负责web前端开发 3.参与现场调试 4.参与后端业务和底层模块接口开发及调试

技能要求: 1.熟练使用nodejs,熟练使用express,koa框架,有相关项目经验; 2.熟练使用前端开发框架vue,有相关项目经验; 3.良好的js基础,熟悉使用typescript或es6 4.熟悉数据库原理及sql语句,熟练使用mysql,mongodb中的至少一个; 5.熟悉Linux,熟悉常用的Shell命令; 6.熟悉使用websocket; 7.熟悉http/https等常用协议; 8.有node.js集群,kakfa,es开发经验优先;

工作素养: 1.做事认真负责,有良好的编码风格; 2.良好的沟通与表达能力,有团队协作精神。 3.有良好的学习方法和较强的学习能力; 4.喜欢迎难而上,爱好挑战困难; 5.有框架思维和多人合作编码能力。

【福利待遇】: 1.有竞争力的薪酬待遇,在行业内口碑好,知名度高; 2.完善的社保福利,按国家规定购买五险一金; 3.周末双休,享受春节、五一、十一、年假、婚假、产假等国家法定有薪假期; 4.每年定期安排员工体检,入职即购买商业意外险; 5.每月2次各类户外活动,日常有专门的小姐姐为您准备零食茶点。

这是个宽阔的平台,只要你有足够的实力,再高的职位你都可以随便晋升;这里用能力和结果说话,不需要勾心斗角,尔虞我诈;踏实把事情做漂亮了,丰厚的奖金,晋升机会,随时等着你去提取;如果你做好了长期奋斗的准备,并且是自我驱动型的人才,无比欢迎您的加入!!


node.js后端开发工程师

0
0

node.js后端开发工程师 薪酬范围:10-16K 工作职责: 1.参与web服务端,内部系统后端业务程序开发 2.参与现场调试 3.参与后端业务和底层模块接口开发及调试

技能要求: 1.熟练使用nodejs,有使用express或koa框架相关项目经验; 2.良好的js基础,熟悉使用typescript或es6,熟悉使用node.js中异步编程 3.熟悉数据库原理及sql语句,有使用mysql或mongodb的经验; 4.熟悉Linux,熟悉常用的Shell命令; 5.熟悉常用TCP/IP协议,熟悉http/https协议;

工作素养: 1.做事认真负责,有良好的编码风格; 2.良好的沟通与表达能力,有团队协作精神。 3.有良好的学习方法和较强的学习能力; 4.喜欢迎难而上,爱好挑战困难; 5.有框架思维和多人合作编码能力。

【福利待遇】: 1.有竞争力的薪酬待遇,在行业内口碑好,知名度高; 2.完善的社保福利,按国家规定购买五险一金; 3.周末双休,享受春节、五一、十一、年假、婚假、产假等国家法定有薪假期; 4.每年定期安排员工体检,入职即购买商业意外险; 5.每月2次各类户外活动,日常有专门的小姐姐为您准备零食茶点。

这是个宽阔的平台,只要你有足够的实力,再高的职位你都可以随便晋升;这里用能力和结果说话,不需要勾心斗角,尔虞我诈;踏实把事情做漂亮了,丰厚的奖金,晋升机会,随时等着你去提取;如果你做好了长期奋斗的准备,并且是自我驱动型的人才,无比欢迎您的加入!!

求助:egg-cluster如何使用vscode调试

0
0

想学习一下egg的多进程模型 自己打断点调试一下 但并不知道该怎么玩这个 希望大佬指点一下想的是自己写一个文件启动cluster image.png配置文件跟报错。。已经安装过依赖了 不知道是不是自己的打开方式有问题 image.png

企业微信浏览器cookie写不进去?

0
0

E23FD8EAEB61620683C1D8E9AE3E97A5.jpg8C9A7CE9F4EB4A37C4E42F43A8E53B3F.jpg

如图,后端egg在响应中设置了Set-Cookie,在普通浏览器中没问题,但是在企业微信内置浏览器中存不进去。

不是domain的问题,我测试了domain设置为空,和设置为当前安全域名一直的域名时都写不进去,但是chrome中没问题。

求解决和调试思路。

逐行分析鸿蒙系统的 JavaScript 框架

0
0

我在前文中曾经介绍过鸿蒙的 Javascript 框架,这几天终于把 JS 仓库编译通过了,期间踩了不少坑,也给鸿蒙贡献了几个 PR。今天我们就来逐行分析鸿蒙系统中的 JS 框架。

文中的所有代码都基于鸿蒙的当前最新版(版本为 677ed06,提交日期为 2020-09-10)。

鸿蒙系统使用 JavaScript 开发 GUI 是一种类似于微信小程序、轻应用的模式。而这个 MVVM 模式中,V 其实是由 C++ 来承担的。JavaScript 代码只是其中的 ViewModel 层。

鸿蒙 JS 框架是零依赖的,只在开发打包过程中使用到了一些 npm 包。打包完之的代码是没有依赖任何 npm 包的。我们先看一下使用鸿蒙 JS 框架写出来的 JS 代码到底长什么样。

export default {
  data() {
    return { count: 1 };
  },
  increase() {
    ++this.count;
  },
  decrease() {
    --this.count;
  },
}

如果我不告诉你这是鸿蒙,你甚至会以为它是 vue 或小程序。如果单独把 JS 拿出来使用(脱离鸿蒙系统),代码是这样:

const vm = new ViewModel({
  data() {
    return { count: 1 };
  },
  increase() {
    ++this.count;
  },
  decrease() {
    --this.count;
  },
});

console.log(vm.count); // 1

vm.increase();
console.log(vm.count); // 2

vm.decrease();
console.log(vm.count); // 1

仓库中的所有 JS 代码实现了一个响应式系统,充当了 MVVM 中的 ViewModel。

下面我们逐行分析。

src 目录中一共有 4 个目录,总计 8 个文件。其中 1 个是单元测试。还有 1 个性能分析。再除去 2 个 index.js 文件,有用的文件一共是 4 个。也是本文分析的重点。

src
├── __test__
│   └── index.test.js
├── core
│   └── index.js
├── index.js
├── observer
│   ├── index.js
│   ├── observer.js
│   ├── subject.js
│   └── utils.js
└── profiler
    └── index.js

首先是入口文件,src/index.js,只有 2 行代码:

import { ViewModel } from './core';
export default ViewModel;

其实就是重新导出。

另一个类似的文件是 src/observer/index.js,也是 2 行代码:

export { Observer } from './observer';
export { Subject } from './subject';

observer 和 subject 实现了一个观察者模式。subject 是主题,也就是被观察者。observer 是观察者。当 subject 有任何变化时需要主动通知被观察者。这就是响应式。

这 2 个文件都使用到了 src/observer/utils.js,所以我们先分析一下 utils 文件。分 3 部分。

第一部分

export const ObserverStack = {
  stack: [],
  push(observer) {
    this.stack.push(observer);
  },
  pop() {
    return this.stack.pop();
  },
  top() {
    return this.stack[this.stack.length - 1];
  }
};

首先是定义了一个用来存放观察者的栈,遵循后进先出的原则,内部使用 stack数组来存储。

  • 入栈操作 push,和数组的 push函数一样,在栈顶放入一个观察者 observer。
  • 出栈操作 pop,和数组的 pop函数一样,在将栈顶的观察者删除,并返回这个被删除的观察者。
  • 取栈顶元素 top,和 pop操作不同,top是把栈顶元素取出来,但是并不删除。

第二部分

export const SYMBOL_OBSERVABLE = '__ob__';
export const canObserve = target => typeof target === 'object';

定义了一个字符串常量 SYMBOL_OBSERVABLE。为了后面用着方便。

定义了一个函数 canObserve,目标是否可以被观察。只有对象才能被观察,所以使用 typeof来判断目标的类型。等等,好像有什么不对。如果 targetnull的话,函数也会返回 true。如果 null不可观察,那么这就是一个 bug。(写这篇文章的时候我已经提了一个 PR,并询问了这种行为是否是期望的行为)。

第三部分

export const defineProp = (target, key, value) => {
  Object.defineProperty(target, key, { enumerable: false, value });
};

这个没有什么好解释的,就是 Object.defineProperty代码太长了,定义一个函数来避免代码重复。

下面再来分析观察者 src/observer/observer.js,分 4 部分。

第一部分

export function Observer(context, getter, callback, meta) {
  this._ctx = context;
  this._getter = getter;
  this._fn = callback;
  this._meta = meta;
  this._lastValue = this._get();
}

构造函数。接受 4 个参数。

context当前观察者所处的上下文,类型是 ViewModel。当第三个参数 callback 调用时,函数的 this就是这个 context

getter类型是一个函数,用来获取某个属性的值。

callback类型是一个函数,当某个值变化后执行的回调函数。

meta元数据。观察者(Observer)并不关注 meta元数据。

在构造函数的最后一行,this._lastValue = this._get()。下面来分析 _get函数。

第二部分

Observer.prototype._get = function() {
  try {
    ObserverStack.push(this);
    return this._getter.call(this._ctx);
  } finally {
    ObserverStack.pop();
  }
};

ObserverStack就是上面分析过的用来存储所有观察者的栈。将当前观察者入栈,并通过 _getter取得当前值。结合第一部分的构造函数,这个值存储在了 _lastValue属性中。

执行完这个过程后,这个观察者就已经初始化完成了。

第三部分

Observer.prototype.update = function() {
  const lastValue = this._lastValue;
  const nextValue = this._get();
  const context = this._ctx;
  const meta = this._meta;

  if (nextValue !== lastValue || canObserve(nextValue)) {
    this._fn.call(context, nextValue, lastValue, meta);
    this._lastValue = nextValue;
  }
};

这部分实现了数据更新时的脏检查(Dirty checking)机制。比较更新后的值和当前值,如果不同,那么就执行回调函数。如果这个回调函数是渲染 UI,那么则可以实现按需渲染。如果值相同,那么再检查设置的新值是否可以被观察,再决定到底要不要执行回调函数。

第四部分

Observer.prototype.subscribe = function(subject, key) {
  const detach = subject.attach(key, this);
  if (typeof detach !== 'function') {
    return;
  }
  if (!this._detaches) {
    this._detaches = [];
  }
  this._detaches.push(detach);
};

Observer.prototype.unsubscribe = function() {
  const detaches = this._detaches;
  if (!detaches) {
    return;
  }
  while (detaches.length) {
    detaches.pop()();
  }
};

订阅与取消订阅。

我们前面经常说观察者和被观察者。对于观察者模式其实还有另一种说法,叫订阅/发布模式。而这部分代码则实现了对主题(subject)的订阅。

先调用主题的 attach方法进行订阅。如果订阅成功,subject.attach方法会返回一个函数,当调用这个函数就会取消订阅。为了将来能够取消订阅,这个返回值必需保存起来。

subject 的实现很多人应该已经猜到了。观察者订阅了 subject,那么 subject 需要做的就是,当数据变化时即使通知观察者。subject 如何知道数据发生了变化呢,机制和 vue2 一样,使用 Object.defineProperty做属性劫持。

下面再来分析观察者 src/observer/subject.js,分 7 部分。

第一部分

export function Subject(target) {
  const subject = this;
  subject._hijacking = true;
  defineProp(target, SYMBOL_OBSERVABLE, subject);

  if (Array.isArray(target)) {
    hijackArray(target);
  }

  Object.keys(target).forEach(key => hijack(target, key, target[key]));
}

构造函数。基本没什么难点。设置 _hijacking属性为 true,用来标示这个对象已经被劫持了。Object.keys通过遍历来劫持每个属性。如果是数组,则调用 hijackArray

第二部分

两个静态方法。

Subject.of = function(target) {
  if (!target || !canObserve(target)) {
    return target;
  }
  if (target[SYMBOL_OBSERVABLE]) {
    return target[SYMBOL_OBSERVABLE];
  }
  return new Subject(target);
};

Subject.is = function(target) {
  return target && target._hijacking;
};

Subject 的构造函数并不直接被外部调用,而是封装到了 Subject.of静态方法中。

如果目标不能被观察,那么直接返回目标。

如果 target[SYMBOL_OBSERVABLE]不是 undefined,说明目标已经被初始化过了。

否则,调用构造函数初始化 Subject。

Subject.is则用来判断目标是否被劫持过了。

第三部分

Subject.prototype.attach = function(key, observer) {
  if (typeof key === 'undefined' || !observer) {
    return;
  }
  if (!this._obsMap) {
    this._obsMap = {};
  }
  if (!this._obsMap[key]) {
    this._obsMap[key] = [];
  }
  const observers = this._obsMap[key];
  if (observers.indexOf(observer) < 0) {
    observers.push(observer);
    return function() {
      observers.splice(observers.indexOf(observer), 1);
    };
  }
};

这个方法很眼熟,对,就是上文的 Observer.prototype.subscribe中调用的。作用是某个观察者用来订阅主题。而这个方法则是“主题是怎么订阅的”。

观察者维护这一个主题的哈希表 _obsMap。哈希表的 key 是需要订阅的 key。比如某个观察者订阅了 name属性的变化,而另一个观察者订阅了 age属性的变化。而且属性的变化还可以被多个观察者同时订阅,因此哈希表存储的值是一个数组,数据的每个元素都是一个观察者。

第四部分

Subject.prototype.notify = function(key) {
  if (
    typeof key === 'undefined' ||
    !this._obsMap ||
    !this._obsMap[key]
  ) {
    return;
  }
  this._obsMap[key].forEach(observer => observer.update());
};

当属性发生变化是,通知订阅了此属性的观察者们。遍历每个观察者,并调用观察者的 update方法。我们上文中也提到了,脏检查就是在这个方法内完成的。

第五部分

Subject.prototype.setParent = function(parent, key) {
  this._parent = parent;
  this._key = key;
};

Subject.prototype.notifyParent = function() {
  this._parent && this._parent.notify(this._key);
};

这部分是用来处理属性嵌套(nested object)的问题的。就是类似这种对象:{ user: { name: 'JJC' } }

第六部分

function hijack(target, key, cache) {
  const subject = target[SYMBOL_OBSERVABLE];

  Object.defineProperty(target, key, {
    enumerable: true,
    get() {
      const observer = ObserverStack.top();
      if (observer) {
        observer.subscribe(subject, key);
      }

      const subSubject = Subject.of(cache);
      if (Subject.is(subSubject)) {
        subSubject.setParent(subject, key);
      }

      return cache;
    },
    set(value) {
      cache = value;
      subject.notify(key);
    }
  });
}

这一部分展示了如何使用 Object.defineProperty进行属性劫持。当设置属性时,会调用 set(value),设置新的值,然后调用 subject 的 notify 方法。这里并不进行任何检查,只要设置了属性就会调用,即使属性的新值和旧值一样。notify 会通知所有的观察者。

第七部分

劫持数组方法。

const ObservedMethods = {
  PUSH: 'push',
  POP: 'pop',
  UNSHIFT: 'unshift',
  SHIFT: 'shift',
  SPLICE: 'splice',
  REVERSE: 'reverse'
};

const OBSERVED_METHODS = Object.keys(ObservedMethods).map(
    key => ObservedMethods[key]
);

ObservedMethods定义了需要劫持的数组函数。前面大写的用来做 key,后面小写的是需要劫持的方法。

function hijackArray(target) {
  OBSERVED_METHODS.forEach(key => {
    const originalMethod = target[key];

    defineProp(target, key, function() {
      const args = Array.prototype.slice.call(arguments);
      originalMethod.apply(this, args);

      let inserted;
      if (ObservedMethods.PUSH === key || ObservedMethods.UNSHIFT === key) {
        inserted = args;
      } else if (ObservedMethods.SPLICE) {
        inserted = args.slice(2);
      }

      if (inserted && inserted.length) {
        inserted.forEach(Subject.of);
      }

      const subject = target[SYMBOL_OBSERVABLE];
      if (subject) {
        subject.notifyParent();
      }
    });
  });
}

数组的劫持和对象不同,不能使用 Object.defineProperty

我们需要劫持 6 个数组方法。分别是头部添加、头部删除、尾部添加、尾部删除、替换/删除某几项、数组反转。

通过重写数组方法实现了数组的劫持。但是这里有一个需要注意的地方,数据的每一个元素都是被观察过的,但是当在数组中添加了新元素时,这些元素还没有被观察。因此代码中还需要判断当前的方法如果是 pushunshiftsplice,那么需要将新的元素放入观察者队列中。

另外两个文件分别是单元测试和性能分析,这里就不再分析了。

相关阅读

api-mom 一个 API 在线管理工具

0
0

免费,安全,简单易用界面简洁。支持团队多人协作,只需安装 chrome 浏览器 api-mom 扩展就可以对 API 进行测试。

  1. 项目里的文件夹,接口列表,接口 Tab 都支持鼠标拖动排序。
  2. 编辑接口时支持常用快捷键 Ctrl + S 保存。
  3. 接口文档和测试结果并排,一切都在眼前,方便校对。
  4. 接口越来越多,可以用模糊搜索快速找出。
  5. 浏览模式 /编辑模式,安全操作。
  6. 可以加入团队成员,并做权限控制。
  7. 有白昼、夜晚两种主题。

网址:api-mom

1.jpg

2.jpg

3.jpg

内部团队招聘 - 北京 - 北京 - 前端

0
0

坐标: 北京 - 美团 岗位:前端开发 简历邮箱:1798448128@qq.com 没有给JD具体,主要是目前任何职级都是可以的~,具体要求可以通过邮件沟通哦 希望小伙伴帮忙宣传宣传,谢啦~ 快来快来~

sequelize 有没有类似 mybatis 的动态判断写法?

0
0

比如查询记录,可能会有多个不同的参数,这些参数不会都传过来,mybatis 可以用 xml 标签来做判断,sequelize 目前只知道调用 where 属性来解决。但是本人比较喜欢写原生的 sql,请问有没有解决方案?如果直接字符串拼接,会有 SQL 注入问题吧?


subversion diff

0
0

使用svn diff比较两个分支的不用

返现一个问题 两个分支都存在这个文件,并且内容相同,但在合并时冲突说是新文件? 可是这两个文件在两个分支都是存在的

使用svn diff结果如下

https://fs-test.7moor.com/N00000002411/customer/2020-09-16/1600241986660/cb334240-f7ef-11ea-8eb8-4119367996da/深度截图_google-chrome_20200916153632.png

https://fs-test.7moor.com/N00000002411/customer/2020-09-16/1600242105361/11f3bf20-f7f0-11ea-8eb8-4119367996da/深度截图_google-chrome_20200916153601.png

Node.js 实现三阶魔方求解器

0
0

最近女儿在学魔方,于是想做个魔方求解的软件,搜索后发现有个 Python 实现的但是很久没更新了,因此将项目里的前端部分重构了下,算法采用的是 kociemba,但没有 Node.js 的实现,因此用 NAPI 绑了下。项目地址:https://github.com/d-band/cube

在线示例

Screen Shot 2020-09-16 at 17.45.04.png

android开发和原生js开发是不是差不多

0
0

java代码 相当于javascript layout/xxx.xml 相当于htm findViewById 相当于 getElemntById setOnClickListener 相当于 addEventListener(‘click’,function(){ }) okhttp的 onSuccess(null, body); 相当于 jquery $.ajax 的 success : function(data) { } 各种的layout对应css的display的对应值
代码都是模板代码,大部分时间在调整样式和处理兼容性

分享几篇关于工作流成图工具bpmn-js的文章

0
0

BPMN是一套标准的业务流程建模符号规范,bpmn-js是基于此规范实现的一套渲染工具包和web建模器,可以实现拖拽生成工作流程图,效果大概如下

最近刚好用到,研究之后写了系列文章,分享给有需要的小伙伴

系列文章,持续更新中,有用到的欢迎关注,同时也搭建了个在线Demo:https://bpmn.ops-coffee.cn,欢迎体验

Nest项目使用Docker配合测试

0
0

前言

Nestjs作为当下对TypeScript支持最好的后端框架之一(可能没有之一),被越来越多的人使用.

最近自己也在学习和使用中,这其中也踩了不少坑,尤其是在和数据库连接的测试中.

这篇文章主要介绍如何使用Docker配合单元测试

测试的三种方法

  • 使用mock,对返回值进行mock,不直接和数据库进行交互
  • 使用内存数据库/磁盘数据库.例如Sqlite.
  • 利用docker增加测试数据库,在测试是开启docker内的数据库服务

三种方式的优缺点

方法优点缺点
使用mock更加灵活,和数据库进行了隔离写法麻烦且难维护;没有和数据库真实产生交互
使用内存数据库/磁盘数据库和数据库产生真实交互,容易维护postgresql的很多特性,内存数据库其实并不支持
利用Docker对你实际使用的数据库类型进行测试复杂度和开销比较大

利用Docker增加测试数据库

以上三种方式,在项目中都使用过,根据实际情况,我最终选择了第三种方法:利用Docker增加测试数据库

思路

每次在跑测试之前,开启Docker启动相应的数据库服务,并初始化数据库;单元测试结束之后,移除数据库,关闭Docker服务.

安装Docker

大家可以参考这个去安装 https://www.runoob.com/docker/ubuntu-docker-install.html

配置docerk-compose

version: "3"
services:
  test-db:
    image: postgres
    restart: always
    ports:
    - "5433:5432"
    environment:
      POSTGRES_PASSWORD: xxx
    volumes:
      - ./test/init.sql:/docker-entrypoint-initdb.d/init.sql #初始化测试数据库

通过npm启动

"posttest": "docker-compose stop test-db && docker-compose rm -f test-db", //测试结束后关闭docker并移除服务
    "pretest": "docker-compose up -d test-db" //测试开始前开启dcoker和服务

配置jest

npm 启动的方式,无法在IDE内单独执行某个测试,所以我开始对jest进行配置

用setup和globalTeardown在测试开始前和结束后,启动/关闭 docker

//setup.ts
const { execSync } = require('child_process');
export default async function() {
  execSync('docker-compose up -d test-db');
}
//globalTeardown.ts
const { execSync } = require('child_process');
export default async function() {
  execSync('docker-compose stop test-db && docker-compose rm -f test-db');
}
//jest.json
 "globalSetup": "./test/setup.ts",
  "globalTeardown": "./test/globalTeardown.ts"

小结

以上就是最近对Nestjs测试的实践.这三种方式对其他Node项目应该也是一样的.如果项目需要集成Redis或者其他服务,都可以通过Docker进行配置

简单require实现

0
0

require方法主要实现的功能

1.module的上下文独立 2.module的上下文缓存 3.根据路径从当前目录下加载文件,从nodemodules中加载模块,原声模块的加载

require.js实现

const path = require('path')
const fs = require('fs')
const mymodule = { 
    catch: {} //用于做module的缓存  require不会重复的读取同一个文件 优先从缓存中读取
}
function myRequire(modulePath) {

    let file = path.join(__dirname, modulePath)
    const exports = mymodule.exports = {}  // cnode大佬有写关于它的文章哦   https://cnodejs.org/topic/52308842101e574521c16e06

    if (mymodule.catch[file]) {
        eval(mymodule.catch[file]) 
        return mymodule.exports 
    }
    // if (mymodule.catch[file]) {  
    //     return mymodule.catch[file]
    // }
    const content = fs.readFileSync(file, 'utf-8')
    console.log(content)
    let res = eval(content)
    // mymodule.catch[file] = res  //不能缓存eval执行完的内容 缓存读取的同一个对象导致module不是独立的上下文
    mymodule.catch[file] = content //只缓存读取的文件内容
    console.log(mymodule, res)
    return mymodule.exports
}
let aa = myRequire('./test.js')
let bb = myRequire('./test.js')
aa.bbb = 777
console.log(aa, bb)

test.js

class aa {
    constructor() {
        this.a = 1
        console.log(this)
    }
}

mymodule.exports = { a: 66 }

那么热更新 热重载等功能的实现 是不是对module的缓存进行处理就可以了呢?

简单猜想:监听到文件发生变化之后 替换掉module的缓存内容 然后再不重启程序的前提下 释放旧模块的引用? 旧模块的引用如何处理呢? 大佬们 给点建议和思路QAQ

【西安高新区】node.js全栈开发工程师

0
0

地址:西安高新区科技路

岗位职责: 1、基于Nodejs技术栈进行Web应用的研发; 2、负责Web前端系统和功能的开发、调试和维护; 3、基于MVC架构进行前端轻量型服务器开发; 4、负责后台服务器端的Rest接口的研发; 任职资格: 1、扎实的Javascript功底,熟悉Javascript技术规范; 2、精通Nodejs整个技术栈,具备full-stack的设计和开发能力; 3、熟悉typescript; 4、熟练使用express,koa,egg开发框架; 5、具有Web前端开发经验,掌握HTML(DIV+CSS)、Html5/CSS3、Jquery等技术; 6、熟悉vue、Jquery、bootstrap等前端框架; 7、熟悉Redis、MongoDB等Nosql系统; 8、具备独立解决问题的能力和经验; 基本要求: 具有高度的团队协作精神和严肃认真的科学态度; 具有良好的沟通能力; 具有良好的职业素养、敬业精神和创新精神; 具有很强的学习能力; 具有不断挑战自我、提高自我的强烈意愿。 优先考虑: 计算机相关专业、地理信息系统专业。 工作环境及福利待遇: 1、除基本薪资外,享受各类节假日补贴和年终分红; 2、公司实行无差别管理,公司经理、主管与所有员工完全相同的办公环境待遇;


RESTful API设计中常见的问题和解决方案

分享一个自己写的清理git分支的cli小工具~

0
0

自己写的小工具,帮助你使用cli的方式管理分支,自己用的还不错,分享一下哈哈**** 文档地址:neater 体面人

  • 告别 git 命令,比 GUI 工具更加方便快捷
  • 支持交互模式、正则匹配和all in模式 欢迎试用~

[Concent速成] (1) 定义和共享模块状态

0
0

cc-learn-1.png

开源不易,感谢你的支持,❤ star concent^_^

序言

**[Concent速成]**是一个帮助新手极速入门concent的系列文章,0障碍地学习和理解concent状态管理思路。

虽然学习和使用过reduxmbox之类的状态管理库,阅读此篇文章会更容易理解,但是没有使用过任何状态管理库的用户也能极速入门concent,真正的0障碍学会使用它并接入到你的react应用里。

注意上面强调了0障碍,包括了学会使用和接入应用两个方面,为了达到此目的,api要足够简单,简单到什么程度呢?简单到无以复加,简单到和react保持100%一致,让新手无需理解额外的概览,以react组件的编写方式就能接入状态管理,但是呢也保留了更高级的抽象接口,让老手可以按照redux的模式去组织代码。

来吧,展示!本期讲解的关键api,包括一个3个顶层apirunuseConcentregister,一个实例上下文apisetState,学会使用这4个api,你就已经会使用concent做为你的状态管理方案了。

Hello world

所有的框架都会以Hello world作为引导,我们此处也不例外,看看concent版本的Hello world是多么的简单。

run 定义模块

concent和redux一样,有一个全局单一的状态树,是一个普通的json对象,不过第一层key规划为模块名,来帮助用户按照业务场景将状态切分为多个模块,便于分开管理。

此处我们需要用到run接口启动concent并载入模块配置,配置一个名为hello的模块,并为其定义状态

import { run } from 'concent';

run({
  hello: {
    state: { greeting: 'Hello world' },
  },
});

register 注册类组件

定义好了模块,我们的组件需要消费模块的状态,对于类组件,使用register即可

import { register } from 'concent';

@register('hello')
class HelloCls extends React.Component{
  state = { greeting: '' };
  changeGreeting = (e)=> this.setState({greeting: e.target.value})
  render(){
    return <input value={this.state.greeting} onChange={this.changeGreeting} />
  }
}

上诉代码用register接口将HelloCls组件注册属于hello模块,concent将向当前组件this上注入一个实例上下文ctx,用于读取数据和调用修改方法,同时也默默替换了this上的statesetState,方便用户可以0改动原组件的代码,仅使用register装饰一下类组件即可接入状态管理,这就是0障碍学会使用并接入到react应用的基础,对于初学者来说,你会写react组件,就已经会使用了concent,没有任何额外的学习成本。

this.state === this.ctx.state; // true
this.setState === this.ctx.setState; // true

上述代码里,还声明了一个类成员变量state等于 { greeting: '' },因为greeting和模块状态里的重名了,所以首次渲染之前它的值将被替换为模块里的Hello world,实际上这里可以不声明这个类成员变量state,写上它只是为了保证删除register装饰器这个组件也能正常工作,而不会得到一个undefinedgreeting初始值。

useConcent 注册函数组件

使用useConcent接口注册当前组件所属模块,useConcent会返回当前组件的实例上下文对象ctx,等同于上面类组件的this.ctx,我们只需要解构它取出statesetState即可。

import { useConcent } from 'concent';

function HelloFn(){
  const { state, setState } = useConcent('hello');
  const changeGreeting = (e)=> setState({greeting: e.target.value})
  return <input value={state.greeting} onChange={changeGreeting} />
}

渲染组件

最后我们看下完整的代码吧,我们发现顶层无Provider之类的组件包裹根组件,因为concent没有依赖React Context api实现状态管理,而是自己独立维护了一个独立的全局上下文,所以你在已有的项目里接入concent是非常容易的,即插即用,无需任何额外的改造。

由于HelloClsHelloFn组件都属于hello模块,它们中的任意一个实例修改模块状态,concent会将其存储到store,并同步到其它同属于hello模块的实例上,状态共享就是这么简单。

import ReactDOM from 'react-dom';
import { run } from 'concent';
import { register, useConcent } from 'concent';

run({/** 略 */});

@register('hello')
class HelloCls extends React.Component{/** 略 */}

function HelloFn(){/** 略 */}

const App = ()=>(
  <div>
     <HelloCls />
     <HelloFn />
  </div>
);

ReactDOM.render(App, document.getElementById('root'));

点我查看源码

依赖收集

无论是类组件还是函数组件,拿到state对象已被转换为一个Proxy代理对象,负责收集当前渲染的数据依赖,所以如果是有条件判断的读取状态,推荐采用延迟解构的写法,让每一次渲染都锁定最小的依赖列表,减少冗余渲染,获得更好的性能。

function HelloFn(){
  const { state, setState, syncBool } = useConcent({module:'hello', state:{show:true}});
  const changeGreeting = (e)=> setState({greeting: e.target.value});
  // 当show为true时,当前实例的依赖是['greeting'],其他任意地方修改了greeting值都会触发当前实例重渲染
  // 当show为false时,当前实例无依赖,其他任意地方修改了greeting值不会影响当前实例重渲染
  return (
  	<>
    {state.show?<input value={state.greeting} onChange={changeGreeting} />:'no input'}
    <button onClick={syncBool('show')}>toggle show</button>
    </>
  );
}

跨多个模块消费模块状态

当组件需要消费多个模块的数据时,可使用connect参数来声明要连接的多个模块。

使用connect参数连接多个模块

如下面示例,连接bar和baz两个模块,通过ctx.connectedState获取目标模块状态:

@register({connect:['bar', 'baz']})
class extends React.Component{
  render(){
    const { bar, baz } = this.ctx.connectedState;
  }
}

connectedState拿到的模块状态依然存在着依赖收集行为,所以如果存在条件渲染语句,推荐延迟解构写法

使用setModuleState修改状态

通过调用实例上下文apictx.setModuleState修改目标模块状态

changeName = e=> this.ctx.setModuleState('bar', {name: e.target.value})

结语

此文仅仅演示了最最基础的api用法,帮助你快速上手concent,如果你已经是老司机,特别是vue3 one piece已宣布正式发布的这个关头,如果你非常的不屑一顾这样笨拙的代码组织方式,暂先不要急着否定它,且打开官网看一下其他特性,一定有你喜欢的亮点,包括为react 量身定制的composition api,模块级别的reducercomputedwatchlifecycle等等新特性,后面的速成会一一提到。

Concent携带一整套完整的方案,支持渐进式的开发react组件,即不干扰react本身的开发哲学和组件形态,同时也能够获得巨大的性能收益,这意味着我们可以至下而上的增量式的迭代,状态模块的划分,派生数据的管理,事件模型的分类,业务代码的分隔都可以逐步在开发过程勾勒和剥离出来,其过程是丝滑柔顺的,也允许我们至上而下统筹式的开发,一开始吧所有的领域模型和业务模块抽象的清清楚楚,同时在迭代过程中也能非常快速的灵活调整而影响整个项目架构.

再造二个轮子,整合cnodejs和v2ex的第3方小程序

0
0

分享一个刚刚通过审核的小程序,整合了cnodejs和v2ex 二个社区的资源,欢迎各位大侠指教。

WechatIMG1.jpeg

有点酷的个人主页

《javascript高级程序设计》学习笔记 | 5.7.单体内置对象

0
0

单体内置对象

相关代码 →

  • 由 ECMAScript 实现提供的,不依赖于宿主环境的对象,在 ECMAScript 程序执行之前就已经存在
  • Global 和 Math

Global 对象

  • 不属于任何其他对象的属性和方法,最终都是 Global 的属性和方法
  • isNan(),isFinite(),parseInt(),parseFloat()
URI 编码方法返回值
encodeURI()URI 编码,冒号、正斜杠、问号、井号除外
encodeURIComponent()URI 编码,所有非标准字符
decodeURI()URI 解码,只针对使用 encode()编码的字符
decodeURIComponent()URI 解码,所有非标准字符
var uri = 'https://element cn/#tab'
console.log(encodeURI(uri)) // https://element%20cn/#tab,本身属于URI的字符不编码(冒号、正斜杠、问号、井号)
console.log(encodeURIComponent(uri)) // https%3A%2F%2Felement%20cn%2F%23tab,编码所有非标准字符
console.log(decodeURI('https%3A%2F%2Felement%20cn%2F%23tab')) // https%3A%2F%2Felement cn%2F%23tab,只针对使用 encode()编码的字符解码
console.log(decodeURIComponent('https%3A%2F%2Felement%20cn%2F%23tab')) // https://element cn/#tab,解码所有非标准字符
eval 方法返回值
eval()将传入的参数当作实际的 EXMAScript 语句解析
  • 将传入的参数当作实际的 EXMAScript 语句解析
  • 被执行的代码具有与该执行环境相同的作用域链
  • eval() 创建的变量或函数不会被提升
  • 严格模式下,外部访问不到 eval() 中创建的变量或函数,为 eval 赋值也会报错
eval("console.log('hi')") // "hi",将传入的参数当作实际的 EXMAScript 语句解析
eval("function sayHi() {console.log('hi')}")
sayHi() // "hi",被执行的代码具有与该执行环境相同的作用域链
// console.log(msg) // 报错,eval() 创建的变量或函数不会被提升
eval("var msg = 'hi'")
console.log(msg) // "hi",被执行的代码具有与该执行环境相同的作用域链
Global 对象属性说明
undefined特殊值 undefined
NaN特殊值 NaN
Infinity特殊值 Infinity
Object构造函数 Object
Array构造函数 Array
Function构造函数 Function
Boolean构造函数 Boolean
String构造函数 String
Number构造函数 Number
Date构造函数 Date
RegExp构造函数 RegExp
Error构造函数 Error
EvalError构造函数 EvalError
RangeError构造函数 RangeError
ReferenceError构造函数 ReferenceError
SyntaxError构造函数 SyntaxError
TypeError构造函数 TypeError
URIError构造函数 URIError
// web浏览器将global全局对象作为window对象
var color = 'red'
function sayColor() {
  console.log(window.color)
}
window.sayColor() // red

Math 对象

max 和 min
min()确定一组数值的最大值
max()确定一组数值的最小值
console.log(Math.max(3, 54, 32, 16)) // 54
console.log(Math.min(3, 54, 32, 16)) // 3
  • 确定数组中的最大值/最小值
var values = [1, 2, 3, 4, 5]
console.log(Math.max.apply(Math, values)) // 把 Math 对象作为 apply()的第一个参数,将数组作为第二个参数
舍入方法
ceil()向上取整
floor()向下取整
round()四舍五入
console.log(Math.ceil(1.9)) // 2
console.log(Math.floor(1.9)) // 1
console.log(Math.round(1.9)) // 2
random 方法
random()返回大于 0 小于 1 的随机数
console.log(Math.random()) // 大于 0 小于 1 的随机数
  • 选择 1-10 之间的整数
console.log(Math.floor(Math.random() * 10 + 1))
  • 选择 m-n 之间的整数
function selectFrom(lowerValue, upperValue) {
  var choices = upperValue - lowerValue + 1 // 获取范围内的数量
  return Math.floor(Math.random() * choices + lowerValue)
}
var num = selectFrom(7, 32)
console.log(num)

总结 & 问点

  • 单体内置对象有哪些?分别有什么特点?
  • Global 对象有哪些方法?分别有什么用法?
  • eval 方法的用法和特点? 严格模式下,eval 方法受到哪些限制?
  • Global 对象有哪些属性?
  • 在 web 浏览器中,将 global 全局对象作为什么对象实现的?
  • Math 对象有哪些方法?分别有什么用法?
  • 请用 Math 对象确定数组中的最大值/最小值
  • 请用 Math 对象随机生成 m-n 之间的整数

前端的按钮,在都需要与后台交互数据的情况下,什么时候用js function,什么时候用超链接呢?求各位大神指导,第一次提问

0
0

如题,自学nodejs时遇到的一个问题,在读其他人的代码。 <td class=“hidden-480”> {{if $value.status==1}} <img src="{{HOST}}/admin/images/yes.gif" onclick=“app.toggle(this,‘admin’,‘status’,’{{@$value._id}}’)” /> {{else}} <img src="{{HOST}}/admin/images/no.gif" onclick=“app.toggle(this,‘admin’,‘status’,’{{@$value._id}}’)” /> {{/if}} </td>

<td>
										<div class="visible-md visible-lg hidden-sm hidden-xs btn-group center">


											<a id='***question at here***' href="{{__HOST__}}/admin/manage/edit?id={{@$value._id}}">

												<button class="btn btn-xs btn-info">
													<i class="icon-edit bigger-120"></i>
												</button>
											</a>

											<a href="{{__HOST__}}/admin/manage/delete?id={{@$value._id}}">


												<button class="btn btn-xs btn-danger">
													<i class="icon-trash bigger-120"></i>
												</button>

											</a>

										</div>

									</td>

最上面img如果点击会修改数据库状态并显示对应状态, 而加黑加粗的id=questionAtHere的标签点击之后直接发起了一个超链接,我想知道为什么会有这样的区别,以及各在什么时候使用。 再次感谢大家宝贵的时间


淘系技术部 [云+端] 开源产品线上发布会邀约

roam outliner 双向链接笔记已经免费,有兴趣的可以尝试一下

用 WebRTC 撸一个在线视频会议应用

0
0

img

基于浏览器的能力 WebRTC 以及 腾讯云开发 CloudBase 能力构建而成的应用的在线视频会议应用,可以支持两人在线视频会议, 功能还不够完善, 还有许多可完善之处。

创建会议后可将会议地址发给他人, 或者在本机另起一浏览器窗口(未避免数据混乱, 可开隐私模式窗口, 或使用另一个浏览器)打开会议地址来体验

在线体验 Demo

应用体验地址: https://tcb-demo-10cf5b-1302484483.tcloudbaseapp.com/meeting-simple/

在线一键部署

点击下面按钮链接可以在线一键独立部署一个自己的在线视频会议应用

img

技术解析

本应用用到的能力、工具、框架有:

  1. CloudBase Framework用于项目开发和一键部署(https://github.com/TencentCloudBase/cloudbase-framework欢迎点击 Github 页面给个 Star )
  2. Simple Peer流行的 WebRTC 库
  3. 云开发-云函数, 包括云函数的定时调用
  4. 云开发-数据库
  5. 云开发-静态网站托管
  6. React
  7. Ant design

完整教程和源代码

https://github.com/oe/serverless-zoom-with-webrtc/tree/master/meeting-simple

npm init egg 报错

0
0
use registry: https://registry.npmjs.org
Got error when check update: Unexpected token < in JSON at position 0 (data json format: "<!DOCTYPE html>\n<!--[if lt IE 7]> <html class=\"no-js ie6 oldie\" lang=\"en-US\"> <![endif]-->\n<!--[if IE 7]>    <html class=\"no-js ie7 oldie\" lang=\"en-US\"> <![endif]-->\n<!--[if IE 8]>    <html class=\"no-js ie8 oldie\" lang=\"en-US\"> <![endif]-->\n<!--[if gt IE 8]><!--> <html class=\"no-js\" lang=\"en-US\"> <!--<![endif]-->\n<head>\n<title>Attention Required! | Cloudflare</title>\n<meta name=\"captcha-bypass\" id=\"captcha-bypass\" />\n<meta charset=\"UTF-8\" />\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"" ...skip... "our IP</span>: 116.6.234.242</span>\n    <span class=\"cf-footer-separator sm:hidden\">&bull;</span>\n    <span class=\"cf-footer-item sm:block sm:mb-1\"><span>Performance &amp; security by</span> <a href=\"https://www.cloudflare.com/5xx-error-landing\" id=\"brand_link\" target=\"_blank\">Cloudflare</a></span>\n    \n  </p>\n</div><!-- /.error-footer -->\n\n\n    </div><!-- /#cf-error-details -->\n  </div><!-- /#cf-wrapper -->\n\n  <script type=\"text/javascript\">\n  window._cf_translation = {};\n  \n  \n</script>\n\n\n</body>\n</html>\n"), GET https://registry.npmjs.org/egg-init/latest 403 (connected: true, keepalive socket: false, socketHandledRequests: 1, socketHandledResponses: 1)
headers: {"date":"Wed, 23 Sep 2020 07:15:29 GMT","content-type":"text/html; charset=UTF-8","transfer-encoding":"chunked","connection":"close","set-cookie":["__cfduid=db28f38d1fcb1d8e5e8e1c474d678df2c1600845329; expires=Fri, 23-Oct-20 07:15:29 GMT; path=/; domain=.npmjs.org; HttpOnly; SameSite=Lax"],"cf-ray":"5d72798c3f6723e0-HKG","cache-control":"private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0","vary":"Accept-Encoding","cf-chl-bypass":"1","cf-request-id":"055b6a4ba7000023e025289200000001","expect-ct":"max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"","x-frame-options":"SAMEORIGIN","server":"cloudflare"}

npm cache clean --force 重置源也不行

请问国内还有哪些 nodejs 社区?

0
0

如题公司比较急招人,我也很希望能够推进这事,所以想问问大家知不知道其他什么 nodejs 的社区可以招人呀?小众或者不能发招聘帖也没关系,因为我自己也是主攻 Javascript 开发的所以也想借此机会开拓下视野。

谢谢

【北京】火币招聘 Node.js,【薪酬: 20K 以上,具体面议】

0
0

工作地点

西二旗

职责描述:

  • 负责各种 cryptocurrency 钱包系统研发
  • 不断优化系统架构设计与实现,使得代码简洁、优雅和高效

任职要求:

  • 三年以上技术相关研发⼯作经验,熟悉 Unix/Linux 环境
  • 精通 Node.js,熟悉主流 Web 框架,扎实的编码能力,解决问题能力强
  • 掌握单元测试和集成测试的技术和流程
  • 具备良好的团队协作精神,能⾼效并⾼质量交付产品
  • 流畅阅读英⽂技术⽂档
  • 对代码质量有⼀定追求,重视 Code Review

加分项:

  • 密码学
  • 容器
  • Vue、Electron
  • AWS
  • 玩过其它语言、业界新技术

公司福利多多,不加班,每天都有下午茶,有自己员工按摩室,各种健身房游泳馆等场地免费使用,福利多到停不下来。

有意向请投递简历至邮箱yanglei01@huobi.com

Node 案发现场揭秘 —— 文件句柄泄露导致进程假死

0
0

文件句柄泄露导致的进程假死

开源的 Node.js 性能监控与线上故障定位工具,欢迎大家关注:https://github.com/hyj1991/easy-monitor

好久没写 Node.js 故障案例了,今天是一枚全新的进程假死无响应案例。

特点时完全不同于之前常规遇到的类死循环引发的阻塞假死,值得记录分析的过程,希望对遇到其它的类似案例的开发者有所启发。

I. 故障现象

Easy-Monitor开源官方讨论群里有一位同学 Midqiu 遇到了进程跑几个小时后就处于假死无响应的问题,而且神奇的是进程假死的同时,监控服务端的 **系统数据 **也同时断开:

image.png

此时 进程数据界面则回退到实例刚刚接入还未上报数据的状态:

image.png

可以看到业务进程依旧在,检查此进程状态则显示 xprofiler 插件未启用:

image.png

可以确认这个进程在几个小时之前是正确接入了 Easy-Monitor 的监控服务端的,经过和 Midqiu 的沟通,发现从进程假死的那一刻开始, xprofiler 插件的日志就没有再生成了。

但是 xprofiler 插件在设计之初为了规避和 JS 工作主线程之间的互相干扰,所以是采用了 uv trhread 起了工作线程处理内核数据的,理论上 JS 线程卡死也不会影响它的内核日志输出。

这样现象就很奇怪了,Node.js 的 JS 主线程卡死竟然会让插件的日志也无法正常输出。

II. 初步排查问题

既然是进程假死无响应,首先猜测的就是是不是 JS 工作主线程卡死导致的,所以线下沟通 SSH 到服务器上查看假死时的 Node.js 负载:

image.png

使用 top -H -p <pid> 查看发现 CPU 占用不到 1%,内存整体在 300MB 附近也非常正常。这时候其实我还不死心,手动在服务器应用下执行了 xprofiler 插件的命令:

./node_modules/.bin/xprofctl check_version -p <pid>

然而事实证明确实不仅仅是 JS 主线程假死,连插件创建的 IPC 通信线程也假死了:

image.png

此时第一次感到这个问题可能没有想象的那么简单了,初步总结了下故障现象:

  • 运行几小时后 Node.js 服务无响应,进程还在(未重启)
  • 进程的 CPU 和 RSS 均正常
  • xprofiler 插件创建的内核日志子线程和 IPC 通信子线程也无响应

此时没什么好办法,只能祭出大招手动生成分析 Core 文件。

III. 分析 Coredump

首先在服务器上安装神器 gcore,然后执行 sudo gcore <pid> 生成 core 文件。Midqiu 将生成的 core 文件和 node 可执行文件打包发给了我,本地使用 GDB 进行分析:

 gdb ./node core.24269
 For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./node...done.
warning: .dynamic section for "/lib64/ld-linux-x86-64.so.2" is not at the expected address (wrong library or version mismatch?)
warning: Could not load shared library symbols for 7 libraries, e.g. /lib64/libdl.so.2.
Use the "info sharedlibrary" command to see the complete listing.
Do you need "set solib-search-path" or "set sysroot"?
Core was generated by `node'.
#0  0x00007f2e584a6483 in ?? ()
[Current thread is 1 (LWP 24270)]
(gdb) bt
#0  0x00007f2e584a6483 in ?? ()
#1  0x0000000000000000 in ?? ()

好家伙全是乱码,这里显然缺少运行时的动态链接库,将服务器上的动态链接库打包后手动指定根目录:

(gdb) set sysroot /home/hyj1991/git/examples/0924/libs
Reading symbols from /home/hyj1991/git/examples/0924/libs/lib64/libdl.so.2...(no debugging symbols found)...done.
Reading symbols from /home/hyj1991/git/examples/0924/libs/lib64/libstdc++.so.6...(no debugging symbols found)...done.
Reading symbols from /home/hyj1991/git/examples/0924/libs/lib64/libm.so.6...(no debugging symbols found)...done.
Reading symbols from /home/hyj1991/git/examples/0924/libs/lib64/libgcc_s.so.1...(no debugging symbols found)...done.
Reading symbols from /home/hyj1991/git/examples/0924/libs/lib64/libpthread.so.0...(no debugging symbols found)...done.
warning: Unable to find libthread_db matching inferior's thread library, thread debugging will not be available.
Reading symbols from /home/hyj1991/git/examples/0924/libs/lib64/libc.so.6...(no debugging symbols found)...done.
Reading symbols from /home/hyj1991/git/examples/0924/libs/home/work/node/node_modules/xprofiler/build/binding/Release/node-v72-linux-x64/xprofiler.node...(no debugging symbols found)...done.

这样总算可以正常查看每一个线程的栈帧回溯:

(gdb) thread apply all bt
Thread 13 (LWP 24269):
#0  0x00007f2e584a6483 in epoll_wait () from /home/hyj1991/git/examples/0924/libs/lib64/libc.so.6
#1  0x00000000013480e0 in uv__io_poll (loop=loop@entry=0x2c9aac0 <default_loop_struct>, timeout=5526) at ../deps/uv/src/unix/linux-core.c:309
#2  0x0000000001335ddf in uv_run (loop=0x2c9aac0 <default_loop_struct>, mode=UV_RUN_DEFAULT) at ../deps/uv/src/unix/core.c:381
#3  0x0000000000a4b5f5 in node::NodeMainInstance::Run() ()
#4  0x00000000009da5a8 in node::Start(int, char**) ()
#5  0x00007f2e583ca3d5 in __libc_start_main () from /home/hyj1991/git/examples/0924/libs/lib64/libc.so.6
#6  0x0000000000979215 in _start ()

Thread 8 (LWP 24285):
#0  0x00007f2e5846ce2d in nanosleep () from /home/hyj1991/git/examples/0924/libs/lib64/libc.so.6
#1  0x00007f2e5846ccc4 in sleep () from /home/hyj1991/git/examples/0924/libs/lib64/libc.so.6
#2  0x00007f2e559809c8 in xprofiler::CreateIpcServer(void (*)(char*)) () from /home/hyj1991/git/examples/0924/libs/home/work/node/node_modules/xprofiler/build/binding/Release/node-v72-linux-x64/xprofiler.node
#3  0x00007f2e5877cdd5 in start_thread () from /home/hyj1991/git/examples/0924/libs/lib64/libpthread.so.0
#4  0x00007f2e584a5ead in clone () from /home/hyj1991/git/examples/0924/libs/lib64/libc.so.6

Thread 7 (LWP 24284):
#0  0x00007f2e5846ce2d in nanosleep () from /home/hyj1991/git/examples/0924/libs/lib64/libc.so.6
#1  0x00007f2e5846ccc4 in sleep () from /home/hyj1991/git/examples/0924/libs/lib64/libc.so.6
#2  0x00007f2e559409b4 in xprofiler::CreateLogThread(void*) () from /home/hyj1991/git/examples/0924/libs/home/work/node/node_modules/xprofiler/build/binding/Release/node-v72-linux-x64/xprofiler.node
#3  0x00007f2e5877cdd5 in start_thread () from /home/hyj1991/git/examples/0924/libs/lib64/libpthread.so.0
#4  0x00007f2e584a5ead in clone () from /home/hyj1991/git/examples/0924/libs/lib64/libc.so.6

可以看到 xprofiler 插件创建的两个线程正常工作,JS 主线程则处于 epoll_wait 状态,没有任何可疑的会导致进程假死的阻塞!

到这里我是真的疑惑了,线程堆栈完全正常,CPU 和内存也正常,那么进程为什么会处于无响应的假死状态呢?

IV. 另一个思路

连万能的 Core 分析都找不到异常的地方,似乎这个问题已经没办法排查了。

无奈之下,我又回到一开始的问题: 为什么处于子线程的 xprofiler 插件不再输出日志

回顾了一遍 xprofiler 插件中定时采集输出日志的逻辑:

static void CreateLogThread(void *unused) {
  uint64_t last_loop_time = uv_hrtime();
  while (1) {
    Sleep(1);

    SetNowCpuUsage();

    if (uv_hrtime() - last_loop_time >= GetLogInterval() * 10e8) {
      last_loop_time = uv_hrtime();
      bool log_format_alinode = GetFormatAsAlinode();

      GetMemoryInfo();
      GetLibuvHandles();

      Sleep(1);

      WriteCpuUsageInPeriod(log_format_alinode);
      WriteMemoryInfoToLog(log_format_alinode);
      WriteGcStatusToLog(log_format_alinode);
      WriteLibuvHandleInfoToLog(log_format_alinode);
      WriteHttpStatus(log_format_alinode, GetPatchHttpTimeout());
    }
  }
}

可以确定只要日志线程依旧存活,一定会走到写日志的逻辑:

#define WRITET_TO_FILE(type)                   \
  uv_mutex_lock(&logger_mutex);                \
  type##_stream.open(filepath, std::ios::app); \
  type##_stream << log;                        \
  type##_stream.close();                       \
  uv_mutex_unlock(&logger_mutex);

这里用了一个宏来将日志写到文件,本质上就是一个 ofstream 的文件流,看到这里我有一个猜测是不是这个文件流打开失败了导致内核日志没有正常写入文件。

于是去翻 Linux Man 手册 open 方法看看哪些情况下会调用失败:

image.png

其它的看起来都不太可能,唯独和文件打开数相关的限制看起来可疑:

The system limit on the total number of open files has been reached.

到这里感觉突然峰回路转,立马联系 Midqiu 查看重启后的进程的文件打开数:

lsof -p <pid> | wc -l

并且将这个值与系统的 ulimit -n 限制进行对比:

image.png

果然,重启后的 Node.js 进程文件打开数随着访问量逐步上涨,逐渐逼近系统的限制,问题就是出在这个假死进程的文件打开数上!

V. 定位问题代码段

有了问题的方向接下来就方便很多,我们可以使用 lsof -p <pid> 查看具体是哪些句柄:

image.png

好家伙,8W+ 的 /home/work/node/logs/important/production.x-access.serverless_runtime.2020-09-24.log 这个文件的重复文件句柄!

经过沟通,这个文件是项目里面用来记录用户请求 access 日志对应的日志文件,那么猜测是记录 access 日志的中间件在重复的 fs.open 此文件。

拿到这个日志中间件的源代码,果然存在一个 Logger 类里有 fs.open 动作:

private async ensureFile(filename: string): Promise < number > {
  if(Object.keys(this.fds).length > 150) {
    for (const [_, fd] of Object.entries(this.fds)) {
      fs.close(fd);
    }
    this.fds = {};
   }
  if (!this.fds[filename]) {
    this.fds[filename] = await new Promise<number>((resolve, reject) => {
      fs.open(filename, "a", (err, fd) => {
        if (err) {
          reject(err);
        } else {
          resolve(fd);
        }
      });
    });
  }
  return this.fds[filename];
}

然后是记录日志的地方会调用这个方法获取日志文件的 fd:

private async appendLine(filename: string, line: string): Promise < void> {
  const fd = await this.ensureFile(filename);
  await new Promise(((resolve, reject) => {
    fs.write(fd, line + "\n", (err) => {
      if (err) {
        reject(err);
      } else {
        resolve();
      }
    });
  }));
}

这个 Logger 类到这里看起来也没问题,然而最后回到服务的入口文件时发现开发者竟然把 Logger 这个需要全局共享的实例初始化放到了 Express 中间件里:

app.use(function(req, res, next) {
  //...
  const logger = new Logger();
  res.locals.log = logger;
  next();
});

这就导致每来一个用户请求都会实例化一个 Logger 实例,此时记录本次请求的 access 日志就会打开一个重复的日志文件句柄,从而导致了进程文件句柄的泄露。

最后进程可使用的文件句柄数超过系统限制后进程就处于假死状态,表现为文件句柄数溢出后,后续此进程任何 I/O 相关的系统调用都会阻塞。

VI. 修复文件句柄泄露

定位到代码问题后,修复也非常简单,这里可以全局初始化 access 日志实例,请求日志共享此文件句柄即可。

另外简单点也可以直接修改 appendLine 方法为 fs.writeFile :

private async appendLine(filename: string, line: string): Promise < void> {
  await new Promise(((resolve, reject) => {
    fs.writeFile(filename, line + "\n", { flag: 'a' }, (err) => {
      if (err) {
        reject(err);
      } else {
        resolve();
      }
    });
  }));
}

这样牺牲了一部分性能但是不会有文件句柄泄露的问题。

VII. 小结

实际上 Node.js 提供的的 stream 、 net 以及 fs 里面的 fd 相关操作函数,这些模块或者函数相对 Node.js 其它封装的上层函数更接近底层库。

而历史经验告诉我们,这部分底层库相关的函数,如果自己没有完全理解千万不要随便想当然的用,大概率一写一个大坑等着你。

最后 Easy-Monitor 即将加入对监控进程自身的文件打开数的监控,帮助大家下次秒解决此类问题。


大前端-前端高级进阶

0
0

下载地址:百度网盘

大前端-前端高级进阶 比前端全栈更上一层 专为实际开发经验1年以上的前端工程师设计 高效全能架构前端 录播+直播+社群+问答等慕课网精心设计8种方式,全方位助力学习

为什么要学大前端?

形势:5G风口,前端岗位的内涵和外延不断扩大

现状:企业缩减人力成本,前端岗位竞争越发激烈

1:BATJ大厂布局小程序、移动

端,拿offer须会多端开发

2:前、后端界限融合,大前端当

道,倒逼程序员转型转行

3:入职几年因缺乏完整项目经验,

升职加薪无望

4:前端技术迭代快,不主动更新技

术就将落后于人

机遇与挑战并存,是时候进击大前端了。

【活动】腾讯云十周年感恩回馈,1核2G云服务器首年95元,4核云服务器额外赠送150GB高性能云数据盘,十年筑梦,伴你同行!

0
0

【活动】腾讯云十周年感恩回馈,1核2G云服务器首年95元,4核云服务器额外赠送150GB高性能云数据盘,十年筑梦,伴你同行! 1核2G1M50G盘,95元/1年, 1核2G1M50G盘,288元/3年, 2核4G3M50G盘,1288元/3年, 活动地址:https://curl.qcloud.com/4H1wdRQS

【阿里云】云服务器,新用户福利专场,云服务器ECS低至102元/年(免费赠送对象存储和数据库),还有400元无门槛券领取!!! https://www.aliyun.com/activity?userCode=wbqjs7bw

字节跳动-飞书业务团队诚招前端开发

0
0

欢迎一起打造最好的协同办公软件 急需优秀的前端开发, 共同探索协同工办公的未来

福利待遇:优厚薪资,六险一金,弹性工作,免费三餐,租房补贴,打车补贴,带薪休假,休闲下午茶+不间断咖啡零食供应,“氛围平等,免费健身房游泳池,专业大牛多,晋升空间大,团队氛围好。 企业文化:追求极致,务实敢为,开放谦逊,坦诚清晰,始终创业,在字节,我就是我,只看能力不讲title,做不一样的烟火。

职位描述 1、负责字节跳动效率工具的前端研发; 2、负责高质量的设计和编码,不断优化用户体验; 3、和产品经理配合,参与产品需求讨论,功能定义等; 4、主要前端框架:React、Electron 等。 职位要求 1、良好的设计和编码品味,热爱写代码,能产出高质量的设计和代码; 2、掌握 WEB 前端开发技术:JavaScript (含 ES6 )、HTML、CSS、DOM、协议、安全等; 3、较好的产品意识,愿意将产品效果做为工作最重要的驱动因素; 4、计算机及其相关专业,本科及以上学历。

地点: 北京

投递渠道:https://job.toutiao.com/s/JDrGsSx

前端性能优化--6大角度综合型优化方案

0
0

下载地址:百度网盘

第1章 课程介绍 介绍这门课程要讲的主要内容,讲解方式、课程主线、以及能有的收获。 1-1 课程导学 【课程背景,大纲速览】 试看 第2章 性能优化的指标和工具 (告别前端小白,成为大神的必经之路) 本章带大家认识前端优化优化的重要性,了解当前的行业标准,流行的模型和测量工具,以及如何有针对性的进行性能的测量,解读性能报告;还会学习到很多与性能相关的APIs的使用和实用的例子。 2-1 为什么要进行Web性能优化【企业刚需】 2-2 性能指标和优化目标【了解行业标准】 2-3 RAIL测量模型【黄金指南】 2-4 使用WebPageTest评估Web网站性能【快捷好用的在线分析工具】 2-5 使用LightHouse分析性能【一站式全面呈现性能指标】 2-6 使用Chrome DevTools分析性能【最大法宝】 2-7 常用的性能测量APIs【不可不知,打开精细化、自定义测量的大门】 试看 第3章 渲染优化 (与浏览器为友,共进退) 本章涵盖现代浏览器的渲染原理,详细解读各个环节的作用和相互联系,具体讲解如何减少和避免回流和重绘,还有如何解决布局抖动的问题。 3-1 浏览器渲染原理和关键渲染路径【大厂前端面试必考】 3-2 回流与重绘, 如何避免布局抖动 3-3 使用FastDom【防止布局抖动的利器】 3-4 复合线程与图层【深入渲染流水线的最后一站】 3-5 避免重绘【必学,加速页面呈现】 3-6 高频事件防抖【解救页面卡顿的秘药】 3-7 React时间调度实现【中高级前端需要了解的React调度原理】 第4章 代码优化 (快来看看怎样写出真正高性能的代码) 本章主要了解在代码层面上可以进行的极致优化,涉及JavaScript,CSS和HTML的方方面面;较深的理论知识会做到深入浅出的讲解,让你了解如何配合JS引擎写会可以被它有效优化的代码。 4-1 JS开销和如何缩短解析时间【为什么我的JS运行慢】 4-2 配合V8 有效优化代码【路走对了才能快】 4-3 函数优化【必会】 4-4 对象优化【JS对象避坑地图】 4-5 HTML优化【必会】 4-6 CSS对性能的影响 【必会】 第5章 资源优化 (经典性能优化解决方案) 本章学习如何对Web加载的资源进行有效的优化,不仅涉及压缩的知识,还详细讲解一些针对图片、字体类资源本身特性和使用方式不同可以带来的性能提升。 5-1 资源的压缩与合并【见效最明显的优化方法】 5-2 图片格式优化【多种图片格式,哪种最合适】 5-3 图片加载优化【突破大型网站图片加载的瓶颈】 5-4 字体优化【千万不可忽略】 第6章 构建优化(揭开webpack性能优化的内幕) 本章讲解基于Webpack项目如何进行全方位的性能优化,让你的企业级应用速度翻倍。以一个基本webpack工程开始,逐项讲解如何进行配置,达到优化的效果。 6-1 webpack的优化配置【了解这些优化配置才敢说会用webpack】 6-2 webpack的依赖优化【小改动,大作用】 6-3 基于webpack的代码拆分【让网站按需加载】 6-4 手把手教你做webpack的资源压缩 6-5 基于webpack的持久化缓存【大型企业级应用的必会技能】 6-6 基于webpack的应用大小监测与分析【webpack性能分析的法宝】 6-7 React按需加载的实现方式【中高级前端必会的React按需加载】 第7章 传输加载优化(前沿技术解决高访问量网站性能优化问题) 本章包括了前沿的网络加载优化技术,从了解现代网络上的问题和多样的流行技术解决方案,给大型、高访问量的网站带来质的飞跃。 7-1 启用压缩Gzip【必会的传输压缩方案】 7-2 启用Keep Alive【通过一个参数提速连接】 7-3 HTTP资源缓存【必会的HTTP缓存方法】 7-4 一次性理解Service workers技术,给网站提速 7-5 HTTP 2的性能提升 7-6 用流行的SSR技术给前端减负 第8章 前沿优化解决方案 本章在之前章节知识点的基础上,补充了更多流行的Web性能优化技术。以例子为导向掌握用法。 8-1 拯救移动端图标SVG【拯救移动端图标】 8-2 使用Flexbox优化布局(上) 8-3 使用Flexbox优化布局(下) 8-4 优化资源加载的顺序【给资源设置优先级】 8-5 预渲染页面【提前完成任务的意义】 8-6 Windowing提高列表性能【开源节流,用什么画什么】 8-7 使用骨架组件减少布局移动【论占位置的重要性】 第9章 性能优化问题面试指南【能胸有成竹的一步】 本章在之前章节的基础之上,针对当前高频的性能优化相关问题进行剖析,让大家同时了解应对的方法,如何准确理解问题,抓住重点进行作答。 9-1 Web加载&渲染基本原理 9-2 首屏加载优化 9-3 JavaScript 内存管理 第10章 互联网外企offer与立足之道【能不能出国,就看这一章了】 本章向大家介绍互联网外企面试中常见流程和考察重点,同时带来在外企生存的职业经验分享。 10-1 互联网外企offer 10-2 英语与工作 本课程已完结

实际项目中,切换数据库的案例多吗

0
0

比如从mysql切换到oracle 或者postgre

前端周刊:2020-16期

0
0

前端周刊:2020-16期

前端开发

调研是了解诉求,以及寻找可能的方案,而验证就是检验诉求及方案的可行性。

Chrome发展太迅猛了。

1.0版本了,感觉可以找个项目试试

JavaScript中的Map和ForEach有什么区别?

webpack编译大型项目太吃CPU了,vite直击痛点,凭Vue全家桶的带货能力,vite非常有戏

辅助实现PWA的webpack插件

很多人都有这个困扰把,解决方法在这里

行业周边

新客购云产品一折起 最高2000元红包

【腾讯云】云产品限时秒杀,爆款1核2G云服务器,首年99元

很多促销广告短信都说回复 TD 退订,但是回复了 TD 之后,为什么同一个号码还会发短信过来?

“机会留给有准备的人”的另一个版本

加群交流

添加好友,备注“加群”

refned_x

求问各位前辈,有用过Koa-static的吗?这个包如何细分各个路径呢?

0
0

最近使用了koa-static 这个包,把服务器静态资源配置进去,访问的方法就是服务器地址加端口加文件名,但是发现配置多个文件都是直接加文件名称就可以访问了,请问路径如何细分呢?比如/business/xxxx.jpg ,谢谢啦


请问大佬,node有没有办法给函数添加一个监听函数。

0
0

比如我给console.log 添加一个监听函数,每次执行log时,都会执行这个监听函数,不用改变console.log本身

[上海] 梯方教育招聘 node.js、桌面端开发2人 跪求各位大佬稍微瞄一眼 真0-1项目 已启动

0
0

本土k12应试教育企业,公司地址杨浦区 12号线隆昌路站出站既是 薪资可放开聊 放! 开! 聊! 目前急需上线可视化桌面的项目 如果有意向可以了解下的话随时加我电话VX 13248010860沟通 24小时营业 VX不回可以直接电话

职位描述 -负责桌面客户端研发工作 -和团队一起探索项目需求,给出良好的解决方案,一起完成开发及调试工作 -持续关注新技术,保持产品技术的先进性 职位要求 -熟练掌握TypeScript和ES,有基于Vue等现代前端框架的项目开发经验 -熟练掌握Electron/NW.js开发 -对Node.js的events/stream/net等模块有实际使用经验 -了解HTTP/WebSocket协议 -熟练使用Git,熟悉CI/CD流程,良好的开发流程习惯 -有跨端调试/整合的经验 加分项 -使用其他技术栈开发过桌面客户端,如QT -有服务端开发经验 -掌握一门其他语言如 C++/Rust/Python -使用过NodeFFI或N-API -使用过NPAPI/PPAPI -使用过Socket.io/RSocket -关注代码的工程实现质量,测试友好

Egg,Controller中回调函数问题

0
0

在项目开发中遇到了使用第三方旧库,仅提供回调方式返回结果。现在遇到的问题是:

MyController{
	async fn(){
		const ctx = this.ctx;
		//调用第三方库
		api('balabala',function(err,cb){
			if(xxx){
			}
			ctx.body="success"; //前端请求到此Fn时,无返回数据.
		})
	}
}

请问如何正确使用this.ctx,才能让Controller正确响应请求.

如何使用 ThinkJS 优雅的编写 RESTful API

0
0

RESTful 是目前比较主流的一种用来设计和编排服务端 API 的一种规范。在 RESTful API 中,所有的接口操作都被认为是对资源的 CRUD,使用 URI 来表示操作的资源,请求方法表示具体的操作,响应状态码表示操作结果。之前使用 RESTful 的规范写过不少 API 接口,我个人认为它最大的好处就是帮助我们更好的去规划整理接口,如果还是按照以前根据需求来写接口的话接口的复用率不高不说,整个项目也会变得非常的杂乱。

文件即路由是 ThinkJS 的一大特色,比如 /user这个路由等价于 /user/index,会对应到 src/controller/user.js中的 indexAction方法。那么就以 /user这个 API 为例,在 ThinkJS 中要创建 RESTful 风格的 API 需要以下两个步骤:

  1. 运行命令 thinkjs controller user -r会创建路由文件 src/controller/user.js
  2. src/config/router.js中使用自定义路由标记该路由为 RESTful 路由
    //src/config/router.js
    module.exports = [
      ['/user/:id?', 'rest']
    ];
    

这样我们就完成了一个 RESTful 路由的初始化,这个资源的所有操作都会被映射成路由文件中对应请求方法的 Action 函数中,例如:

  • GET /user获取用户列表,对应 getAction方法
  • GET /user/:id获取某个用户的详细信息,也对应getAction` 方法
  • POST /user添加一位用户,对应 postAction方法
  • PUT /user/:id更新一位用户资料,对应 putAction方法
  • DELETE /user/:id删除一位用户,对应 deleteAction方法

然而每个 RESTful 路由都需要去 router.js中写一遍自定义路由未免过于麻烦。所以我写了一个中间件 think-router-rest,只需要在 Controller 文件中使用 _REST静态属性标记一下就可以将其转换成 RESTful 路由了。

//src/controller/user.js
module.exports = class extends think.Controller {
  static get _REST() {
    return true;
  }

  getAction() {}
  postAction() {}
  putAction() {}
  deleteAction() {}
}

简单的了解了一些入门知识之后,下面我就讲一些我平常开发 RESTful 接口时对我有帮助的一些知识点,希望对大家开发项目会有所帮助。

表结构梳理

拿到需求之后千万不要急着先敲键盘,一定要把表结构整理好。其实说是表结构,实际上就是对资源的整理。以 MySQL 为例,一般一类资源就会是一张表,比如 user用户表,post文章表等。当你把表罗列出来之后那么其实你的 RESTful 接口就已经七七八八了。比如你有一张 post文章表,那么之后你的接口肯定会有:

  • GET /post获取文章列表
  • GET /post/1获取 id=1的文章信息
  • POST /post添加文章
  • PUT /post/1修改 id=1的文章信息
  • DELETE /post/1删除 id=1的文章

当然不是所有的事情都这么完美,有时候接口的操作可能五花八门,这种时候我们就要尽量的去思考接口行为的本质是什么。比如说我们要迁移文章给其它用户,这时候你就要思考它其实本质上就是修改 post文章资源的 user_id属性,最终还是会映射到 PUT /post/1接口中来。

想清楚有哪些资源能帮助你更好的创建表,接下来就要想清楚资源之间的关系了,它能帮助你更好的创建表结构。一般资源之间会存在以下几类关系:

  • 一对一:如果一位 user只能创建一篇 post文章,则是一对一的关系。在 post中可以使用 user_id字段来关联对应的 user数据,在 user中也可以使用 post_id来关联对应的文章数据。
  • 一对多:如果一位 user能创建多篇 post文章,则是一对多的关系。在 post中可以使用 user_id字段来关联对应的 user数据。
  • 多对多:如果一位 user可以创建多篇 post文章,一篇 post文章也可以有多位 user,则是多对多的关系。多对多关系没办法通过一个字段来表示,这时候为了描述清楚多对多的关系,就需要一张中间表 user_post,用来做 userpost表的关系映射。表内部的 user_id表示 user表 ID,post_id则表示 post表对应数据 ID。
mysql> DESCRIBE user;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| name  | varchar(100) | YES  |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+
2 rows in set (0.01 sec)

mysql> DESCRIBE post;
+-------+---------+------+-----+---------+----------------+
| Field | Type    | Null | Key | Default | Extra          |
+-------+---------+------+-----+---------+----------------+
| id    | int(11) | NO   | PRI | NULL    | auto_increment |
| title | text    | YES  |     | NULL    |                |
+-------+---------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> DESCRIBE user_post;
+---------+---------+------+-----+---------+----------------+
| Field   | Type    | Null | Key | Default | Extra          |
+---------+---------+------+-----+---------+----------------+
| id      | int(11) | NO   | PRI | NULL    | auto_increment |
| user_id | int(11) | NO   |     | NULL    |                |
| post_id | int(11) | NO   |     | NULL    |                |
+---------+---------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

作为一款约定大于配置的 Web 框架,ThinkJS 默认规定了请求 RESTful 资源的时候,会根据当前资源 URI 找到对应的资源表,比如 GET /post会找到 post表。然后再进行查询的之后会进行自动的关联查询。例如当你在模型里标记了 postuser是一对多的关系,且 post表中存在 user_id字段(也就是关联表表名 + _id),会自动关联获取到 project对应的 user数据。这在进行数据操作的时候会节省非常多的工作量。

登录登出

当我第一次写 RESTful API 的时候,我就碰到了这个难题,平常大家都是使用 /login, /logout来表示登录和登出操作的,如何使用资源的形式来表达就成了问题。后来想了下登录操作中涉及到的资源其实就是登录后的 Token 凭证,本质上登录就是凭证的创建与获取,登出就是凭证的删除。

  • GET /token:获取凭证,用来判断是否登录
  • POST /token:创建凭证,用来进行登录操作
  • DELETE /token:删除凭证,用来进行登出操作

权限校验

我们平常写接口逻辑,其实会有很大一部分的工作量是用来做用户请求的处理。包括用户权限的校验和用户参数的校验处理等,这些逻辑其实和主业务场景没有太大的关系。为了将这些逻辑与主业务场景进行解耦,基于 Controller 层之上,ThinkJS 会存在一层 Logic逻辑校验层。Logic 与 Controller 一一映射,并提供了一些常用的校验方法,我们可以将权限校验,参数校验,参数处理等逻辑放在这里,让 Controller 只做真正的业务逻辑。

在 Logic 和 Controller 中,都存在 __before()魔术方法,当前 Controller 内所有的 Action 执行之前都会先执行 __before()操作。利用这个特性,我们可以将一些通用的权限校验逻辑放在这里,比如最平常的登录判断逻辑,这样就不需要在每个地方都做判断了。

//src/logic/base.js
module.exports = class extends think.Logic {
  async __before() {
    //接口 CSRF 校验
    if (!this.isCli && !this.isGet) {
      const referrer = this.referrer(true);
      if (!/^xxx\.com$/.test(referrer)) {
        return this.fail('请不要在非其它网站中使用该接口!');
      }
    }

    // 非登录接口需要做登录校验
    const userInfo = await this.session('userInfo') || {};
    if(think.isEmpty(userInfo) && !/\/(?:token)\.js/.test(this.__filename)) {
      return this.ctx.throw(401, 'UnAuthorized');
    }
  }
}

//src/logic/user.js
const Base = require('./base.js');
module.exports = class extends Base {}

创建一个 Base 基类,所有的 Logic 通过继承该基类就都能享受到 CSRF 和登录校验了。

问:所有的请求都会实例化类,所以 contructor本质上也会在所有的 Action 之前执行,那为什么还需要 __before()魔术方法的存在呢?

答:constructor构造函数虽然有前置执行的特性,但是无法在保证顺序的情况下执行异步操作。构造函数前是不能使用 async标记的,而 __before()是可以的,这也是它存在的原因。

善用继承

在 RESTful API 中,我们其实会发现很多资源是具有从属关系的。比如一个项目下的用户对应的文章,这句话中的三种资源 项目用户文章就是从属关系。在从属关系中包括权限、数据操作等也都是具有从属关系的。比如说文章属于用户,非该用户的话自然是无法看到对应的文章的。而用户又从属于项目,其它项目的人是无法操作该项目下的用户的。这就是所谓的从属关系。

确立了从属关系之后我们会发现越到下级的资源在对其操作的时候要判断的权限就越多。以刚才的例子为例,如果说我们对项目资源进行操作的话,我们需要判断该用户是否在项目中。而如果要对项目下的用户文章进行操作的话,除了需要判断用户是否在项目中,还需要判断该文章是否是当前用户的。

在这个例子中我们可以发现:**资源关系从属的话权限校验也会是从属关系,从属关系中级别越深的资源需要判断的权限越多。**面向对象语言中,继承是一个比较重要的功能,它最大的好处就是能帮助我们进行逻辑的复用。通过继承,我们能直接在子资源中复用父资源的校验逻辑,避免重复劳动。

//src/logic/base.js
module.exports = class extends think.Logic {
  async __before() {
    const userInfo = this.session('userInfo') || {};
    this.userInfo = this.ctx.state.userInfo = userInfo;
    if(think.isEmpty(userInfo)) {
      return this.ctx.throw(401);
    }
  }
}

//src/logic/project/base.js
const Base = require('../base.js');
module.exports = class extends Base {
async __before() {
    await super.__before();

    const {team_id} = this.get();
    const {id: user_id} = this.userInfo;
    const permission = await this.model('team_user').where({team_id, user_id}).find();
    
    const {controller} = this.ctx;
    // 团队接口中只有普通用户只有权限调用获取邀请链接详细信息和接受邀请链接两个接口
    if(controller !== 'team/invitation' && (this.isGet && !this.id)) {
      if(think.isEmpty(permission)) {
        return this.fail('你没有权限操作该团队');
      }
    }
    
    this.userInfo.role_id = permission.role_id;
  }
}

//src/logic/project/user/base.js
const Base = require('../base');
module.eports = class extends Base {
  async __before() {
    await super.__before();
    
    const {role_id} = this.userInfo;
    if(!global.EDITOR.is(role_id)) {
      return this.fail('你没有权限操作该文章');
    }
  }
}

通过创建三个 Base 基类,我们将权限校验进行了合理的拆分同时又能保证校验的完整性。同级别的路由只要继承当前层级的 Base 基类就能享受到通用的校验逻辑。

  • /project路由对应的 Logic 因为继承了 src/logic/base.js所以实现了登录校验。
  • /project/1/user路由对应的 Logic 因为继承了 src/logic/project/base.js所以实现了登录校验以及是否在是项目成员的校验。
  • /project/1/user/1/post路由对应的 Logic 因为继承了 src/logic/project/user/base.js所以实现了登录校验、项目成员校验以及项目成员权限的校验。

瞧,套娃就这么简单!

数据库操作

从属的资源在表结构上也有一定的反应。还是以之前的项目、用户和文章为例,一般来说你的文章表里会存在 project_iduser_id两个关联字段来表示文章与用户和项目资源的关系(简单假设都是一对多的关系)。那么这时候实际上你对项目下的文章操作实际上都需要传入 project_iduser_id这两个 WHERE 条件。

ThinkJS 内部使用 think-model来进行 SQL 数据库操作。它有一个特性是支持链式调用,我们可以这样写一个查询操作。

//src/controller/project/user/post.js
module.exports = class extends think.Controller {
  async indexAction() {
    const ret = await this.model('post').where({project_id: 1}).where({user_id: 2}).select();
    return this.success(ret);
  }
}

利用这个特性,我们可以对操作进行优化,在 constructor的时候将当前 Controller 下的通用 WHERE 条件 project_iduser_id传入。这样我们在其它的 Action 操作的时候就不用每个都传一变了,同时也一定规避了可能会漏传限制条件的风险。

//src/controller/project/user/post.js
module.exports = class extends think.Controller {
  constructor(ctx) {
    super(ctx);
    const {project_id, user_id} = this.get();
    this.modelInstance = this.model('post').where({project_id, user_id});
  }

  async getAction() {
    const ret = await this.modelInstance.select();
    return this.success(ret);
  }
}

后记

RESTful API 除了以上说的一些特性之外,它对响应状态码、接口的版本也有一定的规范定义。像 Github 这种 RESTful 实现比较好的网站还会实现 Hypermedia API规范,在每个接口中会返回操作其它资源时需要的 RESTful 路由地址,方便调用者进行链式调用。

当然 RESTful 只是实现 API 的一种规范,还有其它的一些实现规范,比如 GraphQL。关于 GraphQL 可以看看之前的文章《GraphQL 基础实践》,这里就不多做补充了。

eggjs中能够重定向返回(打开)安卓应用吗?

0
0

场景: 安卓客户端请求苹果登陆认证会跳转浏览器进行认证,完成认证后会重定向至服务端(eggjs),然后服务端完成逻辑(生成token)后需要能够跳回安卓客户端。 问题: 我在一个第三方库提供的例子中,它使用的是express进行服务端的逻辑处理,最后使用

  	const url = `intent://callback?#Intent;package=PACKAGE;scheme=signinwithapple;end`;
  	response.redirect(307, url);

进行重定向回来客户端成功了。 但是在eggjs中我使用类似的重定向方法:

    const url = 'intent://callback?#Intent;package=PACKAGE;scheme=signinwithapple;end';
    ctx.status = 307;
    ctx.redirect(url);

却是报404;

然后我调断点进入egg的redirect方法,发现它会因为我url变量中协议(protocal)写的是intent不是http/https而把url重置为"/",最后跑进了unsafeRedirect()。但我不知道是不是因为这个原因而导致的重定向失败。

所以我想知道,egg中有能够重定向回安卓客户端的方法吗?

我做了个通俗易懂的Git教学视频,希望大家喜欢

saas软件如何解决多客户需求

0
0

公司是一家tob的物联网车联网公司然后做saas软件 很多项目都是需要面对多个客户 每个客户的需求可能大致相同 有些细节或者流程会不同

困扰第一版出来之 后分开写的话 就需要维护很多个项目 如果写到一个项目中的话 会变得很复杂 流程测试也很蛋疼 可能会改动其中一家的需求 影响到另一家的流程

然后公司还有很多中台服务 比如 负责登陆的用户中心 负责认证的 负责数据的 还有业务的中台 等等一堆。。

导致做项目的时候 即使需求不是很多 也会有很多人参与进来 一二十人都很正常 因为涉及的中台服务 岗位 很多 开发周期也很长

感觉体验不是很好 QAQ

个人感觉问题的所在就是如何“优雅”解决不同客户的需求的问题

有没有有过这种经历的大佬 给点建议的QAQ


招聘高级前端开发工程师(腾讯新闻)

0
0

高级前端开发工程师(腾讯新闻) 岗位职责: 1.负责CP、CMS等toB内容生态能力的架构设计和研发 2.负责移动端海量访问web服务开发,支持高并发场景下前端服务处理与优化 3.参与部门前端中台框架等基础设施开发与工程化工具建设 4.参与小程序、客户端app等创新性项目的架构设计和研发

岗位要求: 1.计算机相关专业本科及以上学历,量年以上前端开发工作经验; 2.精通前端HTML、CSS、Javascript,至少两年以上Javascript开发经验 3.熟练掌握常用的数据结构和算法,熟练掌握设计模式和一到两个常用框架 4.精通主流前端框架React、Vue、Angular中的至少一种,对前端开发的前沿技术有深入了解 5.具备跨浏览器开发、PC、移动端开发经验 6.精通前端构建工具Webpack、Gulp、Rollup中至少一种 7.熟悉Nodejs后端开发、Mysql数据库开发,有实践经验者优先 8.熟悉flutter客户端开发、小程序开发,有经验者优先 9.对工作认真负责,思路周密,代码严谨,有互联网企业开发相关工作经验者优先

岗位亮点: 1.参与开发日活上亿大型前端项目,近距离感受海量web服务带来的挑战和成长 2.与多位业界前端大神共事,共同专研新技术攻克难题 3.共同维护多个开源项目,参与高star开源项目开发与共建 4.有充足的的空间和机会实现业界最新前端技术落地与实践

工作地点:北京腾讯总部大厦

欢迎发送你的简历到邮箱 624313307@qq.com

提前两天就休假了,正好晚上听 《创业内幕 Startup Insider》播客有感。

egg多进程实现锁的最佳实践是什么?

0
0

现在egg多个进程间对资源的争抢是怎么设计这个锁的呢?目前我们自己都是使用redis.incr来争抢某个key,值为1的进程就可以操作该资源,其他进程放弃操作或者循环等待。 看到有博客提到Atomics这个模块,不知道egg中是否可以使用这个实现锁?

npm-dist,提取node_modules有用文件

0
0

马上要国庆节了,做了这个npm-dist,取名随意 移除node_modules无用文件,很多npm包加了很多小包,个人觉得不妥,特想精简一下,打包环境适用 https://www.npmjs.com/package/npm-dist/v/1.0.1今年以来,有感这个论坛及node圈热度不在,有意见和建议欢迎反馈,大家共勉

Vue3.0(正式版) + TS 仿知乎专栏企业级项目

0
0

下载地址:百度网盘

第1章 课程介绍 本章节介绍整个课程的内容,让大家了解课程的核心和安排。

1-1 课程介绍(导学 ) 试看 1-2 代码库和在线文档使用注意事项(必看) 第2章 你好 Typescript: 进入类型的世界 本章主要帮助大家理解 TypeScript 可以解决的问题和所带来的优势,带领大家学习 TS 中的各种基础类型,然后进阶到复杂类型,包括:Array, Tuple, interface, function, Class, Enum, Generices等,迅速帮助大家理解 TS 的基础使用方式和语法。

2-1 什么是 Typescript 2-2 为什么要学习 typescript 2-3 安装 typescript 2-4 原始数据类型和 Any 类型 2-5 数组和元组 2-6 Interface- 接口 初探 2-7 函数 2-8 类型推论 联合类型和 类型断言 2-9 class - 类 初次见面 2-10 类和接口 - 完美搭档 2-11 枚举(Enum) 2-12 泛型(Generics) 第一部分 2-13 泛型(Generics) 第二部分 - 约束泛型 2-14 泛型第三部分 - 泛型在类和接口中的使用 2-15 类型别名,字面量 和 交叉类型 2-16 声明文件 2-17 内置类型 第3章 初识 Vue3.0: 新特性详解 首先浏览 vue3 新带来的变化,然后从为什么会有 vue3 引出话题, 带领大家学习 compostion API,自定义Hooks,Teleport,Suspense 和 全局 API 修改等一系列 vue3 的重大更新。

3-1 vue3 新特性巡礼 3-2 为什么会有 vue3 3-3 使用 vue-cli 配置 vue3 开发环境 3-4 项目文件结构分析和推荐插件安装 3-5 vue3 - ref 的妙用 试看 3-6 更近一步 - reactive 3-7 vue3 响应式对象的新花样 3-8 老瓶新酒 - 生命周期 3-9 侦测变化 - watch 3-10 vue3 模块化妙用- 鼠标追踪器 3-11 模块化难度上升 - useURLLoader 3-12 模块化结合typescript - 泛型改造 3-13 Typescript 对 vue3 的加持 3-14 Teleport - 瞬间移动 第一部分 3-15 Teleport - 瞬间移动 第二部分 3-16 Suspense - 异步请求好帮手第一部分 3-17 Suspense - 异步请求好帮手第二部分 3-18 全局 API 修改 第4章 项目起航 - 准备工作和第一个页面 本章从项目的需求开始分析,然后确定项目的整体结构和代码规范,并且为项目选择 Bootstrap 作为样式库,编写 ColumnList 和 GlobalHeader 完成简单的练手以后,开始挑战第一个比较复杂的 Dropdown 下拉菜单组件,最后还抽象抽象出第一个 hooks 函数。…

4-1 项目起航 需求分析 4-2 文件结构和代码规范 4-3 样式解决方案简介和分析 4-4 设计图拆分和组件属性分析 4-5 ColumnList 组件编码 4-6 ColumnList 组件使用 Bootstrap 美化 4-7 GlobalHeader 组件编码 4-8 Dropdown 组件基本功能编码 4-9 Dropdown 组件添加 DropdownItem 4-10 Dropdown 组件点击外部区域自动隐藏 4-11 useClickOutside 第一个自定义函数 第5章 表单的世界 - 完成自定义 Form 组件 本章来到表单的世界,从头到尾非常完整的完成了一个带验证表单组件的全流程开发过程,在整个过程中,我们还学习到了 v-model,$attrs, slot,组件父子通讯 和 mitt 的各种知识点。

5-1 web 世界的经典元素 - 表单 5-2 ValidateInput 第一部分 — 简单的实现 试看 5-3 ValidateInput 第二部分 —抽象验证规则 5-4 ValidateInput 第三部分 — 支持 v-model 5-5 ValidateInput 编码第四部分 — 使用 $attrs 支持默认属性 5-6 ValidateForm 组件需求分析 5-7 ValidateForm 编码第一部分 - 使用插槽 slot 5-8 ValidateForm 编码第二部分 - 尝试父子通讯 5-9 ValidateForm 编码第三部分 - 寻找外援 mitt 5-10 ValidateForm 编码第四部分 - 大功告成 第6章 请你吃全家桶 - 初步使用 vue-router 和 vuex 本章从 SPA 的概念引出,完成了 vue-router 的安装,然后学习它的基本使用,获取信息,动态跳转,前置守卫和元信息等各种知识点,然后又介绍了状态管理工具的具体定义,从而引出 vuex 的安装 和 它的 state,mutation,getter 等多个基本知识点。…

6-1 什么是 SPA(Single Page Application) 应用? 6-2 vue-router 安装和使用 6-3 vue-router 配置路由 6-4 vue-router 添加路由 6-5 添加 columnDetail页面 6-6 状态管理工具是什么 6-7 Vuex 简介和安装 6-8 Vuex 整合当前应用 6-9 使用 Vuex getters 6-10 添加新建文章页面 6-11 Vue router 添加路由守卫 - 前置守卫 6-12 Vue router 添加路由守卫 - 使用元信息完成权限管理 第7章 前后端结合 - 项目整合后端接口 本章从 前后端分离和 RESTful 概念入手,介绍了为学生提供的 swagger 调试工具如何使用,然后引入 axios,通过 vuex action 的添加,实现 async 改造 和 axios 拦截器的基本用法,最后还抽象出一个 Loader 组件的编码和实现过程。

7-1 前后端分离开发是什么 7-2 RESTful API 设计理念 7-3 使用 swagger在线文档查看接口详情 7-4 axios 的基本用法和独家后端API 使用(必看) 7-5 使用vuex action 发送异步请求 7-6 使用vuex action 发送异步请求第二部分 7-7 使用 async 和 await 改造异步请求 7-8 使用axios拦截器添加loading效果 7-9 Loader 组件编码第一部分 - 基本实现 7-10 Loader 组件编码第二部分 - 使用 Teleport 进行改造 第8章 通行凭证 - 权限管理 本章从 获取 token 为起点,讲述了 JWT 通用身份验证工具的原理和实现,然后完成了 axios 设置通用header 和 持久化登录的处理方法,之后还添加了全局通用错误处理,最后抽象出一个通用组件 Message 的编码和实现过程。

8-1 登录第一部分 获取token 8-2 jwt 的运行机制 8-3 登录第二部分 axios 设置通用 header 8-4 登录第三部分 持久化登录状态 8-5 通用错误处理 8-6 创建 Message 组件 8-7 Message 组件改进为函数调用形式 8-8 作业:注册页面的编写 第9章 道高一尺 - 上传组件 本章实现了 Upload 组件从分析,编码的全过程,在这个过程中,我们将会学到:上传文件的原理,使用 axios 完成文件上传的方法,循序渐进的完成一个复杂组件的开发流程。

第10章 最终的功能 - 编辑和删除文章 通过完成文章的编辑和删除功能,引出了之前组件留下的几个 bug,通过解决bug 带给大家持续优化的思路和方案,最后完成了一个通用 Modal 组件的编码过程。

第11章 持续优化 通过分析发现应用中可以优化的两个部分,提出并编码 数组改成对象 的store 优化方案以及防止重复请求的解决方案。最后还抽象除了 useLoadMore 的逻辑实现,最终完成了一个复杂的自定义 Hooks。

第12章 项目构建和部署 从生产环境的概念以及生产环境和开发环境的异同的概念开始,接着实践了应用构建,构建代码上线,和构建代码持续集成的一系列概念。

第13章 课程总结 本章节带领大家回顾课程的内容。

本课程持续更新中

光年VPN - 科学上网, 翻墙利器

0
0

光年vpn logo

光年VPN - 科学上网, 翻墙利器

建议收藏此页面防止失联或者丢失

  • 部署SSR节点和V2Ray节点。
  • 无需配置,一键连接,适合小白。
  • 提供全球40多个节点,包括BGP线路和CN2 GIA线路。
  • 无限流量,流畅观看高清视频。
  • 海外专业团队运营。
  • 支持支付宝和PayPal,可7天内退款。
  • 注册后可免费VPN试用。

官网提供免费SSR节点订阅链接,请访问地址

官网链接

实时更新最新免翻墙地址!

应用下载

<a target="_blank" href=‘https://apps.apple.com/us/app/lightyearvpn-fast-trusted/id1495258888’><img width=“300” alt=‘Get it on Google Play’ src=‘https://applelaneanimalhospital.com/wp-content/uploads/2019/04/apple.png’/></a>

<a target="_blank" href=‘https://play.google.com/store/apps/details?id=com.stingsystemllc.lightyearapp’><img width=“300” alt=‘Get it on Google Play’ src=‘https://applelaneanimalhospital.com/wp-content/uploads/2019/04/google.png’/></a>

网盘下载

如果Github下载速度不理想,可以使用网盘下载,支持Windows,Mac,和Android客户端下载。

https://lightyear.lanzous.com/b00tu4njg

如何从海外的应用商店下载APP

<a target="_blank" href=“https://zhuanlan.zhihu.com/p/36574047”>5分钟注册美国区Apple ID(2020测试有效)</a>

电报群

https://t.me/lightyearvpn

.js.flow后缀文件是typescript?还是facebook搞出来的?

0
0

落伍了哦:( https://github.com/github/fetch/blob/master/fetch.js.flow中的代码:

type RequestOptions = {|
  body?: ?BodyInit;
  credentials?: CredentialsType;
  headers?: HeadersInit;
  method?: string;
  mode?: string;
  referrer?: string;
  signal?: ?AbortSignal;
|}

上面的类型定义{| ……|}什么用? body?: ?BodyInit; 第二个问号又作何解

谢谢






Latest Images