关于node.js:Nodejs安装及环境配置

一、装置环境1、本机系统:Windows 10 专业版(64位) 2、Node.js:node-v8.9.4-x64.msi(64位) 二、装置Node.js步骤1、下载对应本人零碎对应的 Node.js 版本,地址:https://nodejs.org/zh-cn/ 2、选装置目录进行装置 3、环境配置 4、测试 三、后期筹备1、Node.js简介 Node.js® 是一个基于 Chrome V8 引擎的 JavaScript 运行时。 Node.js 应用高效、轻量级的事件驱动、非阻塞 I/O 模型。它的包生态系统,npm,是目前世界上最大的开源库生态系统。 2、下载Node.js 官网地址:https://nodejs.org/en/ 或 https://nodejs.org/zh-cn/ 我下载的是 node-v8.9.4-x64.msi ,如下图: 四、开始装置 1、下载实现后,双击“ node-v8.9.4-x64.msi ”,开始装置: 2、点击“ Next ”按钮 3、抉择装置目录,点击“ Next ”按钮 4、抉择装置项,此处我抉择默认,点击“ Next ”按钮 5、点击“ Install ”按钮,开始装置 6、期待装置实现,点击“ Finish ”按钮实现装置 五、装置实现查看 1、查看是否装置胜利 A、node -v 查看 node 版本 B、npm -v 查看 npm 版本 2、装置实现后,文件目录如下图 六、环境配置 此处的环境配置次要配置的是 npm 装置的全局模块所在的门路,以及缓存cache的门路,之所以要配置,是因为当前在执行相似:npm install express [-g] (前面的可选参数-g,g代表global全局装置的意思)的装置语句时,会将装置的模块装置到【C:\Users\用户名\AppData\Roaming\npm】门路中,占C盘空间。 例如:我心愿将全模块所在门路和缓存门路放在我node.js装置的文件夹中,则在我的装置目录下创立两个文件夹【node_global】及【node_cache】如下图: 1、设置全局目录和缓存目录,创立完两个空文件夹之后,关上cmd命令窗口,输出 ...

July 27, 2020 · 1 min · jiezi

关于node.js:从Deno跟Node的性能对比说起

往年五月份Deno公布了1.0版本,作为一个常常用Node来构建我的项目的前端,对Deno官网形容的那几点长处其实并不太关怀(Deno长处)。次要还是想晓得Deno的性能怎么样,用Deno能不能大幅缩小前端构建我的项目的耗时。对网络上Deno能不能代替Node的探讨也比拟感兴趣,于是便用Deno跟Node去执行一些罕用的办法,比拟它们的性能,钻研下Deno是否能够代替Node。 Deno简介Deno是一个JavaScript和TypeScript运行时,跟Node、Java一样能够运行在服务器上。 测试信息Node版本v12.18.0,Deno版本1.1.0。执行后果是执行了5次雷同办法取的范畴。第三方库用了spark-md5、js-base64、acorn、estraverse和escodegen。 用sort排序数组(数组长度为20万)console.time("sort-array");dataArray.sort((a, b) => { return a - b;});console.timeEnd("sort-array");node:84.645ms-103.086msdeno:134ms-140ms md5(文件大小为2.6M)console.time("md5");spark.append(data);console.log(`md5: ${spark.end()}`);console.timeEnd("md5");node 46.930ms-71.454ms deno 63ms-83ms base64(文件大小为2.6M)console.time("Base64");Base64.encode(data);console.timeEnd("Base64");node 557.819ms-688.570ms deno 1216ms-1269ms 斐波那契数列(n=40)function fib(n) { if (n === 0) { return 0; } else if (n === 1) { return 1; } else { return fib(n - 1) + fib(n - 2); }}console.time("fib");fib(40);console.timeEnd("fib");node 1301.254ms-1387.108msdeno 1447ms-1616ms for in遍历对象(10万个属性)console.time("for in");for (let k in dataObject) { total += dataObject[k].list[0].n;}console.timeEnd("for in");node 39.923ms-56.622msdeno 41ms-49ms ...

July 25, 2020 · 1 min · jiezi

关于node.js:Node-中如何引入一个模块及其细节

本文收录于 GitHub 山月行博客: shfshanyue/blog,内含我在理论工作中碰到的问题、对于业务的思考及在全栈方向上的学习 前端工程化系列Node进阶系列在 node 环境中,有两个内置的全局变量无需引入即可间接应用,并且无处不见,它们形成了 nodejs 的模块体系: module 与 require。以下是一个简略的示例 const fs = require('fs')const add = (x, y) => x + ymodule.exports = add尽管它们在平时应用中仅仅是引入与导出模块,但稍稍深刻,便可见乾坤之大。在业界可用它们做一些比拟 trick 的事件,尽管我不大倡议应用这些黑科技,但略微理解还是很有必要。 如何在不重启利用时热加载模块?如 require 一个 json 文件时会产生缓存,然而重写文件时如何 watch如何通过不侵入代码进行打印日志循环援用会产生什么问题?module wrapper当咱们应用 node 中写一个模块时,实际上该模块被一个函数包裹,如下所示: (function(exports, require, module, __filename, __dirname) { // 所有的模块代码都被包裹在这个函数中 const fs = require('fs') const add = (x, y) => x + y module.exports = add});因而在一个模块中主动会注入以下变量: exportsrequiremodule__filename__dirname module调试最好的方法就是打印,咱们想晓得 module 是何方神圣,那就把它打印进去! const fs = require('fs')const add = (x, y) => x + ymodule.exports = addconsole.log(module) ...

July 22, 2020 · 3 min · jiezi

关于node.js:npm更换国内源解决npm-install慢的问题

1.问题还原在国内应用 npm装置的时候,会十分迟缓, npm install 倡议应用淘宝的镜像仓库来代替的原有的镜像仓库(镜像仓库就是一个npm的下载源,换成国内的下载源,下载速度会晋升很多) 2.解决方案侥幸的是,国内有几个镜像站点能够供咱们应用,速度十分快,镜像站会实时更新,为咱们节俭了好多工夫.如何给本机换源呢?(1)[长期]通过 config 配置指向国内镜像源 # 配置指向源$ npm config set registry http://registry.npm.taobao.org(2)[长期]通过 npm 命令指定下载源 # 在装置时候长期指定$ npm --registry http://registry.cnpmjs.org info express(3)长久应用 $ npm config set registry https://registry.npm.taobao.org(4)永恒批改镜像源 查看 npm 配置$ npm config list# 其余查看配置的形式$ npm config get globalconfig$ npm config ls -l 找到并关上配置文件:~/.npmrc写入配置:registry=https://registry.npm.taobao.org3.验证设置是否胜利npm config get registry# ORnpm info express4.重要揭示不举荐通过cnpm 应用,会呈现各种莫名的问题。5.其余镜像开源镜像: http://npm.taobao.org/mirrorsNode.js 镜像: http://npm.taobao.org/mirrors/nodealinode 镜像: http://npm.taobao.org/mirrors/alinodephantomjs 镜像: http://npm.taobao.org/mirrors/phantomjsChromeDriver 镜像: http://npm.taobao.org/mirrors/chromedriverOperaDriver 镜像: http://npm.taobao.org/mirrors/operadriverSelenium 镜像: http://npm.taobao.org/mirrors/seleniumNode.js 文档镜像: http://npm.taobao.org/mirrors/node/latest/docs/api/index.htmlNPM 镜像: https://npm.taobao.org/mirrors/npm/electron 镜像: https://npm.taobao.org/mirrors/electron/node-inspector 镜像: https://npm.taobao.org/mirrors/node-inspector/

July 21, 2020 · 1 min · jiezi

关于node.js:nodejs-windows-安装-jsdom

刚做逆向须要装置jsdom模块,部署在服务器的时候嫩死canvas@^2.5.0 装不上,又是下载 C++ 环境,又是换32位包,等等3天辛酸泪. 前面看了下本人电脑的 node 安装包 node-v.12.16.1-×64 ,又去下载了这个版本尝试. 发表问题解决. node-v.12.16.1-×64 下载地址

July 17, 2020 · 1 min · jiezi

gyp-ERR-find-Python-解决方案

命令行报错如下E:\vue-admin\node_modules\fibers>if not defined npm_config_node_gyp (node "D:\nodejs\node_modules\npm\node_modules\npm-lifecycle\node-gyp-bin\\..\..\node_modules\node-gyp\bin\node-gyp.js" rebuild --release ) else (node "D:\nodejs\node_modules\npm\node_modules\node-gyp\bin\node-gyp.js" rebuild --release )gyp ERR! find Pythongyp ERR! find Python Python is not set from command line or npm configurationgyp ERR! find Python Python is not set from environment variable PYTHONgyp ERR! find Python checking if "python" can be usedgyp ERR! find Python - "python" is not in PATH or produced an errorgyp ERR! find Python checking if "python2" can be usedgyp ERR! find Python - "python2" is not in PATH or produced an errorgyp ERR! find Python checking if "python3" can be usedgyp ERR! find Python - "python3" is not in PATH or produced an errorgyp ERR! find Python checking if the py launcher can be used to find Python 2gyp ERR! find Python - "py.exe" is not in PATH or produced an errorgyp ERR! find Python checking if Python is C:\Python27\python.exegyp ERR! find Python - "C:\Python27\python.exe" could not be rungyp ERR! find Python checking if Python is C:\Python37\python.exegyp ERR! find Python - "C:\Python37\python.exe" could not be rungyp ERR! find Pythongyp ERR! find Python **********************************************************gyp ERR! find Python You need to install the latest version of Python.gyp ERR! find Python Node-gyp should be able to find and use Python. If not,gyp ERR! find Python you can try one of the following options:gyp ERR! find Python - Use the switch --python="C:\Path\To\python.exe"gyp ERR! find Python (accepted by both node-gyp and npm)gyp ERR! find Python - Set the environment variable PYTHONgyp ERR! find Python - Set the npm configuration variable python:gyp ERR! find Python npm config set python "C:\Path\To\python.exe"gyp ERR! find Python For more information consult the documentation at:gyp ERR! find Python https://github.com/nodejs/node-gyp#installationgyp ERR! find Python **********************************************************gyp ERR! find Pythongyp ERR! configure errorgyp ERR! stack Error: Could not find any Python installation to usegyp ERR! stack at PythonFinder.fail (D:\nodejs\node_modules\npm\node_modules\node-gyp\lib\find-python.js:307:47)gyp ERR! stack at PythonFinder.runChecks (D:\nodejs\node_modules\npm\node_modules\node-gyp\lib\find-python.js:136:21)gyp ERR! stack at PythonFinder.<anonymous> (D:\nodejs\node_modules\npm\node_modules\node-gyp\lib\find-python.js:225:16)gyp ERR! stack at PythonFinder.execFileCallback (D:\nodejs\node_modules\npm\node_modules\node-gyp\lib\find-python.js:271:16)gyp ERR! stack at exithandler (child_process.js:315:5)gyp ERR! stack at ChildProcess.errorhandler (child_process.js:327:5)gyp ERR! stack at ChildProcess.emit (events.js:314:20)gyp ERR! stack at Process.ChildProcess._handle.onexit (internal/child_process.js:274:12)gyp ERR! stack at onErrorNT (internal/child_process.js:468:16)gyp ERR! stack at processTicksAndRejections (internal/process/task_queues.js:80:21)gyp ERR! System Windows_NT 10.0.18363gyp ERR! command "D:\\nodejs\\node.exe" "D:\\nodejs\\node_modules\\npm\\node_modules\\node-gyp\\bin\\node-gyp.js" "rebuild" "--release"gyp ERR! cwd E:\lx-fr-admin-ui\node_modules\fibersgyp ERR! node -v v14.5.0gyp ERR! node-gyp -v v5.1.0gyp ERR! not oknode-gyp exited with code: 1Please make sure you are using a supported platform and node version. If youwould like to compile fibers on this machine please make sure you have setup yourbuild environment--Windows + OS X instructions here: https://github.com/nodejs/node-gypUbuntu users please run: `sudo apt-get install g++ build-essential`RHEL users please run: `yum install gcc-c++` and `yum groupinstall 'Development Tools'`Alpine users please run: `sudo apk add python make g++`'nodejs' 不是外部或外部命令,也不是可运行的程序或批处理文件。npm ERR! code ELIFECYCLEnpm ERR! errno 1npm ERR! fibers@4.0.3 install: `node build.js || nodejs build.js`npm ERR! Exit status 1npm ERR!npm ERR! Failed at the fibers@4.0.3 install script.npm ERR! This is probably not a problem with npm. There is likely additional logging output above.npm ERR! A complete log of this run can be found in:npm ERR! D:\nodejs\node_cache\_logs\2020-07-17T00_46_32_338Z-debug.log解决方案1.装置node-gypnpm install -g node-gyp2.装置python举荐装置2.7版本(自行抉择32位或者64位装置):https://www.python.org/downlo... ...

July 17, 2020 · 3 min · jiezi

搭建node服务三使用TypeScript

JavaScript 是一门动静弱类型语言,对变量的类型十分宽容。JavaScript应用灵便,开发速度快,然而因为类型思维的缺失,一点小的批改都有可能导致意想不到的谬误,应用TypeScript能够很好的解决这种问题。TypeScript是JavaScript的一个超集,扩大了 JavaScript 的语法,减少了动态类型、类、模块、接口和类型注解等性能,能够编译成纯JavaScript。本文将介绍如何在node服务中应用TypeScript。一、 装置依赖npm install typescript --savenpm install ts-node --savenpm install nodemon --save或者 yarn add typescriptyarn add ts-nodeyarn add nodemon另外,还须要装置依赖模块的类型库: npm install @types/koa --savenpm install @types/koa-router --save…或者 yarn add @types/koayarn add @types/koa-router…二、 tsconfig.json当应用tsc命令进行编译时,如果未指定ts文件,编译器会从当前目录开始去查找tsconfig.json文件,并依据tsconfig.json的配置进行编译。 1. 指定文件能够通过files属性来指定须要编译的文件,如下所示: { "files": [ "src/server.ts" ]}另外也能够通过应用"include"和"exclude"属性来指定,采纳相似glob文件匹配模式,如下所示: { "include": [ "src/**/*" ], "exclude": [ "node_modules", "**/*.spec.ts" ]}反对的通配符: 匹配0或多个字符(不包含目录分隔符)? 匹配一个任意字符(不包含目录分隔符)**/ 递归匹配任意子目录2. 罕用配置compilerOptions 属性用于配置编译选项,与tsc命令的选项统一,罕用的配置如下所示: { "compilerOptions": { // 指定编译为ECMAScript的哪个版本。默认为"ES3" "target": "ES6", // 编译为哪种模块零碎。如果target为"ES3"或者"ES5",默认为"CommonJS",否则默认为"ES6" "module": "CommonJS", // 模块解析策略,"Classic" 或者 "Node"。如果module为"AMD"、"System"或者"ES6",默认为"Classic",否则默认为"Node" "moduleResolution": "Node", // 是否反对应用import cjs from 'cjs'的形式引入commonjs包 "esModuleInterop": true, // 编译过程中须要引入的库。target为"ES5"时,默认引入["DOM","ES5","ScriptHost"];target为"ES6"时,默认引入["DOM","ES6","DOM.Iterable","ScriptHost"] "lib": ["ES6"], // 编译生成的js文件所输入的根目录,默认输入到ts文件所在的目录 "outDir": "dist", // 生成相应的.map文件 "sourceMap": true }, "include": [ "src/**/*" ], "exclude": [ "node_modules", "**/*.spec.ts" ]}1) targettarget是编译指标,能够指定编译为ECMAScript的哪个版本,默认为"ES3"。ECMAScript的版本有:"ES3" 、"ES5"、 "ES6" 或者 "ES2015"、 "ES2016"、 "ES2017"、"ES2018"、"ES2019"、 "ES2020"、"ESNext"。 ...

July 17, 2020 · 3 min · jiezi

Node-中如何更好地打日志

Node 中如何更好地打日志本文收录于 GitHub 山月行博客: shfshanyue/blog,内含我在理论工作中碰到的问题、对于业务的思考及在全栈方向上的学习 前端工程化系列Node进阶系列在服务器利用(后端我的项目)中,欠缺并结构化的日志不仅能够更好地帮忙定位问题及复现,也可能发现性能问题的端倪,甚至可能帮忙用来解决线上 CPU 及内存爆掉的问题。 本篇文章将解说如何应用 Node 在服务端更好地打日志 哪里应该打日志: AccessLog、SQLLog、BusinessLog应该打什么日志: server_name、timestamp 以及相干类型日志用什么去打日志: winston、log4j、bunyan产生日志后,将在下一章解说日志的收集解决及检索 日志类型在一个服务器利用中,或作为生产者,或作为消费者,须要与各方数据进行交互。除了最常见的与客户端交互外,还有数据库、缓存、音讯队列、第三方服务。对于重要的数据交互须要打日志记录。 除了外界交互外,本身产生的异样信息、要害业务逻辑及定时工作信息,也须要打日志。 以下简述须要打日志的类型及波及字段 AccessLog: 这是最常见的日志类型,个别在 nginx 等方向代理中也有日志记录,但在业务零碎中有时须要更具体的日志记录,如 API 耗时,具体的 request body 与 response bodySQLLog: 对于数据库查问的日志,记录 SQL、波及到的 table、以及执行工夫,从此能够筛选出执行过慢的SQL,也能够筛选出某条API对应的SQL条数RequestLog: 申请第三方服务产生的日志Exception: 异样RedisLog: 缓存,也有一些非缓存的操作如 zset 及分布式锁等Message Queue Log: 记录生产音讯及生产音讯的日志CronLog: 记录定时工作执行的工夫以及是否胜利要害业务逻辑日志的根本字段对于所有的日志,都会有一些共用的根本字段,如在那台服务器,在那个点产生的日志 app即以后我的项目的命名,在生产环境有可能多个我的项目的日志聚合在一起,通过 app 容易定位到以后我的项目 serverName即服务器的 hostname,通过它很容易定位到出问题的服务器/容器。 现已有相当多公司的生产环境利用应用 kubernetes 进行编排,而在 k8s 中每个 POD 的 hostname 如下所示,因而很容易定位到 Deployment: 哪一个利用/我的项目ReplicaSet: 哪一次上线Pod: 哪一个 Pod# shanyue-production 指 Deployment name# 69d9884864 指某次降级时 ReplicaSet 对应的 hash# vt22t 指某个 Pod 对应的 hash$ hostnameshanyue-production-69d9884864-vt22ttimestamp即该条日志产生的工夫,应用 ISO 8601 格局有更好的人可读性与机器可读性 ...

July 15, 2020 · 3 min · jiezi

linux-centOS-安装node-ffi解析so文件

// 运行环境:linux centOS 7 64位 // 首先切换到root模式su - root,提醒输出明码,实现之后进行以下操作。 // 下载8.6.01.wget https://nodejs.org/dist/v8.6....// 解压2.xz -d node-8.6.0-linux-x64.tar.xz 3.tar -xf node-v8.6.0-linux-x64.tar// 重命名4.mv node-v8.6.0-linux-x64 node-v8.6.0// 增加到全局应用5.ln -s /root/node-v8.6.0/bin/node /usr/local/bin/node 6.ln -s /root/node-v8.6.0/bin/npm /usr/local/bin/npm // 装置ffi(要求node版本8-10,python2.7,g++运行环境)1.首先装置node-gyp到全局,并增加到全局应用npm i node-gyp -gln -s /root/node-v8.6.0/bin/node-gyp /usr/local/bin/node-gyp2.装置g++yum i gcc-c++3.装置ffi,敞开权限平安校验进入到文件目录,npm i ffi -unsafe perm,如果报错Cannot find module 'nan',就npm i nan,而后再从新执行npm i ffi -unsafe perm。

July 14, 2020 · 1 min · jiezi

Nodejs写接口时配置静态文件路径

Nodejs写接口时配置动态文件门路须要应用 express要害代码const express = require('express');const app = express();app.use(express.static(__dirname + '/public'));当初就能够加载public目录下的动态文件了: http://127.0.0.1:8100/images/someimg.jpg Express 会在动态资源目录下查找文件,所以不须要把动态目录作为URL的一部分。虚构动态目录如果要给动态资源文件创建一个虚构的文件前缀(实际上文件系统中并不存在) ,能够应用 express.static 函数指定一个虚构的动态目录,语法如下: app.use('/static', express.static(__dirname + '/public'));当初能够应用 /static 作为前缀来加载 public 文件夹下的文件了: http://127.0.0.1:8100/static/images/someimg.jpg 增加多个动态目录能够通过屡次应用 express.static 中间件来增加多个动态资源目录: app.use(express.static('public'));app.use(express.static('files'));

July 13, 2020 · 1 min · jiezi

Nginx深入详解之upstream分配方式

### 一、调配形式 Nginx的upstream反对5种调配形式,上面将会具体介绍,其中,前三种为Nginx原生反对的调配形式,后两种为第三方反对的调配形式:### 1、轮询轮询是upstream的默认调配形式,即每个申请依照工夫程序轮流调配到不同的后端服务器,如果某个后端服务器down掉后,能主动剔除。upstream backend { server 192.168.1.101:8888; server 192.168.1.102:8888; server 192.168.1.103:8888;}### 2、weight轮询的加强版,即能够指定轮询比率,weight和拜访几率成正比,次要利用于后端服务器异质的场景下。 upstream backend { server 192.168.1.101 weight=1; server 192.168.1.102 weight=2; server 192.168.1.103 weight=3;}### 3、ip_hash每个申请依照拜访ip(即Nginx的前置服务器或者客户端IP)的hash后果调配,这样每个访客会固定拜访一个后端服务器,能够解决session统一问题。upstream backend { ip_hash; server 192.168.1.101:7777; server 192.168.1.102:8888; server 192.168.1.103:9999;}### 4、fairfair顾名思义,偏心地依照后端服务器的响应工夫(rt)来调配申请,响应工夫短即rt小的后端服务器优先调配申请。 upstream backend { server 192.168.1.101; server 192.168.1.102; server 192.168.1.103; fair;}### 5、url_hash与ip_hash相似,然而依照拜访url的hash后果来调配申请,使得每个url定向到同一个后端服务器,次要利用于后端服务器为缓存时的场景下。upstream backend { server 192.168.1.101; server 192.168.1.102; server 192.168.1.103; hash $request_uri; hash_method crc32;}其中,hash_method为应用的hash算法,须要留神的是:此时,server语句中不能加weight等参数。### 二、设施状态从下面实例不难看出upstream中server指令语法如下: server address [parameters]关键字server必选。address也必选,能够是主机名、域名、ip或unix socket,也能够指定端口号。parameters是可选参数,能够是如下参数:down:示意以后server已停用backup:示意以后server是备用服务器,只有其它非backup后端服务器都挂掉了或者很忙才会调配到申请。weight:示意以后server负载权重,权重越大被申请几率越大。默认是1.max_fails和fail_timeout个别会关联应用,如果某台server在fail_timeout工夫内呈现了max_fails次连贯失败,那么Nginx会认为其曾经挂掉了,从而在fail_timeout工夫内不再去申请它,fail_timeout默认是10s,max_fails默认是1,即默认状况是只有产生谬误就认为服务器挂掉了,如果将max_fails设置为0,则示意勾销这项查看。举例说明如下:upstream backend { server backend1.example.com weight=5; server 127.0.0.1:8080 max_fails=3 fail_timeout=30s; server unix:/tmp/backend3; }

July 13, 2020 · 1 min · jiezi

Electron实现音乐播放器-2

实现简略的音乐播放找一个简略的mp3文件作为示例: 能够简略的应用audio标签来播放mp3文件: <!-- index.html --><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <audio controls> <source src="horse.mp3" type="audio/mpeg"> </audio></body></html>效果图: 自定义播放文件咱们不可能永远的只播放horse.mp3这个示例,咱们心愿用户可能通过会话窗口来关上想要关上的音乐文件,实现音乐播放器的核心内容 自定义菜单栏咱们想要实现自定义播放内容,首先须要一个咱们本人的菜单栏,来给用户一个关上文件的入口 首先引入Menu模块: // main.jsconst electron = require('electron') //引入electron模块const { app, BrowserWindow, Menu} = electron //从electron中引入咱们须要的模块let mainWindow; //申明一个变量,作为主窗口app.on('ready', function () { //当app筹备好时执行 mainWindow = new BrowserWindow({ //将mainWindow定义为一个新的浏览器窗口 //这一部分先空着,前面会向窗口增加参数 }) mainWindow.loadFile("index.html");})而后构建咱们的菜单: // main.jsconst electron = require('electron') //引入electron模块const { app, BrowserWindow, Menu} = electron //从electron中引入咱们须要的模块let mainWindow; //申明一个变量,作为主窗口app.on('ready', function () { //当app筹备好时执行 mainWindow = new BrowserWindow({ //将mainWindow定义为一个新的浏览器窗口 //这一部分先空着,前面会向窗口增加参数 }) mainWindow.loadFile("index.html"); mainMenu = Menu.buildFromTemplate(MenuTemplate)//应用Menu模块的内置函数将json格局的菜单模板转换为electron的菜单格局 mainWindow.setMenu(mainMenu)//把菜单插入窗口})const MenuTemplate = [{//菜单模板,json格局 label: "File", submenu: [{//菜单项的子菜单,同json格局 label: "Open File" }]}]效果图: ...

July 9, 2020 · 2 min · jiezi

快速搭建koa项目

一、安装koa生成器npm install koa-generate -g# 查看是否安装成功kao2 -V二、创建项目// 命令 koa2 [name] [-e] e表示使用ejs引擎koa2 hello -e三、安装与运行# 进入项目cd hello# 安装依赖npm install# 运行npm start 四、使用token服务端配置 const Jwt = require('koa-jwt');const Jsonwebtoken = require('jsonwebtoken');//使用jwtapp.use(Jwt({ secret: 's' }).unless({ // 过滤不需要验证的路由 path: [ /^\/public\/login/ ... ]}))// 获取tokenapp.use(async (ctx, next) => { // token解密,获取用户信息 let token = ctx.headers.authorization let user = Jsonwebtoken.verify(token.split(' ')[1], 's'); ... await next()})客户端配置 axios.interceptors.request.use(config => { config.header.Authorization = 'token...' return config;}, error => { return Promise.reject(error);})五、配置路由删除原有的路由配置,修改为动态读取 ...

July 8, 2020 · 1 min · jiezi

anyproxy-转发body

之前一直使用 fiddler 来抓包数据并转发到自建server,但 fiddler 缓存太大,只能通过关闭重启 fiddler 才能清除缓存,电脑内存不够是很麻烦的事.现在有了 anyproxy 就解决了缓存问题.anyproxy 是和 fiddler 基本作用一样的抓包工具,安装不介绍了,都能搜到,写写 rule.js 的二次开发. anyproxy 默认启动加载的规则文件路径在 C:\Users\li\AppData\Roaming\npm\node_modules\anyproxy\lib\rule_default.js我们做数据加工或者转发可通过自建 js 来实现.以windows为例:1.可在桌面创建 sample.js, 将数据处理逻辑写入 sample.js中.2.打开 cmd,进入桌面,或者不进入桌面,第 3 步写 sample.js 觉得路径也行.3.通过 sample.js 启动 anyproxy,启动命令: anyproxy -i --rule ./sample.js4.浏览器登录 127.0.0.1:8002 就能更直观的看到抓包的数据,浏览器作为调试使用,真正抓包时不用开启. 说下我踩的坑: anyproxy 是使用的 node.js 语法,所以编辑 sample.js 时如果使用到 js 的 ajax 语法是无效的. 下面贴一段我使用的抓包数据 body, 并通过 node.js post 请求将数据转发到自建的 server 进行解析. // sample.jsmodule.exports = { //summary: 'customized wechat request', // beforeSendResponse: 在数据获取成功并解析成功后准备返回给 cli 之前做数据处理. *beforeSendResponse(requestDetail, responseDetail) { // 当 anyproxy 匹配到 url 地址中含有 /aw/v4/aw/post/,将数据 body 通过 node.js 以json字符串 post 到自建server if (requestDetail.url.indexOf('/aw/v4/aw/post/') != -1) { var data = { str: responseDetail.response.body.toString() }; // parse 为自己 server 路径 let res = sendPostHttpRequest(data, 'parse'); } },};// node.js 的 post 请求,不能用 js 的 ajax 请求function sendPostHttpRequest(body, route) { var http = require('http') var querystring = require('querystring'); let options = { hostname: '127.0.0.1', port: 5001, path: '/' + route, method: 'POST', headers: {"content-type": 'application/x-www-form-urlencoded'} }; var contents = querystring.stringify(body); var req = http.request(options, function(res){ res.setEncoding('utf8'); }); req.write(contents); req.end();};

July 7, 2020 · 1 min · jiezi

Node

尚硅谷-Node.js基础入门一杯茶的时间,上手Node.js Node简介Node可以在后台编写服务 进程进程负责为程序运行提供必要的环境进程就相当于工厂中的车间线程线程是计算机中最小的计算单位,线程负责执行进程中的程序线程就相当于工厂中的工人单线程 js是单线程多线程 Java/Python都是多线程语言传统的服务器都是多线程的每进来一个请求,就创建一个线程去处理请求Node服务器是单线程的Node处理请求时是单线程,但是在后台拥有一个I/O线程池特征异步、事件驱动、非阻塞IO模块化在node中,一个js文件就是一个模块在node中每一个js代码都是独立运行在一个函数中 而不是全局作用域,所以一个模块中的变量和函数在其他模块中无法访问 不要总是往全局中写东西,会污染全局作用域,污染全局命名空间,这种模块化的方式可以避免污染全局命名空间CommonJs对模块的定义十分简单 模块的引用 reuqire()模块的定义 创建一个js文件 export添加属性或方法进行输出模块的标识01.helloNode.j //引入其他模块/* 1、在node中,通过require()函数引入外部模块 2、require()可以传递一个文件的路径作为参数, node将会自动根据该路径来引入外部模块 这里的路劲,如果是相对路劲,必须以./ 或者 ../开头 3、使用require()引入一个模块以后,该函数会返回一个对象, 这个对象代表的是引入的模块 4、我们使用require()引入外部模块时,使用的就是模块标识,我们可以通过标识来找到指定的模块 -没看分为两大类 核心模块 -由node引擎提供的模块 // var fs = require('fs'); console.log(fs) //文件模块 不用写路劲直接写名字就行 // var path = require('path'); //路劲模块 // var http = require('http'); //服务模块 -核心模块的标识就是模块的名字 文件模块 - 由用户自己创建的模块 - 文件模块的标识就是文件的路径(绝对路径,相对路径) 相对路径使用 ./ 或者 ../ 开头*/var md = require("./02.module.js");console.log(md)02.module.js console.log('我是一个模块,我是02.module.js');/* 我们可以通过exports来向外部暴露暴露变量和方法 只需要将需要暴露给外部的变量或方法设置为exports的属性即可*///向外部暴露属性或者方法export.x = "我是02.module.js中得x";export.y = "我是02.module.js中得y";包package包结构– package.json:描述文件,必须– bin:可执行二进制文件 这里边的文件都是可以直接在系统中运行的 //一般不会有这个文件,除非是一些工具 webpack– lib:依赖的其他js代码,library(图书馆,库),非必须– doc:文档,非必须– test:单元测试,非必须包规范npmnpm -v:查看npm版本npm version:查看所有模块的版本npm seaarch math:搜索math包npm init:初始化package.jsonnpm install math(npm i math):安装math包,安装包的时候是根据package.json来识别的,没有该文件的话不能识别这是一个包,就会全局安装npm remove math(npm r math):删除math包npm i math --save(npm i math -S):安装到生成依赖npm install:自动根据package.json中的依赖下载所有的包npm install math -g:全局安装包(全局安装的包一般都是一些工具)通过npm下载的包都放到node_modules文件夹中我们通过下载的包,直接通过包名引入即可 // var express = require('express')node在使用模块名字引入模块时,他会首先在当前目录的node_modules中寻找是否含有该模块如果有则直接使用,如果没有则去上一层的node_modules中寻找如果有则直接使用,如果没有则在去上一层寻找,直到找到为止直到磁盘根目录中,如果依然没有找到,则报错index.js 入口文件 ...

July 5, 2020 · 2 min · jiezi

Electron实现音乐播放器-1

编写简单的页面初始化项目文件夹创建一个文件夹,进入这个文件夹并通过如下命令初始化项目 npm init package name:项目名称,可自设version:版本号,可自设description:描述,可不填entry point:入口文件,这里我们填main.jstest command:可不填git repository:git仓库,可不填keywords:关键词,可不填author:作者,可自设license:开源协议,可不填输入yes完成初始化,随后会自动生成package.json文件在文件夹内,这是我们项目的描述文件 编写简单的main.js文件main.js是我们项目的入口文件(主文件),而项目启动时运行main.js的进程就是主进程,创建窗口、对渲染进程进行调度等等一系列操作都要依靠main.js来实现 在文件夹内创建main.js文件并编辑它: const electron = require('electron')//引入electron模块const {app,BrowserWindows} = electron//从electron中引入我们需要的模块let mainWindow;//声明一个变量,作为主窗口app.on('ready',function(){//当app准备好时执行 mainWindow = new BrowserWindow({//将mainWindow定义为一个新的浏览器窗口 //这一部分先空着,后面会向窗口添加参数 })})第一、二行引入了我们所需要的模块,然后我们声明了一个mainWindow变量,并在app准备好时将mainWindow定义为一个BrowserWindow对象,也就是一个浏览器窗口 运行和安装依赖我们可以通过如下命令来安装electron模块: cnpm install electron --save-dev接着我们可以通过如下命令来运行我们的项目: electron .注意electron和.之间有一个空格! 效果图: 我们会发现只是弹出了一个窗口,里面并没有什么内容,这是因为我们并没有指定它需要加载的内容 Hello,World!我们可以通过loadURL函数来让我们的应用程序窗口来通过链接加载一个网页 const electron = require('electron')//引入electron模块const {app,BrowserWindow} = electron//从electron中引入我们需要的模块let mainWindow;//声明一个变量,作为主窗口app.on('ready',function(){//当app准备好时执行 mainWindow = new BrowserWindow({//将mainWindow定义为一个新的浏览器窗口 //这一部分先空着,后面会向窗口添加参数 }) mainWindow.loadURL("https://www.baidu.com");})效果图: 但是很显然,我们不能只满足于加载现成的网页,我们可以通过加载HTML文件来达到自定义页面的目的 创建一个HTML文件,编写一个简单的例子: <!-- index.html --><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <h1>Hello,World!</h1></body></html>文件夹结构: 通过loadFile函数来加载HTML文件: const electron = require('electron')//引入electron模块const {app,BrowserWindow} = electron//从electron中引入我们需要的模块let mainWindow;//声明一个变量,作为主窗口app.on('ready',function(){//当app准备好时执行 mainWindow = new BrowserWindow({//将mainWindow定义为一个新的浏览器窗口 //这一部分先空着,后面会向窗口添加参数 }) mainWindow.loadFile("index.html");})效果图: ...

July 4, 2020 · 1 min · jiezi

nodejs负载均衡二RPC负载均衡

简介这一篇确实拖的比较久,上节讲了服务负载均衡实现,但是如果需要调用远程服务, 如何保证不是调用不会集中在一台服务上,如何确保远程服务调用的负载均衡?这就要实现 Consumer 端调用rpc的负载均衡。所以本文章主要讲解 RPC负载均衡算法实现 。 算法下面介绍几个主要的负载均衡算法如何实现,可以看下我写的NPM包 load-balancer-algorithm。 const LBA = require('load-balancer-algorithm');const weightRandomPool = [ { host: "127.0.0.2", weight: 2 }, { host: "127.0.0.1", weight: 3 }, { host: "127.0.0.3", weight: 5 },];const weightedList = []const loadBalance = new LBA.WeightedRoundRobin(weightRandomPool);for(let i = 0; i < 10; i++){ const address = loadBalance.pick(); weightedList.push(loadBalance.getWeight(address.host))}// [5, 5, 3, 5, 2, 3, 5, 2, 3, 5]console.log(weightedList)Round Robin轮询(Round robin) 算法,就是依次轮询服务队列的节点,周而复始,这个实现比较简单。 消费端调用均衡通过当前下标+1对数据池长度数取模,获取到下一个节点下标缺点: 要求服务提供节点性能一致 Weighted Round Robin权重轮询(Weighted round robin) 算法,基于轮询添加权重判断,这样可以针对性能低服务节点减少流量。 ...

June 30, 2020 · 1 min · jiezi

jwt登陆校验携带token

jwt登陆注册jwt概念token是不需要存储在数据库的,只需要后台生成密钥,当客户端发送过来请求时那么就把token塞在请求体或者头中,客户端接收到token时那么就存储在localStorage或者cookie中,注意这里不能把敏感信息进行token存储,要不然会被获取到(比如cookie中或者请求体中)并且进行反向解密从而暴露敏感信息,比如密码; token中包含三块都是以 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9//头信息这个是固定写死的{'type': 'JWT','alg': 'HS256'}.> eyJ1c2VyIjoid2FuZyIsImVudmlyb25tZW50Ijoid2ViIiwiZXhwaXJlcyI6MTUzNTUyMDk1MTcxOX0//载体 就是比如客户端发过来的username.> 4rmP6UeeJ3mQbHfplruH15PvuUxPzFTxAfVnPgA7sgo//密钥根据将头和载体进行转换base64编码,然后在将头和载体根据sha256算法加密生成密钥(也就是第三步)这样就组成了一个token,将这种思路换成代码就会实现jwt-smilp包中的encode,那么decode就是获取到进行反向解码jwt-smilp中的encode和decode{username:username}//载体 载体中还可以设置时间以及其他 jwt.encode({username:username},sercet)//sercet自己定义的密钥 jwt.encode(token,sercet)//进行解密登陆注册全过程进入登陆页,输入账号和密码点击登陆请求接口,后台会在数据库中寻找对应的账号密码 如果存在那么后台会生成一个token并且塞在请求头(或者响应体中),给前端之后,前端获取返回值 并且存放在cookie中或者请求头中,前端通过axios中的请求拦截给请求头中塞进密钥,这样每次发送请求就会携带token,后台接收到token之后会进行解码校验是否是正确的token,如果是的话就进行下一步操作,如果不是的话就给前端提示 node后台代码的实现将node框架搭好之后新建一个src的文件下新建一个app.js的文件代码如下 let express = require('express')let app = express()//获取req的bodylet bodyParser = require('body-parser');//引入自己写的登陆校验是否合规文件let retoken = require('./retoken/retoken.js')//bodyParser对应的以下两个也是解析post中的json数据app.use(express.json())//urlencoded解析x-ww-form-urlencoded请求体app.use(express.urlencoded())//返回的对象是一个键值对,当extended为false的时候,键值对中的值就为'String'或'Array'形式,为true的时候,则可为任何数据类型app.use(bodyParser.urlencoded({ extended: true }))//使用上面自己的写的中间件app.use(retoken)//登陆接口文件app.use('/login', require('./login/index.js'));//统一处理错误信息app.use((err, req, res, next) => { if (err) { res.status(500).json({ message: err.message }) }})//默认监听3000端口app.listen(3000, () => { console.log('服务启用成功');})retoken.js//使用第三方插件let jwt = require('jwt-simple');//定义加密和解密的密钥(随便写)const jstSecret = 'mengyuhang' function checkToken(req,res,next){ //判断如果是登陆接口或者注册接口那么就不需要校验token if(req.url!='/login/login'&&req.url!='/login/create'){ //这里前端是直接将token塞到了cookie中所以我直接在cookie中获取 let tokenClone = req.headers.cookie;//token传递过来的为这种形式:token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Im15aDEyMyIsImV4cGlyZXMiOjE1OTQwNDU4NDc1NzN9.PwIAp3nXIscIvgXykjQumO6CbIFceHpGz6-2PUHgQU4//截取等号后面的 let token = tokenClone.split('=')[1] if(token){ //如果存在那么解码,我这里加密的用户名和校验时间是否过期{ username: 'myh123', expires: 1594045847573 } var decoded = jwt.decode(token, jstSecret) //如果现在的时间超过了我设置的登陆时间那么就返回登陆过期 if(Date.now()>decoded.expires){ res.json({ code:'-2', message: '登陆过期' }) }else{ //否则向下执行 next() } }else{ //如果没有token那么返回提示 res.status(401).json({ code:"-1", message: 'you need login:there is no token' }) } }else{ //如果为登陆或者注册接口直接向下进行 next() } }module.exports=checkTokenlogin.js中的接口书写const express = require('express');let router = express.Router();//我这里使用sequelize操作数据库let sequelize = require('sequelize')let models = require('../../db/models')let jwt = require('jwt-simple');//设置token的过期时间const tokenExpiresTime = 1000 * 60 * 60 * 24 * 7//密钥const jstSecret = 'mengyuhang'router.post('/login', async (req, res, next) => { let { username, password} = req.body; try { //在数据库中的user表中寻找对应的账号密码 let personalInformation = await models.User.findOne({ where: { username, password } }) //如果账号存在 if (personalInformation) { //生成token //需要加密的对象 let payload = { username: personalInformation.username, expires: Date.now() + tokenExpiresTime } //jwt-simple包提供的加密方法,jstSecret自己定义的密钥 let token = jwt.encode(payload, jstSecret) res.json({ message: '登陆成功', token }) } else { //如果账号不存在 res.json({ code: '-1', message: '用户不存在,请校验信息是否正确' }) } } catch (e) { res.json({ message: '错误' }) }})//注册接口router.post('/create', async (req, res, next) => { let { username, password } = req.body; try{ //在数据库中寻找用户名 let user = await models.User.findOne({ where: { username } }) //如果用户名存在 if (user) { res.json({ code: '-1', message: '用户名已存在' }) } else { //如果用户名不存在就可以注册成功啦 await models.User.create({ username, password }) res.json({ code: '0', message: '注册成功' }) } }catch(e){next() } })module.exports = router前端登陆点击登陆时获取到后台返回的token之后直接塞在document.cookie中即可 ...

June 30, 2020 · 2 min · jiezi

使用nodejs实现JSON文件自动转Excel的工具

这段时间做项目,需要把json格式的文档给到业务人员去翻译,每次手动翻译,很麻烦,于是就想着写一个高逼格的自动化工具来完成这件事情。说实现,初步思路就是使用类似"json2excel start"这样的命令,然后它就自己跑。像vue,react运行命令一样。 首先,我们npm init 新建一个项目工程,新建我们项目的核心文件json2excel.js,并运行node json2exce.js,然后控制台就可以打印东西了。 把一个文件转化成另一个文件,我们要知道这个文件的路径,以及保存到的位置,所以命令设计为: json2excel start inpath outpath我们使用一个非常好用的命令行辅助包"commander",提示命令输入,json2excel.js如下, const program = require('commander')// 定义当前的版本program .version(require('../package').version)// 定义命令方法program .usage('<command> [inPath] [toPath]') program .command('start [paths...]') .description('Conversion from JSON to csv') .alias('-s') .action(paths => require('./command/j2c')(paths)) program.parse(process.argv)if (!program.args.length) { program.help()}然后运行node json2excel.js会看到(现在还没安装bin命令,所以用node json2excel代替json2excel), 非常哇瑟的一个操作,就可以看到命令引导提示了。.command() 是定义命令及其后面的参数,我们定义了paths.description() 是描述.alias() 是命令的别名.action() 是运行命令时要执行的操作,paths是command里面传过来的参数 我们新建../command/j2c.js,.action()的时候我们有接受命令参数 module.exports = (paths) => { // 这样就得到了输入、输出的路径了 let [inPath, outPath] = paths}如果命令参数没有附带怎么办?如:node json2excel start 不带路径然后就回车 那我们就引导用户再次输入,使用"co","co-prompt"这两个工具 上代码:../command/j2c.js const co = require('co')const prompt = require('co-prompt')module.exports = (paths) => { co(function* () { let [inPath, outPath] = paths // 处理用户输入 inPath = inPath ? inPath : yield prompt('Input file directory: ') outPath = outPath ? outPath : (yield prompt('Output file directory: ')) || inPath })}co里面接受generator函数,主要是异步操作作同步处理的写法。 ...

June 23, 2020 · 3 min · jiezi

记录在阿里上开服务的过程

从5、6年前就开始想办法做一个个人网站。直到今天才有了一点样子。自己是从一个前端走来的。前端对于一个网站中的占比太小了。要做好网站,有太多非前端的事儿要做。下面记录一次在阿里云上部署node服务的过程。 学习node从前端向后端走,选择node是一个比较好的选择。它有天然的语言衔接。为了做网站,可以再学习一个框架。如:express/koa.我就是学习的express。 在本地开发完网站买一个阿里云的服务注册一个阿里云的账号并登录买一个服务器我买的轻量应用服务器(它比较便宜)。选“地域”这名字一看就知道是选择哪里的服务器。随便选吧。“选择镜像”选项是“系统镜像”、“应用镜像”。若没有“镜像”我就知道是选什么,非要加一个“镜像”。我去~,尽为难我们不懂服务端的人。我就当选择“系统”、“应用”去选择的。再选择时长。然后就是花钱吧。 配置服务器然后进入“控制台”,再进入“轻量应用服务器”,再进入你刚买到的服务里。再进入“应用详情”里,点击“远程登录服务器”,可以在浏览器中登录服务器。在"远程连接"页面里,点击“设置密码”,再输入相应密码。就可以使用客户端登录远程服务器了。 ssh root@47.47.47.47// ssh 账号@公网ip// 输入密码上传代码有几种上传代码的方式。目的都是让服务器上有代码。 使用scpscp -r myapp root@47.47.47.47:/var/www 使用git安装git配置git// 代码一般放在/var/www里// cd /var/wwwgit config --global user.name "yourname"git config --global user.email "youremail@example.com"git config --global --list // 查看配置结果拉取仓库中的代码使用jenkins我不会 自己做个小的应用,接收git的推送事件,自动执行相应的脚本我不会 配置服务器环境安装node安装npm安装git安装nvm全局安装pm2启动服务pm2 start <path/to/server>

June 23, 2020 · 1 min · jiezi

nodejsexpress项目中对特定请求使用不同的bodyparser处理

node+express 项目在全局使用了bodyParser.json()和bodyParser.urlencoded()。但现在碰到个需求,对某些特定前缀的url请求,需要拿到Buffer格式的请求体,而不是json格式化后的。 body-parser官方文档中有写如何针对不同请求使用不同的解析: var express = require('express')var bodyParser = require('body-parser') var app = express() // create application/json parservar jsonParser = bodyParser.json() // create application/x-www-form-urlencoded parservar urlencodedParser = bodyParser.urlencoded({ extended: false }) // POST /login gets urlencoded bodiesapp.post('/login', urlencodedParser, function (req, res) { res.send('welcome, ' + req.body.username)}) // POST /api/users gets JSON bodiesapp.post('/api/users', jsonParser, function (req, res) { // create user in req.body})但是项目中用到了express的路由,没找到如何直接针对特定路由改写的方法,看到一篇文章,里面配置项中的verify(req, res, buf, encoding)函数的第三个参数可以拿到raw body,但是缺点文章中也说了,并且对所有请求都生效了。 ...

June 18, 2020 · 1 min · jiezi

nodeeggjs下使用nsq-实现puppteer生成pdf服务

本篇文章主要介绍如何在nodeJs中使用nsq,其他实现将在后续文章输出。 起因前段时间做了一个网页生成pdf的node服务。由于puppteer和canvas生成过程中对内存的消耗比较大,内容量大的网页生成时间过长,对于第三方组件有时候会生成出问题等原因。 引入了nsq,使项目实现负载均衡,消除单点故障。 但是网上查找之后发现介绍node中加入nsq的方案很少,经过软膜硬泡终于把nsq引入了node中,希望能把自己的收获和大家聊一下吧。 初识nsqNSQ是一个基于Go语言的分布式实时消息平台, 它具有分布式、去中心化的拓扑结构,支持无限水平扩展。无单点故障、故障容错、高可用性以及能够保证消息的可靠传递的特征。另外,NSQ非常容易配置和部署, 且支持众多的消息协议。支持多种客户端,协议简单。 nsq设计很简单,需要了解以下几个核心概念。 1、nsqd:一个负责接收、排队、转发消息到客户端的守护进程 2、nsqlookupd:管理拓扑信息, 用于收集nsqd上报的topic和channel,并提供最终一致性的发现服务的守护进程。 3、Topic:一个topic就是程序发布消息的一个逻辑键,当程序第一次发布消息时就会创建topic。4、Channels:channel组与消费者相关,是消费者之间的负载均衡,channel在某种意义上来说是一个“队列”。每当一个发布者发送一条消息到一个topic,消息会被复制到所有消费者连接的channel上,消费者通过这个特殊的channel读取消息,实际上,在消费者第一次订阅时就会创建channel。Topic只能有一个channel可以有多个,不同的channel用于分发不同的任务下面附上一个金典nsq示意图: 安装中遇到的问题在使用过程中发现gcc版本过低导致报错。因为node.js 4升级了v8引擎,需要gcc版本在4.8以上。 实战开始本项目是在eggjs基础上建立的。首先需要在项目中安装nsqjs $ npm install nsqjs --save在根文件app.js对nsq进行控制,nsq的配置非常简洁,nsq分为写和读两个独立的过程。 const nsq = require('nsqjs')module.exports = app => { app.beforeStart(async () => { // 实例化nsq的写操作 // 在config中配置nsq的host和port,这里是你配置的nsq地址 const writerNsq = new nsq.Writer(app.config.nsq.nsqHostWriter, app.config.nsq.writePort) // 连接nsq的写功能 writerNsq.connect() // 当写操作连接成功后,把其赋值到全局的app上以便写入信息 writerNsq.on('ready', () => { app.writerNsq = writerNsq })当nsq写功能实现后,我们可以通过publish方法向nsq队列写入信息 ctx.app.writerNsq.publish(config.nsq.topic, { // 你所需要传递的参数 })当服务器有空闲的时候nsq会随机分配到空闲线程上实现读操作,我们的核心业务是在读功能启用之后实现的。读和写的初始化过程是在项目启动时候就要执行的。项目运行过程中,我们只是不停的在进行读写操作而已。 // 实例化nsq的读操作 // 参数是对应需要读的topic和channel和应的nsq读的地址 const client = new nsq.Reader(app.config.nsq.topic, app.config.nsq.channel, { lookupdHTTPAddresses: app.config.nsq.nsqHostReader, maxInFlight: 1 }) // 连接nsq的读功能 client.connect() // 每当有消息队列进来的时候就会调用client.on方法 // message是我门在写的过程中传入的信息 client.on方法('message', async msg => { // 对写入的信息格式化 let data = JSON.parse(msg.body.toString()) try { // 为了保持连接状态,处理超时情况 const touch = () => { if (!msg.hasResponded) { msg.touch() // Touch the message again a second before the next timeout. setTimeout(touch, msg.timeUntilTimeout() - 1000) } } let timeTouch = setTimeout(touch, msg.timeUntilTimeout() - 1000) let timeFinish = setTimeout(msg.finish.bind(msg), msg.timeUntilTimeout() * 3 + 1000) // 这里是项目的核心处理部分,具体内容后面文章在说明,这里返回的url便是生成pdf的网络地址 let url = await ctx.service.pdf.index.generate(data) clearTimeout(timeTouch) clearTimeout(timeFinish) // 这里表示这个队列结束告诉nsq可以放下个兄弟进来了 msg.finish() } catch (error) { // 万一出现错误也不要阻塞,nsq在失败后会重新入队 msg.finish() // 这里可以加入网络日志 console.log(error) } }); client.on('error', function(err) { // 这里监听读操作时候发生错误情况处理 // 这里可以做一些错误处理,加错误日志 console.log(err) }); });};刚开始说到用nsq还是有点慌的,毕竟作为一个前端工程师一脸懵逼,但是经过仔细学习,和后端大佬的请教,发现nsq其实就是一个高效队列,简易好用,对于上手来说还是比较简单的。后续文章还会对puppteer生成pdf服务的核心业务做详细介绍,也就是上文ctx.service.pdf.index.generate(data)具体实现过程。以上只是本人的学习总结,如有问题,请大神不吝赐教。 ...

June 18, 2020 · 1 min · jiezi

mongodb数据关联

const mongoose = require('mongoose');mongoose.connect('mongodb://localhost: 27017/mongo-relation', { useNewUrlParser: true, useUnifiedTopology: true }, err => { if (err) { console.log('数据库连接失败'); } console.log('数据库连接成功');})const categorySchema = new mongoose.Schema({ name: { type: String }}, { toJSON: { virtuals: true }});categorySchema.virtual('posts', { // 本地键 localField: '_id', // 关联的模型 ref: 'Post', // 外键 foreignField: 'categories', // 输出多个 justOne: false});const Category = mongoose.model('Category', categorySchema);const postSchema = new mongoose.Schema({ title: { type: String }, body: { type: String }, category: { type: mongoose.SchemaTypes.ObjectId, ref: 'Category' }, categories: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'Category' }]});const Post = mongoose.model('Post', postSchema);async function init() { // const cats = await Category.find().populate('categories'); // console.log(cats); // [ // { _id: 5ee0a7f22adf1624e8b9b64d, name: 'nodejs', __v: 0 }, // { _id: 5ee0a7f22adf1624e8b9b64e, name: 'vue.js', __v: 0 } // ] // 加上lean() // const cats = await Category.find().populate('posts').lean(); // console.log(cats); // [ // { // _id: 5ee0a7f22adf1624e8b9b64d, // name: 'nodejs', // __v: 0, // posts: [ [Object] ] // }, // { // _id: 5ee0a7f22adf1624e8b9b64e, // name: 'vue.js', // __v: 0, // posts: [ [Object], [Object] ] // } // ] // cats[0].posts 查找第1个分类内容 // const cats = await Category.find().populate('posts').lean(); // console.log(cats[0].posts); // [ // { // _id: 5ee0a74dc6a921238454fa52, // title: '第2篇文章', // body: '内容2', // __v: 9, // category: 5ee0a7f22adf1624e8b9b64d, // categories: [ 5ee0a7f22adf1624e8b9b64d, 5ee0a7f22adf1624e8b9b64e ] // } // ] // JSON.stringify const cats = await Category.find().populate('posts').lean(); console.log(JSON.stringify(cats)); // [{"_id":"5ee0a7f22adf1624e8b9b64d","name":"nodejs","__v":0, // "posts":[{"_id":"5ee0a74dc6a921238454fa52", // "title":"第2篇文章","body":"内容2","__v":9, // "category":"5ee0a7f22adf1624e8b9b64d", // "categories":["5ee0a7f22adf1624e8b9b64d","5ee0a7f22adf1624e8b9b64e"]}]}, // {"_id":"5ee0a7f22adf1624e8b9b64e","name":"vue.js","__v":0,"posts":[{"_id":"5ee0a74dc6a921238454fa51","title":"第1篇文章","body":"内容1","__v":5,"category":"5ee0a7f22adf1624e8b9b64d","categories":["5ee0a7f22adf1624e8b9b64e"]},{"_id":"5ee0a74dc6a921238454fa52","title":"第2篇文章","body":"内容2","__v":9,"category":"5ee0a7f22adf1624e8b9b64d","categories":["5ee0a7f22adf1624e8b9b64d","5ee0a7f22adf1624e8b9b64e"]}]}] // await Post.insertMany([{ // title: '第1篇文章', // body: '内容1' // }, { // title: '第2篇文章', // body: '内容2' // }]); // await Category.insertMany([{ // name: 'nodejs' // }, // { // name: 'vue.js' // } // ]); // const category = await Category.find(); // console.log(category); // const cat1 = await Category.findOne({ name: 'nodejs' }); // const cat2 = await Category.findOne({ name: 'vue.js' }); // const post1 = await Post.findOne({ title: '第1篇文章' }); // const post2 = await Post.findOne({ title: '第2篇文章' }); // posts.category // console.log(cat1); // post1.categories = [cat2]; // post2.categories = [cat1, cat2]; // await post1.save(); // await post2.save(); // const post = await Post.find(); // console.log(post); // console.log(post1, post2); // const post = await Post.find().populate('categories') // console.log(post[0], post[1]);}init()

June 10, 2020 · 2 min · jiezi

NodeJsExpress简单的用户注册登录和授权

前言:新建node-auth文件夹,新建server.js文件,初始化文件夹 npm init -y(git init)1.安装express, mongoose,rest-client2.开启服务器const express = require('express)const app = express()//连接数据库require('./modles/db')//jwtconst jwt = require('jsonwebtoken')app.use(express.json()) //密钥const SECRET = 'sajkFAjscbhsafchdsvjkks';app.get('/api', async(req, res) => { const user = await User.find(); res.send(user); // res.send('ok');})//注册app.post('/api/register', async(req, res) => { const user = await User.create({ username: req.body.username, password: req.body.password }) res.send(user); // console.log(req.body);});//登录app.post('/api/login', async(req, res) => { const user = await User.findOne({ username: req.body.username }); if (!user) { return res.status(422).send({ message: '用户名不存在' }) } //验证密码 compareSync const isPasswordValid = require('bcryptjs').compareSync(req.body.password, user.password); if (!isPasswordValid) { return res.status(422).send({ message: '密码错误' }); } // 生成token const token = jwt.sign({ id: String(user._id) }, SECRET) res.send({ user, token }) // res.send(isPasswordValid); // res.send(user);});// 中间件const auth = async(req, res, next) => {//获取token const raw = String(req.headers.authorization.split(' ').pop()); //解析 const { id } = jwt.verify(raw, SECRET); req.user = await User.findById(id); next()}// 个人信息app.get('/api/profile', auth, async(req, res) => { res.send(req.user);})app.listen(3000, () => {` console.log('listening port 3000!');})3.连接数据库 ...

June 10, 2020 · 2 min · jiezi

NodeJs与python的使用对比

写这篇文章的目的是想记录下NodeJs(后面简称node)与python的使用对比,希望看完之后大家对node跟python有个基本的认识。 (本文使用的node版本为v12.14.0,python为v3.8.3。) 简介node 是一个基于 Chrome V8 引擎的 JavaScript(简称js) 运行时。简单的说就是通过v8引擎(由c++编写)解释并执行js代码,然后就能运行在服务器上。python则是一门面向对象的解释型编程语言,目前最广泛的python解释器是CPython,就是通过C语言把python代码编译成字节码然后在虚拟机上运行。node适用于前端代码的打包发布、服务端开发跟桌面端应用开发等。而python则适合科学计算、数据分析、自动化运维等场景。 数据结构node的数组对应python的列表,都可以存放多种不同类型的数据。node对象则对应python的字典,都是使用key-value的形式。set结构也是类似的概念,都是没有重复元素的集合。node没有python中的元组类型,但是可以通过Object.freeze实现类似的效果。node let list = [1,2,3]list.push(4) // list [1,2,3,4]list.splice(2,1) // list [1,2,4]list.concat([5,6]) // [1,2,4,5,6]list.slice(1) // [2,4]let [a,b,c] = list // a=1 b=2 c=4let tuple = [1,2]Object.freeze(tuple)tuple[0] = 3 // tuple [1,2]//一般可以使用for、forEach、for...of进行遍历list.forEach((item)=>{console.log(item)}) //1 2 4python list = [1,2,3]list.append(4) # list [1,2,3,4]del list[2] # list [1,2,4]list + [5,6] # [1,2,4,5,6]list[1:] # [2,4]a,b,c = list # a=1 b=2 c=4tuple = (1,2)tuple[0] = 3 # 报错 tuple (1,2)#通过for in遍历for item in list: print(item) # 1 2 4变量与作用域nodenode中全局变量在global对象上定义,可以在多个模块中访问。模块中声明变量可以通过var、let和const,其中let跟const在代码块(if、for等)内无法被外面的方法访问,而var可以。除了块级作用域外,还有函数作用域,函数作用域内的变量想被函数外访问则需要通过闭包。另外每个js文件就是一个模块,而模块最终会被一个匿名函数包裹(exports跟module就是匿名函数里的参数),所以模块里的变量也是局部变量。 ...

June 9, 2020 · 2 min · jiezi

nodejsbuffer-基础篇

概念buffer存了什么buffer是一个操作字节的对象,它的底层是一个字节数组,存储着16进制数字。 var str = 'hello buffer'var buffer = new Buffer(str, 'utf-8')console.log(buffer) //输出的是十六进数字buffer的每个元素是16进制的两位数,也就是每个元素的大小是0-255. 因为 F X 16 + F X 16^0 = 255 溢出了怎么办我们可以直接对buffer的元素进行赋值 var buffer = new Buffer(10)buffer[0] = 300 console.log(buffer[0])如果赋的值是小数,小数部分会直接被舍弃。溢出的时候,就是如果大于255就减去256直到小于255,如果小于0则加上256直到大于0 if ( x > 255 ) { while(x > 255) { x = x - 256 } } if ( x < 0 ) { x = x + 256 }补充:~~~~ 负数在计算机里面存储的是补码,最高位为符号位(0为正,1为负),除符号位,其他位会取反,最低位取反后加1如:-1取反前: 1000 0001取反: 1111 1110末位加1:1111 1111这样计算机读出来就是255啦 ...

June 6, 2020 · 1 min · jiezi

基于Nodejs平台web框架Express-VS-Koa

Express和Koa都是基于Nodejs平台的web框架,也是目前比较常见的用于快速开发web服务的框架,且两者都是基于middleware的方式去处理客户端请求,那么两者有何区别呢?简单点说就是,“Express是直线型,Koa是洋葱模型”。(喊口号!!!)我们先来看看下面的示例代码: // for express exampleconst express = require('express');const app = express();function cb1(req, res, next) { console.log('>>>>>>cb1'); next(); console.log('<<<<<<cb1');}function cb2(req, res, next) { console.log('>>>cb2<<<'); res.send('hello world');}app.use('/', [cb1, cb2]);app.listen(3000);// for koa2 exampleconst koa = require('koa2');const app = koa();function cb1(ctx, next) { console.log('>>>>>>cb1'); next(); console.log('<<<<<<cb1');}function cb2(ctx, next) { console.log('>>>cb2<<<'); ctx.body = 'hello world';}app.use(cb1);app.use(cb2);app.listen(3000);以上两段代码的输出皆为: >>>>>>cb1>>>cb2<<<<<<<<<cb1所以,当middleware为同步函数时,两者从执行结果上来看并无区别。我们再来看看下面的示例代码: // for express exampleconst express = require('express');const app = express();async function cb1(req, res, next) { console.log('>>>>>>cb1'); await next(); console.log('<<<<<<cb1');}async function cb2(req, res, next) { return new Promise((resolve) => { setTimeout(resolve, 500); }).then(() => { console.log('>>>cb2<<<'); res.send('hello world'); });}app.use('/', [cb1, cb2]);app.listen(3000);// for koa2 exampleconst koa = require('koa2');const app = new koa();async function cb1(ctx, next) { console.log('>>>>>>cb1'); await next(); console.log('<<<<<<cb1');}async function cb2(ctx, next) { return new Promise((resolve) => { setTimeout(resolve, 500); }).then(() => { console.log('>>>cb2<<<'); ctx.body = 'hello world'; });}app.use(cb1);app.use(cb2);app.listen(3000);express-example的输出为: ...

June 6, 2020 · 3 min · jiezi

Nodejs基础

第一节:模块化跟Vue一样,单独创一个.js文件!运行方法首先你自己建一个文件夹比如叫test然后在顶部路径:如F:/test选中后输入cmd运行就能直接获取到路径,如果要运行起来就运行你自己要运行的文件名node ./app.js 主页面引入就是模块化他们的区别就是Vue: //B.jsconst router =[ ]export default router//A文件import router from './router'node: 这里提到的是如果B是封装对象就用module.exports = test如果是方法就用function test(){}exports.xxxx = test //B文件暴露出来const test ={ set(){ console.log(123) }}module.exports = test//A文件引入const test = require('./test')test.set()第二节:package.json默认的都是index,如果有人非要起自己的名!可以直接在你的文件目录输入 npm init --yes自己就生成了一个文件package.json,main这个你随意改吧,想怎么改就怎么改! { "name": "cnm", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC"}第三节:第三方包安装这个安装很简单第一步:npm i md5 -S第二步:在app.js里面引入MD5第三步:在这里进行赋值即可 npm i md5 -Svar MD5 require("md5)console.log(MD5("haha438741"))第四节:fs模块说白了就是文件路径,你的数据改了怎么保存。保存到了哪里?这里有几个常用的 ...

June 6, 2020 · 1 min · jiezi

Nodejs-Express-Mongodb-基础

1--》快速上手路由const express = require("express"); const app = express(); app.get('/', (req, res) => { res.send({ success: 'ok'});}) app.listen(3000, () => { console.log("APP is listening 3000!");}) 2--》静态文件托管const path = require("path"); app.use(express.static(path.join(__dirname, 'public'))) 3--》CORS跨域请求1:npm i cors app.use(require('cors)()); 2: app.all('*', function(req, res, next) { res.header('Access-Control-Allow-Origin', '*');res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With');res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');next();}); 4--》MongoDB基础const mongoose = require("mongoose"); mongoose.connect('mongodb://localhost:27017/database', {useNewUrlParser: true,}); const testSchema = new mongoose.Schema({ title: String}) const Test = mongoose.model('Test',testSchema) ...

June 5, 2020 · 1 min · jiezi

Nodejs获取本机内网ip

通过引入os模块获取系统信息var os = require('os');var serverIP = getIPAddress();console.log('serverIP', serverIP);// 获取内网ipfunction getIPAddress() { let IPAddress = ''; var interfaces = os.networkInterfaces(); for (var devName in interfaces) { var iface = interfaces[devName]; for (var i = 0; i < iface.length; i++) { var alias = iface[i]; if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) { IPAddress = alias.address; } } } return IPAddress;};

June 5, 2020 · 1 min · jiezi

nodejssequelize扩展使用

安装Sequelizenpm i sequelize驱动安装(选择以下其一) npm install --save pg pg-hstore # Postgresnpm install --save mysql2npm install --save mariadbnpm install --save sqlite3npm install --save tedious # Microsoft SQL Server连接数据库测试连接初次体验导入sequelize包 创建sequelize实例(通过实例构造方法传入参数创建连接数据库地址) .authenticate()函数测试连接是否正常 const Sequelize = require('sequelize');const sequelize = new Sequelize('dog', 'root', '123456', { host: 'localhost', /* 选择 'mysql' | 'mariadb' | 'postgres' | 'mssql' 其一 */ dialect: 'mysql' });try { sequelize.authenticate(); console.log("ok");} catch (err) { console.log(err)}构造函数参数详解https://sequelize.org/master/... 关闭连接默认情况下,在保持连接打开的状态,并对所有的查询使用相同的连接 sequelize.close()模型模型模型的本质其实是代表的是数据库中表的抽象,包含数据库表的名称以及该名称下所具有的列(以及列的数据类型) 模型定义-define方式sequelize.define(modelName, attributes, options)函数详解https://sequelize.org/master/... ...

June 4, 2020 · 2 min · jiezi

实时日志监控

实时日志监控服务: log.io, 主要有两部分组成, 分别为 服务端 和 客户端.要求安装log.io时, node的版本应该为最新的稳定版,我的node版本为v12.17.0ref: centos7安装最新稳定版本的nodejs 服务端安装全局安装 log.io-servernpm install -g log.io添加默认配置文件# 添加目录sudo mkdir -p /root/.log.io# 添加配置文件(server.json的具体内容请参看下面给出的示例)vim /root/.log.io/server.json{ "messageServer": { "port": 6689, "host": "0.0.0.0" }, "httpServer": { "port": 6688, "host": "0.0.0.0" }, "debug": false, "basicAuth": { "realm": "abc123xyz", "users": { "登录用户名": "登录密码" } }}PM2启动服务端如果没有pm2服务, 请使用npm安装, 如: npm install -g pm2 pm2 start log.io-server如果想用pm2以配置文件方式启动, 参考文中底部的说明. 验证浏览器输入ip + 6688端口, 输入用户名和密码, 就可以进入实时日志界面 (当然, 需要看到日志,还需要往下配置客户端) http://localhost:6688 客户端安装全局安装log.io-file-inputnpm install -g log.io-file-input添加默认配置文件# 添加目录sudo mkdir -p /root/.log.io/inputs# 添加配置文件vim /root/.log.io/inputs/file.json{ "messageServer": { "host": "0.0.0.0", "port": 6689 }, "inputs": [ { "source": "服务器名称", "stream": "网关", "config": { "path": "/data/xxxx/gateway-0.0.6.log" } }, { "source": "服务器名称", "stream": "xxxx_user", "config": { "path": "/data/xxx/logs/consumer-user-test.log" } } ]}PM2启动服务端pm2 start log.io-file-input验证再次在浏览器中访问 6688 端口, 就可以看到效果了 ...

June 3, 2020 · 1 min · jiezi

JWT

什么是 JWTJSON Web Token(简称 JWT)是目前最流行的跨域认证解决方案。是一种认证授权机制。JWT 是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准(RFC 7519)。JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。可以使用 HMAC 算法或者是 RSA 的公/私秘钥对 JWT 进行签名。因为数字签名的存在,这些传递的信息是可信的。阮一峰老师的 JSON Web Token 入门教程 讲的非常通俗易懂,这里就不再班门弄斧了生成 JWTjwt.io/www.jsonwebtoken.io/ JWT 的原理 JWT 认证流程:用户输入用户名/密码登录,服务端认证成功后,会返回给客户端一个 JWT客户端将 token 保存到本地(通常使用 localstorage,也可以使用 cookie)当用户希望访问一个受保护的路由或者资源的时候,需要请求头的 Authorization 字段中使用Bearer 模式添加 JWT,其内容看起来是下面这样Authorization: Bearer复制代码 服务端的保护路由将会检查请求头 Authorization 中的 JWT 信息,如果合法,则允许用户的行为因为 JWT 是自包含的(内部包含了一些会话信息),因此减少了需要查询数据库的需要因为 JWT 并不使用 Cookie 的,所以你可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS)因为用户的状态不再存储在服务端的内存中,所以这是一种无状态的认证机制JWT 的使用方式客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。方式一当用户希望访问一个受保护的路由或者资源的时候,可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求头信息的 Authorization 字段里,使用 Bearer 模式添加 JWT。 GET /calendar/v1/events Host: api.example.com Authorization: Bearer <token>用户的状态不会存储在服务端的内存中,这是一种无状态的认证机制服务端的保护路由将会检查请求头 Authorization 中的 JWT 信息,如果合法,则允许用户的行为。由于 JWT 是自包含的,因此减少了需要查询数据库的需要JWT 的这些特性使得我们可以完全依赖其无状态的特性提供数据 API 服务,甚至是创建一个下载流服务。因为 JWT 并不使用 Cookie ,所以你可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS)方式二跨域的时候,可以把 JWT 放在 POST 请求的数据体里。方式三通过 URL 传输 http://www.example.com/user?token=xxx项目中使用 JWT**项目地址: https://github.com/yjdjiayou/... ** ...

June 1, 2020 · 1 min · jiezi

前端包管理工具的配置流程

前端包管理工具的配置流程一:新建目录结构,初始化项目1 新建一个文件夹作为新项目的工作空间。2 使用vscode工具打开文件夹,使用【npm init -y】命令初始化项目,项目初始化后出现一个【package.json】的文件。3 新建一个【src】目录,用来存放源代码。4 新建一个【dist】目录,用来存放产品的打包文件。5【src】下新建首页【index.html】。6【src】下新建入口文件【index.js】。 二:安装webpack的包管理工具7 安装webpack打包工具 【cnpm i webpack webpack-cli -D】8 全局运行【npm i cnpm -g】9 在【webpack.config.js】配置文件中配置webpack //向外暴露一个打包对象module.exports = { mode:'development'//development production}10 约定的打包的入口文件为【index.js】文件,【约定大于配置的规则】 11 使用【webpack】打包,打包后在【dist】目录下生成一个【main.js】的文件。 三:配置修改后自动编译:12 实时打包编译工具:【webpack-dev-server】 13 安装:【cnpm i webpack-dev-server -D】 14 在【package.json中配置】 "dev":"webpack-dev-server"15 执行:【npm run dev】,即可完成修改代码后的自动编译 16 生成的【mian.js】是放在内存中的根目录下,引用内存中的【main.js】 四:配置编译后自动打开浏览器17 "dev":"webpack-dev-server --open"五:配置编译后自动跳转到浏览器后自动打开首页1 问题:编译后没有自动跳转到首页 2 解决:配置编译后自动跳转到首页,即配置首页到内存中 3 安装【html-webpack-plugin】插件,【cnpm i html-webpack-plugin -D】 4 在【webpack.config.js】中进行配置【html-webpack-plugin】插件 //配置插件 const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') //配置在内存中自动生成index.js的插件 //创建一个插件的实例对象 const htmlPlugin = new HtmlWebpackPlugin({ template:path.join(__dirname,'./src/index.html'), filename:'index.html' })//向外暴露一个打包对象module.exports = { mode:'development', plugins:[ htmlPlugin ]}六:总结:总结:(1)完成了打包后的【mian.js】文件进内存(2)【index.html】进内存(3)并且打包好的【mian.js】自动注入到【index.html】中,使用包管理工具的基本环境设置完成。 ...

June 1, 2020 · 1 min · jiezi

Java爬虫使用JvppeteerPuppeteer轻松爬淘宝商品

Java爬虫:使用Jvppeteer(Puppeteer)轻松爬淘宝商品想要爬取某宝的商品,如果只是用HttpURLConnection发个请求,失败率是很高的。一般想要保证成功率的话,都会选择真实的浏览器去抓取。 以前常用的解决方案是selenium或phantomjs,但是它两的环境配置太麻烦了,对程序员极度不友好,自从谷歌推出Puppeteer后,puppeteer迅速流行起来,获得大家一致称赞。它是一个NodeJS库,但今天并不是要使用它来爬取某宝商品,而是使用Java语言写的Jvppeteer,Jvppeteer与Puppeteer是同样的实现原理。 思路使用多线程,一个线程负责一个页面的爬取(接下来的内容会使用page代替页面)创建与线程池线程数相同的page队列,page放在LinkedBlockingQueue队列里,每当有爬取任务时,就从队列里取出一个page,爬取任务完成时,将page放回队列的后面。这样做的原因是重复利用page,减少页面的创建频率,但是要注意的是一个页面不能利用太久或者次数太多,防止出现crash的情况拦截图片和多媒体资源的加载,多媒体资源和图片的加载会极大影响页面的加载速度,从而影响爬虫效率,所以要拦截(可选)。我们选择获取整个页面内容,然后解析得到商品信息 代码实现1.启动浏览器 //指定启动路径,启动浏览器 String path = new String("F:\\java教程\\49期\\vuejs\\puppeteer\\.local-chromium\\win64-722234\\chrome-win\\chrome.exe".getBytes(), "UTF-8"); ArrayList<String> argList = new ArrayList<>(); LaunchOptions options = new OptionsBuilder().withArgs(argList).withHeadless(false).withExecutablePath(path).build(); argList.add("--no-sandbox"); argList.add("--disable-setuid-sandbox"); Browser browser = Puppeteer.launch(options);2.创建page队列与线程池//启动一个线程池多线程抓取 int threadCount = 5; ThreadPoolExecutor executor = new ThreadPoolExecutor(threadCount, threadCount, 30, TimeUnit.SECONDS, new LinkedBlockingDeque<>()); CompletionService service = new ExecutorCompletionService(executor); //打开5个页面同时抓取,这些页面可以多次利用,这样减少创建网页带来的性能消耗 LinkedBlockingQueue<Page> pages = new LinkedBlockingQueue<>(); for (int i = 0; i < threadCount; i++) { Page page = browser.newPage(); //拦截请求,可选,但是存在线程切换比较严重,建议不拦截// page.onRequest(request -> {// if ("image".equals(request.resourceType()) || "media".equals(request.resourceType())) {// //遇到多媒体或者图片资源请求,拒绝,加载页面加载// request.abort();// } else {//其他资源放行// request.continueRequest();// }// });// page.setRequestInterception(true); pages.put(page);//往队列后面放,阻塞 }3.定义爬虫线程静态内部类static class CrawlerCallable implements Callable<Object> { private LinkedBlockingQueue<Page> pages; public CrawlerCallable(LinkedBlockingQueue<Page> pages) { this.pages = pages; } @Override public Object call() { Page page = null; try { page = pages.take(); PageNavigateOptions navigateOptions = new PageNavigateOptions(); //如果不设置 domcontentloaded 算页面导航完成的话,那么goTo方法会超时,因为图片请求被拦截了,页面不会达到loaded阶段 navigateOptions.setWaitUntil(Arrays.asList("domcontentloaded")); page.goTo("https://item.taobao.com/item.htm?id=541605195654", navigateOptions); String content = page.content(); return parseItem(content); } catch (Exception e) { e.printStackTrace(); } finally { if (page != null) { try { pages.put(page);//把已经抓取完的网页放回队列里 } catch (InterruptedException e) { e.printStackTrace(); } } } return null; } }4.解析商品,获取结果//结果集 List<Future<Object>> futures = new ArrayList<>(); //抓取100次 long start = System.currentTimeMillis(); for (int i = 0; i < 100; i++) { Future<Object> future = service.submit(new CrawlerCallable(pages)); futures.add(future); } //关闭线程池 executor.shutdown(); //获取结果 int i = 0; for (Future<Object> result : futures) { Object item = result.get(); i++; System.out.println(i + ":" + Constant.OBJECTMAPPER.writeValueAsString(item)); } long end = System.currentTimeMillis(); System.out.println("时间:" + (end - start));经过测试,爬取的速度非常之快,100个任务用了15s就完成了,但不同的电脑配置和带宽有不同结果,因为爬虫是比较吃配置还有带宽的。 ...

May 30, 2020 · 2 min · jiezi

nodejs-包管理器安装

src: Github Installation instructionsNode.js v14.x: Using Ubuntucurl -sL https://deb.nodesource.com/setup\_14.x | sudo -E bash -sudo apt-get install -y nodejsUsing Debian, as rootcurl -sL https://deb.nodesource.com/setup\_14.x | bash -apt-get install -y nodejsNode.js v13.x: Using Ubuntucurl -sL https://deb.nodesource.com/setup\_13.x | sudo -E bash -sudo apt-get install -y nodejsUsing Debian, as rootcurl -sL https://deb.nodesource.com/setup\_13.x | bash -apt-get install -y nodejsNode.js v12.x: Using Ubuntucurl -sL https://deb.nodesource.com/setup\_12.x | sudo -E bash -sudo apt-get install -y nodejsUsing Debian, as rootcurl -sL https://deb.nodesource.com/setup\_12.x | bash -apt-get install -y nodejsNode.js v10.x: ...

May 30, 2020 · 1 min · jiezi

Segmentation-fault-node报错

报错信息 (dv) root@JoJo:~# node -vSegmentation fault(dv) root@JoJo:~# npm -vSegmentation fault使用n管理,在node版本从9.2.0回退到8.7.0的时候,可能是在二进制文件下载完成,然后node命令和npm命令执行都会返回 Segmentation fault 在Stack Overflow查了一圈,尝试把nodejs卸载掉,但无效 apt-get remove --purge nodejs解决使用n安装另一个版本, n 14.0.0查看所有版本: sudo n移除有问题的版本: n rm 8.7.0n 选择要使用的版本。 root@JoJo:~# node -vv8.7.0

May 30, 2020 · 1 min · jiezi

egg多进程模型注意事项梳理

背景最近在项目中使用egg进行服务端开发,在开发过程中遇到了比较诡异的问题,具体表现为mq在监听到信息时,其回调函数会被多次执行,那么这会导致某个文件被同时操作等问题。 问题成因这边梳理egg文档时,重点过了一下egg多进程的设计模式,了解到egg的master-agent-worker模式,那么这里面有些问题是需要我们在开发时去注意的了。 首先介绍下egg的多进程实现方式egg通过node提供的cluster实现了多进程模式,为了更好地利用多核环境,egg一般会启用相当于cpu核数的worker,以此来最大化利用cpu能力。 在egg启动时,master,agent,worker的关系如图所示 +---------+ +---------+ +---------+| Master | | Agent | | Worker |+---------+ +----+----+ +----+----+ | fork agent | | +-------------------->| | | agent ready | | |<--------------------+ | | | fork worker | +----------------------------------------->| | worker ready | | |<-----------------------------------------+ | Egg ready | | +-------------------->| | | Egg ready | | +----------------------------------------->|在这种模式下,master、agent、worker各司其职,主要制作分配如下:master:负责维护整个应用稳定性,当有worker因异常而退出时,master负责拉起新的worker,以确保应用正常运行。agent:由于egg的多进程模型会在每个进程中运行一份我们的应用实例,那么在某些情况下,这种机制会导致问题。比如,保存日志的逻辑如果在每个进程中都执行的话,那么在触发日志保存操作的时候,会有多个进程同时操作日志文件,那么此时就会导致文件读写问题。所以egg设计了agent进程,agent进程只会有一个,不会出现上述问题,这样,对于类似上述的后台运行的逻辑就统一放到agent中去处理了。worker:负责执行业务代码,处理用户请求和定时任务,egg在框架层保证了定时任务只会在单个worker中执行,所以可以放心使用。 分析egg多进程导致的问题上面我们分析过了egg的多进程机制,所以我们知道了问题成因,出现我们最开始说的问题的原因是我们把mq的监听和处理逻辑放到了worker中,那么这样的话在实际运行过程中,就会导致mq收到消息时,回调函数被执行多次。 到这里我们已经知道如何优化了,那就是把mq的处理逻辑放到agent中,以确保mq消息的回调仅执行一次。但是细心地你肯定发现了,这里有个问题,agent只有一个实例,如果事情在agent里面做,那么不是无法利用多核性能了吗?agent与worker通信的确,我们可以在agent中处理仅需要单次执行的逻辑,但是这样做就没法利用多核性能了。那么有什么办法吗?没错,就是进程间通信,具体思路就是,agent还是负责mq的连接和监听逻辑,但是回调函数不在agent中执行,而是写在worker里面。那么worker什么时候执行这个逻辑呢?答案是,agent通过进程间通信通知worker。egg内部实现了一个进程间通信机制,我们直接调用即可,主要实现方式如下: 广播消息: agent => all workers +--------+ +-------+ | Master |<---------| Agent | +--------+ +-------+ / | \ / | \ / | \ / | \ v v v +----------+ +----------+ +----------+ | Worker 1 | | Worker 2 | | Worker 3 | +----------+ +----------+ +----------+指定接收方: one worker => another worker +--------+ +-------+ | Master |----------| Agent | +--------+ +-------+ ^ | send to / | worker 2 / | / | / v +----------+ +----------+ +----------+ | Worker 1 | | Worker 2 | | Worker 3 | +----------+ +----------+ +----------+这里我们可以看出来,进程间通信都是基于master转发的,所以我们可以利用egg提供的机制,解决我们的问题。 ...

May 29, 2020 · 1 min · jiezi

Deno来了Node要凉了吗

背景故事最近Deno讨论比较热门,并且有说 “Deno很可能是下一个前端的大事件”,所以关注下。 Node.js 和 Deno 的起源作者都是Ryan Dahl他从2009年开始从事Node项目,但是几年后却退出了该项目。在2018年的时候,他发表了我对Node.js感到遗憾的10件事,另外他宣布了创建全新的Deno项目 。 作者GitHub https://github.com/ry Ryan DahI 提到的Node十个设计错误,很多都是基层方面的设计错误,要深入理解需要有专业功底。本人作为小白,只是整理罗列。 后悔 没有坚持使用Promise 的结果是导致Node里面充满了async / await和promise的不同async API设计,直到现时都极难整合。 后悔 没有从GYP加固系统转到GN后悔 继续使用GYP,没有提供FFI后悔 在任何地方也可以require(“ somemodule”)后悔 package.json提供了错误的“ module”观念后悔 没有注重安全性(Security)后悔 设计了软件界黑洞node_modules 有的时候 npm install 要等很久,然后发现应用下载了几百mb的node_module。 附上演讲视频地址:https://www.bilibili.com/vide...DenoDeno是使用JavaScript和TypeScript编写应用程序的新平台。两种平台具有相同的理念-事件驱动架构和异步非阻塞工具来构建Web服务器和服务。 Node 和 Deno 有何不同?这两个平台具有相同的目的,但是使用不同的机制。Deno使用ES模块作为默认模块系统,而Node.js使用CommonJS。外部依赖项是使用URL加载的,类似于浏览器。也没有包管理器和集中式注册表,可以在Internet上的任何位置托管模块。与Node.js相反,Deno在沙箱中执行代码,这意味着运行时无法访问网络,文件系统和环境。需要明确授予访问权限,这意味着更好的安全性。Deno开箱即用地支持TypeScript,这意味着我们不需要手动安装和配置工具来编写TypeScript代码。另一个区别是Deno提供了一组内置工具,例如测试运行器,代码格式化程序和捆绑程序。 Deno不需要npm包管理# Denoimport { serve } from "https://deno.land/std@0.53.0/http/server.ts";# Node const server requrie('server')Deno通过URL导入代码,可以在互联网上的任何地方托管模块。无需集中注册表即可分发Deno软件包。也不需要package.json文件和依赖项列表,因为所有模块都是在应用程序运行时下载,编译和缓存的。 Deno 真的会取代node?Krzysztof Piechowicz:Deno的目标不是取代Node.js,而是提供替代方案。其中一些差异颇具争议,很难预测它们是否将以正确的方式格式化。我建议所有Node.js程序员都注意这个项目。我不确定该项目是否会成功,但这是观察Node.js如何以不同方式实现的绝佳机会。演讲视频地址:https://www.bilibili.com/vide...尝试一下附录资源 官网:https://deno.land源码:https://github.com/denoland/deno # 安装curl -fsSL https://deno.land/x/install/install.sh | sh# 运行Demodeno run https://deno.land/std/examples/welcome.ts例子 import { serve } from "https://deno.land/std@0.53.0/http/server.ts";const s = serve({ port: 8000 });console.log("http://localhost:8000/");for await (const req of s) { req.respond({ body: "Hello World\n" });}总结Deno 是否会带来变革,我们拭目以待!! ...

May 27, 2020 · 1 min · jiezi

node进程间通信

作为一名合格的程序猿/媛,对于进程、线程还是有必要了解一点的,本文将从下面几个方向进行梳理,尽量做到知其然并知其所以然: 进程和线程的概念和关系进程演进进程间通信理解底层基础,助力上层应用进程保护进程和线程的概念和关系用户下达运行程序的命令后,就会产生进程。同一程序可产生多个进程(一对多关系),以允许同时有多位用户运行同一程序,却不会相冲突。进程需要一些资源才能完成工作,如CPU使用时间、存储器、文件以及I/O设备,且为依序逐一进行,也就是每个CPU核心任何时间内仅能运行一项进程。 进程与线程的区别:进程是计算机管理运行程序的一种方式,一个进程下可包含一个或者多个线程。线程可以理解为子进程。 摘自wiki百科 也就是说,进程是我们运行的程序代码和占用的资源总和,线程是进程的最小执行单位,当然也支持并发。可以说是把问题细化,分成一个个更小的问题,进而得以解决。 并且进程内的线程是共享进程资源的,处于同一地址空间,所以切换和通信相对成本小,而进程可以理解为没有公共的包裹容器。 但是如果进程间需要通信的话,也需要一个公共环境或者一个媒介,这个就是操作系统。 进程演进我们的计算机有单核的、多核的,也有多种的组合方式: 单进程因为是一个进程,所以某一时刻只能处理一个事务,后续需要等待,体验不好 多进程为了解决上面的问题,但是如果有很多请求的话,会产生很多进程,开销本身就是一个不小的问题,而进程占据独立的内存,这么多响应是的进程难免会有重复的状态和数据,会造成资源浪费。 多进程多线程由之前的进程处理事务,改成使用线程处理事务,解决了开销大,资源浪费的问题,还可以使用线程池,预先创建就绪线程,减少创建和销毁线程的开销。 但是一个cpu某一时刻只能处理一个事务。像时间分片来调度线程的话,会导致线程切换频繁,是非常耗时的。 单进程单线程类似也就是v8,基于事件驱动,有效的避免了内存开销和上下文切换,只需要线程间通信,即可在适当的时刻进行事务结果等的反馈。 但是遇到计算量很大的事务,会阻塞后续任务的执行。像这样: 单进程单线程(多进程架构)node提供了cluster和child_process两个模块进行进程的创建,也就是我们常说的主(Master)从(Worker)模式。Master负责任务调度和管理Worker进程,Worker进行事务处理。 进程间通信node本身提供了cluster和child_process模块创建子进程,本质上cluster.fork()是child_process.fork()的上层实现,cluster带来的好处是可以监听共享端口,否则建议使用child_process。 child_processchild_process提供了异步和同步的操作方法,具体可查看文档。 常见的异步方法有: .exec.execFile.fork.spawn除了fork出来的进程会长期驻存外,其他方式会在子进程任务完成后以流的方式返回并销毁进程。 异步方法会返回ChildProcess的实例,ChildProcess不能直接创建,只能返回。 来看几张图吧: 举个例子有一个很长很长的循环,如果不开启子进程,会等循环之后才能执行之后的逻辑。 我们可以将耗时的循环放到子进程中,主进程会接受子进程的返回,不影响后续事物的处理。 // 主进程const execFile = require('child_process').execFile;execFile('./child.js', [], (err, stdout, stderr) => { if (err) { console.log(err); return; } console.log(`stdout: ${stdout}`);});console.log('用户事务处理');// 子进程#!/usr/bin/env nodefor (let i = 0; i < 10000; i++) { process.stdout.write(`${i}`);}而对于fork,它是专门用来生产子进程的,也可以说是主进程的拷贝,返回的ChildProcess中会内置额外的通信通道,也就是IPC通道,允许消息在父子进程间传递,例如通过文件描述符,不过由于创建的是匿名通道,所以只有主进程可以与之通信,其他进程无法进行通信。但相对的还有命名通道,详见下一节。 看一个简单的例子: //parent.jsconst cp = require('child_process');const n = cp.fork(`${__dirname}/sub.js`);n.on('message', (m) => { console.log('PARENT got message:', m);});n.send({ hello: 'world' });//sub.jsprocess.on('message', (m) => { console.log('CHILD got message:', m);});process.send({ foo: 'bar' });父进程通过fork返回的ChildProcess进行通信的监听和发送,子进程通过全局变量process进行监听和发送。 ...

May 26, 2020 · 1 min · jiezi

流批一体机器学习算法平台

针对正在兴起的机器学习广泛而多样的应用场景,阿里巴巴计算平台基础算法团队在2017年开始基于Flink研发新一代的机器学习算法平台。该项目名称定为Alink,取自相关名称(Alibaba, Algorithm, AI, Flink, Blink)的公共部分。经过三年的投入研发,Alink在算法性能、算法规模、算法易用性等方面取得了不错的成果,并实现了产品化。这使得数据分析和应用开发人员能够轻松搭建端到端的业务流程。 在后面的篇幅中,我们将从算法功能、算法性能、用户界面、可视化等方面对Alink做一个系统的介绍。 算法功能Alink拥有丰富的批式算法和流式算法,能够帮助数据分析和应用开发人员能够从数据处理、特征工程、模型训练、预测,端到端地完成整个流程。如下图所示,Alink提供的功能算法模块中,每一个模块都包含流式和批式算法。比如线性回归,包含批式线性回归训练,流式线性回归预测和批式线性回归预测。另外,Alink算法覆盖分类、回归、聚类、评估、统计分析、特征工程、异常检测、文本、在线学习、关联分析等经典领域,是一个通用的机器学习算法平台。 目前,Alink已经被阿里巴巴集团内部多个BU使用,并取得了不错的业务提升。特别是在2019年天猫双11中,单日数据处理量达到 970PB,每秒处理峰值数据高达 25 亿条。Alink 成功经受住了超大规模实时数据训练的检验,并帮助天猫产品推荐的点击率提高了4%。 算法性能下图给出的是一些经典算法与Spark的性能对比,通过该图可以看出,Alink在大部分算法性能优于Spark,个别算法性能比Spark弱,整体是一个相当的水平。 但是,“在功能的完备性方面,Alink更有优势”,Alink除了覆盖Spark的算法,还包含流式算法、流批混跑、在线学习、中文分词等。 用户使用界面为了提供更好的交互式体验,我们提供两种用户使用界面:web和PyAlink。 首先我们介绍一下web界面。Web界面提供拖拽的方式创建试验,通过对每一个组件进行配置完成整个试验的参数配置。下图给出的是web界面创建的批式、流式、流批混合的试验。 并且Alink可以支持节点的级别实验运行状态显示。在各个算法节点旁,我们用闪烁的小灯泡表示“运行中”的状态,用对勾表示“运行完成”的状态。一般情况下,只有批式(batch)组件才有可能运行结束。基于各个组件的运行状态,可以十分方便地判断当前实验运行到了什么程度。并且,如果实验运行中出现了报错或者长时间不结束的情况,也能根据组件运行状态更加方便地定位潜在出问题的组件。除了简单的运行状态以外,Alink还提供了查看组件输入、输出数据量指标的功能。对于不同类型的组件,Alink提供了不同的指标展现方式:对于流式(stream)组件来说,在组件运行时,可以接近实时地看到组件的输出BPS和RPS数值。而对于批式(batch)组件,在组件运行完成后,会展示总的输出数据条数和字节数。这些指标的展示对于判断实验/业务是否正常运行可以提供很多的参考,尤其对于一些线上实时的业务,通过这些指标就能直观地看到是否正常运行。 下面我们继续介绍PyAlink。为了满足脚本用户的需求,我们提供了PyAlink on notebook,用户可以通过PyAlink的python包使用Alink。PyAlink支持单机运行,也支持集群提交。并且打通Operator(Alink算子)和DataFrame的接口,从而使得Alink整个算法流程无缝融入python。PyAlink也提供使用Python函数来调用UDF或者UDTF。PyAlink在notebook中使用如下图,展示了一个模型训练预测,并打印出预测结果的过程: 可视化Alink中的可视化包括统计相关的可视化、模型类可视化以及评估可视化等,当前能进行大屏可视化的组件包括:统计分析类组件,直接展示的统计算法的结果;机器学习模型类组件,展示训练好的模型的信息;评估类组件,展示评估接口。下图给出的是统计可视化,通过下图可以看到我们的统计可视化支持窗口统计和累计统计,并且支持曲线、柱状图、统计表、矩阵图等多种展示方式。 同样,下面两幅图给出的是模型的可视化和评估的可视化。 总结展望经过三年的发展,Alink已经成为一个功能完备的机器学习算法平台,而且已经在2019年FFA19将代码开源到社区,让更多的人能够使用这个平台解决业务问题。虽然Alink开源已经取得了阶段性成果,但是我们将继续积极向FlinkML贡献代码,我们希望将更多优秀的机器学习算法贡献给Flink项目,也希望和社区一起努力,共同促进Flink社区机器学习生态的发展和繁荣。 上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/本文为阿里云原创内容,未经允许不得转载

May 26, 2020 · 1 min · jiezi

nodejs入门二数据交互之GET请求

打印 req.url,查看请求urlreq.url 可以看到我们GET请求的链接以及数据 我们新建一个html,写一个简单的form表单,用get方式提交请求 <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <form action="http://127.0.0.1:8081/aaa" method="get"> 用户:<input type="text" name="username" /> <br> 密码:<input type="password" name="password" /> <br> <input type="submit" value="提交"> </form></body></html>打开html文件,输入账号密码,在服务端打印 req.url 数据,请看图1.1。 // 4 .jsconst http = require('http')let server = http.createServer(function(req, res){ console.log(req.url) // 打印请求 url ->/aaa?username=123&password=qweqwe})server.listen(8081)【图1.1】 使用 querystring 模块处理url数据知识点:quertstring 我们可以看到,服务端接收到的url数据是 /aaa?username=123&password=qweqwe,"?"号后面就是我们再form表单中输入的数据,我门可以提取问号后面的字符串,然后用 querystring.parse() 将字符串处理成对象。 const http = require('http')const querystring = require('querystring') let server = http.createServer(function(req, res){ let [url, query] = req.url.split('?'); // url->'/aaa', query->'username=123&password=qweqwe' let get = querystring.parse(query); // { username: '123', password: 'qweqwe' }})server.listen(8081)使用 url 模块处理url数据知识点:url.parse()通过url.parse(req.url, true)会直接帮我处理url,第二个参数设置为true,会帮我们将form提交的数据转化成对象,请看图1.2。 ...

May 26, 2020 · 1 min · jiezi

Copy攻城狮日志Node快速重命名文件告别Potplay字幕困扰问题

↑开局一张图,故事全靠编↑ 前言Copy攻城狮日志的惯例,开局一张图,开始为您讲述一个鲜为人知的故事。故事的开头要从本大狮从盗版网站下载udemy课程的犯罪伊始说起,去年的某月某天,我真正接触到了“你的大学”udemy,本来想好好学学nodejs,结果发现Max的课199刀,因为恰好遇到没打折,然后一路google,结果到了某个bt网站找到了相应的种子。埋头一想,先开他个某雷会员下一波再说。别说,公司的网还是比较炸天的,17G的文件没多会就下载完毕了,用老司机工具PotPlay把玩一下,结果全都是英文字幕。然后“从入门到放弃”,一放就是大几个月…… My English is poor当专业的您看到这个标题的时候,您就能猜到我的“英国历史”水平,对的,另一段鲜为人知的故事是我的确没有拿到四级证书,这不得不提到我荒废的大学时代,现在依旧为过去的不努力而偿债。大学一开始我有想过好好学英语的,可能因为英语老师深深地吸引了我,甚至还加了wx,后来就不了了之了,哪怕是一句“hi”都不曾发过火。再后来就是每年都考四级,每年都挂,除了宿舍那考研的哥们在女友的“威逼利诱”之下在最后一学期终于顺利通过并考研成功。而我,和English却越走越远,直到工作中不得不去认识记忆那些乏味的API名字、专有名词……说回来udemy,个人感觉上面的课还是比较给力的,看过一些入门课,里面的讲师个个都是人才,说话又好听,带来的知识理念都比较新。所以我对下载的这个学习资料还是比较满意的,除了英文字幕看不太懂直到慢慢放弃。 PotPlay字幕及翻译Potplay是一款老司机软件,这里就不多表。16年的时候第一次使用就被深深的迷住了,不仅可以正规学习还可以特殊学习,一度导致营养更不上。后来装了左边的18年版本或就再也没有更新过,直到最近才知道新版加了字幕在线翻译功能。通过学习一些博文,似乎掌握了怎么看懂片中的情节--实时翻译字幕。凑巧的是下载的学习资料刚好有外挂字幕,符合字幕在线翻译的基本要求,不带字幕的视频+外挂字幕+在线翻译这样的结构是比较符合字幕在线翻译的。操作也十分简单,我这里用的“被泼冷水的”某度AI,先试用的标准版,目前使用体验尚佳。发达的大佬可以去撸个google云300刀的券直接对接google翻译。具体翻译接入可以直接上手把玩探索.百度翻译接入可参考:PotPlayer_Subtitle_Translate_Baidu翻译API配置(估计先要注册对应平台账号并开通服务): *-en.srt翻车其实一直有个问题在困扰我,同样的文件,旧版播放能正常显示字幕,新版的就无法正常显示字幕。通过细致的对比,发现旧版播放器能正常读取*-en.srt字幕文件,而新版播放器不能且认为是另外视频文件的字幕,当我把字幕文件文件名中的-en去掉之后发现新版播放器也能正常显示字幕并且还自动翻译了。至于是不是bug,我也没打算去深究。问题很明确,因为字幕文件名和视频文件名不匹配,导致播放器无法自动识别。解决问题的其中一个途径是复制并重命名所有的字幕文件,将文件名中最后的“-en”替换为空字符。既然问题和解决方法都有了,那么是时候表演真正的Copy技术了…… Node批量处理文件名对面的大佬“人生苦短,他用Python”,而我“万寿无疆,我用NodeJS”。直接Copy代码: const fs = require("fs");const path = require("path");const util = require("util");const readdir = util.promisify(fs.readdir);const stat = util.promisify(fs.stat);const timeStart = new Date();const filePath = path.resolve("./");function readDirRecur(file, callback) { return readdir(file).then((files) => { files = files.map((item) => { let fullPath = file + "/" + item; return stat(fullPath).then((stats) => { if (stats.isDirectory()) { return readDirRecur(fullPath, callback); } else { /*not use ignore files*/ if (item[0] == ".") { //console.log(item + ' is a hide file.'); } else { callback && callback(fullPath); } } }); }); return Promise.all(files); });}readDirRecur(filePath, function (filePath) { // 只处理.srt文件 将-en.srt处理为.srt if (path.extname(filePath) === ".srt") { let newPath = filePath.replace(/(.*)-en/, "$1"); if (fs.existsSync(newPath)) { console.log("该路径已存在"); // fs.unlinkSync(newPath) } else { fs.copyFileSync(filePath, newPath); } }}) .then(function () { console.log("done", new Date() - timeStart); //done 3.3 }) .catch(function (err) { console.log(err); });处理结果(还算比较理想):随机打开一个视频都能正常显示双语字幕了: ...

May 26, 2020 · 1 min · jiezi

nodejs入门一简单的服务和文件请求

hello world!知识点:response.write(), response.end() const http = require('http')let server = http.createServer(function(request, response){ response.write('hello world!'); // 把内容发个客户端(浏览器) response.end() // 结束请求,断开链接,不写end浏览器会以为数据没发完,一直转圈})server.listen(8080)如何从文件中读写数据知识点: fs模块,fs.writeFile(path, data, callback), fs.readFile(path, callback) const fs = require('fs'); fs.writeFile('./a.txt', '学习fs模块', function(err){ if(err){ console.log('写入失败') }else{ console.log('写入完成') }})fs.readFile('./a.txt', function(err, data){ if(err){ console.log(err) }else{ console.log(data) // console.log(data.toString()) // 如果只是文本,可以用同toString()方法 }})node XX.js运行文件,提示写入完成,会发现我们的目录下出现了 a.txt 文件。 读文件成功后会输出 Buffer 数据,Buffer数据是原始的二进制数据,因为nodejs出了处理文字数据外,也会处理文件、图片数据等,随便的转成字符串有的数据就毁了。 buffer 数据转换成字符串是给人看的,机器不需要看,直接返回给客户端可以直接显示请求文件知识点:req.url, res.writeHeader const http = require('http')const fs = require('fs')let server = http.createServer(function(req, res){ console.log(req.url) // 查看请求的url fs.readFile(`files@{req.url}`, (err, data)=>{ if(err){ res.writeHeader(404) res.write('Not Found!') res.end(); }else{ res.write(data); // 注意: 这里没有转换成字符串,浏览器可以直接显示。 res.end(); } })})server.listen(8080)我们新建文件夹 files, 新建一个1.html文件,写入一些内容。运行node服务后,我们请求 127.0.0.1:8080/1.html, 可以看到我们可以访问文件了。图:【新建文件夹 files】 ...

May 25, 2020 · 1 min · jiezi

nodejs入门目录

nodejs入门(一):简单的服务和文件请求

May 25, 2020 · 1 min · jiezi

Dapps-上架-baidupcsweb百度网盘-Web-版

前言:之前上架过一个命令行版本的百度客户端,但大部分用户不知道怎么使用,本期上架一个带界面的易使用版本 dapps是什么?dapps是一个应用程序商店,包含丰富的软件,一键安装程序;多版本共存。 使用官网文档:dapps应用商店 应用商店中安装 BaiduPCS-web使用说明:查看效果 目前包含的软件注:每周会上架一款新应用,持续更新百度网盘web版:下载速度比官方快很多 ------ 查看效果AriaNg高速下载器:2倍迅雷速度,迅雷无法下载的资源,也能下载 ------ 查看效果百度网盘下载器(命令行) ------ 查看效果wordpress ------ 查看效果py12306抢票 ------ 查看效果magnetw: 种子搜索神器 ------ 查看效果PhpMyAdmin:mysql管理工具 ------ 查看效果adminmongo: mongo管理工具 ------ 查看效果PHP: 世界上最好的语言(版本:5.6,7.1,7.2,7.3)------ 查看效果Mysql:数据库(版本:5.6,5.7,,8.0)------ 查看效果Nginx:服务器(版本:1.16)------ 查看效果redis:nosql数据库(版本:5.0)------ 查看效果mongo:是一个基于分布式文件存储的数据库(版本:3.4,4.0)------ 查看效果gogs版本控制 ------ 查看效果rabbitmq3.7队列服务 ------ 查看效果2048游戏 ------ 查看效果等......

November 13, 2019 · 1 min · jiezi

前端知识点总结ES6入门

1.var、let、constES6 推荐在函数中使用 let 定义变量const 用来声明一个常量 (值类似值不能改变,引用类型地址不能改变)let 和 const 只在最近的一个块中(花括号中)有效 //1.let不能重复申明 var n = 10;var n = 100;console.log(n) //100let a=10let a=100console.log(a) // SyntaxError 'a' has already been declared//2.var能改变全局作用域(变量提升) let则不能var b =1{ var b=10}console.log(b) //10let c=1{ let c=10}console.log(c) //1//3.同作用域内不能重申明var d=1let d=2console.log(d) // SyntaxError'd' has already been declaredlet d=1var d=2console.log(d) //SyntaxError 'd' has already been declared//4.var不管作用域对于同个变量赋值则更改var e=1{ let e=2}console.log(e) //1let f=1{ var f=2}console.log(f) //SyntaxError 'f' has already been declared//5.常量不能重新赋值const A = [1,2];A.push = 3;console.log(A); //[1,2,3]A = 10; //Error 这里地址改变了2.箭头函数、thisES6箭头函数内的this指向的是函数定义时所在的对象,而不是函数执行时所在的对象。箭头函数背部没有自己的this,this总是指向上一层的this,层层递上,直到找到有自己的this函数为止。ES5函数里的this总是指向函数执行时所在的对象,尤其在非严格模式下,this有时候会指向全局对象。 ...

November 13, 2019 · 3 min · jiezi

rcpress-基于React的文档生成器

前言以前开发vue组件时,写文档使用的是vuepress,之后转战react后觉得没有顺手的文档生成工具,就模仿vuepress写了这个rcpress。 特点RcPress 是一个基于 React.js 的静态文档生成器。文档UI是模仿 ant design 官网功能配置模仿Vuepress支持mdx,可以在markdown中使用jsx。支持service worker。生产模式下支持生成静态html页面和打包spa两种模式。开发模式下支持ssr,spa两种模式。技术栈ReactJsAnt Designmdxremarkprismjsservice worker快速上手安装安装命令行工具 @rcpress/cli yarn global add @rcpress/cli# 或者如果你用npmnpm i @rcpress/cli -g用法创建目录以及markdown文件 # 创建 docs 目录(docs是默认的文档目录)mkdir docs#创建markdown文件echo '# Hello RcPress' > docs/README.md运行 # 启动spa模式的服务rcpress dev# 启动服务端渲染的服务rcpress server# 访问`3000`端口即可。打包构建 # 在生产环境下构建sparcpress build# 在生产环境下构建ssr并且声称静态html文件rcpress generate文档获取详细的文档, 推荐访问网站上的向导一节。 首页截图 与vuepress的对比首先说下不同点 rcpress 使用了 react.js 驱动,而 vuepress 是由 vue 驱动的。rcpress 是使用了 Ant Design 作为 UI 框架,而 vuepress 是使用了自定义的样式。说下欠缺的功能 没有plugin(插件)这个概念,当然以后可以考虑加入。说下优势 可以在文档里使用所有ant design的组件,不用自己写。支持在开发模式下运行spa,ssr两种模式。vuepress貌似只能运行spa模式。支持生产spa打包。相关链接GitHub 仓库地址: rcpress文档地址: rcpress

November 5, 2019 · 1 min · jiezi

js获取指定时间范围内的连续小时天周月列表

function getDuration(type,start,stop){ var $array = new Array(); var current = new Date(start); stop = new Date(stop); while (current <= stop) { $array.push( new Date (current) ); if(type == 'hour'){//小时 current.setHours(current.getHours() + 1); }else if(type == 'day'){//天 current.setDate(current.getDate() + 1); }else if(type == 'week'){//周 current.setDate(current.getDate() + 7); }else if(type == 'month'){//月 current.setMonth(current.getMonth() + 1); }else{//默认天 current.setDate(current.getDate() + 1); } } return $array;}console.log(getDuration('day','2019-10-05 10:23:16','2019-11-05 18:23:16'));

November 5, 2019 · 1 min · jiezi

docker安装AriaNg下载器

AriaNg是一个非常优秀的下载工具,基于aria2。个人感觉可以替代迅雷。 效果图: 代码:docker-compose.yml version: '3'services: ariang: image: wahyd4/aria2-ui:latest container_name: dapps-ariang ports: - "${ARIANG_HOST_PORT}:80" volumes: - ${ARIANG_DATA_DIR}:/data restart: always.env文件 # app infoAUTHOR_UID=10000000AUTHOR_NAME=kakaAPP_ID=ariangAPP_NAME=ariang高速下载器APP_INTRODUCTION=2倍迅雷下载速度APP_UPDATE_CONTENT=APP_VERSION=1.0.0APP_PORT=8011/ui# environment config fileSOURCE_DIR=./www# TimezoneTZ=Asia/Shanghai# environment configARIANG_HOST_PORT=8011ARIANG_DATA_DIR=./downloads对docker不太熟悉的同学,可以使用dapps应用商店,一键安装。 Dapps项目:使用

November 5, 2019 · 1 min · jiezi

前端技术之命令模块及其执行方法

一、创建一个命令模块1、package.json { "name": "@uad/nat-cli", "version": "0.0.2", "description": "Demo", "main": "index.js", "bin": { "artisan": "./src/artisan.js" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git" }, "keywords": [ "CLI" ], "author": "chunrong.liu", "license": "ISC", "dependencies": { "shelljs": "^0.8.3", "yargs": "^13.2.4" }}2、src/artisan.js #!/usr/bin/env noderequire('shelljs/global');var argv = require('yargs').option('n', {alias : 'name',demand: true,default: 'tom',describe: 'your name',type: 'string'}).usage('Usage: hello [options]').example('hello -n tom', 'say hello to Tom').help('h').alias('h', 'help').epilog('Copyright 2019').command("morning", "good morning", function (yargs) {echo("Good Morning");var argv = yargs.reset().option("m", {alias: "message",description: "provide any sentence"}).help("h").alias("h", "help").argv;echo(argv.m);}).argv;console.log('hello ', argv.n);console.log(argv._);二、使用方法1、将命令模块通过npm link进行全局注册后,即可在命令行窗口直接使用该命令2、在其它模块中的package.json中引用命令模块,并增加scripts ...

November 4, 2019 · 1 min · jiezi

使用nvm安装管理多个nodejs版本

前言现在node对于前端来说已经是必备知识,我在两个项目中用到的node 版本有冲突 所以就在网上找了解决办法

November 4, 2019 · 1 min · jiezi

自动盖楼刷淘宝喵币能自动的坚决不动手是程序员的基本素养

这几天,双十一的活动有没有把你折腾的不轻呢?如果没有,只有两种可能:一,你没女朋友;二,你没有女性朋友。 要知道阿里每年都会发放大量红包、各种优惠券和各种互助游戏......而这两天,马爸爸的“ATM”们可都沉迷于这些个互助游戏里不可自拔,吾日三省吾身:签到了?做任务了么?升级了么?: 一般我们程序员对这种游戏都是没什么太大兴趣,毕竟每天都有找不完的Bug,加不完的班,但是假如在这时候你的女神向你求帮助的时候,就你那个等级你拿的出手么? 所以,程序员怎么能错过这么一个在女神面前展现专业能力的时候呢,这不,今天猿妹就在GitHub上发现,有程序员写了一个小脚本,可以每天自动做任务,领喵币,这下帮女神盖楼,再也不用担心等级低了: 可以看出来目前这个小脚本还没有引起程序员的注意,一个星星都还没获得(GitHub地址:https://github.com/sleepybear...) 其实,这个脚本并不难,基于auto.js框架来实现,auto.js是一个支持无障碍服务的Android平台上的JavaScript IDE,以控件为基础,能自动运行一些操作,实现悬浮窗录制和运行。所以这个脚本只支持安卓系统。 详细的脚本代码如下: let deviceWidth = device.width;let deviceHeight = device.height;function openBeginningBtnItem(delay) { let items = textStartsWith("gif;base64").depth(19).find(); console.log("寻找--领喵币"); if (items.length > 0) { let item = items[items.length - 1]; console.log("点击--领喵币"); clickItemInCenter(item); sleep(delay); return 1; } return -1;}function isOpenBeginning() { let signIn = textContains("签到").findOnce(); if (signIn != null) { console.log("成功--打开领取中心"); return 1; } return -1;}function ensureOpenBeginning(waitDelay) { if (isOpenBeginning() === -1) { openBeginningBtnItem(waitDelay); } if (isOpenBeginning() === 1) return 1; console.error("失败--打开领取中心"); toast("失败--打开领取中心"); return -1;}function clickItemInCenter(item, time) { if (time == null) time = 50; if (item == null) return; let x = item.bounds().centerX(); let y = item.bounds().centerY(); press(x, y, time);}function goShopping() { let shopping = text("去浏览").findOne(1000); if (shopping == null) { toastLog("结束--未知问题"); return -1; } console.log("开始浏览..."); clickItemInCenter(shopping); return 1;}function swipeUp() { let x = parseInt(deviceWidth / 2); let duration = 500; let y = [parseInt(deviceHeight * 0.75), parseInt(deviceHeight * 0.25)]; swipe(x, y[0], x, y[1], duration); swipe(x, y[0], x, y[1], duration);}function isFull() { for (let i = 0; i < 10; i++) { if (descContains("已达上限").findOnce() || textContains("已达上限").findOnce()) { console.log("今日已达上限"); return 1; } sleep(1000); } return 0}function waitSwipe() { let swipeAppear; let shoppingFull; for (let i = 0; i < 3; i++) { swipeAppear = desc("滑动浏览得").findOne(1000); if (swipeAppear != null) break; shoppingFull = descContains("已达上限").findOne(1000); if (shoppingFull != null) return 0; console.log("i" + i); } sleep(1000); if (swipeAppear != null) { console.log("开始滑动"); swipeUp(); console.log("等待15s"); sleep(1000 * 16); } else { console.log("slow"); console.log("等待20s"); sleep(1000 * 20); } let shoppingFinish = desc("任务完成").findOne(2000); if (shoppingFinish != null) { console.log("逛完,准备返回"); } else { toastLog("未知逛完,返回"); } return 1;}function browseFinish() { for (let i = 0; i < 10; i++) { let normalFinishDesc = descContains("已获得").findOnce(); let normalFinishText = textContains("已获得").findOnce(); let swipeFinish = desc("任务完成").findOnce(); if (normalFinishDesc != null || swipeFinish != null || normalFinishText != null) { console.log("浏览结束"); return 0; } sleep(250); } console.log("浏览未知"); return -1;}function judgeWay() { let timeOut = 1000 * 7; let delay = 250; let loops = parseInt(timeOut / delay); for (let i = 0; i < loops; i++) { let swipeAppearDesc = descContains("滑动浏览得").findOnce(); let swipeAppearText = textContains("滑动浏览得").findOnce(); if (swipeAppearDesc != null || swipeAppearText != null) { console.log("已获取到滑动浏览模式"); return 0; } let directBrowseDesc = desc("浏览").findOnce(); let directBrowseText = text("浏览").findOnce(); if (directBrowseDesc != null || directBrowseText != null) { if (descContains("00喵币").findOnce() != null || textContains("00喵币").findOnce() != null) { console.log("已获取到正常浏览模式"); return 1; } } sleep(delay); } console.log("超时"); return -1;}function reopenAgain() { console.log("reopen"); let tbs = id("taskBottomSheet").findOnce(); if (tbs == null) return -1; let close = tbs.child(1); if (close != null) { console.log("关闭"); clickItemInCenter(close); sleep(1000); return ensureOpenBeginning(1000); } return -1;}function runGoShopping() { let isSuccess; for (let i = 0; i < 20; i++) { isSuccess = ensureOpenBeginning(1000); if (isSuccess !== 1) break; isSuccess = goShopping(); let count = 0; while (isSuccess !== 1) { if (reopenAgain() === 1) { isSuccess = 1; break; } if (count++ >= 2) break; } if (isSuccess === -1) break; let st = waitSwipe(); if (st === 0) { toastLog("已达上限,结束脚本"); return 0; } back(); sleep(1000); } if (isSuccess === 0) { toastLog("正常结束"); return 0; } else if (isSuccess === -1) { toastLog("异常结束"); return 1; }}function clickGoBrowse() { let browse = text("去浏览").findOne(1000); if (browse != null) { let guessYouLike = textContains("猜你喜欢").findOnce(); if (guessYouLike != null) { console.log("出现猜你喜欢"); let pp = browse.parent.bounds().top; let ppp = guessYouLike.parent.parent.bounds().top; if (ppp === pp) { console.log("跳过--猜你喜欢"); let allBrowse = text("去浏览").find(); for (let i = 0; i < allBrowse.length; i++) { let item = allBrowse[i]; if (item.bounds().top !== browse.bounds().top) { browse = item; } } } } console.log("点击--去浏览"); clickItemInCenter(browse); return 1; } return -1;}function runGoBrowse() { let isSuccess = 1; for (let i = 0; i < 40; i++) { isSuccess = ensureOpenBeginning(1000); if (isSuccess !== 1) break; for (let j = 0; j < 3; j++) { isSuccess = clickGoBrowse(); if (isSuccess !== 1) { reopenAgain(); } else break; } if (isSuccess === -1) break; let jw = judgeWay(); sleep(1000); if (jw === 0) swipeUp(); else if (jw === -1) { if (isFull() === 1) { console.log("已达上限"); back(); sleep(2000); reopenAgain(); continue; } console.log("4s"); sleep(1000 * 4); } console.log("15s"); sleep(1000 * 15); let isF = browseFinish(); if (isF === 0) { console.log("浏览结束,返回"); } else if (isF === -1) { console.log("浏览未正常结束,返回"); } back(); sleep(2000); }}function removeFile(fileName) { if (files.exists(fileName)) { files.remove(fileName); }}function clearNewScript() { threads.start(function () { removeFile("/sdcard/脚本/淘宝喵币/script.js"); removeFile("/sdcard/脚本/淘宝喵币/version.txt"); toastLog("清除完成"); });}function warning(n) { let items = ["不更新,但还是试试新脚本(不保证能用)", "清除本地下载的新脚本,使用默认脚本", "点击这里下载新APP"]; let ch = dialogs.select("当前新版本不适用于此旧APP,请更新到新APP。", items, function (index) { if (index >= 0) { if (index === 0) { threads.start(function () { sleep(1000); runRun(n); }); } else if (index === 1) { clearNewScript(); } else if (index === 2) { alert("哪里下载的旧APP就去哪里下载新APP,我可没心思发布"); } } });}function runRun(n) { sleep(500); let statue = runGoBrowse(); toastLog("去浏览--浏览结束"); alert("结束");}function moveFloating(n) { let i = -1; dialogs.confirm("由于需要,请将悬浮窗移动至靠左。", "点击确认表示已完成,直接运行脚本。\n点击取消则手动前去调整。\n" + "(中间浏览过程中可能会跳转到淘宝首页进行浏览,此时需要手动再次切回猫铺。)", function (clear) { if (clear) { console.log("直接运行"); i = 1; } else { toastLog("请将悬浮窗移动至靠左"); i = 0; } }); while (i === -1) { slepp(100); } if (i === 1) { runRun(n); }}function runChoose(n) { let currentVersion = app.versionCode; if (currentVersion === 1) { warning(n); } else { moveFloating(n); }}module.exports = runChoose;如果你不懂的话,就直接下载使用,记得开启悬浮窗,然后再切换到淘宝: ...

November 3, 2019 · 4 min · jiezi

定制你私有的前端构建部署Github-CICD

近来手痒,又陷入了自我捣腾的无限循环。 其实事情是这样的,最近阿里云搞活动(嗯,友情打广告),229买了个3年版低配服务器;前端时间写用React + Github Graphql API自定义你的博客, 见识了Github Action的强大,所以就尝试打造自己的前端构建部署工作流程;也许你看到过很多大厂的前端自动构建部署,但鲜有尝试,今天就可以自己动手啦,撸起来吧。 从workflow看流程定制后的Github Action workflow大概长这样: name: Deploy static source to my serveron: push: branches: -masterjobs: build: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v1 - name: build run: npm install && npm run pub - name: deploy uses: closertb/deploy-static-action@master with: name: 'doddle' token: ${{ secrets.Deploy_Token }} requestUrl: 'http://114.55.38.249:8080/operate/deploy' dist: 'dist' target: 'adminManage'大概流程是这样: 监听master分支的push操作;checkout:新建构建分支build:依赖安装,打包构建deploy:将上一步的构建产物,打包部署到你的服务器静态资源文件中over部署的实现思路构建很简单,就是打包,这种工具很多,什么script-build, roadhog,或自定义webpack。这里主要说部署;其实部署也很简单,看图: 嗯,部署也讲完了。详细实现过程看参见我自定义的action:deploy-static-action 关于上面的几个构建参数: name:一个名字,自己随便啦,根据自己需要token:这个比较重要,服务器的通关口令。这里最好的方式是通过项目的secrets来设置dist:构建打包后的文件夹名,会根据这个文件夹名来获取其中的构建产物, 默认是disttarget:静态资源的目标文件夹名, 默认是distrequestUrl:一个部署API关于上传服务器deploy-static-action其实只做了部署中的构建产物收集,真正的部署其实是依赖requestUrl来实现的,所以要实现 http://114.55.38.249:8080/operate/deploy 这个服务也很重要,你可以重用我的deploy-static-action,但部署API不能,因为这个API是给我的服务器私有定制的。不过我可以提供示例代码参考: 参考代码, deploy.js ...

November 3, 2019 · 1 min · jiezi

mongodb和mysql查询当前记录的上一条和下一条

思路:根据当前记录的id查询前后记录。 mongodb实现方法:mongo可以通过时间或者通过id来判断上一条记录或者下一条记录: 通过记录的_id上一条记录 db.数据库名称.find({ '_id': { '$lt': ids } }).sort({_id: -1}).limit(1)下一条记录 db.数据库名称.find({ '_id': { '$gt': ids } }).sort({_id: 1}).limit(1)通过时间字段来查询:上一条记录 db.数据库名称.find({ 'created': { '$lt': created } }).sort({_id: -1}).limit(1)下一条记录 db.数据库名称.find({ 'created': { '$gt': created } }).sort({_id: 1}).limit(1)mysql实现方法:mysql查询,网上有很多方法,通常我们用如下方法: 查询上一条记录的SQL语句(如果有其他的查询条件记得加上other_conditions以免出现不必要的错误): select * from table_a where id = (select id from table_a where id < {$id} [and other_conditions] order by id desc limit 1 ) [and other_conditions];查询下一条记录的SQL语句(如果有其他的查询条件记得加上other_conditions以免出现不必要的错误): select * from table_a where id = (select id from table_a where id > {$id} [and other_conditions] order by id asc limit 1 ) [and other_conditions];

November 2, 2019 · 1 min · jiezi

JavaScript中发布订阅模式观察者模式

发布/订阅模式的前身-观察者模式观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。 观察者模式有一个别名叫“发布-订阅模式”,或者说是“订阅-发布模式”,订阅者和订阅目标是联系在一起的,当订阅目标发生改变时,逐个通知订阅者。 什么是发布/订阅模式其实24种基本的设计模式中并没有发布订阅模式,上面也说了,他只是观察者模式的一个别称。但是经过时间的沉淀,似乎他已经强大了起来,已经独立于观察者模式,成为另外一种不同的设计模式。在现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。 根本作用广泛应用于异步编程中(替代了传递回调函数)对象之间松散耦合的编写代码基本案例介绍背景:成都老妈兔头真香,买的人太多需要预定才能买到,所以顾客就等于了订阅者,订阅老妈兔头。而老妈兔头有货了得通知顾客来买啊,不然没有钱赚,得通知所有的订阅者有货了来提兔头,这时老妈兔头这家店就是发布者。 /*兔头店*/ var shop={ listenList:[],//缓存列表 addlisten:function(fn){//增加订阅者 this.listenList.push(fn); }, trigger:function(){//发布消息 for(var i=0,fn;fn=this.listenList[i++];){ fn.apply(this,arguments); } }}/*小明订阅了商店*/shop.addlisten(function(taste){ console.log("通知小明,"+taste+"味道的好了");});/*小龙订阅了商店*/shop.addlisten(function(taste){ console.log("通知小龙,"+taste+"味道的好了");});/*小红订阅了商店*/shop.addlisten(function(taste){ console.log("通知小红,"+taste+"味道的好了");}); // 发布订阅shop.trigger("中辣");//console通知小明,中辣味道的好了通知小龙,中辣味道的好了通知小红,中辣味道的好了案例升级上面的案例存在问题,因为在触发的时候是将所以的订阅都触发了,并没有区分和判断,所以需要一个Key来区分订阅的类型,并且根据不同的情况触发。而且订阅是可以取消的。 升级思路: 创建一个对象(缓存列表)addlisten方法用来把订阅回调函数fn都加到缓存列表listenList中trigger方法取到arguments里第一个当做key,根据key值去执行对应缓存列表中的函数remove方法可以根据key值取消订阅/*兔头店*/ var shop={ listenList:{},//缓存对象 addlisten:function(key,fn){ // 没有没有key给个初值避免调用报错 if (!this.listenList[key]) { this.listenList[key] = []; } // 增加订阅者,一个key就是一种订阅类型 this.listenList[key].push(fn); }, trigger:function(){ const key = Array.from(arguments).shift() const fns = this.listenList[key] // 这里排除两种特殊情况,第一种为触发的一种从未订阅的类型,第二种订阅后取消了所有订阅的 if(!fns || fns.length===0){ return false; } // 发布消息,触发同类型的所有订阅, fns.forEach((fn)=>{ fn.apply(this,arguments); })/* for(var i=0,fn;fn=fns[i++];){ fn.apply(this,arguments); } */ }, remove:function(key,fn){ var fns=this.listenList[key];//取出该类型的对应的消息集合 if(!fns){//如果对应的key没有订阅直接返回 return false; } if(!fn){//如果没有传入具体的回掉,则表示需要取消所有订阅 fns && (fns.length=0); }else{ for(var l=fns.length-1;l>=0;l--){//遍历回掉函数列表 if(fn===fns[l]){ // 这里是传入地址的比较,所以不能直接用匿名函数了 fns.splice(l,1);//删除订阅者的回掉 } } } }}function xiaoming(taste){ console.log("通知小明,"+taste+"味道的好了");}function xiaolong(taste){ console.log("通知小龙,"+taste+"味道的好了");}function xiaohong(taste){ console.log("通知小红,"+taste+"味道的好了");}// 小明订阅了商店shop.addlisten('中辣',xiaoming);shop.addlisten('特辣',xiaoming);// 小龙订阅了商店shop.addlisten('微辣',xiaolong);// 小红订阅了商店shop.addlisten('中辣',xiaohong);// 小红突然不想吃了shop.remove("中辣",xiaohong);// 中辣口味做好后,发布订阅shop.trigger("中辣");shop.trigger("微辣");shop.trigger("特辣"); ...

November 2, 2019 · 1 min · jiezi

ZooTeam-前端周刊|第-54-期

政采云前端小报第54期浏览更多往期小报,请访问: https://weekly.zoo.team ES6、ES7、ES8、ES9、ES10新特性一览 掌握不断更新的ES新特性从程序媛角度去看项目管理 | Aotu.io「凹凸实验室」 项目管理一般是从技术负责人、项目产品负责人的角度去看的,程序员虽然码代码很重要,但对项目的领悟能力也同样重要。我们经常会遇到各种困惑:手上的项目需求越来越多,BUG列表只增不减,该采取怎样的措施,保证自己的生产力?希望以下的讲述带给你莫名的认同感,或多或少让你磨刀霍霍一试。 需求管理下图描述的是程序员从接到需求到开发环节的过程: 一般我们首先会收到产品的PRD或交互稿,被询问今天什么时间点是...我不想成为不懂 GUI 的 UI 开发者 - 掘金 作为一个程序员,我是从切图开始职业生涯的。行业内一般把我这种编写用户界面 (UI) 的岗位,叫做前端开发。工作几年后我发现了个奇怪的现象,那就是整个前端圈子里,虽然大家常常谈 UI,但很少有人谈 GUI。 这话要从何说起呢?前端圈子里从上游到下游,强调的都是...技术栈:为什么 Node 是前端团队的核心技术栈 小菜前端的基建在一步步走过来的过程中,NodeJS 是如何使用的及扮演了哪些角色,它对于工程师个人,团队能力,公司研发效率,业务支撑,技术的探索与突破等等到底有什么实际的意义...从“愚昧之巅”到“绝望之谷”,讲讲我价值几千万的认知 不要缝合自己的脑洞,打开,打开,重新打开自己的逻辑闭环。当前端遇到机器学习?听 TensorFlow.js 负责人怎么说 第十四届 D2 前端技术论坛将于12月14日拉开帷幕,其中, Google TensorFlow.js 团队的负责人 Ping Yu 将作为「智能化」专场的嘉宾,为我们讲解 TensorFlow.js 生态系统,以及如何将现有的机器学习模型植入到前端。网站性能优化实战——从12.67s到1.06s的故事 - 腾讯Web前端 IMWeb 团队社区 | blog | 团队博客 Web前端 腾讯IMWeb 团队社区再谈javascriptjs原型与原型链及继承相关问题 - 腾讯Web前端 IMWeb 团队社区 | blog | 团队博客 Web前端 腾讯IMWeb 团队社区React Concurrent 模式抢先预览上篇: Suspense the world - 掘金 ...

November 2, 2019 · 1 min · jiezi

http模块和fs模块

文章链接:http模块和fs模块http模块response对象常用方法: response.writeHead(200,{'Content-Type':'text/plain:charset=UTF-8'});此方法只能在消息上调用一次,并且必须在调用response.end()之前调用。 response.write()发送一块相应主体,用来给客户端发送相应数据。write可以使用多次,但是最后一定要使用end来结束响应,否则客户端会一直等待。response.end()此方法向服务器发出信号,表示已发送所有响应头和主体,该服务器应该视为此消息完成。必须在每个响应上调用方法response.end()。const http = require('http');http.createServer(function(req, res) { res.writeHead(200, {'Content-Type': 'text/html'}); res.write('hello world'); res.write('<h1>hello node.js</h1>'); res.end();}).listen(8080);console.log('Server running at http://127.0.0.1:8080');request对象 request.url获取请求路径,获取到的是端口号之后的那一部分路径,也就是说所有的url都是以/开头的,判断路径处理响应。request.socket.localAddress获取ip地址。request.socket.remotePort获取源端口。const http = require('http');let server = http.createServer();server.on('request', function(req, res) { console.log('收到请求,请求路径是:' + req.url); console.log('请求我的客户端的地址是:', req.socket.remoteAddress, req.socket.remotePort); let url = req.url; res.writeHead(200, {'Content-Type': 'text/html;charset=UTF-8'}); switch (url) { case '/': res.end('<h1>Index page</h1>'); break; case '/login': res.end('<h1>Login page</h1>'); break; default: res.end('404 Not Found.'); break; }});server.listen(8080, function() { console.log('服务器启动成功,可以访问了。。。');});fs模块所有文件系统操作都具有同步和异步的形式。异步方法中回调的第一个参数总是留给异常参数,如果方法成功完成,那么这个参数为null或undefined。 因为Node.js是单线程的,所以在Node.js中绝大部分需要在服务器运行期反复执行业务逻辑的代码,必须使用异步代码,否则,同步代码在执行期,服务器将停止响应。 服务器启动时如果需要读取配置文件,或结束时需要写入到状态文件时,可以使用同步代码,因为这些代码只在启动和结束时执行一次,不影响服务器正常运行时的异步执行。 ...

November 2, 2019 · 3 min · jiezi

阿里云部署-3redis

redis下载和编译wget http://download.redis.io/releases/redis-4.0.2.tar.gztar xzf redis-4.0.2.tar.gzcd redis-4.0.2make启动服务后台启动redis cd redis-4.0.2/src/redis-server &查询redis进程 ps -ef | grep redis可以看到redis已经启动了 root 19141 19065 0 12:50 pts/1 00:00:03 ./src/redis-server 0.0.0.0:6379root 19238 19196 0 14:00 pts/0 00:00:00 grep --color=auto redis结束进程 kill -9 pid初步测试启动redis客户端 cd redis-4.0.2/src/redis-cli127.0.0.1:6379> set test 1OK127.0.0.1:6379> get test"1"redis安装成功了。 配置服务器远程连接默认配置只能是本地访问,我们修改redis-4.0.2/redis.conf配置文件将 bind 127.0.0.1修改为 bind 0.0.0.0 防火墙你需要添加安全组规则,打开服务器防火墙上的6379端口。 设置远程连接密码默认配置开启了保护模式 protected-mode yes这时你需要设置密码才可以远程连接上redis,密码设置非常简单,只需要在requirepass字段上填写你的密码即可 requirepass 你的密码配置完毕,后台启动你的redis可以了。 ./redis-server /etc/redis/redis.confnode客户端连接我用的是npm上的redis包,此时根据前面你的配置就可以远程连接服务器上的redis了。结合开发文档,就可以进行实际开发了。 const redis = require('redis');const config = require('../config');const logger = require('log4js').getLogger('app');class RedisClient { constructor() { if (!RedisClient.instance) { this.client = redis.createClient({ host: config.redis.host, port: config.redis.port, password: config.redis.password, }); const client = this.client; RedisClient.instance = client; client.on("error", (err) => { logger.error('redis connect err: %s', err.toString()); }); client.on("connect", () => { logger.info('redis connect success'); }); } }}module.exports = new RedisClient().client; ...

October 23, 2019 · 1 min · jiezi

webpackdevserver-代理服务返回的-json-内容被截断或部分乱码

原因Original problem spdy-http2/node-spdy#357. Unfortunately we can fix it right now, it was be fixed after express will be support http2. Now we implement http2 option in master to disable http2, you can use this as workaround由于 webpack-dev-server 默认启用了http2, 使用的是 spdy-http2/node-spdy 库的实现, 这个 bug 是该库造成的, 并在3.4.7版本被修复.该问题发生在Node v8中, v10不存在此问题. 解决方法1webpack.config.js devServer: { ..., http2: false }方法2Node升级到v10 参考https://github.com/webpack/webpack-dev-server/issues/1574https://github.com/spdy-http2/node-spdy/issues/357

October 17, 2019 · 1 min · jiezi

TypeScript-基础精粹

原文地址地址:TypeScript 基础精粹 基础笔记的github地址:https://github.com/qiqihaobenben/Front-End-Basics ,可以watch,也可以star。 类型注意事项数组类型有两种类型注解方式,特别注意第二种使用 TS 内置的 Array 泛型接口。 let arr1: number[] = [1,2,3]// 下面就是使用 TS 内置的 Array 泛型接口来实现的let arr2: Array<number | string> = [1,2,3,"abc"]元组类型元组是一种特殊的数组,限定了数组元素的个数和类型 let tuple: [number, string] = [0, "1"];需要注意元祖的越界问题,虽然可以越界添加元素,但是仍然是不能越界访问,强烈不建议这么使用 tuple.push(2) // 不报错console.log(tuple) // [0, "1", 2] 也能都打印出来console.log(tuple[2]) // 但是想取出元组中的越界元素,就会报错元组长度是2,在index为2时没有元素函数类型函数类型可以先定义再使用,具体实现时就可以不用注明参数和返回值类型了,而且参数名称也不用必须跟定义时相同。 let compute: (x: number, y: number) => number;compute = (a, b) => a + b;对象类型对象如果要赋值或者修改属性值,那么就不能用简单的对象类型,需要定义完整的对象类型 let obj: object = { x: 1, y: 2 };obj.x = 3; // 会报错,只是简单的定义了是object类型,但是里面到底有什么属性没有标明// 需要改成如下的对象类型定义let obj: { x: number; y: number } = { x: 1, y: 2 };obj.x = 3;symbol 类型symbol 类型可以直接声明为 symbol 类型,也可以直接赋值,跟 ES6 一样,两个分别声明的 symbol 是不相等的。 ...

October 17, 2019 · 13 min · jiezi

解密HTTP2与HTTP3-的新特性

前言HTTP/2 相比于 HTTP/1.1,可以说是大幅度提高了网页的性能,只需要升级到该协议就可以减少很多之前需要做的性能优化工作,当然兼容问题以及如何优雅降级应该是国内还不普遍使用的原因之一。 虽然 HTTP/2 提高了网页的性能,但是并不代表它已经是完美的了,HTTP/3 就是为了解决 HTTP/2 所存在的一些问题而被推出来的。 一、HTTP/1.1发明以来发生了哪些变化?如果仔细观察打开那些最流行的网站首页所需要下载的资源的话,会发现一个非常明显的趋势。 近年来加载网站首页需要的下载的数据量在逐渐增加,并已经超过了2100K。但在这里我们更应该关心的是:平均每个页面为了完成显示与渲染所需要下载的资源数已经超过了100个。 正如下图所示,从2011年以来,传输数据大小与平均请求资源数量不断持续增长,并没有减缓的迹象。该图表中绿色直线展示了传输数据大小的增长,红色直线展示了平均请求资源数量的增长。 HTTP/1.1自从1997年发布以来,我们已经使用HTTP/1.x 相当长一段时间了,但是随着近十年互联网的爆炸式发展,从当初网页内容以文本为主,到现在以富媒体(如图片、声音、视频)为主,而且对页面内容实时性高要求的应用越来越多(比如聊天、视频直播),于是当时协议规定的某些特性,已经无法满足现代网络的需求了。 二、HTTP/1.1的缺陷1.高延迟--带来页面加载速度的降低虽然近几年来网络带宽增长非常快,然而我们却并没有看到网络延迟有对应程度的降低。网络延迟问题主要由于队头阻塞(Head-Of-Line Blocking),导致带宽无法被充分利用。 队头阻塞是指当顺序发送的请求序列中的一个请求因为某种原因被阻塞时,在后面排队的所有请求也一并被阻塞,会导致客户端迟迟收不到数据。针对队头阻塞,人们尝试过以下办法来解决: 将同一页面的资源分散到不同域名下,提升连接上限。 Chrome有个机制,对于同一个域名,默认允许同时建立 6 个 TCP持久连接,使用持久连接时,虽然能公用一个TCP管道,但是在一个管道中同一时刻只能处理一个请求,在当前的请求没有结束之前,其他的请求只能处于阻塞状态。另外如果在同一个域名下同时有10个请求发生,那么其中4个请求会进入排队等待状态,直至进行中的请求完成。Spriting合并多张小图为一张大图,再用JavaScript或者CSS将小图重新“切割”出来的技术。内联(Inlining)是另外一种防止发送很多小图请求的技巧,将图片的原始数据嵌入在CSS文件里面的URL里,减少网络请求次数。.icon1 { background: url(data:image/png;base64,<data>) no-repeat; }.icon2 { background: url(data:image/png;base64,<data>) no-repeat; }拼接(Concatenation)将多个体积较小的JavaScript使用webpack等工具打包成1个体积更大的JavaScript文件,但如果其中1个文件的改动就会导致大量数据被重新下载多个文件。2.无状态特性--带来的巨大HTTP头部由于报文Header一般会携带"User Agent""Cookie""Accept""Server"等许多固定的头字段(如下图),多达几百字节甚至上千字节,但Body却经常只有几十字节(比如GET请求、204/301/304响应),成了不折不扣的“大头儿子”。Header里携带的内容过大,在一定程度上增加了传输的成本。更要命的是,成千上万的请求响应报文里有很多字段值都是重复的,非常浪费。 3.明文传输--带来的不安全性HTTP/1.1在传输数据时,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份,这在一定程度上无法保证数据的安全性。 你有没有听说过"免费WiFi陷阱”之类的新闻呢?黑客就是利用了HTTP明文传输的缺点,在公共场所架设一个WiFi热点开始“钓鱼”,诱骗网民上网。一旦你连上了这个WiFi热点,所有的流量都会被截获保存,里面如果有银行卡号、网站密码等敏感信息的话那就危险了,黑客拿到了这些数据就可以冒充你为所欲为。 4.不支持服务器推送消息三、SPDY 协议与 HTTP/2 简介1.SPDY 协议上面我们提到,由于HTTP/1.x的缺陷,我们会引入雪碧图、将小图内联、使用多个域名等等的方式来提高性能。不过这些优化都绕开了协议,直到2009年,谷歌公开了自行研发的 SPDY 协议,主要解决HTTP/1.1效率不高的问题。谷歌推出SPDY,才算是正式改造HTTP协议本身。降低延迟,压缩header等等,SPDY的实践证明了这些优化的效果,也最终带来HTTP/2的诞生。 HTTP/1.1有两个主要的缺点:安全不足和性能不高,由于背负着 HTTP/1.x 庞大的历史包袱,所以协议的修改,兼容性是首要考虑的目标,否则就会破坏互联网上无数现有的资产。如上图所示,SPDY位于HTTP之下,TCP和SSL之上,这样可以轻松兼容老版本的HTTP协议(将HTTP1.x的内容封装成一种新的frame格式),同时可以使用已有的SSL功能。 SPDY 协议在Chrome浏览器上证明可行以后,就被当作 HTTP/2 的基础,主要特性都在 HTTP/2 之中得到继承。 2.HTTP/2 简介2015年,HTTP/2 发布。HTTP/2是现行HTTP协议(HTTP/1.x)的替代,但它不是重写,HTTP方法/状态码/语义都与HTTP/1.x一样。HTTP/2基于SPDY,专注于性能,最大的一个目标是在用户和网站间只用一个连接(connection)。从目前的情况来看,国内外一些排名靠前的站点基本都实现了HTTP/2的部署,使用HTTP/2能带来20%~60%的效率提升。 HTTP/2由两个规范(Specification)组成: Hypertext Transfer Protocol version 2 - RFC7540HPACK - Header Compression for HTTP/2 - RFC7541四、HTTP/2 新特性1.二进制传输HTTP/2传输数据量的大幅减少,主要有两个原因:以二进制方式传输和Header 压缩。我们先来介绍二进制传输,HTTP/2 采用二进制格式传输数据,而非HTTP/1.x 里纯文本形式的报文 ,二进制协议解析起来更高效。 HTTP/2 将请求和响应数据分割为更小的帧,并且它们采用二进制编码。 ...

October 17, 2019 · 1 min · jiezi

javascript-设计模式-发布订阅模式

1、实现发布--订阅模式(1)首先要指定好谁充当发布者(2)然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者(记录客户的花名册)(3)最后发布消息的时候,发布者遍历这个缓存列表,依次触发里面订阅者回调函数(遍历花名册,挨个发短信)

October 17, 2019 · 1 min · jiezi

源码解读一文彻底搞懂Events模块

前言为什么写这篇文章? 清楚的记得刚找node工作和面试官聊到了事件循环,然后面试官问事件是如何产生的?什么情况下产生事件。。。Events 在哪些场景应用到了?之前封装了一个 RxJava 的开源网络请求框架,也是基于发布-订阅模式,语言都是相通的,挺有趣。表情符号Events 模块是我公众号 Node.js 进阶路线的一部分面试会问说一下 Node.js 哪里应用到了发布/订阅模式Events 模块在实际项目开发中有使用过吗?具体应用场景是? Events 监听函数的执行顺序是异步还是同步的? 说几个 Events 模块的常用函数吧? 模拟实现 Node.js 的核心模块 Events 文章首发Github 博客开源项目 https://github.com/koala-codi... 发布/订阅者模式发布/订阅者模式应该是我在开发过程中遇到的最多的设计模式。发布/订阅者模式,也可以称之为消息机制,定义了一种依赖关系,这种依赖关系可以理解为 1对N (注意:不一定是1对多,有时候也会1对1哦),观察者们同时监听某一个对象相应的状态变换,一旦变化则通知到所有观察者,从而触发观察者相应的事件,该设计模式解决了主体对象与观察者之间功能的耦合。 生活中的发布/订阅者模式警察抓小偷在现实生活中,警察抓小偷是一个典型的观察者模式「这以一个惯犯在街道逛街然后被抓为例子」,这里小偷就是被观察者,各个干警就是观察者,干警时时观察着小偷,当小偷正在偷东西「就给干警发送出一条信号,实际上小偷不可能告诉干警我有偷东西」,干警收到信号,出击抓小偷。这就是一个观察者模式 订阅了某个报社的报纸生活中就像是去报社订报纸,你喜欢读什么报就去报社去交钱订阅,当发布了新报纸的时候,报社会向所有订阅了报纸的每一个人发送一份,订阅者就可以接收到。 你订阅了我的公众号我这个微信公号作者是发布者,您这些微信用户是订阅者「我发送一篇文章的时候,关注了【程序员成长指北】的订阅者们都可以收到文章。 实例的代码实现与分析以大家订阅公众号为例子,看看发布/订阅模式如何实现的。(以订阅报纸作为例子的原因,可以增加一个type参数,用于区分订阅不同类型的公众号,如有的人订阅的是前端公众号,有的人订阅的是 Node.js 公众号,使用此属性来标记。这样和接下来要讲的 EventEmitter 源码更相符,另一个原因是这样你只要打开一个订阅号文章是不是就想到了发布-订阅者模式呢。) 代码如下: let officeAccounts ={ // 初始化定义一个存储类型对象 subscribes:{ 'any':[] }, // 添加订阅号 subscribe:function(type='any',fn){ if(!this.subscribes[type]){ this.subscribes[type] = []; } this.subscribes[type].push(fn);//将订阅方法存在数组中 }, // 退订 unSubscribe:function(type='any',fn){ this.subscribes[type] = this.subscribes[type].filter((item)=>{ return item!=fn;// 将退订的方法从数组中移除 }); }, // 发布订阅 publish:function(type='any',...args){ this.subscribes[type].forEach(item => { item(...args);// 根据不同的类型调用相应的方法 }); }}以上就是一个最简单的观察者模式的实现,可以看到代码非常的简单,核心原理就是将订阅的方法按分类存在一个数组中,当发布时取出执行即可 ...

October 17, 2019 · 3 min · jiezi

前端开发对MySql使用总结

数据库的五个概念数据库服务器数据库数据表数据字段数据行那么这里下面既是对上面几个概念进行基本的日常操作。 数据库引擎使用这里仅仅只介绍常用的两种引擎,而InnoDB是从MySQL 5.6.版本以后InnoDB就是作为默认启动使用的存储引擎。 (1) InnoDB a,支持ACID,简单地说就是支持事务完整性、一致性; b,支持行锁,以及类似ORACLE的一致性读,多用户并发; c,独有的聚集索引主键设计方式,可大幅提升并发读写性能; d,支持外键; e,支持崩溃数据自修复; InnoDB设计目标是处理大容量数据库系统,它的CPU利用率是其它基于磁盘的关系数据库引擎所不能比的。 它是一个可靠地事务处理引擎,不支持全文本搜索(2) MyISAM a,不支持 每次查询具有原子性 b,只支持表所 c,强调的是性能,其执行数 度比InnoDB类型更快,但是不提供事务支持 d,如果执行大量的SELECT,MyISAM是更好的选择 e,缺点:就是不能在表损坏后恢复数据。(是不能主动恢复)既然知道了这俩种引擎的优缺点,那么写一下几个常用的API操作。 show engines; --显示所有可用的引擎show table status from myDB; --查看myDB数据库下的所有表使用的引擎show create table 表名; --指定查看表名的所有段名以及引擎create table 表名(id int primary key, name varchar(50)) engine=MyISAM; --建表的时候指定引擎 alter table 表名 Engine= MyISAM; --建完表后修改引擎为MyISAM当然,也可以通过修改配置文件my.ini在[mysqld]最后添加为上default-storage-engine=InnoDB,重启服务,数据库默认的引擎修改为InnoDB。 数据库操作>net start mysql //启动数据库和停止net stop mysql>mysql -u root -p //默认登陆本机(-h是主机地址)>SELECT USER(); //显示当前用户>create database 数据库名; //创建数据库>SELECT DATABASE(); //显示当前使用数据库>SHOW DATABASES //显示所有数据库列表>USE DATABASE 库名; //使用该数据库>DROP DATABASE 库名 //删除数据库>CMD终端:mysqladmin -u用户名 -p旧密码 password 新密码 //修改密码>mysql语句:set password for 用户名@localhost = password('新密码'); 数据表操作>SHOW TABLES; //列出库中所有的表>DESCRIBE table1; //查看表结构>show columns from 数据表; //显示表的所有段名以及类型>CREATE TABLE 表名 (字段名 VARCHAR(20), 字段名 CHAR(1)); //增加数据表和字段名>DROP TABLE 表名; //删除表>alter table stu rename as students; //将旧表明stu改为新表明students。表字段操作添加字段语法:ALTER TABLE 表名 ADD COLUMN 字段名 字段类型 DEFAULT NULL;示例:ALTER TABLE dictionary ADD COLUMN calss VARCHAR(10) DEFAULT NULL;-- dictionary是表名修改字段名称语法:ALTER TABLE 表名 CHANGE 旧字段名 新字段名 新字段类型 DEFAULT NULL;示例:ALTER TABLE dictionary CHANGE calss class VARCHAR(10) DEFAULT NULL;删除字段语法:ALTER TABLE 表名 DROP COLUMN 字段名示例:ALTER TABLE dictionary DROP COLUMN calss;批量增加字段bagin; //事务开始alter table em_day_data add f_day_house7 int(11);alter table em_day_data add f_day_house8 int(11);alter table em_day_data add f_day_house9 int(11);alter table em_day_data add f_day_house10 int(11);commit; //提交事务,事务结束数据增删改查MySQL语句忽略大小写的。每张数据表只能存在一个主键。 ...

October 16, 2019 · 2 min · jiezi

Hexo-博客快速整合gitalk组件给静态博客添加动态评论功能

什么是 hexo-plugin-gitalk ???? Hexo 整合 gitalk 组件实现博客评论功能???? 主页效果 用法Step #1 - 更新 _config.yml 配置文件在 _config.yml 配置文件中,配置 gitalk 插件相关信息,详情见 gitalk. plugins: gitalk: clientID: GitHub Application Client ID clientSecret: GitHub Application Client Secret repo: GitHub repo owner: GitHub repo owner admin: - GitHub repo owner and collaborators, only these guys can initialize github issues distractionFreeMode: false注意: 前往 gitalk 申请开通 gitalk 功能后,一定要替换成自己的相关配置!其中,主要配置参数含义如下: clientID String必须. GitHub Application Client ID. clientSecret String必须. GitHub Application Client Secret. ...

October 16, 2019 · 1 min · jiezi

阿里云部署-1起步

起步先在阿里云官网[https://www.aliyun.com] 选择适合自己的云服务器ECS,按照购买的流程就可以使用阿里云服务器了。 点击控制台,再选择左侧栏的云服务器ECS,你就可以进到云服务器控制台。 再点击左侧栏的实例,首先初始化一下密码。 密码初始化后你就可以远程连接服务器了。 测试例子我们稍微修改一下node.js官方文档的例子,开启一个服务器。示例代码:https://github.com/mfaying/le... const http = require('http');const hostname = '0.0.0.0';const port = 3001;const server = http.createServer((req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end('Hello, World!\n');});server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`);});登录我们的服务器,将代码通过git clone https://github.com/mfaying/le... 的方式克隆下来启动服务器 [root@iZ8vbfhrv1vsbp44n9fdtoZ start]# node server.jsServer running at http://0.0.0.0:3001/你还需要添加安全组规则,打开服务器防火墙上的3001端口。具体操作如下在实例列表页点击"更多",找到安全组配置点击配置规则点击快速创建规则具体配置如下配置完毕后,就可以调用接口了。

October 16, 2019 · 1 min · jiezi

node如何实现大文件上传

在实际开发过程中我们可能会遇到大文件上传的场景,在node中是如何实现的呢?如果还是采用将文件一次性读写到服务端,将非常耗时、耗内存,而且网络发生中断后又要重新上传,性能很低。那如何实现一个高性能的上传功能呢?本文将为您一一揭晓。1、大文件上传的基本流程前端对文件进行MD5加密,生成MD5值,这个值是文件的唯一标识,可以用来校验文件的完整性。发送请求校验文件的MD5值,检测文件是否上传,如果文件已经上传,则不用上传;如果文件上传了一部分,则把剩余文件块上传;如果没有上传过,则全部上传。确定文件块大小,切分文件,并发调用上传接口,将文件块上传到服务端。前端上传完文件分片后请求通知后端合并文件块,整合成初始文件。2、生成文件MD5值1、什么是MD5? MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。2、怎样生成文件MD5值?我是用spark-md5.js库生成文件MD5值。SparkMD5是MD5算法的快速实现,非常适合浏览器使用,因为nodejs版本可能更快。 spark-md5.js库github链接:https://github.com/satazor/js-spark-md5 官网告诉我们增量MD5在散列大型数据(文件)时表现更好,首先使用FileReader和Blob以块的形式读取文件,将块追加到MD5散列中,最终会生成一个文件MD5值,同时生成过程保持了低内存使用率。 FileReader对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用File或Blob对象指定要读取的文件或数据。 FileReader说明链接:https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader 3、校验文件MD5值MD5值作为一个存放文件块的目录,检验MD5值即使检验该目录是否存在,如果存在,则文件已经完全上传或上传了一部分;如果不存在,则该文件没有被上传。 4、分片上传 5、服务端合并文件 源码参考github链接:https://github.com/Revelation2019/node-参考文章:https://segmentfault.com/a/1190000008899001

October 16, 2019 · 1 min · jiezi

微信公众号网页开发

基本配置1.设置—公众号设置—功能设置—配置JS接口安全域名安全域名配置规则如下 2.开发—基本配置开发者密码第一次使用需要重新设置记录 开发者ID(AppID) 开发者密码(AppSecret)后面会用到 3.IP白名单配置推荐填写当前本地开发IP地址和服务器IP地址本地开发地址获取方式服务器IP地址(根据自己的服务器Ip地址自行填写)多个IP地址填写用回车隔开 4重要的一步在:微信公众号-开发-接口权限查看想要调用的开发接口是否可用如果有相关接口权限无法开启,推荐使用:微信公众平台-开发-开发者工具-公众平台测试帐号开发 开始开发1.引入JS文件在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/... 2通过config接口注入权限验证配置(最重要的一步)wx.config({ debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: '', // 必填,公众号的唯一标识 timestamp: , // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串 signature: '',// 必填,签名 jsApiList: [] // 必填,需要使用的JS接口列表});appID(前面在微信公众号基本配置中已经拿到了)jsApiList:['uploadImage','updateAppMessageShareData'] (例:上传图片接口,和自定义分享接口) 签名算法(微信官方提供)jsapi_ticket生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket 。 参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token):https://developers.weixin.qq....用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket):https://api.weixin.qq.com/cgi...2.1签名获取拆解第一步GET请求access_tokenaccess_token的有效期为7200秒(不必反复请求)https://api.weixin.qq.com/cgi... grant_type是获取access_token填写client_credentialappid是第三方用户唯一凭证secret是第三方用户唯一凭证密钥,即appsecret**appid 和 secret 在前面的基本配置中其实都已经拿到。但是由于开发者密码(AppSecret)是校验公众号开发者身份的密码,具有极高的安全性。不能直接暴露在前端代码中,所以access_token的请求需在后端完成,这里签名的生成过程都在后端完成。 当前以node搭建后端服务//获取到access_token示例var url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`; request(url, function (error, response, body) { if (!error && response.statusCode == 200) { console.log("access_token值" +JSON.parse(body).access_token) } });第二步GET请求jsapi_ticketjsapi_ticket的有效期为7200秒(不必反复请求)https://api.weixin.qq.com/cgi...用第一步获取到的access_token的值进行请求 //var url = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${access_token}&type=jsapi` request(url, function (error, response, body) { if (!error && response.statusCode == 200) { console.log("jsapi_ticket值" + JSON.parse(body).ticket); } });第三步生成算法签名const timestamp = parseInt(Date.now() / 1000) //生成签名的时间戳const nonceStr = Math.random().toString(36).substr(2, 15) //生成签名的随机串let jsapi_ticket //在第二步生成let url//签名用的url必须是调用JS接口页面的完整URL(前端请求服务端接口带入) ...

October 16, 2019 · 2 min · jiezi

node-利用命令行交互生成相应模板

创建时间:2019-10-15测试环境:win10 node-v10.16.1受 vue-cli 初始化项目的启发,想探究其原理和自己实现一套类似方法,以便在项目中创建公用模板块。 这里采用三种方式实现 node 自带的 readline使用process实现第三方包 inquirer还有其它实现方式如 commander.js 等,这里不做具体实现 所有实现方式的完整代码 github 链接 链接文件结构如下 |-- generatorTemplate.js (生成模板)|-- readline.js (readline 方式完整代码)|-- process.js (process 方式完整代码)|-- inquirer.js (inquirer 方式完整代码)创建的模板示例:根据用户输入的不同,返回不同结果,包括实现了生成一个文件夹,文件夹内容如下 |--template |--css |--images |--js |-- index.jsreadline 实现引入 node 自带的 readline const readline = require('readline');初始创建 const rl = readline.createInterface({ /* 监听可读流 */ input: process.stdin, /* 读取写入的 可写流 */ output: process.stdout, /* 提示信息 */ // prompt: '请输入:'});这里会一直监听用户的输入 当输入template时 创建模板 rl.on('line', function(input) { if(input === 'template') { /* 这里的generator方法参见下方 */ generatorTemplate.generator() rl.close() } else if (input === 'pause') { rl.pause() } else { rl.write('please input right: '); }})完整代码查看 readline.js ...

October 16, 2019 · 2 min · jiezi

nodejs基础篇回顾

一、nodejs介绍1.1 简介nodejs是一个JavaScript运行环境。它让JavaScript可以开发后端程序,实现几乎其他后端语言实现的所有功能Nodejs是基于V8引擎,V8是Google发布的开源JavaScript引擎,本身就是用于Chrome浏览器 的JS解释部分,V8搬到了服务器上,用于做服务器的软件短短几年的时间,Node 取得了巨大的成功。在企业界,Node 的应用也越来越广泛,2016 年 nodeJS 官方的调查报告。2016 年全球有 350 万开发者使用 nodeJS,相比去年保持了 100%的增长率。像 Yahoo、 Microsoft 这样的大公司,有好多应用已经迁移到 Node 了。国内的阿里巴巴、网易、腾讯、新浪、百度等 公司的很多线上产品也纷纷改用 Node 开发,并取得了很好的效果。据统计很多 A 轮、 B 轮的创业公司更 喜欢使用 NodeJs 开发。1.2 NodeJs 的优势1. NodeJs 语法完全是 js 语法,只要你懂 JS 基础就可以学会 Nodejs 后端开发 Node 打破了过去 JavaScript 只能在浏览器中运行的局面。前后端编程环境统一,可以大大降低开发成本2. NodeJs 超强的高并发能力 Node.js的首要目标是提供一种简单的、用于创建高性能服务器及可在该服务器中运行的各种应用程 序的开发工具首先让我们来看一下现在的服务器端语言中存在着什么问题。 在Java、PHP或者.net等服务器端语言中,会为每一个客户端连接创建一个新的线程。而每个线程需要耗费大约2MB内存 理论上,一个8GB内存的服务器可以同时连接的最大用户数为4000个左右 。要让Web应用程序支持更多的用户,就 需要增加服务器的数量,而Web应用程序的硬件成本当然就上升了Node.js不为每个客户连接创建一个新的线程,而仅仅使用一个线程。当有用户连接了,就触发一个 内部事件,通过非阻塞I/O、事件驱动机制,让Node.js程序宏观上也是并行的。使用Node.js,一个8GB内存的服务器,可以同时处理超过4 万用户的连接3. 实现高性能服务器 严格地说,Node.js是一个用于开发各种 Web 服务器的开发工具。在Node.js服务器中,运行的是高性能V8 JavaScript脚本语言,该语言是一种可以运行在服务器端的JavaScript脚本语言那么,什么是V8 JavaScript脚本语言呢?该语言是一种被V8 JavaScript引擎所解析并执行的脚本语言。V8 JavaScript引擎是由Google公司使用 C++语言开发的一种高性能JavaScript引擎,该引擎并不局限于在浏览 器中运行。Node.js将其转用在了服务器中,并且为其提供了许多附加的具有各种不同用途的API。例如, 在一个服务器中,经常需要处理各种二进制数据。在JavaScript脚本语言中,只具有非常有限的对二进制数 据的处理能力,而Node.js所提供的Buffer类则提供了丰富的对二进制数据的处理能力另外,在V8 JavaScript引擎内部使用一种全新的编译技术。这意味着开发者编写的高端的JavaScript脚本代码与开发者编写的低端的C语言具有非常相近的执行效率,这也是Node.js服务器可以提供的一个重要特性4. 开发周期短、开发成本低、学习成本低。 ...

October 16, 2019 · 3 min · jiezi

node基础篇回顾

一、nodejs介绍1.1 简介nodejs是一个JavaScript运行环境。它让JavaScript可以开发后端程序,实现几乎其他后端语言实现的所有功能Nodejs是基于V8引擎,V8是Google发布的开源JavaScript引擎,本身就是用于Chrome浏览器 的JS解释部分,V8搬到了服务器上,用于做服务器的软件短短几年的时间,Node 取得了巨大的成功。在企业界,Node 的应用也越来越广泛,2016 年 nodeJS 官方的调查报告。2016 年全球有 350 万开发者使用 nodeJS,相比去年保持了 100%的增长率。像 Yahoo、 Microsoft 这样的大公司,有好多应用已经迁移到 Node 了。国内的阿里巴巴、网易、腾讯、新浪、百度等 公司的很多线上产品也纷纷改用 Node 开发,并取得了很好的效果。据统计很多 A 轮、 B 轮的创业公司更 喜欢使用 NodeJs 开发。1.2 NodeJs 的优势1. NodeJs 语法完全是 js 语法,只要你懂 JS 基础就可以学会 Nodejs 后端开发 Node 打破了过去 JavaScript 只能在浏览器中运行的局面。前后端编程环境统一,可以大大降低开发成本2. NodeJs 超强的高并发能力 Node.js的首要目标是提供一种简单的、用于创建高性能服务器及可在该服务器中运行的各种应用程 序的开发工具首先让我们来看一下现在的服务器端语言中存在着什么问题。 在Java、PHP或者.net等服务器端语言中,会为每一个客户端连接创建一个新的线程。而每个线程需要耗费大约2MB内存 理论上,一个8GB内存的服务器可以同时连接的最大用户数为4000个左右 。要让Web应用程序支持更多的用户,就 需要增加服务器的数量,而Web应用程序的硬件成本当然就上升了Node.js不为每个客户连接创建一个新的线程,而仅仅使用一个线程。当有用户连接了,就触发一个 内部事件,通过非阻塞I/O、事件驱动机制,让Node.js程序宏观上也是并行的。使用Node.js,一个8GB内存的服务器,可以同时处理超过4 万用户的连接3. 实现高性能服务器 严格地说,Node.js是一个用于开发各种 Web 服务器的开发工具。在Node.js服务器中,运行的是高性能V8 JavaScript脚本语言,该语言是一种可以运行在服务器端的JavaScript脚本语言那么,什么是V8 JavaScript脚本语言呢?该语言是一种被V8 JavaScript引擎所解析并执行的脚本语言。V8 JavaScript引擎是由Google公司使用 C++语言开发的一种高性能JavaScript引擎,该引擎并不局限于在浏览 器中运行。Node.js将其转用在了服务器中,并且为其提供了许多附加的具有各种不同用途的API。例如, 在一个服务器中,经常需要处理各种二进制数据。在JavaScript脚本语言中,只具有非常有限的对二进制数 据的处理能力,而Node.js所提供的Buffer类则提供了丰富的对二进制数据的处理能力另外,在V8 JavaScript引擎内部使用一种全新的编译技术。这意味着开发者编写的高端的JavaScript脚本代码与开发者编写的低端的C语言具有非常相近的执行效率,这也是Node.js服务器可以提供的一个重要特性4. 开发周期短、开发成本低、学习成本低。 ...

October 16, 2019 · 3 min · jiezi

前后端开发数据大小限制

背景编程过程中在存储用户数据的时候,会遇到数据存储大小的限制。经常遇到的限制可以分为:用户侧、服务端、数据库三个方面,按照流程可以划分为5个阶段,如图所示。作为开发人员,需要了解这些限制,避免撞墙。 第一道墙-client:浏览器限制用户通过http请求提交数据,http请求本身是没有数据大小的限制,但是浏览器对URI的长度进行了限制。 浏览器限制的是URI长度,而不是你的请求参数浏览器限制大小(字符)IE2083Chrome8182curl8167汉字字符:在utf-8编码格式中,3个字节,在gbk编码中,2个字节,英文就一个字符一个字节超过限制长度就会返回错误码414 第二道墙-server:服务端限制服务端对于数据的处理能力不同,对于提交的数据限制能力也不同,不同服务器对请求的限制不同,会存在两个方面的限制: URI长度限制(以Node为例)数据包大小限制(以post请求为例)NodeNode对URI的大小有一定的大小限制, 最大值8kb(8 * 1024),一般情况下,不会触发这个限制,如果程序想单独限制一个大小,通过中间件限制下就行了 req.on('data', function(chunk){ received += chunk.length; if (received > bytes) req.destroy();});如果想修改最大的大小,只能去改变源码文件 http_parser.h 了 POST服务器限制大小(字符)apache8192IIS16384nginx8kb附加:get请求是幂等操作,所以可以用缓存来处理,post请求不是幂等的,所以无法应用缓存 第二道墙-数据库代理:DBProxy线上环境会部署几个数据库,通常情况下,会使用DBProxy进行代理,实现负载均衡、IP地址过滤、数据库分表等操作。常见的数据库代理mycat/mybuh/dbproxy等。不同的数据库代理默认的数据大小不同,比如有的公司,DBProxy的代理设置大小最大为150kb。 第三道墙-max_allowed_packet在实际读写数据库的时候,数据库本身会有容量限制,mysql中max_allow_packet,就是第三道墙。max_allow_pocket是数据库对于单个数据包的大小限制。 参数大小默认大小64M(v >= 8.0.3)/4M最大值1G第四道墙-数据库本身大小限制每一条数据在数据库中都有对应的字段对应,而每一个字段会有对应的大小限制,所以在写入数据库的时候就有对应的大小限制。下面以mysql数据库为例说明一下:mysql的数据库类型分为五类:数字类型、日期类型、字符串类型,特殊类型、JSON类型 数字类型类型存储(位)无符号范围有符号范围默认值TINYINT1-128~1270~255SMALLINT2-32768~327670~65535MEDIUMINT3-8388608~83886070~16777215INT4-2147483648~21474836470~4294967295BIGINT8-2的63方~2的63方-10~2的64方-1注意⚠️:超过范围怎么处理? 在严格模式下,会直接报错在非严格模式下,会显示范围的边界,比如要存储9223372036854775808的值,会显示9223372036854775807,因为有符号整数最大值为9223372036854775807假如就是想显示这样的值怎么办? 将值变为有符号的,或者使用字符串。其他基本的数据类型和大小,在这里就不赘述了查看详情 JSON类型对于JSON数据类型,可以通过JSON_STORAGE_SIZE,获取可以存储的JSON数据的大小 总结本文目的不是让你记住这些限制,而是让你知道哪里有数据存储的限制,当出现问题的时候,知道去哪里排查。 参考文献JSON MERGE FUNCTION 对GET和POST的理解很形象 url长度限制 如何选择合适的数据库代理 美团点评的DBProxy 常见的Mysql数据库类型 长度限制 推荐一个面试 JSON_STORAGE_SIZE()

October 15, 2019 · 1 min · jiezi

Zepto源代码学习一

最近开始学习Zepto源代码,分享出来,共同学习。前端新人,技术欠佳,多多见谅参考的gitbook地址感谢作者奉献 代码主体机构: 定义Zepto——核心自执行函数 函数里面的变量都为私有变量,防止污染全局var zepto = (function (){})(); 自执行函数 定义全局Zepto 核心函数 函数常用示例 $('div'); init函数: zepto.init = function (selector, context) { var dom // If nothing given, return an empty Zepto collection if (!selector) return zepto.Z() // Optimize for string selectors else if (typeof selector == 'string') { selector = selector.trim() // If it's a html fragment, create nodes from it // Note: In both Chrome 21 and Firefox 15, DOM error 12 // is thrown if the fragment doesn't begin with < if (selector[0] == '<' && fragmentRE.test(selector)) dom = zepto.fragment(selector, RegExp.$1, context), selector = null // If there's a context, create a collection on that context first, and select // nodes from there else if (context !== undefined) return $(context).find(selector) // If it's a CSS selector, use it to select nodes. else dom = zepto.qsa(document, selector) } // If a function is given, call it when the DOM is ready else if (isFunction(selector)) return $(document).ready(selector) // If a Zepto collection is given, just return it else if (zepto.isZ(selector)) return selector else { // normalize array if an array of nodes is given if (isArray(selector)) dom = compact(selector) // Wrap DOM nodes. else if (isObject(selector)) dom = [selector], selector = null // If it's a html fragment, create nodes from it else if (fragmentRE.test(selector)) dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null // If there's a context, create a collection on that context first, and select // nodes from there else if (context !== undefined) return $(context).find(selector) // And last but no least, if it's a CSS selector, use it to select nodes. else dom = zepto.qsa(document, selector) } // create a new Zepto collection from the nodes found return zepto.Z(dom, selector) }如果传递的第一个参数为string——首字母为< 并且时html标签 创建新的DOM否则如果为正常的字符串直接获取DOM如果有第二项参数 递归调用只有单参数本方法 并且执行find方法如果传递的参数为函数——作为ready的回调传递如果已经时zepto对象 直接返回如果是DOM数组 如果是单个DOM如果是html片段 ...

October 15, 2019 · 2 min · jiezi

比原Bapp红包应用

喜迎国庆期间,比原链在自己的移动端钱包Bycoin(下载地址)和google插件钱byone中推出了红包应用,在国庆期间深受大家好评。 那我们今天就来大概介绍一下比原红包,以及基于比原链开发dapp应用的一些流程梳理。 我们知道绝大多数公有区块链项目的TPS都只有几十上百。所以像红包这种及时到账的应用在公链上实现还是有一定的技术难度,但是比原链却在自己的侧链上率先推出了红包应用。比原侧链的TPS有数十万,整个红包应用程序的并发量有将近2000。所以在国庆期间数万人同时抢红包都完全没出现任何问题,这足以说明比原链侧链性能优越,适合开发区块链应用。下面我们来看一下整个红包的实现过程。 下图是整个红包的架构图: 由于红包是基于侧链的应用,如果你是主链资产。要通过federation进行资产跨链,资产跨链到侧链就可以使用红包应用。整个红包应用有四大模块组成,第一个模块是Blockcenter,你可以把看成一个与比原侧链交互的中间件。第二个模块就是智能合约,第三个模块是整个红包应用的服务端和存储模块使用的Mysql和Redis,最后一个模块是前端应用,主要包括移动端的Bycoin和网页端的Byone。 对整个红包框架做一个大概的拆分以后,我们就能明白整个红包应用的开发流程就是我们的Bapp开发流程。那我们首先从Bapp的前端来看,基于Google插件钱包的网页应用和Bycoin上的应用都属于Bapp的前端。这个模块都是一些基本的功能,主要是收红包,发红包,以及红包记录等几个页面。 红包的智能合约模块使用了猜谜合约,这个模块的实现需要参考合约的开发流程,将合约部署好以后的参数配置在后端服务器。为什么使用猜谜合约呢?其实发红包的过程就是发起一个猜谜合约,红包口令就是谜底,用户领取红包的时候,输入的口令其实就是谜底,然后资产就可以自动转移,就是领取红包的这个过程。 下面我们来介绍一下Server模块做的事情,整个Server是红包的核心业务逻辑和数据处理模块,数据存储则是存放在Mysql和Redis中。同是Server也和Blockcenter相互调用(Blockcenter管理了BUTXO和一些主侧链的接口封装,可以看成中间件),和比原的侧链进行交互。 通过我们对整个红包应用的模块拆分,我们就能很清楚看到,Bapp的开发是多个模块组合,技术社区已经将一些标准的模块进行封装,做成中间件方便开发者调用。开发其他类似的Bapp应用就可以参考这个流程。 对红包应用的基础介绍就这些,如果你想了解详细的过程,开发流程,以及某个模块的具体实现,可以在开源库中查看源码,和相关接口文档。如果有技术问题可以加微信:pymgdsb1314

October 15, 2019 · 1 min · jiezi

魅族官网基于-nextjs-重构实践总结与分享

项目背景俗话说,脱离业务谈代码的都是耍流氓。在此我先简单介绍下重构项目的背景。 截图镇楼:魅族官网首页 在 2015 年,公司前端大佬猫哥基于 FIS3 深度定制开发了一套前端工程体系 mz-fis,该框架经历3年来的网站改版升级需求,都很好的完成了需求任务。 但随着项目越来越大,以及前端技术快速迭代。老项目的痛点越发明显。 此次重构解决了那些痛点1.随着项目越来越大,前端编译打包流程巨慢。(算上图片视频等资源,仓库有3.9G大小)2.运营需要经常改动网站内容,由于需要SEO,哪怕改几个字也需要前端打包发布。3.旧框架的核心还是Jquery,虽然结果3年开发积累了很多组件,但在数据维护、模块化以及开发体验上已经落后了。 以上痛点想必手上有老项目的,都感同身受。改起来伤筋动骨,但不改吧工作效率太低了。 此次重构需要满足哪些要求再说说重构的基本要求,咱得渐进增强而不是优雅降级。:D 1.支持SEO,也就是说需要服务端渲染。2.解放前端、测试劳动力,让运营在网站内容管理平台编辑数据后发布,官网及时生效。(不同于传统AJAX,这里数据需要SEO)。3.支持多国语言。4.需要新旧框架同存,同域名下无缝对接,要求两套工作流都可以正常工作。(一些不频繁改动的页面,可以不改,减少重构成本)。5.更快的页面性能、更畅快的开发体验和更好可维护性。 此次重构技术选型首先,服务端渲染 SSR 是没跑了,它可以更快渲染首屏,同时对 SEO 更友好。 于是我在带着鸭梨与小兴奋寻遍各大SSR方案后,最终选择了 Next.jsNext.js 是一个轻量级的 React 服务端渲染应用框架。目前在 github 已获得 4W+ 的 star。 之所以火爆,是因为它有以下优点:1.默认服务端渲染模式,以文件系统为基础的客户端路由2.代码自动分隔使页面加载更快3.简洁的客户端路由(以页面为基础的)4.以webpack的热替换为基础的开发环境5.使用React的JSX和ES6的module,模块化和维护更方便6.可以运行在其他Node.js的HTTP 服务器上7.可以定制化专属的babel和webpack配置 这里不做过多讲解了,大家可以访问 next.js中文网、github地址了解更多。 重构过程中遇到的问题以及解决方案问题一:网站采用 next.js 的 start 模式服务,还是 export 出静态化文件让 ngxin 做web服务两种方案都可行,但各有优缺点。 考虑到运营并不在乎那点等待时间,相比之下项目稳定性更重要。于是选择方案二:「export 出静态化文件让 ngxin 做web服务」。 ok~ 选定后要做的就是静态化了。 问题二:如何静态化如何做呢? 恩... 最简单的就是 cd 到项目目录下 npm run build && npm run export 下,打包出文件到./out文件夹,然后打个zip包扔服务器上。当然,为了运营数据及时更新,你得24小时不停重复以上步奏,还不能手抖出错。 为了不被同事打死,我设计了一套开发流程,在项目中写一个shell脚本: #!/bin/bashecho node版本:$(node -v)BASEDIR=$(dirname $0)cd ${BASEDIR}/../sudo npm run buildwhile true;do whoami && pwd sudo npm run export >/dev/null 2>&1 || continue sudo chown -R {服务器用户名} ./out || echo 'chown Err' sudo cp -ar ./out/* ./www || echo 'cp Err' sudo chown -R {服务器用户名} ./www || echo 'chown Err' echo '静态化并复制完毕' sleep 15done好了,只要执行这段 shell,你的服务器就会cd到项目目录,先build构建项目,然后每间隔15秒构建一次。并输出当前环境和相关信息。 ...

October 14, 2019 · 5 min · jiezi

如何在SAP云平台ABAP编程环境里创建自己的Z表

选中ABAP包,右键创建一个新的Database Table: 维护表名为ZBOOKING: 表实现的源代码: @EndUserText.label : 'Jerry''s booking'@AbapCatalog.enhancementCategory : #NOT_EXTENSIBLE@AbapCatalog.tableCategory : #TRANSPARENT@AbapCatalog.deliveryClass : #A@AbapCatalog.dataMaintenance : #LIMITEDdefine table zbooking { key client : abap.clnt not null; key booking : abap.int4 not null; customername : abap.char(50); numberofpassengers : abap.int2; emailaddress : abap.char(50); country : abap.char(50); dateofbooking : timestampl; dateoftravel : timestampl; @Semantics.amount.currencyCode : 'zbooking.currencycode' cost : abap.curr(15,2); currencycode : abap.cuky; lastchangedat : timestampl;}激活: 下一步,创建一个ABAP类,以代码的方式往Z表里插入数据。 这个ABAP类要实现if_oo_adt_classrun接口,类似Java里的console应用: ...

October 14, 2019 · 1 min · jiezi

教你如何从零创建Vue项目

第一步Node安装下载Node解压后双击 node-v6.14.4-x64.msi(版本号可能不同)安装,在安装界面一直Next,直到Finish完成安装。打开控制命令行程序(CMD),检查是否正常(查看node、npm版本号) 第二步安装Vue脚手架在命令行运行 npm install vue-cli -g      //全局安装 vue-cli查看vue-cli是否成功,不能检查vue-cli,需要检查vue,如下: 第三步新建vue项目还是在命令行选定路径新建vue项目,然后运行 vue init webpack  “项目名称”,下面我在E盘根目录以项目名为wukong新建vue项目 创建完成后在命令行cd到项目文件夹 如: cd wukong再运行 npm install安装项目依赖依赖下载完毕后再运行 npm run dev启动项目出现如下图即启动vue项目成功 另:使用淘宝NPM镜像大家都知道国内直接使用npm的官方镜像是非常慢的,这里推荐使用淘宝 NPM镜像。命令行运行 npm  install  -g  cnpm  --registry=https://registry.npm.taobao.org这样就可以使用cnpm 命令来安装模块了,如果是内网开发的猿们建议就不要安装淘宝镜像了,我之前就遇过因为内网无法联网运行不了的问题,安装依赖也需要网络,所有可以在互联网机器上构建好项目再搬迁到内网开发。附上部分代码简写-S:安装到上线环境 --save-D:安装到开发环境 --save-dev-g:安装到全局 vue-cli-i:install(等同)

October 14, 2019 · 1 min · jiezi

基于阿里egg框架搭建博客5置顶导航条

相关文章基于阿里egg框架搭建博客(1)——开发准备基于阿里egg框架搭建博客(2)——Hello World基于阿里egg框架搭建博客(3)——注册与登录基于阿里egg框架搭建博客(4)——权限控制基于阿里egg框架搭建博客(5)——置顶导航条基于阿里egg框架搭建博客(6)——浏览、发表文章基于阿里egg框架搭建博客(7)——编辑文章 githttps://github.com/ZzzSimon/e...喜欢就点个赞吧! 正文模板继承导航条是博客不可或缺的一部分,几乎每一个页面都有置顶导航条。我们不可能在每个页面都复制一份相同的代码,因为这样代码变得冗余,而且当需要修改导航条时,改动量会非常之大。所幸的是,我们使用了nunjucks作为view模板,其提供的模板继承功能可以解决这个问题。 官方文档:https://nunjucks.bootcss.com/...页面设计 功能设计如页面设计图中所示,导航条共有6块功能: 站点名称,点击后可以回到首页文章按钮,点击后可以查看文章列表搜索框,可以按文章名称搜索文章发表文章按钮,顾名思义,可以发表博文用户头像下拉按钮,我的文章可以查看自己的文章或重新编辑设置,可以修改个人信息注销,注销用户,删除session,返回登录父模板parent.tpl代码(包含导航条)我们创建/app/view/parent.tpl文件: <!DOCTYPE html><html><head> <link rel="stylesheet" href="/public/bootstrap/css/bootstrap.css"> <script type="text/javascript" src="/public/js/jquery.min.js"></script> <script type="text/javascript" src="/public/bootstrap/js/bootstrap.min.js"></script> {% block head %}{% endblock %}</head><body><div id="nav"> <nav class="navbar navbar-default navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="/">妖云小离</a> </div> <div class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li><a href="/articleList.htm">文章</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">预留 <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">Action</a></li> <li><a href="#">Another action</a></li> <li><a href="#">Something else here</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> <li role="separator" class="divider"></li> <li><a href="#">One more separated link</a></li> </ul> </li> </ul> <form class="navbar-form navbar-left" action="/search?_csrf={{ ctx.csrf | safe }}"> <div class="input-group"> <input type="text" class="form-control" placeholder="搜索" name="keyword"> <span class="input-group-btn"> <button class="btn btn-default" type="submit"> <span class="glyphicon glyphicon-search " aria-hidden="true"></span> </button> </span> </div> </form> <ul class="nav navbar-nav navbar-right"> <li><a href="/edit.htm"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> 发表文章</a> </li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> <img src="" id="avatarNav" class="img-circle" style="width: 26px; margin-top: -6px"> <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="/myarticle.htm">我的文章</a></li> <li><a href="/myInfo.htm">设置</a></li> <li role="separator" class="divider"></li> <li><a href="/user/logout">注销</a></li> </ul> </li> </ul> </div> </div> </nav></div><div id="top" class="row" style="margin-top: 10%"></div><div class="container"> {% block content %}{% endblock %}</div><script type="text/javascript"> function getCookie(c_name) { if (document.cookie.length > 0) { c_start = document.cookie.indexOf(c_name + "=") if (c_start != -1) { c_start = c_start + c_name.length + 1 c_end = document.cookie.indexOf(";", c_start) if (c_end == -1) c_end = document.cookie.length return unescape(document.cookie.substring(c_start, c_end)) } } return "" } $('#avatarNav').attr('src', getCookie('avatarUrl'));</script>{% block script %}{% endblock %}</body></html>父模板中可以继承重写的地方有3处: ...

October 14, 2019 · 2 min · jiezi

基于阿里egg框架搭建博客3注册与登录

相关文章基于阿里egg框架搭建博客(1)——开发准备基于阿里egg框架搭建博客(2)——Hello World基于阿里egg框架搭建博客(3)——注册与登录基于阿里egg框架搭建博客(4)——权限控制基于阿里egg框架搭建博客(5)——置顶导航条基于阿里egg框架搭建博客(6)——浏览、发表文章基于阿里egg框架搭建博客(7)——编辑文章 githttps://github.com/ZzzSimon/e...喜欢就点个赞吧! 正文俗话说万事开头难,此章节涉及大量的知识点,在每个代码后面都有解释,需要大家查看官方文档。 user表设计简单来说,注册与登录就是对user表的读写。所以我们首先对user表进行设计: 字段说明字段解释iduuidusername用户名password密码phone手机号create_time创建时间update_time更新时间avatar_url头像urlsql脚本DROP TABLE IF EXISTS `user`;CREATE TABLE `user` ( `id` varchar(20) NOT NULL, `username` varchar(18) NOT NULL, `password` varchar(20) NOT NULL, `phone` varchar(11) DEFAULT NULL, `create_time` datetime NOT NULL, `update_time` datetime NOT NULL, `avatar_url` varchar(255) DEFAULT NULL, PRIMARY KEY (`username`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;注册页面设计页面设计如上图所示,需要用户输入用户名,密码,手机号,还可以上传自己的头像。 前端代码官方文档推荐我们使用nunjucks作为模板。 使用方法:https://eggjs.org/zh-cn/intro...我们在app\view\home目录下创建register.tpl文件: <html><head> <title>注册</title> <link rel="stylesheet" href="/public/bootstrap/css/bootstrap.css"> <script type="text/javascript" src="/public/js/jquery.min.js"></script> <script type="text/javascript" src="/public/bootstrap/js/bootstrap.min.js"></script></head><body><div class="container "> <form class="center-block" style="width: 50%;margin-top: 10%" method="POST" action="/user/register?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data"> <div class="form-group "> <img src="/public/avatar/default.jpg" id="avatarPic" class="img-circle center-block" style="width: 64px;"> <input type="file" id="avatarBtn" name="file" style="visibility: hidden"> <p class="text-center help-block">点击头像更改,只支持jpg,png格式,大小≤ 200 kb</p> </div> <div class="form-group"> <label for="username">账号</label> <input type="text" class="form-control" id="username" placeholder="用户名" name="username"> </div> <div class="form-group"> <label for="password">密码</label> <input type="password" class="form-control" id="password" placeholder="密码" name="password"> </div> <div class="form-group"> <label for="phone">手机号</label> <input type="text" class="form-control" id="phone" placeholder="手机号" name="phone"> </div> <div class="form-group"> <button type="submit" class="btn btn-info pull-right">注册</button> </div> </form></div><script> $('#avatarPic').bind('click', function () { $('#avatarBtn').click(); }); $('#avatarBtn').bind('change',function (e) { if (window.FileReader) { var reader = new FileReader(); reader.readAsDataURL(e.target.files[0]); //监听文件读取结束后事件 reader.onloadend = function (e) { $('#avatarPic').attr("src",e.target.result); //e.target.result就是最后的路径地址 }; } });</script></body></html>有3点需要注意: ...

October 14, 2019 · 4 min · jiezi

基于阿里egg框架搭建博客4权限控制

相关文章基于阿里egg框架搭建博客(1)——开发准备基于阿里egg框架搭建博客(2)——Hello World基于阿里egg框架搭建博客(3)——注册与登录基于阿里egg框架搭建博客(4)——权限控制基于阿里egg框架搭建博客(5)——置顶导航条基于阿里egg框架搭建博客(6)——浏览、发表文章基于阿里egg框架搭建博客(7)——编辑文章 githttps://github.com/ZzzSimon/e...喜欢就点个赞吧! 正文上一篇文章我们实现了用户的注册与登录,接下来就需要对用户权限进行控制了,比如:普通用户只能评论,管理员可以发表文章,最高管理员可以修改用户权限等等。由于权限控制是一个通用的功能,我们把这块功能做成中间件。关于中间件: 官方文档:https://eggjs.org/zh-cn/basic...功能设计一个用户对应1个角色可以通过配置文件配置,某一个角色无权限使用的页面与接口可以配置无需验证用户与权限的path。比如:登录与注册的相关页面与接口只有登录过才能访问的path,否则跳转登录页。User表,增加role(角色)字段 配置文件我们在config/config.default.js中加入以下内容: auth : { noAuth:['/login.htm','/user/login','/register.htm','/user/register'], noPermission:{ admin:[], manager:['/admin.htm'], user:['/admin.htm','/edit.htm'] } }其中:noAuth节点配置的是无需验权就能访问的pathnoPermission节点配置的是各个角色无权限访问的path auth.js中间件代码我们创建app/middleware/auth.js文件: module.exports = (options, app) => { return async function auth(ctx, next) { //如果用户session没失效 if (typeof (ctx.session.user) !== 'undefined') { const username = ctx.session.user.username; //这里有两种做法,第一种每次都查库校验角色,优点:实时,角色变更对用户无感。缺点:查库效率低,可考虑用redis //第二种,把角色信息放进session,优点:无需查库,效率高。缺点:角色变更时需额外逻辑来处理老的session,否则客户端的用户角色无法实时更新 const role = await ctx.service.user.getRoleByUsername(username); const noPerList = options.noPermission[role]; if (noPerList && !noPerList.includes(ctx.path)) { await next(); } else { ctx.body = '无权限,请联系网站管理员!'; } //登录注册页面不需要权限 } else if (options.noAuth.includes(ctx.path)) { await next(); //如果session失效后则重定向到登录页 } else { ctx.redirect('/login.htm') } }};效果我们创建一个用户,并给与他user角色,由配置文件可以看出,user角色无权限访问/edit.htm路径。如图: ...

October 14, 2019 · 1 min · jiezi

前端工程师学Docker-看这篇就够了-原创精读

前端工程师,为什么要学习Docker ?传统的虚拟机,非常耗费性能 Docker可以看成一个高性能的虚拟机,并且不会浪费资源,主要用于Linux环境的虚拟化,类似VBox这种虚拟机,不同的是Docker专门为了服务器虚拟化,并支持镜像分享等功能。前端工程师也可以用于构建代码等等目前看,Dokcer不仅带火了GO语言,还会持续火下去 首先,我们看看传统的虚拟机和Docker的区别传统的虚拟机: Docker: 可以看到,传统的虚拟机是每开一个虚拟机,相当于运行一个系统,这种是非常占用系统资源的,但是Docker就不会。但是也做到了隔离的效果 Docker容器虚拟化的优点:环境隔离Docker实现了资源隔离,实现一台机器运行多个容器互不影响。 更快速的交付部署使用Docker,开发人员可以利用镜像快速构建一套标准的研发环境,开发完成后,测试和运维人员可以直接通过使用相同的环境来部署代码。 更高效的资源利用Docker容器的运行不需要额外的虚拟化管理程序的支持,它是内核级的虚拟化,可以实现更高的性能,同时对资源的额外需求很低。 更易迁移扩展Docker容器几乎可以在任意的平台上运行,包括乌力吉、虚拟机、公有云、私有云、个人电脑、服务器等,这种兼容性让用户可以在不同平台之间轻松的迁移应用。 更简单的更新管理使用Dockerfile,只需要小小的配置修改,就可以替代以往的大量的更新工作。并且所有修改都是以增量的方式进行分发和更新,从而实现自动化和高效的容器管理。 正式开始本文撰写于2019年10月13日 电脑系统:Mac OS 使用最新版官网下载的Docker 以下代码均手写,可运行 下载官网的Docker安装包,然后直接安装 https://www.docker.com/ Docker官网下载地址安装后直接打开 打开终端命令行,输入docker,会出现以下信息,那么说明安装成功 下载安装成功后,首先学习下Docker的两个核心知识点 container(容器)和image(镜像) Docker的整个生命周期由三部分组成:镜像(image)+容器(container)+仓库(repository) 思维导图如下: 该如何理解呢?每台宿主机(电脑),他下载好了Docker后,可以生成多个镜像,每个镜像,可以创建多个容器。发布到仓库时,以镜像为单位。可以理解成:一个容器就是一个独立的虚拟操作系统,互不影响,而镜像就是这个操作系统的安装包。想要生成一个容器,就用安装包(镜像)生成一次 上面就是Docker的核心概念,下面开始正式操作 补充一点:如果想深入Docker , 还是要去认真学习下原理,今天我们主要讲应用层面的 首先,我们回到终端命令行操作 输入: docker images如果你的电脑上之前有创建过的镜像,会得到如下: 如果没有的话就是空~ 我们首先创建一个自己的镜像 先编写一个Node.js服务 创建index.js // index.jsconst Koa = require('koa');const app = new Koa();app.use(async ctx => { ctx.body = 'Hello docker';});app.listen(3000);然后配置package.json文件 { "name": "app", "version": "1.0.0", "private": true, "scripts": { "start": "node server.js" }, "dependencies": { "koa": "^2.5.0" } } 正常情况下 使用npm start 或 node index.js 就可以启动服务可是我们这里需要打包进Docker中,这里就需要写一个配置文件dockerfile ...

October 14, 2019 · 2 min · jiezi

归纳JS中数组的使用二元素遍历

forEach()函数示例代码 #1 基础使用 let arr = [1,2,3];arr.forEach(function(currentValue){ console.log(currentValue);// 每次输出 1,2,3}) #2 遍历的时候获取角标 let arr = [1,2,3];arr.forEach(function(currentValue,index){ console.log(currentValue);// 每次输出 1,2,3 console.log(index);// 每次输出,0,1,2}) #3 代入被遍历的数组本身 let arr = [1,2,3];arr.forEach(function(currentValue,index,originArr){ console.log(currentValue);// 每次输出 1,2,3 console.log(index);// 每次输出,0,1,2 console.log(originArr);// 每次输出 [1,2,3]}) #4 绑定this let arr = [1,2,3];let obj = {age:18}arr.forEach(function(currentValue,index,originArr){ console.log(currentValue);// 每次输出 1,2,3 originArr[index] = this.age+currentValue},age) 关注点forEach() 被调用时,不会改变原数组(即调用它的数组)遍历中无法中断,直到数组被遍历完毕arr.forEach(callback);callback 无返回值/永远返回undefinedarr.forEach(); 这个函数本身也无返回值/返回值是undefined灵活使用 第二参数,进行this的绑定,这个往往在封装功能函数的时候起到很大作用 every()函数这个函数的目的,是测试数组内的每个元素是否都能通过指定的函数的测试,如果都通过了那就返回true,否则就返回false示例代码 # 1 检查数组里面的每个元素是否符合条件 ...

October 14, 2019 · 1 min · jiezi

AngularJS项目中遇到的问题

一、从git clone后用vscode打开项目,执行npm run start时报错 gulp[10500]: c:\ws\src\node_contextify.cc:635: Assertion `args[1]->IsString()' failed. 1: 00007FF7106AD1BA v8::internal::GCIdleTimeHandler::GCIdleTimeHandler+4506 2: 00007FF710687F96 node::MakeCallback+4534 3: 00007FF71068804F node::MakeCallback+4719 4: 00007FF71065C460 node::DecodeWrite+13120 5: 00007FF710BB5212 std::vector<v8::internal::compiler::MoveOperands * __ptr64,v8::internal::ZoneAllocator<v8::internal::compiler::MoveOperands * __ptr64> >::_Umove+79442 6: 00007FF710BB6379 std::vector<v8::internal::compiler::MoveOperands * __ptr64,v8::internal::ZoneAllocator<v8::internal::compiler::MoveOperands * __ptr64> >::_Umove+83897 7: 00007FF710BB56BC std::vector<v8::internal::compiler::MoveOperands * __ptr64,v8::internal::ZoneAllocator<v8::internal::compiler::MoveOperands * __ptr64> >::_Umove+80636 8: 00007FF710BB55DB std::vector<v8::internal::compiler::MoveOperands * __ptr64,v8::internal::ZoneAllocator<v8::internal::compiler::MoveOperands * __ptr64> >::_Umove+80411 9: 000000EB7B65C5C1npm ERR! code ELIFECYCLEnpm ERR! errno 134npm ERR! ESettle@1.0.0 start: `gulp default`npm ERR! Exit status 134npm ERR!npm ERR! Failed at the ESettle@1.0.0 start script.npm ERR! This is probably not a problem with npm. There is likely additional logging output above.npm ERR! A complete log of this run can be found in:npm ERR! C:\Users\zhanxl\AppData\Roaming\npm-cache\_logs\2019-10-14T01_09_23_529Z-debug.log原因:由node早于10版本开发的项目升级到了10以上的版本 执行gulp,node 10 版本都会出现这个问题解决方法:安装node.js的原生javascript模块 natives 可以解决命令:npm install natives ...

October 14, 2019 · 1 min · jiezi

基于Koa和React搭建的基础项目框架

引言在个人开发过程中,因为个人主要是做前端方向的,所以在使用后端的时候更加偏向于Node这一块,目前Node比较热门的框架主要有Express和Koa两款,当然还有各大厂商基于这两款框架的二次封装框架比如Eggjs,Thinkjs。但是个人使用之后觉得并不适合个人开发,主要是框架封装太多可扩展性以及自由度不高,基础框架过于笨重即使很小的项目依赖也很多。由此引发自己弄一套基础的开发框架(可能使用框架这个词语不太合适)。目前项目主要是基于Koa和React实现。后端主要使用Koa,Knex(Sql Builder)主要支持Mysql,Redis等常见数据库,支持controller,service自动加载。前端基于React使用React hooks,React Context等较为新型的技术,加入了ant design,less等功能。 项目地址>>>https://github.com/southorange1228/so.fullstack.system目前项目处于初期开发阶段,可能存在很多问题和不足的地方,希望各位大佬能提供意见和帮助。如有任何问题请提issue或者邮件至15280970040@163.com。最后厚颜无耻的求一个star

October 14, 2019 · 1 min · jiezi

Promise的秘密

写在前面本篇文章将会带大家从分解promise入手,一步步实现一个promise。但阅读之前需要比较熟练地了解了解用法,结合用法看文章可能更容易理解。 结构先看一下简单的用法。 const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') })}).then(value => { ... }, reason => { ... }).catch(error => { ... })Promise的构造函数接收了一个回调,这个回调就是下面要讲到的执行器,执行器的参数resolve, reject也是两个函数,负责改变promise实例的状态和它的值,then函数中的回调在状态改变后执行。 注意:不是then函数在状态改变后执行,而是then中的回调函数在状态改变后执行。then方法会将其中的回调放入执行队列,promise的状态改变后再将队列中的函数一一执行 如果要实现一个最简单的promise类,内部结构都要包含什么呢? 状态:fulfiled、rejected、pending值:promise的值执行器:提供改变promise状态的入口resolve和reject方法:前者将promise改变为fulfiled,后者将其改变为rejected。可以在执行器内根据实际业务来控制是resolve或reject。then方法:接收两个回调,onFulfilled, onRejected。分别在promise状态变为fulfiled或rejected后执行,这里涉及到将回调注册进两个执行队列的操作,后文会讲到 const PENDING = 'pending'const FULFILLED = 'fulfiled'const REJECTED = 'rejected'class NewPromise { constructor(handler) { this.state = PENDING this.value = undefined this.successCallback = [] this.failureCallback = [] try { handler(this.resolve.bind(this), this.reject.bind(this)) } catch (e) { this.reject(e) } } // resolve和reject方法 resolve(value) { ... } reject(reason) { ... } // then方法 then(onFulfilled, onRejected) { ... }}结构中的每个部分是如何实现的呢? ...

October 14, 2019 · 7 min · jiezi

归纳JS中数组的使用一元素新增和删除

新增元素js中给数组新增元素主要通过2个方法 push 和 unshift Array.prototype.push功能概述push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度(该方法修改原有数组)。 语法arr.push(element1, ..., elementN)关注点新增的元素出现在数组的末尾可以一次性添加多个元素返回值是数组的最新长度代码示例简单使用let arr = [1,2];let newLength = arr.push(3,4); console.log(newLength)// newLength: 4对比通过脚本添加和ES6析构的方式添加性能对比function push() { let arr = [1,2]; let newLength = arr.push(3,4); }function add() { let arr = [1,2]; let len = arr.length; arr[len] = 3; arr[len+1] = 4;}function es6() { let arr = [1,2]; arr = [ ...arr, 3, 4 ]}let sum = 1000000;//1百万次console.time('push')for (let index = 0; index < sum; index++) { push();}console.timeEnd('push');console.time('add')for (let index = 0; index < sum; index++) { add();}console.timeEnd('add')console.time('es6')for (let index = 0; index < sum; index++) { es6();}console.timeEnd('es6')输出的结果push: 470.538818359375msadd: 454.7177734375mses6: 2625.546142578125ms ...

October 14, 2019 · 1 min · jiezi

模型设计对博客中评论的合理建模-MongoDB

最近,闲着没事,又把上个月写得代码拿出来了,随便完善一下没完成的评论的路由接口。 评论应该是在整个博客数据存储中,模型最为复杂的一部分了。首先要考虑的是和文章进行关联。这个可以用 mongoDB 的 ref 进行关联,随后可以使用 populate 计算出被关联的字段。 最后关系复杂的是父子层级的评论,又或者是多级评论。这个时候就要想该怎么做才能合理的管理这些层级关系,在删除父评论的同时又能把所有子评论一起删除。查询的时候如何去由根到叶顺序输出层级关系。 建立评论模型const schema = new mongoose.Schema({ // comment id cid: { type: Number, required: true, unique: true }, // post id pid: { type: Number, required: true }, post: { type: mongoose.SchemaTypes.ObjectId, ref: 'Post' }, content: { type: String, required: true }, createTime: { type: Number, default: Date.now() }, author: { type: String, required: true }, owner: { type: String, required: true }, isOwner: { type: Boolean, required: true }, email: { type: String }, url: { type: String }, key: { type: String, required: true, unique: true }, parent: { type: mongoose.SchemaTypes.ObjectId, ref: 'Comment' }, hasChild: { type: Boolean, default: false }, ipAddress: { type: String, required: true }, userAgent: { type: String }, // 0 审核 1 发布 2 垃圾 state: { type: Number, required: true, default: 0 }})在模型中,post列中关联引用表(post表)的 _id(文章),在 hasChild 中记录是否存在回复。在后期处理回复路由的时候不要忘记修改他的值。最关键的是 key 列,这个用来记录平行层级。如 post 中的一篇 pid 为 11 的文章下有一条评论,那么 key 中命名 11#001,这是第一条评论,如果该评论下存在一条回复,则回复的 key 为 11#001#001,下层亦是如此。使用该命名方式可以容纳的每条评论的回复量为 999,可以根据需求调整0的数量。 ...

October 14, 2019 · 2 min · jiezi

基于vuecli的webpack打包优化实践及探索

转眼已经是2019年,短短三四年时间,webpack打包工具成为了前端开发中必备工具,曾经一度的面试题都是问,请问前端页面优化的方式有哪些?大家也是能够信手拈来的说出缓存、压缩文件、CSS雪碧图以及部署CDN等等各种方法,但是今天不一样了,可能你去面试问的就是,请问你是否知道webpack的打包原理,webpack的打包优化方法有哪些?所以该说不说的,笔者闲着没事研究了一下webpack的打包优化,可能大家都有看过类似的优化文章~ 但是笔者还是希望能够给大家一些新的启发~1、准备工作:测速与分析bundle既然我们要优化webpack打包,肯定要提前对我们的bundle文件进行分析,分析各模块的大小,以及分析打包时间的耗时主要是在哪里,这里主要需要用到两个webpack插件,speed-measure-webpack-plugin和webpack-bundle-analyzer,前者用于测速,后者用于分析bundle文件。 具体配置const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPluginconst smp = new SpeedMeasurePlugin({ outputFormat:"human",});module.exports = {configureWebpack: smp.wrap({ plugins: [ new webpack.ProvidePlugin({ $: "zepto", Zepto: "zepto", }), new BundleAnalyzerPlugin(), ], optimization: { splitChunks: { cacheGroups: { echarts: { name: "chunk-echarts", test: /[\\/]node_modules[\\/]echarts[\\/]/, chunks: "all", priority: 10, reuseExistingChunk: true, enforce: true, }, demo: { name: "chunk-demo", test: /[\\/]src[\\/]views[\\/]demo[\\/]/, chunks: "all", priority: 20, reuseExistingChunk: true, enforce: true, }, page: { name: "chunk-page", test: /[\\/]src[\\/]/, chunks: "all", priority: 10, reuseExistingChunk: true, enforce: true, }, vendors: { name: "chunk-vendors", test: /[\\/]node_modules[\\/]/, chunks: "all", priority: 5, reuseExistingChunk: true, enforce: true, }, }, }, }, })}由于是基于vue-cli脚手架的,所以其实vue-cli中已经帮你做了一些优化的工作,可以看到,原先项目最初的配置设置了splitchunk,进行代码分割,这在大型项目中是很有必要的,毕竟你不希望你的用户阻塞加载一个5MB大小的JS文件,所以做代码分割和懒加载是很有必要的。说远了,我们来看看这个配置,你需要用smp对配置进行再包裹,因为SpeedMeasurePlugin会对你的其他Plugin对象包裹一层代理,这样的目的是为了能够知道plugin开始和结束的时间~其次,BundleAnalyzerPlugin就跟普通的plugin一样,加载plugins数组的后面即可。接下来我们看一下最初的打包时间以及包内容分析: ...

October 13, 2019 · 2 min · jiezi

使用-nrm-管理镜像源

获取当前使用的镜像源npm config get registryhttps://registry.npmjs.org/使用指定的镜像源npm config set registry https://registry.npm.taobao.org/使用 npm config 命令来更改镜像源太麻烦, 我们使用 nrm 来帮我们管理多个镜源npm i nrm -gnrm -V1.2.1nrm 展示当前可用源nrm ls* npm -------- https://registry.npmjs.org/ yarn ------- https://registry.yarnpkg.com/ cnpm ------- http://r.cnpmjs.org/ taobao ----- https://registry.npm.taobao.org/ nj --------- https://registry.nodejitsu.com/ npmMirror -- https://skimdb.npmjs.com/registry/ edunpm ----- http://registry.enpmjs.org/nrm 列出了当前可切换使用的源, 左边带星号的表示当前正在使用的源。 使用列表中的源nrm use njRegistry has been set to: https://registry.nodejitsu.com/nrm 还可以用来测试镜像源的延迟nrm test yarncnpm --- 208msnrm test taobaotaobao - 102ms

October 13, 2019 · 1 min · jiezi

Node-使用-Selenium-进行前端自动化操作

Node 使用 Selenium 进行前端自动化操作前言:最近项目中有类似的需求:需要对前端项目中某一个用户下的产品数据进行批量的处理。手动处理的流程大概是首先登录系统,获取到当前用户下的产品列表,点击产品列表的中产品项进入详情页,对该产品进行一系列的操作,然后保存退出。因为当前有20多万条数据,手动一条一条的处理不太现实,所以希望通过写脚本的方式来进行处理。需求分析其实这个需求还算比较简单,需要实现的点主要有三个,一是如何进行登录,获取登录信息,查询当前用户下的产品数据;二是如何知道当前数据是否处理完,然后退出当前的处理流程;三是如何异步的处理一批数据。 所以需要做的工作就是模拟登录,调用产品列表的查询接口获取产品ID集合,然后循环遍历当前的集合,通过产品ID跳转产品详情页面,模拟页面按钮的点击操作,监听处理完成的动作,退出当前的流程。 Selenium 介绍What is Selenium?Selenium automates browsers. That's it! What you do with that power is entirely up to you. Primarily, it is for automating web applications for testing purposes, but is certainly not limited to just that. Boring web-based administration tasks can (and should!) be automated as well.Selenium has the support of some of the largest browser vendors who have taken (or are taking) steps to make Selenium a native part of their browser. It is also the core technology in countless other browser automation tools, APIs and frameworks.翻译过来大致意思就是: Selenium 可以自动化操作浏览器。怎么去使用Selenium 的功能完全取决于我们自己。它主要还是使用在web应用的自动化测试上。但是他的功能并不仅限于此。那些枯燥的基于web的管理任务也可以自动化。很多流行的浏览器都采取了一些措施来支持Selenium实现本地化。它也是很多浏览器自动化工具、API自动化以及框架的核心技术。 ...

October 9, 2019 · 2 min · jiezi

单线程Redis性能为何如此之高

文章原创于公众号:程序猿周先森。本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号。 实际项目开发中现在无法逃避的一个问题就是缓存问题,而缓存问题也是面试必问知识点之一,如果面试官好一点可能会简单的问你二八定律或者热数据和冷数据,但是如果问的深入一点可能就会问到缓存更新、降级、预热、雪崩、穿透等问题,而这些问题可能会拦下大部分平时不怎么关注缓存的朋友,这些问题实际上都和缓存服务器息息相关,我们日常中经常使用的缓存服务器一般有两种:Redis和Memcached。本篇开始正式进入Redis系列文章,本篇主要讲讲Redis使用单线程为何速度还能如此之快? 既然谈到缓存服务器有两种,那我们为何要选择Redis呢?Redis与Memcached两者之间有何区别呢? Redis 和 Memcached 的区别 Redis支持常见数据类型:Redis 不仅仅支持简单的 key/value 类型的数据,同时还提供string(字符串)、list(链表)、set(集合)、zset(有序集合)和hash(哈希类型)等数据结构的存储。而Memcache 只支持简单的数据类型 String。Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memecache 把数据全部存在内存之中。集群模式:Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 Cluster 模式的。Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型。 Redis是一个key-value存储系统。它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了主从同步。简单来说 Redis 就是一个数据库,不过与传统数据库不同的是 Redis 的数据是存在内存中的,所以存写速度非常快,因此 Redis 被广泛应用于缓存方向。Redis 也经常用来做分布式锁。Redis 提供了多种数据类型来支持不同的业务场景。除此之外,Redis 支持事务 、持久化、LUA 脚本、LRU 驱动事件、多种集群方案。Redis中常用的数据类型实际上只有5种:String、Hash、List、Set、ZSet,我们可以先看下这五种基本数据类型的用法: String 常用命令:set、get、decr、incr、mget 等。String 数据结构是简单的 Key-Value 类型,Value 可以是string或者数字。常规 Key-Value 缓存应用;常规计数:博客数,阅读数等。 Hash 常用命令:hget、hset、hgetall 等。Hash 特别适合用于存储对象。 List 常用命令:lpush、rpush、lpop、rpop、lrange 等。链表是 Redis 最重要的数据结构之一,Redis List 为一个双向链表,支持反向查找和遍历,更方便操作,不过带来了额外的内存开销。 Set 常用命令:sadd、spop、smembers、sunion 等。Set 其实和List都是列表的选项,Set 是可以自动去重的。当需要存储一个不出现重复数据的列表数据,Set 是一个最好的选择。你可以基于 Set 轻易实现交集、并集、差集的操作。 ...

October 9, 2019 · 1 min · jiezi

细谈Redis五大数据类型

文章原创于公众号:程序猿周先森。本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号。 上一篇文章有提到,Redis中使用最频繁的有5种数据类型:String、List、Hash、Set、SortSet。上一篇文章只是单纯介绍了下这5种数据类型使用到的指令以及常用场景,本篇文章会谈谈5种数据类型的底层数据结构以及各自常用的操作命令来分别进行解析。Redis作为目前最流行的Key-Value型内存数据库,不仅数据库操作在内存中进行,并且可定期的将数据持久化到磁盘中,所以性能相对普通数据库高很多,而在Redis中,每个Value实际上都是以一个redisObject结构来表示:typedef struct redisObject{ unsigned type:4; unsigned encoding:4; void *ptr; int refCount; unsigned lru:}我们可以看看这几个参数分别的含义: type:对象的数据类型,一般情况就是5大数据类型。encode:redisObject对象底层编码实现,主要编码类型有简单动态字符串,链表,字典,跳跃表,整数集合及压缩列表。*ptr:指向底层实现数据结构的指针。refCount:计数器,当引用计数值为0将会释放对象。lru:最后一次访问本对象的时间。String数据类型 String 数据结构是简单的 Key-Value 类型,是Redis中最常用的一种数据类型,Value 可以是string或者数字。String数据类型实际上可以存储字符串、整数、浮点数三种不同类型的值,Redis是如何做到自动识别字符串、整数、浮点数三种不同类型的值。Redis是使用C实现的,但是并未使用C中的字符串,实际上Redis自己实现了一个结构体SDS来替代String类型:struct sdshdr{ //记录buf数组中已使用字节的长度 int len; //记录buf数组中剩余空间的长度 int free; //字节数组,用于存储字符串 char buf[];}; 我们可以看到free参数是用来判断剩余可使用空间的长度,len表示字符串的长度,buf存储字符串的每一个字符以及结尾的'0'。为什么Redis要自己实现SDS结构体呢?因为SDS结构体有几个优点: 由于len保存了当前字符串的实际长度,所以获取长度时间复杂度为O(1)。SDS在拼接之前会对当前字符串的空间进行自动调整和扩展,防止当前字符串数据溢出。减少内存分配次数,SDS拼接字符串发生时,如果此时的字符串长度len小于1M,则SDS会分配和len大小相同的未使用空间给free,如果此时的字符串长度len大于1M,则SDS会分配和1M的未使用空间给free,当字符串缩短时,缩短的空间会叠加到free中,用于后续的拼接使用。String数据类型常用命令: 常用命令:set、get、decr、incr、mget 等。String数据类型适用场景: 分布式锁分布式session:将分布式应用session存储到Redis中商品秒杀常规计数:博客数,阅读数List数据类型 List数据结构是用来存储多个有序的字符串,List中的每个字符串成为元素,List提供了节点重排和节点顺序访问的能力,在Redis中,List可以在两端push和pop元素,还可以获取指定范围的元素列表,获取指定索引下标的元素等,List数据结构主要有zipList(压缩链表)和LinkedList(双向链表)两种实现方式。首先我们可以先看看LinkedList的结构:type struct list{ //表头节点 listNode *head; //表尾节点 listNode *tail; //包含的节点总数 unsigned long len;}; 可以看到每个LinkedList中都会包含一个表头节点head和一个表尾结点tail,在LinkedList中每个节点都会有一个prev指向前一个元素,同时还有一个next指向后一个元素,每个节点的value就是节点的值。从而实现双向链表,理解起来实际上和C中的双向链表有很大程度的相似性。而另一种实现方式zipList是基于连续内存实现,有点类似于数组方式,但是和数组有点不一致的是zipList的每一个entry的大小可能不一致,需要特殊方法去控制解决,但是在执行push,pop操作时会有数据的迁移,时间复杂度为O(n), 所以一般只有在元素较少时才会使用zipList,我们可以看看zipList的结构: type struct ziplist{ //整个压缩列表的字节数 uint32_t zlbytes; //记录压缩列表尾节点到头结点的字节数,直接可以求节点的地址 uint32_t zltail_offset; //记录了节点数,有多种类型,默认如下 uint16_t zllength; //节点 List entryX;} ...

October 9, 2019 · 1 min · jiezi

明白Nodejs中的Worker-Threads

原文 对于想了解,进程,线程,io这些东西的朋友推荐个文章 想要明白workers,首先需要明白node是怎样构成的。当一个node进程开始,它其实是: 一个进程。一个线程。一个事件轮垂。一个js引擎实例。一个node.js实例。一个进程:是指一个全局对象,这个对象能够访问任何地方,并且包含当前处理时的此时信息。 一个线程:单线程意味着单位时间内只有一组指令在给定的进程中执行。 一个事件轮垂:这是理解Node最重要的概念。它使Node更够异步以及拥有无锁定I/O。即使js是单线程的,通过提供一些系统核心的操作像是回调函数,promise函数以及异步的async/await函数这些功能。 一个JS引擎实例:这是个计算机程序,用来执行js的代码。 一个Node.js实例:一个计算机程序用来执行node.js的代码。 一句话,Node运行在一个单线程上,每次事件轮垂只有一个进程存在。一个代码一次执行(不是并行执行)。这个非常重要,因为它很简单,你不用考虑并发的问题。 这么设计的原因是因为js生出来最初是用来开发客户端交互的(像是页面交互,表单这些),没有对线程这种用复杂的需求。 但是,和所有的事情一样,这样也有缺点:如果你有cpu敏感的代码,例如内存中有大量的来回计算的复杂数据,那么这能锁住其他需要进行处理计算的任务。像是,你向服务器发起一个请求,应对这个请求的接口有cpu敏感的代码,那么它就能锁定事件轮垂进而阻止其他请求的处理(笔者:其实就是其他请求就需要长时间排队甚至超时)。 如果主事件轮垂必须等待一个函数执行完成然后才能执行其他命令,那么这个函数就是“锁定中”。一个无锁定函数会允许主事件轮垂从开始就持续地运行,并且在其执行完成时通知主事件轮垂调用回调函数。 黄金准则:不要锁定事件轮垂,尽量关注和避免那些可能造成锁定的任务,像是同步网路调用或者无线死循环。 明白cpu操作和i/o操作是很重要的。如上所讲,Node中的代码不能并行执行。只是i/o是并行,因为他们是异步执行的。 所以,worker线程(以下我们会使用这个node特有的概念)不能提升多少i/o敏感的任务,因为异步i/o本身就比worker高效很多。worker的主要任务是提升cpu敏感操作的性能。 已有的解决方案此外,这里已经有一些应对cpu敏感处理的方案:多进程(例如,cluster API)来保证cpu最大被利用。 这个方法好处是允许每个进程间是独立的,如果某个线程出了问题,不会影响到其他的。他们稳定且相同的api。然而,这意味着牺牲了内存共享,并且数据通信必须用json(有额外开销,性能稍低)。 JavaScript和Node.js是永远不会有多线程的。原因如下:so,有人或许会考虑给node.js添加一个新的模块来允许我们创建一个同步线程,以此来解决cpu敏感处理的问题。 然而,这不会实现的。如果添加一个线程,这个语言的本质就会发生变化。使用类或者函数添加一个线程作为新特性是不可能。在支持多线程的语言中(如java),“synchronized”之类的关键字就能帮助实现多线程。 还有,一些数据不是原子的,意味着如果你不是同步处理他们,你可能的结果是在两个线程上都可以访问并更改这个值得变量,最后得到一个两个线程都对这个者进行了一些改变的无效的值。例如一个简单的0.1+20.2的操作,这个操作拥有17为小数。 因为小数点不是100%准确的,所以如果不是同步的,有一个整数可能使用worker之后得到一个非整数的数字。 最好的解决方案是提高cpu性能的最好的方案是使用worker线程。浏览器很早既有了worker这个概念了。 使亿有的结构从: 一个进程 一个线程 一个事件轮垂 一个JS隐情实例 一个Node.js实例 变成: 一个进程 多个线程 每个线程一个事件轮垂 每个线程一个JS隐情实例 每个线程一个Node.js实例 worker_threads模块能够实现使用线程实现并行执行js。 const worker = require('worker_threads');Worker Theads在Node.10时开始可以使用,但是一直处于实验状态,在12.11.0时,变成稳定版本。 这个方案的意思是,在一个进程中拥有多个Node.js的实例。在worker threads中,一个线程可以有一些节点,这个节点不必是父进程。worker结束后还被分配着一些资源不是好的实践,这会导致内存泄漏。我们想把node.js整个的潜入其中,并且给与Node.js去创建新的现成的能力,然后在线程中创建一个新的Node.js实例。本质上是独立运行在一个进程中的线程中。 下面这些使Worker Theads与众不同: ArrayBuffers在线程间传递内存。SharedArrayBuffer每个线程都可访问,在线程间分享内存。(只限二进制数据)。Atomics已可用,允许你并行执行一些处理,更高效且允许你在js中实现条件变量。MessagePort,用来在不同线程间进行通信。可以用来传递结构数据,内存域,以及不同Worker之间的MessagePort(对象)。MessageChannel代表一个异步的,双向通信的频道,用来在不同的(worker)线程间通信。WorkerData用来传递起始数据。任意js数据的复制版本会被传递到这个Worker的构造函数中。如果使用postMessage(),数据也会被复制。 接口APIconst {worker, parentPort} = require('worker_threads'),worker类表示一个独立执行js的线程,parentPort是一个message port的实例。 new Worker(filename)或者new worker(code,{eval:true})两种开始一个worker的方法。(传递一个文件名字或需要执行的代码)。建议在生产中使用文件名字。worker.on('message'),worker.postmessage(data)`监听信息以及在不同的线程间发布数据。parentPort.on('message'),parentPort.postMessage(data),使用parentPort.postMessage()发送信息,在父线程中使用worker.on('message')来获取。在父线程中使用worker.postMessage()在该线程中(当前线程是子)使用parentPort.on('message')类获取。示例const { Worker } = require('worker_threads');const worker = new Worker(`const { parentPort } = require('worker_threads');parentPort.once('message', message => parentPort.postMessage({ pong: message })); `, { eval: true });worker.on('message', message => console.log(message)); worker.postMessage('ping'); 执行: ...

October 8, 2019 · 1 min · jiezi

这一次彻底弄懂-JavaScript-执行机制

以下内容转载掘金有位大牛ssssyoki写的的文章,链接地址:https://juejin.im/post/59e85e... 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我。 不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定的几行代码,我们需要知道其输出内容和顺序。因为javascript是一门单线程语言,所以我们可以得出结论: -javascript是按照语句出现的顺序执行的 看到这里读者要打人了:我难道不知道js是一行一行执行的?还用你说?稍安勿躁,正因为js是一行一行执行的,所以我们以为js都是这样的: let a = '1';console.log(a);let b = '2';console.log(b); 然而实际上js是这样的: setTimeout(function(){ console.log('定时器开始啦')});new Promise(function(resolve){ console.log('马上执行for循环啦'); for(var i = 0; i < 10000; i++){ i == 99 && resolve(); }}).then(function(){ console.log('执行then函数啦')});console.log('代码执行结束'); 依照js是按照语句出现的顺序执行这个理念,我自信的写下输出结果: //"定时器开始啦"//"马上执行for循环啦"//"执行then函数啦"//"代码执行结束"去chrome上验证下,结果完全不对,瞬间懵了,说好的一行一行执行的呢? 我们真的要彻底弄明白javascript的执行机制了。 一、关于javascriptjavascript是一门单线程语言,在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变。所以一切javascript版的"多线程"都是用单线程模拟出来的,一切javascript多线程都是纸老虎! 二、javascript事件循环既然js是单线程,那就像只有一个窗口的银行,客户需要排队一个一个办理业务,同理js任务也要一个一个顺序执行。如果一个任务耗时过长,那么后一个任务也必须等着。那么问题来了,假如我们想浏览新闻,但是新闻包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来?因此聪明的程序员将任务分为两类: -同步任务-异步任务 当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。关于这部分有严格的文字定义,但本文的目的是用最小的学习成本彻底弄懂执行机制,所以我们用导图来说明: 导图要表达的内容用文字来表述的话: -同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。 -当指定的事情完成时,Event Table会将这个函数移入Event Queue。-主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。 -上述过程会不断重复,也就是常说的Event Loop(事件循环)。 我们不禁要问了,那怎么知道主线程执行栈为空啊?js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。 说了这么多文字,不如直接一段代码更直白: let data = [];$.ajax({ url:www.javascript.com, data:data, success:() => { console.log('发送成功!'); }})console.log('代码执行结束');上面是一段简易的ajax请求代码: -ajax进入Event Table,注册回调函数success。-执行console.log('代码执行结束')。-ajax事件完成,回调函数success进入Event Queue。-主线程从Event Queue读取回调函数success并执行。 ...

October 8, 2019 · 2 min · jiezi

深入浅出webpack有感

对于前端仔来说,相信大家对webpack都再熟悉不过了,但是你对webpack的了解程度又有多深呢,笔者花了几天时间看了一下《深入浅出webpack》,虽然说书中大部分介绍的是配置和使用相关的,但是如果你对webpack的配置、使用、原理和构建流程更加熟悉的话,对于你的开发可以说是百里无一害!本文不会局限于介绍配置,也不会详细介绍打包原理(后面打算写一篇有关webpack打包原理的~),更多着重于webpack打包的思想介绍。没有打包构建的日子nodejs的出现对于构建工具具有重要的意义,在没有nodejs之前,js只能执行在浏览器环境下,所以意味着对发布前的js文件要进行处理,十分局限,没有打包工具,只能用PHP脚本来处理文件,甚至还需要借助一些在线压缩网站,开发体验十分差劲,在史前时代存在以下几个痛点:1、缺乏文件处理工具,对文件进行编译或其他预处理,进行打包压缩等工作;2、缺乏文件的模块化,引入第三方库直接用cdn引入,需要处理依赖管理,人为控制脚本的加载顺序,并且存在全局变量命名冲突的问题;3、缺乏代码校验和自动化测试,在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。 有了打包构建工具的日子随着nodejs的诞生,我们可以在开发环境下书写nodejs代码脚本,对我们的前端代码做预处理,编译压缩等工作,最初诞生的是grunt和gulp,Grunt和Gulp都属于任务流工具Tast Runner,两者都是通过配置好配置文件,但是相比之下,gulp通过函数式编写配置文件,以及前端人员所熟悉的链式调用,让大家觉得更易懂更易上手,gulp本身借鉴了grunt的经验进行升级和加入一些新特性。正因为流管理多任务配置输出方式的提高,人们逐渐选择使用Gulp而放弃grunt。有了grunt和gulp,文件压缩处理的工作解决了,代码校验和测试也可以处理了,但是模块化仍没有结果?其实前端的痛点还远不止模块化那么简单,频繁的DOM节点处理,JS里杂糅了交互逻辑、请求逻辑、数据处理和校验逻辑、DOM操作逻辑,导致JQ书写的代码就更意大利炒大便,呸!意大利炒面一样。在团队开发中,可能你的代码要给别人维护,这就非常痛苦了。 webpack诞生记1、模块化思想隔离不同的js文件,模块化开发,仅暴露当前模块所需要的其他模块,这是模块化思想想要传递给我们的。nodejs诞生后,后端所采取的模块化思想是commonjs,然而,不同于后端,前端的代码运行在浏览器端,有两点不同之处:1、没有nodejs执行环境,不支持module.exports的书写格式;2、后端require一个文件,是读取本地文件的形式,速度极快,而对于前端而言,需要去动态加载一个js文件,存在额外耗时。于是AMD思想应运而生,对此的相应实现是requireJS,允许你定义好模块名称、模块依赖以及当前的模块代码(function),通过广度优先遍历的方式,递归加载父模块所依赖的子模块,但是这也暴露出了一些问题:1、通过js加载执行后再去加载其依赖的子模块,这个递归加载过程本身是耗时的;2、模块化思想提倡我们分隔逻辑,管理好各个js文件内的逻辑,一旦分割的JS文件过多,最终造成前端资源加载压力。不过不用担心,requireJS提供了r.js来处理发布前的模块合成,帮助你把多个JS文件打包成一个文件。再到后来,国内出现了CMD的思想,不同于AMD的声明依赖的形式,允许你动态加载依赖,但是其实现以及具体的运行结果被大家诟病。再到后后来,为了解决这种不同库的模块实现不一致的问题,提出了UMD,其实很简单,只是写一段hack,让你的模块能够兼容不同的模块加载场景,无论是commonjs还是AMD,如果都没有的话就直接声明为一个全局变量的形式。下面引用一段UMD的代码: (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.Vue = factory());}(this, function () { 'use strict'; //your code here}再到后后后来,未来的大一统,es6中所提出的import和export的形式来统一前后端的模块化加载方式。相比以往的实现,import/export的形式,在模块化加载JS文件的时候,保留动态的执行引用,其次,不允许动态控制加载依赖,使得tree-shaking成为可能。 2、组件化思想组件化的思想并非前端所特有,在客户端也会面临相同的问题。想象一下,A跟B被同时分配一起开发完成一个首页页面,包括导航栏、轮播图、网站列表数据,登录框等,两人需要如何分工协作?导航栏、列表这种需要在多个页面复用的HTML怎么办?假如在没有组件化处理的情况下:1、A和B分工困难麻烦,代码提交时会处理大量的冲突;2、导航栏、列表等多处复用的地方,需要cv大法直接复制粘贴到另一个页面中去使用。组件化思想,让我们把页面划分为一个个组件,组件内部维护自己的UI展示、交互逻辑,组件间可以进行数据通信,实现一种变相的相互隔离,便不会出现A和B两人一起编辑一段html的难受场景,同时,提高了代码的可维护性和复用性,这是其解决的关键痛点。引用vue官网的一张有关组件化思想的图: 3、MVC框架、MVVM框架的流行在模块化和组件化的基础上,实现了页面组件之间的隔离和各自维护,但是面临的最大的一个痛点问题,仍然是前面所说的,前端的JS逻辑中杂糅了各种处理逻辑,交互逻辑、请求逻辑、数据处理和校验逻辑、DOM操作逻辑;而其实这一切可以划分为两个层次,一个是数据层,一个是视图层,如何避免重复的书写操作DOM的逻辑,如果说早期的各种模板引擎给了我们初期的解决方案,那么vue、react以及angular就是在模板引擎的基础上的上层建筑。为了让我们更加专注于数据的处理,MVC框架和MVVM框架帮我们做了以下两件事:1、监听页面操作事件,触发相应的事件钩子,执行代码逻辑,即V层到M层的过程;2、执行代码逻辑后,数据层发生修改,帮我们更新渲染页面,即M层到V层的过程;vue中通过vm实现,react中通过触发setState通知。如此,我们只需要书写一次html,在html中写明绑定或展示的数据,同时绑定好事件监听器,后续便不需要再处理视图层相关的操作,只需要关注于自己的业务逻辑代码、数据层的处理等。MVVM架构流程图: 4、代码打包构建前面介绍了grunt、gulp打包构建工具,其实webpack本质也是打包构建工具,但是webpack呈现出来的功能更为强大和成熟。对于代码的预处理、模块化加载、代码分割等,webpack具有更大的优势。 webpack诞生! 读者读到这里,可能仍有些许疑惑,前面讲了这么多,为啥还是没有介绍到webpack相关的,其实不然,仔细回想一下前面所介绍的思想,以及你平时使用webpack来打包构建的时候,其实webpack正是帮你处理了这些繁杂琐碎的事情。如果代码预处理压缩足以,那么grunt和gulp已经满足了;如果说模块化开发足以,那么requireJS和Browserify已经满足了;如果说组件化开发、MV*框架足以,那么只需要在页面内引入相应的vue或react框架,足矣。笔者写这边文章,更多是想让大家能够思考工具或者框架背后,所呈现出来的思想,webpack就像是一个巨无霸,集大成者,它解决了打包构建,它处理了模块化开发,它帮助你和其他框架完美融合实现组件化开发;而这几年来MV*框架的流行对于webpack市场的迅速扩展有着不小的贡献。 webpack为我们做了以下这些事:(引自《深入浅出webpack》)代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等。 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等。 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。 ...

October 8, 2019 · 1 min · jiezi

基于Topic消息路由的M2M设备间通信Node-JS-SDK-示例

概述M2M(即Machine-to-Machine)是一种端对端通信技术。本章节以Node JS SDK为例,使用基于Topic消息路由的M2M设备间通信,主要介绍如何基于物联网平台构建一个M2M设备间通信架构。实验步骤第一部分:配置相关 1、产品、设备、Topic的创建参考链接 消息路由建立 本部分目前不支持门户直接配置,需要基于管理API: CreateTopicRouteTable 来建立消息路由关系。测试可以直接使用OpenAPI来快速实现相关功能,本地集成相关功能直接基于SDK即可。 2、JAVA SDK Demo import com.aliyuncs.DefaultAcsClient;import com.aliyuncs.IAcsClient;import com.aliyuncs.exceptions.ClientException;import com.aliyuncs.exceptions.ServerException;import com.aliyuncs.iot.model.v20170420.CreateTopicRouteTableRequest;import com.aliyuncs.iot.model.v20170420.CreateTopicRouteTableResponse;import com.aliyuncs.profile.DefaultProfile;import com.google.gson.Gson;import java.util.*;public class CreateTopicRouteTable { public static void main(String[] args) { DefaultProfile profile = DefaultProfile.getProfile("cn-shanghai", "LTAIOZZg********", "v7CjUJCMk7j9aKduMAQLjy********"); IAcsClient client = new DefaultAcsClient(profile); CreateTopicRouteTableRequest request = new CreateTopicRouteTableRequest(); request.setRegionId("cn-shanghai"); List<String> dstTopicList = new ArrayList<String>(); dstTopicList.add("/a12OcQ4****/device2/user/RouteData"); request.setDstTopics(dstTopicList); request.setSrcTopic("/a12OcQ4****/device1/user/RouteData"); try { CreateTopicRouteTableResponse response = client.getAcsResponse(request); System.out.println(new Gson().toJson(response)); } catch (ServerException e) { e.printStackTrace(); } catch (ClientException e) { System.out.println("ErrCode:" + e.getErrCode()); System.out.println("ErrMsg:" + e.getErrMsg()); System.out.println("RequestId:" + e.getRequestId()); } }}注意:SDK版本差异按照实际版本调整即可。3、查询路由关系 ...

October 8, 2019 · 2 min · jiezi

写给前端工程师看的Docker教程基础篇

最近公司在推进容器化和k8s,项目都要改成Docker部署。负责的工程里有几个node项目,只能从零开始学习Docker了。 安装Docker支持window, Mac, Linux, 教程参考Docker安装教程。 建议在Mac和Linux系统里使用Docker。 平时开发,我使用的是vscode编辑器,可以顺便安装docker插件。在插件商店搜索docker,安装完成后,我们可以很方便的管理Docker镜像和容器。 快速使用首先我们来体验一下Docker。 平时工作中,如果我们电脑的开发环境是Windows, 有一天希望在Linux环境做一些事情,那该怎么办?(没有云服务器的前提下)大多数人这时会选择去用虚拟机安装一个ubuntu系统。不过安装虚拟机前,你得先去下载几个G的镜像,然后在VMware里配置一些参数,最后还要等待最少十几分钟的系统安装。等你安装完一个ubuntu系统,估计已经浪费了几个小时。 然而使用Docker,你只需要几分钟! # 拉取ubuntu镜像docker pull ubuntu# 创建一个ubuntu容器并且使用终端进行交互docker run -it --name my-ubuntu --rm ubuntu /bin/bash创建成功后,你就进入一个ubuntu系统里,现在你可以在其中进行任意的操作了。 注意:虽然当前容器里是ubuntu系统,但是你只能把它想象成一个精简版的ubuntu,因此有很多常用命令,需要自己去安装。 curl -v bilibili.com直接运行curl命令会提示命令不存在 # 安装curlapt-get updateapt-get install -y curl安装完成后,才能使用curl命令 退出容器 exit基本概念镜像(Image):类似于虚拟机中的镜像。镜像有两种:基础镜像和个人镜像。基础镜像由各大厂商提供,比如ubuntu镜像,node镜像。个人镜像则是由个人开发者构建上传。容器(Container):类似于一个轻量级的沙盒。容器是基于镜像来创建的,ubuntu镜像并不能和我们进行各种交互,我们希望有个环境能运行ubuntu,于是基于ubuntu镜像创建了一个容器。仓库(Repository):类似于代码仓库,这里是镜像仓库,是Docker用来集中存放镜像文件的地方。我们可以这样类比: # 下载源代码git clone deepred5/app# 启动appnpm run start# 拉取镜像docker pull deepred5/app# 创建容器docker run deepred5/appDocker是基于c/s架构:我们在Client中执行Docker命令,最后创建的Container和Image则会在Server中运行 # 可以查看server和client信息docker info镜像(Image)常用命令 # 查找镜像docker search ubuntu# 拉取特定tag版本的镜像(默认是latest)docker pull ubuntu:18.0.4# 查看下载的所有本地镜像docker images# 删除镜像docker rmi ubuntu:18.0.4构建镜像 我们一般都是基于基础镜像来构建个人镜像。镜像是由一条条指令构建出来(Dockerfile) 我们来构建一个node-pm2镜像,这个镜像自带node和pm2: 创建一个node-pm2目录,并新建一个Dockerfile文件 ...

October 8, 2019 · 2 min · jiezi

写给前端工程师看的Docker教程实战篇

在上篇文章里,我们学习了Docker常用的命令和基本操作,现在可以开始实战了。 单页应用前端工作中最常见的就是单页应用了。我们首先用create-react-app快速创建一个应用 npm i create-react-app -gcreate-react-app react-appcd react-appnpm run start可以看见正常启动的页面。 打包试一下 npm run build可以看到本地生成了一个build目录,这就是最后线上运行的代码。 <!-- more --> 我们先在本地运行下build目录看看 npm i http-server -ghttp-server -p 4444 ./build访问 http://localhost:4444 即可看到打包后的页面 单页应用Docker化在react-app目录下新建Dockerfile .dockerignore和nginx.conf .dockerignore node_modulesbuilddockerignore指定了哪些文件不需要被拷贝进镜像里,类似.gitignore。 我们知道单页应用的路由一般都被js托管,所以对于nginx需要特别配置 nginx.conf server { listen 80; server_name localhost; location / { root /app/build; # 打包的路径 index index.html index.htm; try_files $uri $uri/ /index.html; # 防止重刷新返回404 } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; }}Dockerfile ...

October 8, 2019 · 3 min · jiezi

聊聊程序员的几条出路

背景前段时间, 又被35岁失业论刷了屏。说实话, 我也仔细考虑过这个问题,偶尔也很会焦虑 。 这个问题, 简而言之就是: 作为程序员, 未来的路在何方? 我觉得, 自己会有这个疑问和焦虑, 主要是对自己的定位和规划不明确导致的。 如果你也有过类似的疑惑和思考, 说明你已经开始考虑自己的职业规划了。 先说说我吧,16年毕业, 如今已经迈入了毕业的第四年。 前三年我和大家一样, 勤勤恳恳的搬砖。 后来有幸得到老板赏识, 开始带人做项目, 为另一条路开了一扇门。 程序员的几条出路 这个图很形象, 我简单的划分一下: 道路一: 成为技术专家道路二: 进入管理层道路三: 转型其他职业(比如: 产品经理, 项目经理,或者下海卖炒粉)道路一:如今各大公司一般都有清晰的职业等级划分,大体上可以分成: 初级中级高级资深/专家之前我也写过一篇文章介绍这几种不同等级的区别, 感兴趣的可以看看: [聊一聊初中高级工程师] https://segmentfault.com/a/11... 在这里也再简单总结下: 初级工程师: 掌握基础的编程技能,一般是刚毕业没多久, 或者从业不 太久的一类人, 需要有人带。中级工程师: 参与过一些项目, 能独立完成开发任务,知道遇到问题如何协调。高级工程师: 能独当一面,可以协调一些资源, 影响一个团队, 是每一个公司的核心人员。资深/专家: 有点超纲。 不过我个人认为,这类人可以影响一个领域,或者公司的某个部门, 有一定的影响力, 有比较深的工程经验。 其实, 看完之后不难发现, 上面的内容, 简单可以归结为两个方面: 能力影响力可以说, 程序员的发展过程, 就是发展 个人能力 和 个人影响力 的过程。 如果你想走技术这条线, 就可以把精力放在这两个点上。 不过要走这条路之前, 你可能考虑一个问题: 我有没有计划写代码到退休? 自己的血条能不能撑到这一天?如果回答是No, 那可能就要另寻出路了。 ...

October 8, 2019 · 1 min · jiezi