用-node-搭建个人博客

项目地址这个项目是为了学习 node 而建的,从前端到后端一手包办。相对来说,还是有一定难度的,适合有一定编程基础的人进阶学习。 如果有问题,欢迎提 issues 注意,本项目的前后端代码都是放在一起的,前端代码放在 src 目录,后端代码放在 server 目录。 相关文档多个请求下 loading 的展示与关闭Vue 实现前进刷新,后退不刷新的效果Vue 页面权限控制和登陆验证用 node 搭建个人博客(一):代码热更新用 node 搭建个人博客(二):导出模块同时兼容 import 和 require用 node 搭建个人博客(三):token用 node 搭建个人博客(四):评论功能用 node 搭建个人博客(五):数据库前端页面首页(index)包含内容及标签子页面 编辑(editor)登陆(login)管理(manage)使用的库、框架前端vuevue-routervuexvue-markdowniviewaxios后端nodeexpressjsonwebtoken数据库mongodb测试mocha使用需要先下载 mongodb,建议按照windows 安装教程一步步安装。 mongodb 教程一mongodb 教程二在安装完 mongodb 后,打开 mongod.exe 和 mongo.exe,然后克隆项目 git clone git@github.com:woai3c/node-blog.git安装依赖 npm i开发环境(前端代码热更新,修改完即可查看效果,后端代码修改完需要重启服务) npm run dev生产环境 (打包并开启服务,打包过程有点长,需求等待) npm run build测试 npm run test访问地址 http://localhost:8080/

June 23, 2019 · 1 min · jiezi

Yarn-cache

Yarn cache之坑迫于电脑C盘只是128G的固态,虽然还有1个T的机械空间,但完全没用呀????。眼看C盘越来越小,没办法开始网上各种搜索,优化C盘的办法。 首先是发现了TreeSize神器,这个能直接看到每个盘下面文件夹的大小,真的是一目了然,强烈推荐。 这个时候就发现了Yarn会在C盘个人目录下面缓存大量的包,发现的时候已经6个G了????。 下面就介绍一下Yarn提供的几个缓存命令: 一、yarn cache dir运行这个命令会显示出当前缓存目录,默认为C盘。例如我电脑的为 C:\Users\***\AppData\Local\Yarn\Cache\v4二、yarn cache clean [<module_name...>]运行这个命令会清理缓存包,如果没有指定包名则会全部清理,指定了就清理相关包。不过要注意,要先清理然后再配置目录,可能会因为配置完目之后,导致之前缓存下来的没法删除。 三、yarn config set cache-folder <path>设置当前缓存目录,例如我指定了缓存目录: yarn config set cache-folder G:\YarnCache运行完当然可以再次运行yarn cache dir,来检查下是否配置成功,当然也可以找一个项目运行yarn install来试下。 四、同样的道理npm也会在用户目录下面缓存大量的文件,不过比yarn少了很多,只有1个G左右。npm config get cachenpm cache clean --forcenpm config set cache "D:\ProgramFile\nodejs\node_modules\node_cache"npm cache verify:验证清理的有效性原文地址:https://xiaofeihe1993.github.... 参考https://yarn.bootcss.com/docs...

June 23, 2019 · 1 min · jiezi

记录vuecli脚手架引入弹出层layer插件

如何引入 在vue-cli里,引入文件有几钟方法 一种是用npm或者cnpm指令去下载对应的插件,然后在main.js里用import方法引入,这里不讨论这种方法 我比较喜欢采用的是直接下载对应的js,然后引入到vue项目中去 问题来了,如何引入呢方法如下: 下载对应的js文件或者css文件,一般下载插件相关联的都会在一起进入vue-cli项目工程里的index.html文件,分别引入css文件和js文件 在这里,分别是 <link rel="stylesheet" href="./static/layer.css" /> <script src="./static/jquery.js"></script> <script src="./static/jquery.min.js"></script> <!-- 必须先引入上面jq 1.8版本以上的才能引layer --> <script src="./static/layer.js"></script> 这样在全局文件里都可以使用layer弹出层插件了,需要注意,必须先引入jq 1.8以上的版本才可以使用layer噢。 2.弹出层显示不出 作为一个java开发小菜鸟,踩前端的坑基本是面向百度进行尝试解决,经历了时常两个半小时的挣扎,解决了几个问题:第一个是前端控制台报错,印象里是i is not a function 还有什么layer.open is not a function等,这些问题基本是出于引入的问题,解决途径:检查是否在之前引入了1.8以上的jq第二个是弹出层只有文字显示,但是缺乏样式,没有弹出层的feel。经过大量百度和分析,最后还是依靠调试前端样式看出端倪,问题在于里面用到的class在我引入的css文件里并不存在。经过检查,导入的css文件错误(注意有手机的css样式和另一个css,这里要用另一个,名称一样)。最终问题解决。

June 23, 2019 · 1 min · jiezi

node使用JsonWebToken-生成token完成用户登录登录检测

最近研究登陆,怎么用node完成token的登陆验证,前端为vue。jsonwebtoken是一个跨域认证标准,它的好处就是可以跨域,跨平台。而且由于服务端不需要保存token信息,开发起来非常简单。第一步安装: npm install jsonwebtoken第二步密钥:详细参数可以看这里链接描述jwt.sign(payload, secretOrPrivateKey, [options, callback]),在里面有三个参数:1、payload可以是object, buffer或者string。2、secretOrPrivateKey,这个参数是加密的key,包含HMAC算法的密钥或RSA和ECDSA的PEM编码私钥的string或buffer3、其余的一些设置。包括:{ algorithm:加密算法(默认值:HS256) expiresIn:以秒表示或描述时间跨度zeit / ms的字符串。如60,"2 days","10h","7d",Expiration time,过期时间} 这里我为了增加密钥的复杂性,我使用了RSA256加密jwt,借助openssl来创建RSA256密钥对:方法如下:先下载安装openssl,安装链接参考。然后打开openssl.exe,生成密钥文件 第三部,密钥有了,我们可以在登陆接口生成jwt给客户端了:先引入 const jwt = require('jsonwebtoken'); fs读取文件的时候读相对路径经常会找不到,借助path来读绝对路径给fsjwt里存储用户的id和username,expiresIn表示过期时间。第四步验证: 生成、验证token大致就是这样了。链接参考:阮一峰老师的这篇博客JSON Web Token 入门教程

June 22, 2019 · 1 min · jiezi

深入koa源码一架构设计

本文来自《心谭博客·深入koa源码:架构设计》前端面试、设计模式手册、Webpack4教程、NodeJs实战等更多专题,请来导航页领取食用所有系列文章都放在了Github。欢迎交流和Star ✿✿ ヽ(°▽°)ノ ✿最近读了 koa 的源码,理清楚了架构设计与用到的第三方库。本系列将分为 3 篇,分别介绍 koa 的架构设计和 3 个核心库的原理,最终会手动实现一个简易的 koa。 koa 的实现都在仓库的lib目录下,如下图所示,只有 4 个文件: 对于这四个文件,根据用途和封装逻辑,可以分为 3 类:req 和 res,上下文以及 application。 req 和 res对应的文件是:request.js 和 response.js。分别代表着客户端请求信息和服务端返回信息。 这两个文件在实现逻辑上完全一致。对外暴露都是一个对象,对象上的属性都使用了getter或setter来实现读写控制。 上下文对应的文件是:context.js。存了运行环境的上下文信息,例如cookies。 除此之外,因为request和response都属于上下文信息,所以通过delegate.js库来实现了对request.js和response.js上所有属性的代理。例如以下代码: /** * Response delegation. */delegate(proto, "response") .method("attachment") .method("redirect");/** * Request delegation. */delegate(proto, "request") .method("acceptsLanguages") .method("acceptsEncodings");使用代理的另外一个好处就是:更方便的访问 req 和 res 上的属性。比如在开发 koa 应用的时候,可以通过ctx.headers来读取客户端请求的头部信息,不需要写成ctx.res.headers了(这样写没错)。 注意:req 和 res 并不是在context.js中被绑定到上下文的,而是在application被绑定到上下文变量ctx中的。原因是因为每个请求的 req/res 都不是相同的。 Application对应的文件是: application.js。这个文件的逻辑是最重要的,它的作用主要是: 给用户暴露服务启动接口针对每个请求,生成新的上下文处理中间件,将其串联对外暴露接口使用 koa 时候,我们常通过listen或者callback来启动服务器: const app = new Koa();app.listen(3000); // listen启动http.createServer(app.callback()).listen(3000); // callback启动这两种启动方法是完全等价的。因为listen方法内部,就调用了callback,并且将它传给http.createServer。接着看一下callback这个方法主要做了什么: ...

June 21, 2019 · 1 min · jiezi

解决npm-install卡住不动的小尴尬

npm install卡顿问题记录遇到的问题npm install -g @angular/cli安装angular cli工具时,发现进度条一直卡住不动,相信很多朋友也遇到过。原因应该是国内的网络连接npm速度较慢,甚至很多东西都无法下载安装。那么如何解决这个问题呢? 方案一:安装cnpm镜像这个是比较常用的方法,我首先也是使用了这个方法。cnpm的安装方法,参考http://npm.taobao.org/ npm install -g cnpm --registry=https://registry.npm.taobao.orgcmd输入以上命令就可以了,然后输入 cnpm install -g @angular/cli后面的操作跟不使用镜像的操作是差不多的。但是笔者在后续使用过程中遇到了一些问题,运行ng eject后发生了一些错误,所以放弃了这个方案,采用了方案二。 方案二:使用代理registry在网上查阅了一些资料后,决定使用代理的方式,方法也很简单,就是 npm config set registry https://registry.npm.taobao.org然后后续的install等命令还是通过npm运作,而不是cnpm。有点小强迫症的我还是喜欢npm install...

June 21, 2019 · 1 min · jiezi

Linux-安装-nodejs-和-npm

记录一次完整的部署服务到远程服务器上的过程 - 第三篇今天开始捣鼓服务器,安装 node 环境,查看了网上的方法,大体分两种:1.直接命令行安装。 2.本地下载安装包上传到服务器解压安装。本人更倾向于第二种,因为年纪大了,需要更直观些。 1. 首先,查看一下服务器的系统信息,然后用本地电脑去node.js官网下载一个包。1-1. 查看服务器信息,输入 uname -a [root@localhost ~]# uname -a显示如下内容显示的是 linux x86_64 系统 1-2. 于是,我在官网下载了下图的安装包到本地,等待上传到服务器。 2. 压缩包从本地(我放置在做桌面上)上传到服务器任意位置,我是放置到 /test 下 hallluck:~ faithfairy$ scp /Users/faithfairy/Desktop/node-v12.4.0-linux-x64 root@192.168.888.888:/test/ hallluck:~ faithfairy$ ssh root@192.168.888.888 [root@localhost ~]# mv /test/node-v12.4.0-linux-x64 /usr/local/node3. 在/usr/bin 目录下建立软连接 ,变为全局 [root@localhost ~]# cd /usr/bin [root@localhost ~]# ln -s /usr/local/node/bin/node node [root@localhost ~]# ln -s /usr/local/node/bin/npm npm4. 环境搭建完毕,查看下效果 [root@localhost ~]# node -v [root@localhost ~]# npm -v5. 选装 cnpm ...

June 21, 2019 · 1 min · jiezi

apiDoc生成接口文档不费吹灰之力

效果 背景之前做前端的时候,后端同学仗着自己是老同志,不给我接口文档 苦逼如我,需要拿着笔坐在他的旁边,听他口述 写下需要的api接口url和参数等等 现在自己做后端了,那不能这样子胡作非为了 自己吃的苦,怎能给其他同学吃呢? 这时候,apiDoc你值得拥有,稳稳的输出一篇优质的接口文档 安装apidoc官网上是全局安装,我是喜欢安装到项目中,这样可以在另一个环境下,npm install就可以下载有所有依赖包 npm install apidoc --save-dev/-D写注释注册接口的注释/** * @api {post} /v1/auth/register User Register * @apiName UserRegister * @apiGroup userAuthentication * * @apiParam {String} username New user's username. * @apiParam {String} password New user's password. * * @apiSuccess {String} username The username of the register user. * @apiSuccess {string} message The registering success info. * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK * { * "username": "username", * "message": "User registered successful" * } * * @apiError REGISTER_FAILURE The register failure. * * @apiErrorExample Error-Response: * HTTP/1.1 500 Internal Server Error * { * "err": "REGISTER_FAILURE", * "message": "User register failure!" * } */删除接口的注释/** * @api {delete} /v1/auth/user/ User delete * @apiName UserDelete * @apiGroup userAuthentication * * @apiParam {String} username User's username. * @apiParam {String} executor Executor of this operation. * * @apiSuccess {String} username The username of the deleted user. * @apiSuccess {string} message The message if deleting successful. * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK * { * "username": "username", * "message": "Delete User Successful" * } * * @apiError NOT_LOGIN The register failure. * * @apiErrorExample Error-Response: * HTTP/1.1 401 Unauthorized * { * "err": "NOT_LOGIN", * "message": "User has not logon in!" * } */写入命令将apidoc -i routers/写入package.json的命令中 ...

June 21, 2019 · 4 min · jiezi

gulp小结

gulp是什么?一个自动化构建工具,基于nodejs的自动任务运行器。 为什么要使用它?易于使用,易于学习。它能自动化地完成javascript/coffee/sass/less/html/image/css 等文件的的测试、检查、合并、压缩、格式化、浏览器自动刷新、部署文件生成,并监听文件在改动后重复指定的这些步骤。在实现上,她借鉴了Unix操作系统的管道(pipe)思想,前一级的输出,直接变成后一级的输入,使得在操作上非常简单。 和grunt、webpack的区别gulp和grunt非常类似,但相比于grunt的频繁IO操作,gulp的流操作,能更快地更便捷地完成构建工作。前两者定位是工具,webpack则是种模块化解决方案。 说到 browserify / webpack ,那还要说到 seajs / requirejs 。这四个都是JS模块化的方案。其中seajs / require 是一种类型,browserify / webpack 是另一种类型。 seajs / require : 是一种在线"编译" 模块的方案,相当于在页面上加载一个 CMD/AMD 解释器。这样浏览器就认识了 define、exports、module 这些东西。也就实现了模块化。 browserify / webpack : 是一个预编译模块的方案,相比于上面 ,这个方案更加智能。 gulp也能调用webpack。 为什么要用4.0?组合任务。比如以前的gulp对多个异步任务很难控制,必须借助于第三方模块,如run-sequence、event-stream等,效果也并不理想。 现在gulp带来了两个新的api:gulp.series【顺序】和gulp.parallel【并行】,这两个革命性的api将帮助开发者解决恼人的任务流程控制问题。 //clear任务执行完后,才会执行copygulp.task("build", gulp.series("clear","copy") );//inject:home和inject:list这2个任务同时执行 gulp.task("inject-all", gulp.parallel("inject:home", "inject:list" ) );小技巧:一个任务中需要立即执行一下任务,以前版本有gulp run XX,新的没有,但可以这样: gulp.parallel('XX')();gulp.series('XX')();支持异步任务有3种方式确认gulp能够识别任务何时完成。后2者重要是return 回调返回一个流返回一个Promise更多参见这篇文章:http://codecloud.net/10666.html npm包管理包管理主要在根目录下的 package.json文件。scripts中是一些npm的任务,npm run dev 即可执行。 dependencies是项目中必须的包,目前我们没有用到,只有个vue。devDependencies是开发所用的包,发布到生产环境不需要的都放在这里,平时安装时需要用npm install -save-dev XX,可简写作npm i -D XX。 同时安装多个包可以这样:npm install -save-dev aa bb。或者将包复制到package文件里,直接在根目录命令行里npm i或npm install。 ...

June 20, 2019 · 2 min · jiezi

使用express来代理服务

nodejs和nginx都可以反向代理,解决跨域问题。 本地服务const express = require('express')const app = express()//如果它在最前面,后面的/开头的都会被拦截app.get('/', (req, res) => res.send('Hello World!'))app.use(express.static('public'));//静态资源app.use('/dist', express.static(path.join(__dirname, 'public')));//静态资源//404app.use('/test', function (req, res, next) { res.status(404).send("Sorry can't find that!");});app.use(function (req, res, next) { //TODO 中间件,每个请求都会经过 next();});app.use(function (err, req, res, next) { //TODO 失败中间件,请求错误后都会经过 console.error(err.stack); res.status(500).send('Something broke!'); next();});app.listen(4000, () => console.log('Example app listening on port 4000!'))与request配合使用这样就将其它服务器的请求代理过来了 const request = require('request');app.use('/base/', function (req, res) { let url = 'http://localhost:3000/base' + req.url; req.pipe(request(url)).pipe(res);});使用http-proxy-middlewareconst http_proxy = require('http-proxy-middleware');const proxy = { '/tarsier-dcv/': { target: 'http://192.168.1.190:1661' }, '/base/': { target: 'http://localhost:8088', pathRewrite: {'^/base': '/debug/base'} }};for (let key in proxy) { app.use(key, http_proxy(proxy[key]));}监听本地文件变化使用nodemon插件。--watch test指监听根目录下test文件夹的所有文件,有变化就会重启服务。 ...

June 20, 2019 · 1 min · jiezi

再谈前端项目的组件化

再谈前端项目的组件化之前详细聊过的前端项目的组件化,可以参考 组件化 与 私有 npm 仓库,今天来更进一步的说说前端项目的组件化。 1. 之前的组件化目录结构: -project1 # 项目1-project2 # 项目2-component1 # 组件1-component2 # 组件2project1 的 package.json: { "dependencies": { "@yourCompany/component1": "^0.0.1", "@yourCompany/component2": "^0.0.1" }}在代码中使用: import component1 from '@yourCompany/component1';2. 之前的组件化方式存在的问题更新组件比较麻烦,特别是对于一些与业务耦合比较深的组件,频繁更新会比较头疼组件太多的时候,管理起来就感觉比较累,因为每个组件都是一个单独的项目,都有一套独立的构建环境对于有些代码量小的组件,做一个单独的项目,实在有点大才小用3. 另外的项目组件化方式针对上面讲到的问题,另一种方式可以很好的解决: 目录结构: -project1 # 项目1-project2 # 项目2-components # 组件集合项目components 组件集合项目的目录结构: - src/ # 源代码目录 - component1 # 组件1 - component2 # 组件2 - component3 # 组件3 - ...- package.json- README.md- CHANGELOG.md- .eslintrc.js- .stylelintrc.js- .prettierrc.js- ...把 components 目录软链接 project1 目录下: ...

June 20, 2019 · 1 min · jiezi

Vue多环境代理配置

Vue多环境代理配置背景: 多人协作模式下,修改代理比较麻烦,而且很容易某个开发人员会修改了vue.config.js文件后提交了。第一,很容易引起冲突。 第二,很容易出现代理错误,需要排查。而且现在微服务盛行,在生产环境下有网关配置,不会出错,但是在本地调试会造成麻烦,如修改了代理地址需要同步修改代理中的地址,很麻烦也很容易出错。解决思路: 1.开发中定义常量js文件,例如constants.js。用户存放各个服务需要代理的服务名。 let api = "" let loginServer = "/loginServer" let businessServer = "/businessServe" if(process.env.NODE_ENV == "development"){ api = "/api" loginServer = api + LoginServer businessServer = api + businessServer } export { loginServer, businessServer }其中api为代理规则中配置,loginServer为服务名,可根据业务需要替换在实际的业务中就可以这么用 import {loginServer} from 'constants' function login(params){ return axios.post(loginServer+"/login",params) }其中 loginServer为服务名,login为方法名,params为参数。在vue.config.js中配置代理 modules.exports = { publicPath:"/" , devServer: { port: 8080, proxy:{ '/api/loginServer':{ target:"http://localhost:8080", ws:true, changeOrigin:true, pathRewrite:{ '^/api':'/' } }, '/api/businessServer':{ target:"http://localhost:8081", ws:true, changeOrigin:true, pathRewrite:{ '^/api':'/' } } } }}这么配置可以满足需求,但是会有多人改动vue.config.js的情况,造成以上说的错误。 ...

June 20, 2019 · 1 min · jiezi

postman使用说明

本文主要描述postman的功能与使用方法Postman是404大厂的基于javascript语言完成的一款超级强大的插件,名字也很亲近(邮递员)。可以用于做API请求测试。前端后台测试使用Postman都可以提供很多帮助,使用方便而且功能全面。最赞的是还有接口文档在线生成,一边测试一边就可以完成文档的编写。 环境Postman有各种版本Mac,windows,linux上都能运行。此文主要介绍windows上的。安装1.可以选择在postman官网免费下载安装。2.如果怕麻烦可以直接点击右方下载 下载地址安装postman如果有不懂的小伙伴 可以传送至postman详细安装教程 功能模块 Collections:在postman初始化启动时,可以选择创建Collections。Collections就是项目所有的接口集合,可以分别存放各个模块的请求。通过collection的归类,良好的支持了分类测试软件所提供的API。Collection 还支持一键导入和分享,节约开发团队沟通成本。最厉害的是它还可以设置程序的环境,设置全局变量。提供json格式一键导入,让我们可以区分测试环境与正式环境的环境变量不同。也可以从面板将请求创建Collections: Workspace:postman对合作开发非常友好,在postman中可以通过邀请好友加入团队来共同协作完成测试与编写。点击Invite即可邀请成员加入团队。也可以创建自己的独立Workspace来隔离不同的项目。一个分布式的项目管理系统。 personal里是自己的工作空间,Team是你创建的团队或所加入的团队。 Built-in Tools:postman提供了一系列的工具方便测试,主要体现在面包板右侧的builder区。 点击send发送请求以验证您的API是否200。预请求脚本(pre-request Script)可以设置环境参数且可以先从其他接口获取数据再传入变量,可以使你的调试更加有效强大。在面板下方显示栏中,可以检查API的响应。点击右上角齿轮按钮可以配置环境,可以设置在其中的多个位置保存和重用值。postman可以配置proxy代理。Postman支持Markdown作为请求,Collection 和文件夹设置文本描述样式的方法。您还可以嵌入屏幕截图和其他图像,获得更具描述性的风格的API文档。Postman提供mockServer功能,模拟服务器自定义接口返回数据。目前只支持http请求 (未完待续。。。)

June 20, 2019 · 1 min · jiezi

服务器发布VueNuxt项目指南多图

很多前端朋友可能不是那么了解服务器配置。今天突然翻到之前写的这篇文章,修改完善了之后分享给大家一些常见的的Web服务器部署项目的方式。 写在前面下面讲的每一种服务器深入进去都很复杂,在这篇文章只是讨论一下基本的部署和使用。更高级的知识和用法还需要各位朋友自行去探索和发现, 开始阅读之前希望大家能先了解一些Linux基础,就不至于看起来吃力了。 注: 下面所有的例子大多基于vue-router的history模式下打包生成的静态文件,其他框架也都大同小异 Nginx服务器Nginx 是一个高性能的HTTP和反向代理web服务器,在连接高并发的情况下,Nginx表现相当出色。 Nginx安装这里就不讲这个了吧, 有需要的朋友可以看这个 配置修改为了支持history模式, 我们要修改nginx/conf/nginx.conf文件 location / { root html; try_files $uri $uri/ /index.html; # 只需要加上这么一行 index index.html index.htm;} 然后把静态资源放在html文件夹内 然后启动Nginx服务器 cd usr/local/nginx/sbin./nginx接着访问你的服务器就行OK了。 GZip支持nginx实现资源压缩的原理是通过ngx_http_gzip_module模块拦截请求,并对需要做gzip的类型做gzip压缩,该模块是默认基础的,不需要重新编译,直接开启即可。大体配置如下 #开启和关闭gzip模式gzip on|off;#gizp压缩起点,文件大于1k才进行压缩gzip_min_length 1k;# gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间gzip_comp_level 1;# 进行压缩的文件类型。gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript ;#nginx对于静态文件的处理模块,开启后会寻找以.gz结尾的文件,直接返回,不会占用cpu进行压缩,如果找不到则不进行压缩gzip_static on|off# 是否在http header中添加Vary: Accept-Encoding,建议开启gzip_vary on;# 设置压缩所需要的缓冲区大小,以4k为单位,如果文件为7k则申请2*4k的缓冲区 gzip_buffers 2 4k;# 设置gzip压缩针对的HTTP协议版本gzip_http_version 1.1;Nginx配置虽然简单,但是它本身是非常强大的,代理,负载等等都是非常具有实用性的。 Apache服务器Apache是世界使用排名第一的Web服务器软件, 使用非常广泛。由于VueRouter的hash模式本质上和静态资源没什么区别,在Apache上发布又比较简单,这里就跳过了发布直接进入配置支持History模式 基础配置修改Apache默认配置首先要重新修改\conf\httpd.conf文件让文件支持rewrite 找到 // 这一行需要解开注释 引入这个模块LoadModule rewrite_module modules/mod_rewrite.so 然后新增或者修改下面得代码 ...

June 19, 2019 · 2 min · jiezi

Mac-升级-vue-3x-之前卸载-vue-296-失败的原因和解决方法

之前安装了 vue 2.9.6 的版本,现在 vue 官方升级到了 3.x 版本,所以想卸载掉 2.9.6 的版本,安装 3.x 版本,结果输入官方给出的命令 npm uninstall vue-cli -g 却无法成功卸载,一直提示 up to time in 0.03s,然后百度和 Google 了好久,也没见到有效的解决方法,最终沉下心慢慢的研究,终于知道了原因,记录下来,方便遇到相同问题的人。先介绍原因吧,可能大家都知道,npm 是 node 包管理器的简称(node package manager),在我们安装 node 的时候会一并安装。正常情况下,我们安装的 node 在 /usr/local/bin 目录下,而 npm 在 /usr/local/lib/node_modules 目录下,然后我们通过 npm 的全局命令安装的模块也在 /usr/local/bin 目录下(例如 npm install vue-cli -g),平时我们在终端里使用的 node --vesion 或者 vue --version 这种命令其实调用的就是 /usr/local/bin 中的命令。而我们使用 npm uninstall vue-cli -g 命令卸载 vue-cli 模块的时候,也是在这个目录去找,去卸载。但是如果我们在已经安装 vue 的情况下再安装 nvm(node 版本管理器,node version manager),就会出问题,nvm 会改变 npm 全局安装模块的路径,我这边的路径变成了 ~/.nvm/versions/node/v10.15.1/bin,在安装 nvm 之后我再使用 npm 的全局安装命令安装的模块全都在这个目录下。那么我们使用 npm uninstall vue-cli -g 命令去卸载 vue 当然是不会成功的,因为它是在 ~/.nvm/versions/node/v10.15.1/bin 目录去卸载,而我们的 vue 安装在 /usr/local/bin 目录下。那么知道了原因之后,就得解决问题了,下面的方法可能不是最好的,大家可以探索更好的方法。 ...

June 19, 2019 · 1 min · jiezi

node函数使用

在JavaScript中,一个函数可以作为另一个函数的参数。我们可以先定义一个函数,然后传递,也可以在传递参数的地方直接定义函数。 Node.js中函数的使用与Javascript类似。 function say(word) { console.log(word); } function execute(someFunction, value) { someFunction(value); } execute(say, "Hello");以上代码中,我们把 say 函数作为execute函数的第一个变量进行了传递。这里传递的不是 say 的返回值,而是 say 本身!这样一来, say 就变成了execute 中的本地变量 someFunction ,execute可以通过调用 someFunction() (带括号的形式)来使用 say 函数。当然,因为 say 有一个变量, execute 在调用 someFunction 时可以传递这样一个变量。 node.js函数 1.不带参的函数 function sayhello(){ console.log('Hello World'); } sayhello() //运行结果 Hello World 2.带参的函数 function sayyouwrite(youwrite){ console.log(youwrite); } sayyouwrite('你好') //运行结果 你好 3.多个参数函数 function sayyouwrite2(youwrite1,youwrite2,youwrite3){ console.log(youwrite1+youwrite2+youwrite3); console.log(youwrite1); console.log(youwrite2); console.log(youwrite3); } sayyouwrite('你好') // 运行结果 // 你好!世界!中国! // 你好! // 世界! // 中国! 4.匿名函数 function execute(someFunc, value) { someFunc(value) } execute(function (world) { console.log(world) }, "Hello world")函数的调用 ...

June 19, 2019 · 2 min · jiezi

使用AWS-Lambda打包S3文件

首先创建lambda函数: const AWS = require('aws-sdk')const s3Zip = require('s3-zip')exports.handler = function (event, context) { console.log('event', event) const region = event.region const bucket = event.bucket const folder = event.folder const files = event.files const zipFileName = event.zipFileName // Create body stream try { const body = s3Zip.archive({ region: region, bucket: bucket}, folder, files) const zipParams = { params: { Bucket: bucket, Key: folder + zipFileName } } const zipFile = new AWS.S3(zipParams) zipFile.upload({ Body: body }) .on('httpUploadProgress', function (evt) { console.log(evt) }) .send(function (e, r) { if (e) { const err = 'zipFile.upload error ' + e console.log(err) context.fail(err) } console.log(r) context.succeed(r) }) } catch (e) { const err = 'catched error: ' + e console.log(err) context.fail(err) }}调用lambda函数: ...

June 19, 2019 · 1 min · jiezi

nvm管理nodejs版本Windows系统

nvm管理node版本在开发过程中偶尔会碰见不同项目依赖的node版本不一样,而node没有提供自动切换版本的功能,这个时候就需要nvm出马了,n也可以管理node版本(注:不支持Windows) 先去下载nvm-windows下载链接地址 注:目前版本1.1.6。如果你之前安装过node,先卸载掉。 安装nvm-windows刚才下载的是 nvm-setup.zip 这个文件 输入 nvm 这就安装成功了。 安装node安装最新稳定版 node nvm install latest 安装指定版本 node nvm install 8.9.0 切换node版本 nvm use 9.3.0nvm list 显示当前安装node版本列表 * 代表当前使用的版本 注: 安装的npm全局模块不会在各个版本的node.js之间共享。还有可能有些npm包不支持当前使用的node版本 nvm基本命令nvm arch [32|64]: 设置node是以32还是64位模式运行。 指定32或64来覆盖默认操作系统版本。nvm install <version> [arch]: version 版本号 例如 8.9.0 或者 latest(最新稳定版),[arch]可选、指定是否安装32位或64位版本(默认为系统架构),将[arch]设置为 all 安装32和64位版本。nvm list [available]: 显示当前安装node版本列表 * 代表当前使用的版本,在末尾输入 available 显示可供下载的所有版本列表。nvm on: 使用Node.js版本管理。nvm off: 禁用node.js版本管理(不会卸载任何东西)。nvm proxy [url]: 设置下载的代理,将[url]留空以查看当前代理。 将[url]设置为 none 以删除代理。nvm uninstall <version>: 卸载指定的node版本。nvm use <version> [arch]: 切换指定的node版本,可选[arch]32和64位版本。nvm root <path>: 设置nvm存放不同版本的node.js的目录。 如果没有设置 path ,则显示当前的根目录。nvm version: 显示Windows的NVM的当前运行版本。nvm node_mirror <node_mirror_url>: 设置node节点镜像。国内可以使用 https://npm.taobao.org/mirror...nvm npm_mirror <npm_mirror_url>: 设置npm节点镜像。国内可以使用 https://npm.taobao.org/mirror... ...

June 19, 2019 · 1 min · jiezi

爬虫爬-JSON-HTML-数据

最近这两周在忙着给公司爬一点数据,更文的速度有一点下降,预计今天就爬完了,总结总结经验。 其实之前我司是有专门做爬虫的,不用前端这边出人干活。后来那人离职了,有可能就没有爬虫这方面的需求了。突然又有了一些,前端这边出人做一下。老大说用 py 做,前期先调研一下。 原理爬虫其实原理上很简单,我们==客户端,他们==服务端。客户端发送请求 req,服务端返回响应 rsp。拿到响应之后解析数据,入库,就完事了。 请求数据 req一般来说请求分为两种,拉数据 get 比较多。偶尔部分接口需要登录,那就是多带 cookie 或者 headers。其实还有一部分工作就是分析入参。 get参数拼接在 url 上post参数放在 body 里响应数据 rsp返回数据大体上是两种 JSON一般来说,通过 抓包 或者说 network 工具。我们找到了服务端的接口,那么我直接访问这个接口即可。本文第一个重点来了:切换到移动端再查一遍,往往有不一样的收获,一般来说 PC 和 M 端的进度不了,有可能都不是一个项目组,所以实现方式就会有差别。html比较坑的一种方式,因为没有找到 JSON 接口。无奈只能走解析 HTML 的路子。调研Node 之前给后台搭架子的时候使用过,主要功能点如下: 自动登录,(拿headers、cookie)存储本地,每次请求带上 token启动代理服务py 老大说要用这个东西。咨询了一下其他朋友,说可以使用下面的工具。 requests + beautifulSoup使用起来其实就是 requests 发请求, beautifulSoup 解析 HTML。比较原始。scrapy一个爬虫的框架。我在这里学的 www.scrapyd.cn。实现上比较完整,可以设置请求间隔,随机 ua 等功能。前端实现 我一个铁头娃,怎么能轻言放弃?身为一个前端er,还是这些 api 让我更加亲切 XHR发请求利器,打开对方页面,cookie 啥的都自带。无敌就是这么寂寞。其实还可以找到对方请求发起的位置,打个断点,把对方内部的代码绑定到全局,这样一些内部逻辑什么的也都没问题。而且还 JSON HTML 通吃。iframe针对 HTML 类型的处理。同域的情况下,也无敌好吗? HTML 获取 DOM 节点?甚至可以取 window 上的对象。vue SSR 你感觉到了威胁吗?网上其他服务商提供的接口(真香啊)。有免费的有收费的,一般免费的限量。 ...

June 19, 2019 · 2 min · jiezi

gulp40升级小记

前言周日在公司的新电脑在以前gulp3.9配置的目录按下npm install时发现报了错,百度了一下得知原来gulp已经到了4.0版本,就花了一点时间去升了个级,顺便记下我个人使用到的配置文件新版本的不同点,文笔和水平有限,多多见谅 1. 新Api引入// v3.9let gulp = require('gulp');// v4.0const { series, src, dest, watch } = require('gulp');// 新引入的src,dest可替换老版的gulp.src和gulp.dest,代码更简洁// watch是任务监听// series是任务按顺序执行2. 新的创建任务方式 // 下面以压缩图片插件 gulp-imagemin 为例let imagemin = require('gulp-imagemin');// v3.9gulp.task('imagemin', () => { gulp.src('/path') .pipe(imagemin()) .pipe(gulp.dest('/path'))})// 4.0function minImage() { return src('/path') .pipe(imagemin()) .pipe(dest('/path'))}// 新版本使用了函数和return进行任务设置,函数名不能和引入的插件变量名称重复3. 执行任务方式 // v3.9gulp.task('default', [task1, task2])// v4.0,taskFn是设置任务的函数名function defaultTask() { return series(taskFn1, taskFn2, taskFn3); // series让任务按顺序执行}export.default = defaultTask() // 输出控制台执行任务的名称// 新版本的export.xxxx,这个xxxx就是在控制台中gulp执行任务的名称,可以同时export设置多个任务// 例如export.dev= dev(),想执行dev函数中返回的任务就在控制台输入gulp dev加回车!,如果是export.build = build(),则在控制台输入gulp build加回车!,如果是export.default = default(),直接输入gulp回车即可,以此类推4. watch和series Api // v3.9,老版本好像要安装一个queue的插件才可以实现按顺序执行任务let watch = require('gulp-watch');gulp.task('watch', () => { gulp.watch(['filePath1', 'filePath2'], [task1, task2]);});// 4.0const { watch, series} = require('gulp');function watchTask() { // 注意这里不需要使用return watch(['filePath1', 'filePath2'], series(taskFn1, taskFn2, taskFn3));}// 新版本直接引入watch即可实现任务监听功能,不用安装插件// series也可以配合watch按顺序执行设置的任务函数5. 插件gulp-autoprefixer配置变化 // v3.9.pipe(autoprefixer({ browsers: ['last 2 versions'], cascade: false}))// v4.0,需要在package.json文件添加browserslist键名或者在根目录添加一个.browserslistrc文件进行gulp-autoprefixer配置// 详情可以参考:https://github.com/browserslist/browserslist#queries.pipe(autoprefixer())// .browserslistrc文件last 1 version> 1%maintained node versionsnot dead// package.json"browserslist": [ "last 1 version", "> 1%", "maintained node versions", "not dead"]其他的配置感觉新版本和老版本都是大同小异,暂时就是发现了这么多,亲测可用后记:我是使用sass + gulp-autoprefixer进行开发的,无意发现当autoprefixer碰到-webkit-box-orient: vertical;时会自动把这个样式给删了 0.0,折腾了一番找到解决方法如下 -webkit-line-clamp: 3;/* autoprefixer: off */-webkit-box-orient: vertical;/* autoprefixer: on */overflow: hidden;// 在/* autoprefixer: off */和/* autoprefixer: on */之间的代码不会被删除

June 19, 2019 · 1 min · jiezi

基于Nodejs的大文件分片上传

基于Node.js的大文件分片上传我们在做文件上传的时候,如果文件过大,可能会导致请求超时的情况。所以,在遇到需要对大文件进行上传的时候,就需要对文件进行分片上传的操作。同时如果文件过大,在网络不佳的情况下,如何做到断点续传?也是需要记录当前上传文件,然后在下一次进行上传请求的时候去做判断。 先上代码:代码仓库地址 前端1. index.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>文件上传</title> <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script> <script src="https://code.jquery.com/jquery-3.4.1.js"></script> <script src="./spark-md5.min.js"></script> <script> $(document).ready(() => { const chunkSize = 1 * 1024 * 1024; // 每个chunk的大小,设置为1兆 // 使用Blob.slice方法来对文件进行分割。 // 同时该方法在不同的浏览器使用方式不同。 const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; const hashFile = (file) => { return new Promise((resolve, reject) => { const chunks = Math.ceil(file.size / chunkSize); let currentChunk = 0; const spark = new SparkMD5.ArrayBuffer(); const fileReader = new FileReader(); function loadNext() { const start = currentChunk * chunkSize; const end = start + chunkSize >= file.size ? file.size : start + chunkSize; fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); } fileReader.onload = e => { spark.append(e.target.result); // Append array buffer currentChunk += 1; if (currentChunk < chunks) { loadNext(); } else { console.log('finished loading'); const result = spark.end(); // 如果单纯的使用result 作为hash值的时候, 如果文件内容相同,而名称不同的时候 // 想保留两个文件无法保留。所以把文件名称加上。 const sparkMd5 = new SparkMD5(); sparkMd5.append(result); sparkMd5.append(file.name); const hexHash = sparkMd5.end(); resolve(hexHash); } }; fileReader.onerror = () => { console.warn('文件读取失败!'); }; loadNext(); }).catch(err => { console.log(err); }); } const submitBtn = $('#submitBtn'); submitBtn.on('click', async () => { const fileDom = $('#file')[0]; // 获取到的files为一个File对象数组,如果允许多选的时候,文件为多个 const files = fileDom.files; const file = files[0]; if (!file) { alert('没有获取文件'); return; } const blockCount = Math.ceil(file.size / chunkSize); // 分片总数 const axiosPromiseArray = []; // axiosPromise数组 const hash = await hashFile(file); //文件 hash // 获取文件hash之后,如果需要做断点续传,可以根据hash值去后台进行校验。 // 看看是否已经上传过该文件,并且是否已经传送完成以及已经上传的切片。 console.log(hash); for (let i = 0; i < blockCount; i++) { const start = i * chunkSize; const end = Math.min(file.size, start + chunkSize); // 构建表单 const form = new FormData(); form.append('file', blobSlice.call(file, start, end)); form.append('name', file.name); form.append('total', blockCount); form.append('index', i); form.append('size', file.size); form.append('hash', hash); // ajax提交 分片,此时 content-type 为 multipart/form-data const axiosOptions = { onUploadProgress: e => { // 处理上传的进度 console.log(blockCount, i, e, file); }, }; // 加入到 Promise 数组中 axiosPromiseArray.push(axios.post('/file/upload', form, axiosOptions)); } // 所有分片上传后,请求合并分片文件 await axios.all(axiosPromiseArray).then(() => { // 合并chunks const data = { size: file.size, name: file.name, total: blockCount, hash }; axios .post('/file/merge_chunks', data) .then(res => { console.log('上传成功'); console.log(res.data, file); alert('上传成功'); }) .catch(err => { console.log(err); }); }); }); }) window.onload = () => { } </script></head><body> <h1>大文件上传测试</h1> <section> <h3>自定义上传文件</h3> <input id="file" type="file" name="avatar"/> <div> <input id="submitBtn" type="button" value="提交"> </div> </section></body></html>2. 依赖的文件axios.jsjqueryspark-md5.js ...

June 19, 2019 · 4 min · jiezi

写了一个小工具每日一句诗词-npm-cli

tang-poems Uses the API from this great article on 今日诗词API.Feel free to make a PR to add! Ensure you have Node.js version 8 or higher installed. Then run the following: Installnpm install tang-poems -g or npm install tang-poems Usage --global$ poem --help Usage $ poem [options] Examples $ poem 不知江月待何人,但见长江送流水。 Options --all -a Show all the poems content Usage localconst poem = require('tang-poems')poem()不知江月待何人,但见长江送流水。LicenseMIT © Carrie999

June 18, 2019 · 1 min · jiezi

createreactapp-源码学习上

原文地址Nealyang/personalBlog前言对于前端工程构建,很多公司、BU 都有自己的一套构建体系,比如我们正在使用的 def,或者 vue-cli 或者 create-react-app,由于笔者最近一直想搭建一个个人网站,秉持着呼吸不停,折腾不止的原则,编码的过程中,还是不想太过于枯燥。在 coding 之前,搭建自己的项目架构的时候,突然想,为什么之前搭建过很多的项目架构不能直接拿来用,却还是要从 0 到 1 的去写 webpack 去下载相关配置呢?遂!学习下 create-react-app 源码,然后自己搞一套吧~ create-react-app 源码代码的入口在 packages/create-react-app/index.js下,核心代码在createReactApp.js中,虽然有大概 900+行代码,但是删除注释和一些友好提示啥的大概核心代码也就六百多行吧,我们直接来看 index.js index.js 的代码非常的简单,其实就是对 node 的版本做了一下校验,如果版本号低于 8,就退出应用程序,否则直接进入到核心文件中,createReactApp.js中 createReactApp.jscreateReactApp 的功能也非常简单其实,大概流程: 命令初始化,比如自定义create-react-app --info 的输出等判断是否输入项目名称,如果有,则根据参数去跑安装,如果没有,给提示,然后退出程序修改 package.json拷贝 react-script 下的模板文件准备工作:配置 vscode 的 debug 文件 { "type": "node", "request": "launch", "name": "CreateReactApp", "program": "${workspaceFolder}/packages/create-react-app/index.js", "args": [ "study-create-react-app-source" ] }, { "type": "node", "request": "launch", "name": "CreateReactAppNoArgs", "program": "${workspaceFolder}/packages/create-react-app/index.js" }, { "type": "node", "request": "launch", "name": "CreateReactAppTs", "program": "${workspaceFolder}/packages/create-react-app/index.js", "args": [ "study-create-react-app-source-ts --typescript" ] }这里我们添加三种环境,其实就是 create-react-app 的不同种使用方式 ...

June 18, 2019 · 7 min · jiezi

webpack4x升级摘要

一:前端工程化的发展很久以前,互联网行业有个职位叫做 “软件开发工程师” 在那个时代,大家可能会蒙,放在现今社会,大家会觉得这个职位太过笼统,所以在此提一下那个时候的互联网行业状态。 那个时候的APP还不是ios/安卓 多数是嵌入式应用(和网页没关系 使用c++或java开发)后续到了Symbian时代出现了可移植的sis[x]类型应用,曾经一统移动APP市场。那个时候css百分之90还是用来做布局那个时候js仅仅是为了类似弹出提示的功能而存在的那个时候从服务器 - 数据库 - 业务逻辑 - 页面 全都由所谓的 “软件开发工程师” 来完成所以大家不必问软件开发工程师具体是干啥的,我只能说 啥都干1:个人对前端开发的体验过程第一个阶段就是开始对着W3C的文档,拿着txt文本文件一个字母字母的敲着代码,那个年代,真的单纯舒服,上来就是一个项目的文件夹,然后就开始img、js、css三个完美的文件夹,再接上一个index.html,就开始到网上各种下载类库jquery、underscore.js,然后手动的引入各种类库,当然过程也伴随着痛苦。 每次来一个项目就开始建立各种繁琐的文件夹,和拷贝复制类库引入完类库的时候发现控制台报错,$ is not defined,依赖关系出错,部分类库需要把jquery作为依赖新写一个页面就需要去重新复制其他页面的header资源,维护变的困难(完全没有组件化的想法)部分css3属性需要自己不断的手动添加样式前缀最大的问题再维护的时候,不敢轻易的去动一些类库的引入,不清楚各个库之前的依赖关系第二个阶段就是在接触到Vue的时候直接上vue-cli的时候,几行脚本就可以启动本地开发服务和打包线上资源,初次尝试了webpack这个打包工具,好像对其他问题不需要做过多的考虑,直接开始业务开发,其他的事情cli都帮助处理好了。vue init webpack helloWorld, cd helloWorld && cnpm install && npm run dev,真香警告,对工程化一知半解,但是好用,方便 前端需要一些工具来处理重复的大量繁琐的事 (资源压缩 代码混淆 css前缀 ),以前会用Gulp等Task处理任务前端需要一些需要用一些预处理器来处理样式文件,less以前用第三方工具去编译 -> less提供的命令行去编译-> 配置到webpack中自动化实时编译前端需要更加细粒度的配置一下代码体积的优化,代码混淆压缩的操作前端部分业务越多,代码量越多(文件体积越大)需要做文件合并,压缩以及按需加载等开发阶段依然在本地开发,但同时保持和线上API的同步,为此我们需要本地服务器(可以是静态服务器)代理转发(Nginx webpack-dev-server node-server等方式)第三个阶段就是给公司项目升级webpack过程中体验到了更细粒度的控制工程,体验工程化的过程,实际的体验到了工程化为我们做的事情 模块化开发,不用在担心以往开发方式带来的全局作用域的污染问题,当然以往也可以通过闭包来实现私有变量的概念组件化开发,代码重用度高,便于维护多了构建,编译过程,可以在适当的时间去做一些提高工程质量的任务,比如代码规范的检测,常用的是Eslint (Airbnb, Prettier 规范等)提高开发效率,比如css浏览器前缀的自动添加,使用postCss甚至可以提前使用一些好用的东西,比如css变量的概念通过chunkHash,contentHash 等实现资源的缓存根据工程代码通过合理的代码分割提升用户体验,做到按需加载,甚至可以在未来做一些用户使用的习惯,做一些提前的预加载二:webpack的基本使用1:webpack和Grunt / Gulp的区别这两类是不同的东西,一个可以理解为是任务执行器而另外一个是模块打包器,任务执行器是可以自动化的执行一些以前你需要手动操作的过程,见下面简单的代码 // coffee 源码console.log 'Hello World' //coffee转 js coffee -c a.coffee (function(){ console.log('Hello World');}).call(this); //执行编译压缩 uglify -s a.js -o a.min.js(function(){console.log("Hello World")}).call(this);coffee需要编译成浏览器支持的js,你需要手动的去执行上面的几个命令,如果现在需要去修改源码,在Hello World 后面加上一个!,加完你需要手动的去执行两条命令重复的去编译操作。以 gulp 为例,编写 gulpfile.js来帮助我们完成重复的工作 ...

June 18, 2019 · 3 min · jiezi

undefined和null的区别

null: Null类型,代表“空值”,代表一个空对象指针,使用typeof运算得到 “object”,所以你可以认为它是一个特殊的对象值。 undefined: Undefined类型,当一个声明了一个变量未初始化时,得到的就是undefined。 null是javascript的关键字,可以认为是对象类型,它是一个空对象指针,和其它语言一样都是代表“空值”,不过 undefined 却是javascript才有的。undefined是在ECMAScript第三版引入的,为了区分空指针对象和未初始化的变量,它是一个预定义的全局变量。没有返回值的函数返回为undefined,没有实参的形参也是undefined。 javaScript权威指南: null 和 undefined 都表示“值的空缺”,你可以认为undefined是表示系统级的、出乎意料的或类似错误的值的空缺,而null是表示程序级的、正常的或在意料之中的值的空缺。 哈哈哈!! javaScript高级程序设计: 在使用var声明变量但未对其加以初始化时,这个变量的值就是undefined。 null值则是表示空对象指针。

June 18, 2019 · 1 min · jiezi

CommonJS浅析

规范地址 http://www.commonjs.org/nodejs modules文档地址 http://nodejs.cn/api/modules.... 核心逻辑在执行模块代码之前,nodejs会使用一个闭包(The module wrapper)封装起来,这就是它每一个文件都是一个独立的域的原因。但如果值没有用var声明的变量,会直接提升到全局上去,在其他文件也可以直接使用。 nodejs会通过下面一个闭包把文件给包起来 (function(exports, require, module, __filename, __dirname) {// 模块的代码实际上在这里});我们可以使用 console.log(JSON.stringify(arguments))可以看到当前js文件封装之后的参数例:aa.jsconsole.log(JSON.stringify(arguments))// {"0":{},"2":{"id":".","exports":{},"parent":null,"filename":"D:\\code\\js\\aa.js","loaded":false,"children":[],"paths":["D:\\code\\js\\node_modules","D:\\code\\node_modules","D:\\node_modules"]},"3":"D:\\code\\js\\aa.js","4":"D:\\code\\js"}‘可以看到这里返回的对象对应着exports, require, module, __filename, __dirname exports在整个域里,会有两个exports,一个模块封装器里的首个参数,一个是module类里的。两个exports引用了同一块堆内存,require引用时实际上拿的是module类里的 require用于引入模块、 JSON、或本地文件。 可以从 node_modules 引入模块。 可以使用相对路径(例如 ./、 ./foo、 ./bar/baz、 ../foo)引入本地模块或 JSON 文件,路径会根据 __dirname 定义的目录名或当前工作目录进行处理。 上面是官网对require的定义,require的一些属性使用在官网也有定义 module在每个模块中, module 的自由变量是对表示当前模块的对象的引用。 为方便起见,还可以通过全局模块的 exports 访问 module.exports。 module 实际上不是全局的,而是每个模块本地的。 __filename当前模块的文件名。 这是当前的模块文件的绝对路径(符号链接会被解析)。 __dirname当前模块的目录名。 与 __filename 的 path.dirname() 相同。 特殊使用循环引用 当循环调用 require() 时,一个模块可能在未完成执行时被返回。 例如以下情况: a.js:console.log('a 开始');exports.done = false;const b = require('./b.js');console.log('在 a 中,b.done = %j', b.done);exports.done = true;console.log('a 结束');b.js:console.log('b 开始');exports.done = false;const a = require('./a.js');console.log('在 b 中,a.done = %j', a.done);exports.done = true;console.log('b 结束');main.js:console.log('main 开始');const a = require('./a.js');const b = require('./b.js');console.log('在 main 中,a.done=%j,b.done=%j', a.done, b.done);当 main.js 加载 a.js 时, a.js 又加载 b.js。 此时, b.js 会尝试去加载 a.js。 为了防止无限的循环,会返回一个 a.js 的 exports 对象的 未完成的副本 给 b.js 模块。 然后 b.js 完成加载,并将 exports 对象提供给 a.js 模块。 ...

June 17, 2019 · 1 min · jiezi

让NodeJS在你的项目中发光发热

近些年来借着NodeJS的春风,前端经历了一波大洗牌式得的发展。使得前端开发在效率,质量上有了质的飞跃。可以说NodeJS已经是前端不可欠缺的技能了。但是是事实上大部分的前端对于本地安装的NodeJS的使用可能仅限于node -v和npm了????。其实NodeJS作为真正意义上的服务端语言,在我们开发的时候可以运用NodeJS强大的模块和众多的npm包来为我们自己服务。 写在前面注意:这篇文章基本上不会去将一些非常基础的东西,希望大家自备ES6+语法, NodeJS基础, 简单的Linux操作等知识。还有这篇文章侧重点不会放在技术的实现细节,主要是提供一些思路和方向。更加深层次的使用,还是要各位朋友自己去挖掘。而且这篇文章会有点长???? 快速创建模块这个部分我之前在加快Vue项目的开发速度中提到过,不过那个版本写的比较简单(糙),而且基本全都都是通过Node写的。说白了就是用NodeJS去代替我们生成需要复制粘贴的代码。 在大型的项目中,尤其是中后台项目在开发一个新的业务模块的时候可能离不开大量的复制粘贴,像中后台项目中可能很多模块都是标准的CURD模块,包括了列表,新增,详情,编辑这些页面。那么这就意味着有大量的重复代码存在,每次复制粘贴完之后还有修修改改删删等一大堆麻烦事儿,最重要的是复制粘贴很容易忘记哪一部分就忘记改了,导致项目做的很糙而且也会浪费不少时间。那我们的需求就是把人工复制粘贴的那部分交给Node去做,力求时间和质量得到双重的保障。 之前在Vue项目中写过这个模块,那接下来的demo我们以一个Vue项目来做示例,项目地址。 前期准备文件结构划分:视图文件,路由文件,Controler文件该怎么放一定要划分清楚。这个就像是地基,可以根据自己项目的业务去划分,我们的项目目录是下面这样划分 vue-base-template │ config // webpack配置config/q其他一些config文件 │ scripts // 帮助脚本文件 ===> 这里存放我们的项目脚本文件 │ │ template // 模块文件 │ │ build-module.js // build构建脚本 │ │ └───src // 业务逻辑代码 │ │ api // http api 层 │ └── router // 路由文件 │ │ │ modules // 业务路由文件夹 ==> 业务模块路由生成地址 │ │ │ module.js // 制定模块 │ │ store // vuex │ └── views // 视图文件 │ │ │ directory // 抽象模块目录 │ │ │ │ module // 具体模块文件夹 │ │ │ │ │ index.vue // 视图文件 │ │ global.js // 全局模块处理 │ │ main.js // 入口文件业务模块我基本上是通过抽象模块+具体模块的方式去划分: ...

June 17, 2019 · 10 min · jiezi

socketio

写了一个socket.io服务,实现了用户区分、公聊、私聊等。。。码云地址https://gitee.com/liuoomei/so... app.js //app.jsvar express = require('express')var app = express();var server = require('http').Server(app);// var io = require('socket.io')(server);var path = require('path');app.use(express.static(path.join(__dirname, 'public')))app.get('/', function(req, res){ res.sendFile(path.join(__dirname, 'index.html'));});module.exports = app;bin/www #!/usr/bin/env node/** * Module dependencies. */let app = require('../app');let debug = require('debug')('mysocket:server');let http = require('http');let _ = require('underscore');/** * Get port from environment and store in Express. */let userLs = []let port = normalizePort(process.env.PORT || '3000');app.set('port', port);/** * Create HTTP server. */let server = http.createServer(app);/** * Listen on provided port, on all network interfaces. */server.listen(port);let io = require('socket.io')(server);io.on('connection', function(socket){ socket.on('login', function(userid){ let obj = { userid, id: socket.id } userLs.push(obj) console.log(userid + '建立了链接'); socket.emit('userLs',userLs) socket.emit('login',socket.id) }); // 判断用户离线事件可以通过socket.io自带的disconnect事件完成,当一个用户断开连接,disconnect事件就会触发 socket.on('disconnect', function(){ let _user = _.where(userLs,{id:socket.id}) console.log('_user',_user) console.log(socket.id + '中断了链接'); userLs = userLs.filter(it =>{ return _user.every(item =>{ return it.id != item.id }) }) // do somethings console.log('del',userLs) }); socket.on('message', function (data) { //服务端像所以也没发送数据 let _user = _.where(userLs,{id:socket.id}) io.sockets.emit('message', {id:_user[0].id,userid:_user[0].userid,message:data.message}); //给所有人(包括自己)发送消息 // socket.broadcast.emit('message', data.message); //给所有人(不包括自己)发送消息 }); socket.on('sayTo', function (data) { let toMsg = data.message; let toId = data.id; // nodejs的underscore扩展中的findWhere方法,可以在对象集合中,通过对象的属性值找到该对象并返回。 let _user = _.where(userLs,{id:toId}) if(_user){ let toSocket = _.findWhere(io.sockets.sockets, {id: toId}); // 通过该连接对象(toSocket)与链接到这个对象的客户端进行单独通信 socket.emit('message', {id:socket.id,message:toMsg}) //向建立该连接的客户端广播 toSocket.emit('message', {id:socket.id,message:toMsg}); }else{ socket.emit('error','该用户不在线') } // socket.emit() :向建立该连接的客户端广播 // socket.broadcast.emit() :向除去建立该连接的客户端的所有客户端广播 // io.sockets.emit() : 向所有客户端广播,等同于上面两个的和 });});server.on('error', onError);server.on('listening', onListening);/** * Normalize a port into a number, string, or false. */function normalizePort(val) { let port = parseInt(val, 10); if (isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false;}/** * Event listener for HTTP server "error" event. */function onError(error) { if (error.syscall !== 'listen') { throw error; } let bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': console.error(bind + ' requires elevated privileges'); process.exit(1); break; case 'EADDRINUSE': console.error(bind + ' is already in use'); process.exit(1); break; default: throw error; }}/** * Event listener for HTTP server "listening" event. */function onListening() { let addr = server.address(); let bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; debug('Listening on ' + bind); console.log(`服务已启动,端口:${addr.port}`)}public/index.html ...

June 17, 2019 · 3 min · jiezi

修改npm全局安装包的位置解决EACCES权限拒绝问题

简介 在macOS系统下,npm默认的全局安装路径是在/usr/local/lib/node_modules下。例如:当我们执行npm i -g vue-cli时,实际上是把vue-cli这个模块安装到了/usr/local/lib/node_modules目录下了。 ⚠️注意: /usr/local/lib这个是系统目录,会有权限问题,虽然可以使用sudo执行,但是还是有部分机器,即使使用root用户执行npm i -g xxx 全局安装某个模块还是会出现EACCES permissions权限被拒绝问题。 ????解决办法: 通过修改npm全局安装模块的路径解决,将npm全局安装模块的路径,修改到当前登陆用户的HOME目录下即可,这样用不用sudo都不会出现EACCES permissions权限被拒绝问题了。 ⚠️️注意:本教程不适合Windows系统 操作0、查看当前npm的默认配置npm config ls1、在你的用户主目录下创建.npm-global文件夹作为npm全局安装的目录 mkdir ~/.npm-global2、修改npm使用新的全局安装路径npm config set prefix '~/.npm-global'3、修改PATH环境变量vim ~/.bash_profile //编辑.bash_profile文件,这个文件是用户登陆终端的时候会自动执行的文件4、在~/.bash_profile文件添加下面这行代码export PATH=~/.npm-global/bin:$PATH 5、更新系统变量,获取重启命令行终端 source ~/.bash_profile6、测试配置,在不使用sodu的情况下全局安装一个包npm install -g jshint 可以看到此后全局安装的模块都被安装到了/Users/jameswain/.npm-global目录下了 总结 其实解决全局安装模块权限不足问题的方法:主要是将npm全局安装模块的目录修改到了用户的主目录下,这样用户不需要sodu也能够全局安装模块,因为它是在自己的主目录下操作,永远不会存在权限问题。

June 17, 2019 · 1 min · jiezi

49-跨域cors博客后端ApiNodeJsExpressMysql实战

跨域corshttps://github.com/expressjs/...var config = { cors: { //跨域请求 origin: ['http://localhost:5000', 'http://localhost:5001'],//也可以使用"*"代表允许所有 },} 此时咱们启动服务,在接口调用时就会有跨域白名单拦截校验

June 17, 2019 · 1 min · jiezi

StepByStep高频面试题深入解析-周刊04

关于【Step-By-Step】Step-By-Step (点击进入项目) 是我于 2019-05-20 开始的一个项目,每个工作日发布一道面试题。每个周末我会仔细阅读大家的答案,整理最一份较优答案出来,因本人水平有限,有误的地方,大家及时指正。 如果想 加群 学习,可以通过文末的公众号,添加我为好友。 __ 本周面试题一览:什么是闭包?闭包的作用是什么?实现 Promise.all 方法异步加载 js 脚本的方法有哪些?请实现一个 flattenDeep 函数,把嵌套的数组扁平化可迭代对象有什么特点?15. 什么是闭包?闭包的作用是什么?什么是闭包?闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包最常用的方式就是在一个函数内部创建另一个函数。 创建一个闭包function foo() { var a = 2; return function fn() { console.log(a); }}let func = foo();func(); //输出2闭包使得函数可以继续访问定义时的词法作用域。拜 fn 所赐,在 foo() 执行后,foo 内部作用域不会被销毁。 无论通过何种手段将内部函数传递到所在的词法作用域之外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。如: function foo() { var a = 2; function inner() { console.log(a); } outer(inner);}function outer(fn){ fn(); //闭包}foo();闭包的作用能够访问函数定义时所在的词法作用域(阻止其被回收)。私有化变量function base() { let x = 10; //私有变量 return { getX: function() { return x; } }}let obj = base();console.log(obj.getX()); //10模拟块级作用域var a = [];for (var i = 0; i < 10; i++) { a[i] = (function(j){ return function () { console.log(j); } })(i);}a[6](); // 6创建模块function coolModule() { let name = 'Yvette'; let age = 20; function sayName() { console.log(name); } function sayAge() { console.log(age); } return { sayName, sayAge }}let info = coolModule();info.sayName(); //'Yvette'模块模式具有两个必备的条件(来自《你不知道的JavaScript》) ...

June 17, 2019 · 4 min · jiezi

提升90加载速度vuecli下的首屏性能优化

前言之前用vuecli做了个博客,是一个单页面项目,大概有十个路由直接npm run build打包出来,有一个1M的巨大js文件 先挂载到服务器上试试好家伙 这加载时间 仿佛过了半个世纪 首屏页面整整加载了9s 光加载那个大文件就花了8s这必须得做个优化了,没有用户能忍受9s的白屏而不关闭页面的 过程中,我还顺便把项目从vuecli 2.x迁移到了vuecli 3,所以接下来还会介绍一些它们在优化上的异同 分析vuecli 2.x自带了分析工具只要运行npm run build --report 如果是vuecli 3的话,先安装插件 cnpm intall webpack-bundle-analyzer –save-dev然后在vue.config.js中对webpack进行配置 chainWebpack: (config) => { /* 添加分析工具*/ if (process.env.NODE_ENV === 'production') { if (process.env.npm_config_report) { config .plugin('webpack-bundle-analyzer') .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin) .end(); config.plugins.delete('prefetch') } } }再运行npm run build --report 会在浏览器打开一个项目打包的情况图,便于直观地比较各个bundle文件的大小 可以看到 项目中所有的依赖,所有的路由,都被打包进了同一个文件中 另外,在浏览器中,也可以通过converge来查看代码的使用状况 红色的是下载了但未使用的部分 路由懒加载当打包构建应用时,JavaScript包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。在一开始就下载完所有路由对应的组件文件,这明显是不合适的,这就像下载一个app了,所以我们就需要使用路由懒加载 在router.js文件中,原来的静态引用方式 import ShowBlogs from '@/components/ShowBlogs'routes:[ path: 'Blogs', name: 'ShowBlogs', component: ShowBlogs ]改为 ...

June 17, 2019 · 2 min · jiezi

Gulp40入门指南

安装Gulp官网 gulp4.0分离了处理和核心部分,所以需要分别安装这两个包,另外对环境要求如下: node >= 8.11.1npm >= 5.6.0npx >= 9.7.1全局安装gulp-clinpm i -g gulp-cli本地安装gulpnpm i -D gulp查看版本号$ gulp -v# 输出CLI version: 2.2.0Local version: 4.0.2配置文件在项目根目录创建gulpfile.js文件(如果使用ts或者babel,也可用gulpfile.ts、gulpfile.babel.js分别代替),此文件即gulp会默认读取的配置文件,我们可以在里面配置需要的task。如果task较多或者较复杂,可以创建gulpfile.js目录,在目录中拆分task为多个文件,只要保证该目录下有个index.js作为入口即可。 Tasktask分为两种: Private tasks:配置文件中的一个function,仅能在该文件中使用Public tasks:将Private tasks导出,可以供gulp命令执行const { series, parallel } = require('gulp');// Private tasksfunction clean(cb) { // body omitted cb();}// Private tasksfunction build(cb) { // body omitted cb();}exports.build = build; // Public tasks, 执行gulp buildexports.default = series(clean, parallel(css, javascript)); // Public tasks, 执行gulp注意:在task中,操作完成时,我们必须要通过cb()或者return的方式来告知gulp此任务已完成。 // cbfunction clean(cb) { del(['dist]); cb();});// return function minifyjs() { return src('src/**/*.js') .pipe(minify()) .pipe(dest('dist'));});function promiseTask() { return new Promise(function(resolve, reject) { // body omitted resolve(); });});运行taskgulp <export task name> gulp // 导出为default的task可以直接允许gulp组合taskseries:序列(顺序执行)// task1执行完再执行task2exports.taskName = series(task1, task2)parallel:并行(同时执行)// task1和task2同时执行exports.taskName = parallel(task1, task2)混用:exports.taskName = series(clean, parallel(css, javascript))输入与输出gulp借鉴了Unix的管道(pipe)思想,处理文件采用流的方式,前一步的输出作为后一步的输入,中途不会在磁盘写入文件,仅在dest时输出文件,所以非常快速高效。 ...

June 17, 2019 · 3 min · jiezi

多站点单点登录实现方案

今天写一篇关于多域名下单点登录的实现。 有这么个场景,公司下有多个不同域名的站点,我们期望用户在任意一个站点下登录后,在打开另外几个站点时,也是已经登录的状态,这么一过程就是单点登录。 因为多个站点都是用的同一套用户体系,所以单点登录可以免去用户重复登录,让用户在站点切换的时候更加流畅,甚至是无感知。 单点登录所要实现的就是,某一站点登录后,将其登录态会同步到其他另外几个站点。 我们分两部分,先说单个站点的登录流程,在说同步登录态的流程。 登录相关架构服务端采用 nodejs ,缓存采用 redis 。用户登录凭证采用基于 session 的 cookies 维系,采用 cookie 作为登录凭证是目前比较主流的方式。session 信息用 redis 承载,从数据层面上看, redis 中存储 session 对象的 key 便是 cookie 中的 valuekey是由 UUID 生成的唯一标识为了保证 session 与 cookie 保持对应, session 对象创建与修改都会触发服务端往浏览器写入 cookie登录流程我们先看单个站点的登录流程 用户首次打开站点,服务端生成 session 对象,此时 session 中没有用户信息,同时服务端往浏览器写入 cookie用户触发登录操作服务端校验参数处理登录逻辑后,生成用户信息,将用户信息写入 session 对象,更新缓存 redis我们来画个图,如下 同步登录态一个站点完成登录后,接下来就是如何让其他站点也拥有登录态。既然登录态是由 cookie 和 session 决定的,而 cookie 又是由 session 写入的,那么也就是说,只要把 session 同步到其它站点,其它站点只要获取到 session 后,就可以在该域名创建或更新 cookie ,这样一来,两个不同域名下的站点就拥有相同的登录信息了。 因此,同步登录态其实就是,如何同步 session 的问题。 而我们的 session 是采用 redis 作为载体,那么其他站点只要能获取到 redis 中存储的用户信息,不就可以创建自己的 session 对象了么? ...

June 15, 2019 · 1 min · jiezi

强烈推荐-GitHub-上值得前端学习的开源实战项目

强烈推荐 GitHub 上值得前端学习的开源实战项目。 Vue.jsvue-element-admin 是一个后台前端解决方案,它基于和 element-ui 实现基于 iView 的 Vue 2.0 管理系统模板基于 vue2 + vuex 构建一个具有 45 个页面的大型单页面应用基于 vue + element-ui 的后台管理系统基于Vue.js + Element UI 的后台管理系统解决方案基于 Vue(2.5) + vuex + vue-router + vue-axios +better-scroll + Scss + ES6 等开发一款移动端音乐 WebAppSpring Boot 后端 + Vue 管理员前端 + 微信小程序用户前端 + Vue 用户移动端高仿网易云音乐的 webapp,只实现了 APP 的核心功能Vue + TypeScript + Element-Ui 支持 markdown 渲染的博客前台展示更多...React.js一套优秀的中后台前端解决方案网易云音乐第三方一个 react + redux 的完整项目 和 个人总结 react 后台管理系统解决方案这是一个用来查看GitHub最受欢迎与最热项目的App,它基于React Native支持Android和iOS双平台RN写的饿了么,还原度相当高,实现了各类动效仿知乎日报一个商城类的RN项目react + Ant Design + 支持 markdown 的博客前台展示基于 pro.ant.design 的 react + Ant Design 的博客管理后台项目使用 react hooks + koa2 + sequelize + mysql 搭建的前后台的博客 基于typescript koa2 react的个人博客更多...Angular基于angular.js,weui和node.js重写的新闻客户端 管理仪表板模板基于Angular 7+,Bootstrap 4Node.js基于 node.js + Mongodb 构建的后台系统Nodeclub 是使用 Node.js 和 MongoDB 开发的社区系统基于Node.js+MySQL开发的开源微信小程序商城(微信小程序)NideShop 开源微信小程序商城服务端 API(Node.js + ThinkJS)基于react, node.js, go开发的微商城(含微信小程序) React+Express+Mongo ->前后端博客网站 基于 node + express + mongodb 的博客网站后台最后笔者博客首更地址 :https://github.com/biaochenxuying/blog ...

June 15, 2019 · 1 min · jiezi

基于前端技术生成PDF方案

需求背景业务系统需要预览报告(如产品周报,体检报告等)并生成pdf格式供用户下载,或者定期发送给指定用户报告格式相对固定,由文本,图片和图表组成,基本与前端页面保持一致解决方案需求分为两步:报告预览和报告生成。 报告预览在前端进行展示,可使用前端技术,如React/Vue等技术栈对其进行还原,数据从服务端获取。报告生成需要对第一步生成的HTML进行PDF的转换生成,HTML2PDF的方式又分为两种: 基于canvas的客户端生成方案基于nodejs + puppeteer的服务端生成方案一个完整的案例下面以一个体检报告的案例进行这两种方案的说明:体检报告展示形式如下,格式相对固定,分为四个页面:个人信息页,建议页,原理页,个人信息页与建议页数据来源于服务器。report.png 基于canvas的客户端生成方案canvas是HTML5标准中新增的元素,可用于通过使用JS的脚本来绘制图形。canvas提供了toDataURL/toBlob方法,用于把canvas中的内容转换为图片,API文档如下(来源于MDN): 由于HTML文档再浏览器中是以DOM树的形式存在,所以我们可以通过三步完成HTML到PDF的转换: 将DOM树转换为canvas对象,可使用html2canvas完成将canvas转换为图片,可使用canvas.toDataURL完成将图片转换为PDF,可使用jsPDF完成完整代码实现:https://github.com/simonwoo/d... 截图如下,点击下载按钮可进行pdf生产: 该方案完全基于客户端的方式生成,不需要服务器进行支持。在使用该方案的过程中,发现了一些问题: 生产的PDF比较模糊,质量不高如果HTML中有外链图片,无法生成由于第一步是通过DOM去生成canvas,所以针对特别长的报告,DOM尚未加载完便点击下载时,会造成报告生成问题因为是客户端方案,所以需要用户主动触发生成,但对于一些定期发送给用户的报告,该方案无法使用基于nodejs + puppeteer的服务端生成方案puppeteer是google推出的headless浏览器,即没有图形界面的浏览器,但又可以实现普通浏览器HTML/JS/CSS的渲染,以及其他基本浏览器功能。你可以理解为一个没有界面的Chrome浏览器。主要有以下几种使用场景: 生成页面的截图和PDF抓取SPA并生成预先呈现的内容(即“SSR”)爬虫,从网站抓取你需要的内容自动化测试,自动表单提交,UI测试,键盘输入等创建一个最新的自动化测试环境。使用最新的JavaScript和浏览器功能,直接在最新版本的Chrome中运行测试通过理解puppeteer的功能,我们可以开启一个实例去渲染HTML报告,然后再利用其提供的转换PDF功能进行PDF的生成。 两个重要的API: page.goto(url, [options]) - 打开指定url的文件,可以是本地文件(file://)也可以是网络文件(http://)page.pdf([options]) - 转换页面成PDF文件puppeteer使用一个小例子,将百度网页转换为pdf: 完整代码如下: 前端部分:https://github.com/simonwoo/d...后端部分:https://github.com/simonwoo/d...项目启动流程如下: 进入到webapp目录,使用npm install和npm run start启动前端服务器,地址为:localhost:3000进入到server目录,使用npm install和npm run dev启动node服务器,地址为:localhost:7001整个服务架构如下: node服务器通过路由增加一个pdf生成的controller,该controller通过启动puppeteer实例去加载localhost:3000的页面并生成pdf。直接在浏览器中通过http://localhost:7001/pdf即可访问到生成的pdf. 在实际环境中,前端页面可部署在nginx服务器上或者直接放在Node服务器上,puppeteer也支持使用cookie的操作,这样可以避免一些需要身份认证的问题。 相比客户端生成方式,使用puppeteer生成的pdf质量比较高,可满足生产要求。 本文中提到的两种方案中,均省去了ajax后端请求数据部分,读者可根据需要自行增加。 Referencehtml2canvas - https://html2canvas.hertzen.c...jsPDF - https://github.com/MrRio/jsPDFpuppeteer - https://zhaoqize.github.io/pu...Eggjs - https://eggjs.org/zh-cn/原文链接:https://juejin.im/post/5d036a...

June 15, 2019 · 1 min · jiezi

使用Nodejs爬取任意网页资源并输出高质量PDF文件到本地

本文适合无论是否有爬虫以及Node.js基础的朋友观看~需求:使用Node.js爬取网页资源,开箱即用的配置将爬取到的网页内容以PDF格式输出如果你是一名技术人员,那么可以看我接下来的文章,否则,请直接移步到我的github仓库,直接看文档使用即可仓库地址:附带文档和源码,别忘了给个star哦本需求使用到的技术:Node.js和puppeteerpuppeteer 官网地址: puppeteer地址Node.js官网地址:链接描述Puppeteer是谷歌官方出品的一个通过DevTools协议控制headless Chrome的Node库。可以通过Puppeteer的提供的api直接控制Chrome模拟大部分用户操作来进行UI Test或者作为爬虫访问页面来收集数据。环境和安装Puppeteer本身依赖6.4以上的Node,但是为了异步超级好用的async/await,推荐使用7.6版本以上的Node。另外headless Chrome本身对服务器依赖的库的版本要求比较高,centos服务器依赖偏稳定,v6很难使用headless Chrome,提升依赖版本可能出现各种服务器问题(包括且不限于无法使用ssh),最好使用高版本服务器。(建议使用最新版本的Node.js)小试牛刀,爬取京东资源const puppeteer = require('puppeteer'); // 引入依赖 (async () => { //使用async函数完美异步 const browser = await puppeteer.launch(); //打开新的浏览器 const page = await browser.newPage(); // 打开新的网页 await page.goto('https://www.jd.com/'); //前往里面 'url' 的网页 const result = await page.evaluate(() => { //这个result数组包含所有的图片src地址 let arr = []; //这个箭头函数内部写处理的逻辑 const imgs = document.querySelectorAll('img'); imgs.forEach(function (item) { arr.push(item.src) }) return arr }); // '此时的result就是得到的爬虫数据,可以通过'fs'模块保存'})() 复制过去 使用命令行命令 ` node 文件名 ` 就可以运行获取爬虫数据了 这个 puppeteer 的包 ,其实是替我们开启了另一个浏览器,重新去开启网页,获取它们的数据。上面只爬取了京东首页的图片内容,假设我的需求进一步扩大,需要爬取京东首页中的所有<a> 标签对应的跳转网页中的所有 title的文字内容,最后放到一个数组中。 ...

June 15, 2019 · 2 min · jiezi

Electron酷家乐客户端开发实践分享-下载管理器

作者:钟离,酷家乐PC客户端负责人原文地址:https://webfe.kujiale.com/electron-ku-jia-le-ke-hu-duan-kai-fa-shi-jian-fen-xiang-jin-cheng-tong-xin/酷家乐客户端:下载地址 https://www.kujiale.com/activity/136文章背景:在酷家乐客户端在V12改版成功后,我们积累了许多的宝贵的经验和最佳实践。前端社区里关于Electron知识相对较少,因此希望将这些内容以系列文章的形式分享出来。系列文章: 【Electron】酷家乐客户端开发实践分享 — 入坑篇【Electron】酷家乐客户端开发实践分享 — 软件自动更新【Electron】酷家乐客户端开发实践分享 — 浏览器启动客户端【Electron】酷家乐客户端开发实践分享 — 进程通信【Electron】酷家乐客户端开发实践分享 — 下载管理器不定期更新...背景打开酷家乐客户端,可以在左下角的更多菜单中找到下载管理这个功能,今天我们就来看看在Electron中如何实现一个下载管理器。 如何触发下载行为由于Electron渲染层是基于chromium的,触发下载的逻辑和chromium是一致的,页面中的a标签或者js跳转等等行为都可能触发下载,具体视访问的资源而定。什么样的资源会触发浏览器的下载行为呢? response header中的Content-Disposition为attachment。参考MDN Content-Dispositionresponse header中的Content-Type,是浏览器无法直接打开的文件类型,例如application/octet-stream,此时取决于浏览器的具体实现了。例子: IE无法打开pdf文件,chrome可以直接打开pdf文件,因此pdf类型的url在chrome上可以直接打开,而在IE下会触发下载行为。在Electron中还有一种方法可以触发下载: webContents.download。相当于直接调用chromium底层的下载逻辑,忽略headers中的那些判断,直接下载。 上述两种下载行为,都会触发session的will-download事件,在这里可以获取到关键的downloadItem对象 整体流程 设置文件路径如果不做任何处理的话,触发下载行为时Electron会弹出一个系统dialog,让用户来选择文件存放的目录。这个体验并不好,因此我们首先需要把这个系统dialog去掉。使用downloadItem.savePath即可。 // Set the save path, making Electron not to prompt a save dialog.downloadItem.setSavePath('/tmp/save.pdf');为文件设置默认下载路径,就需要考虑文件名重复的情况,一般来说会使用文件名自增的逻辑,例如:test.jpg、test.jpg(1)这种格式。文件默认存放目录,也是一个问题,我们统一使用app.getPath('downloads')作为文件下载目录。为了用户体验,后续提供修改文件下载目录功能即可。 // in main.js 主进程中const { session } = require('electron');session.defaultSession.on('will-download', async (event, item) => { const fileName = item.getFilename(); const url = item.getURL(); const startTime = item.getStartTime(); const initialState = item.getState(); const downloadPath = app.getPath('downloads'); let fileNum = 0; let savePath = path.join(downloadPath, fileName); // savePath基础信息 const ext = path.extname(savePath); const name = path.basename(savePath, ext); const dir = path.dirname(savePath); // 文件名自增逻辑 while (fs.pathExistsSync(savePath)) { fileNum += 1; savePath = path.format({ dir, ext, name: `${name}(${fileNum})`, }); } // 设置下载目录,阻止系统dialog的出现 item.setSavePath(savePath); // 通知渲染进程,有一个新的下载任务 win.webContents.send('new-download-item', { savePath, url, startTime, state: initialState, paused: item.isPaused(), totalBytes: item.getTotalBytes(), receivedBytes: item.getReceivedBytes(), }); // 下载任务更新 item.on('updated', (e, state) => { // eslint-disable-line win.webContents.send('download-item-updated', { startTime, state, totalBytes: item.getTotalBytes(), receivedBytes: item.getReceivedBytes(), paused: item.isPaused(), }); }); // 下载任务完成 item.on('done', (e, state) => { // eslint-disable-line win.webContents.send('download-item-done', { startTime, state, }); }); });现在触发下载行为,文件就已经会下载到Downloads目录了,文件名带有自增逻辑。同时,对下载窗口发送了关键事件,下载窗口可以根据这些事件和数据,创建、更新下载任务。 ...

June 14, 2019 · 2 min · jiezi

究竟什么是前端脚手架

???? 咱也不知道咱也不敢问啊 先查查百度百科里对“脚手架”的定义吧: 脚手架是为了保证各施工过程顺利进行而搭设的工作平台。然后搜一下“脚手架”,基本上都是以下几类: Vue/React 脚手架使用 Node、yeoman 打造自己的脚手架从零搭建 webpack 脚手架此时还是无法确定什么是“脚手架”,也许我心目中的脚手架应该是 vue/cli 或者 create-react-app 吧,然后我打开他们的文档,但是他们的介绍上并没有说这是一款脚手架... 谈一下我自己的“脚手架”???? 我最怕别人看到我的“脚手架”后说这有什么卵用... 看了一下我第一次在 Github 上的提交记录 Commits on Feb 13, 2017,我入行三年多,那时应该是我第一次进入前端工程化的时候吧。在此之前我在公司接手的都是一些很简单的项目,直接新建文件夹,随手下载一个jQuery,然后新建 index.html main.js style.css,有没有同样经历的小伙伴们? 后来做的项目多了,觉得每次这么新建项目太麻烦,我新建了一个文件夹,专门存放初始的模板,然后复制粘贴,继续撸。 再后来,接触到 vue,他可以实现在终端内输入一行指令就能生成模板,这可比我复制粘贴看起来高端多了,为了装逼我开始了研究如何开发自己的“脚手架”。 当时还真是没有几篇文章把这种操作说的很明白,甚至我并不知道我要做的东西叫什么(脚手架)。 我理解的“脚手架”???? 百科里说的很对我的思路,首要的就是保证我的项目可以顺利进行。 能够快速帮我生成新项目的目录模板(Node.js)。能够提升我的开发效率和开发的舒适性(webpack)。分享点干货从零搭建这种我就不说了,毕竟一搜一堆,基本上就是推荐几个库,例如 commander、inquirer、ora 等等。分享一些在我的脚手架开发时遇到的一些问题和需求,分享想给大家。 版本验证首先推荐工具库: semver,版本对比。 request,发送请求,当然你也可以用 axios。 const semver = require('semver');const request = require('request');Node.js 版本验证如果你的脚手架想分享给别人用,这步最好不要避免,因为如果你用了一些现代化的 ES 语法,比如说 async await,老版本跑不起来的。 function checkNodeVersion (wanted) { // process.version 可以获取当前的 node 版本 if (!semver.satisfies(process.version, wanted)) { console.log('Node版本不支持巴拉巴拉'); // 退出进程 process.exit(1); }}脚手架版本验证正如上面所说,你如果分享给别人用,在你修改了一些 bug 后,希望其他人用最新的版本,那就应该提示他。 ...

June 14, 2019 · 2 min · jiezi

Nodejs学习记录-crypto

crypto.createHash()

June 14, 2019 · 1 min · jiezi

实现node日志管理

第一次写node项目,之前除了前端的脚手架构建接触过一些简单的,所以总是碰到很多坑。比如权限验证,比如异常处理,比如日志管理。在看log4js使用方法的时候突然想到自己就可以实现简单的业务,不需要借助组件,虽然简单但是实现了挺开心的。 为什么需要日志管理自己的node项目写了一段时间了,但一直没有加上日志管理的功能,因为觉得没必要,很多时候都是在自己电脑上面调试的。但突然有一天在线上访问自己的项目,发现页面报错了,想知道为什么报错了,发现竟然没有什么很好的方法,如果我没有通过一个东西去记录的话,所以日志管理这个时候就显得尤为重要了。 日志的产生过程页面出现错误根据错误类型创建日志文件写入错误信息创建日志方法的实现先判断要写入的路径是否存在,不存在则创建判断日志要创建在的文件夹存不存在,不存在则创建判断当前要创建的日志存不存在,存在继续写入,不存在则创建并写入fs.stat检查路径是否存在 fs.mkdir创建目录的方法 fs.readFile读取文件的方法 fs.writeFile写入文件的方法 完成的写入日志函数我的业务是定义了两个类型,错误和sql,然后传入日志内容 /** * 写入日志 * @param {String} type // 日志类型 err 错误日志 sql sql日志 * @param {String} content */writeLog (content, type = 'err') { // 创建不存在的文件夹 await this.dirExists(`log/file/${type}`) // 获取到文件files fs.readFile(`log/file/${type}/${utils.switchTime(new Date(), 'YYYY-MM-DD')}.log`, (err, data) => { if (err) { console.log(err) } // 写入文件 fs.writeFile(`log/file/${type}/${utils.switchTime(new Date(), 'YYYY-MM-DD')}.log`, `${data || ''}\n${content}`, async (err) => { if (err) { console.log(err) } }) }) }使用在sql执行函数上使用function query (sql) { // 写入sql NodeLog.writeLog(sql, 'sql') return new Promise((resolve, reject) => { pool.getConnection((err, conn) => { if (err) { // 如果是连接断开,自动重新连接 if (err.code === 'PROTOCOL_CONNECTION_LOST') { setTimeout(query(), 2000); reject('断开重连'); } else { console.error(err.stack || err); reject(err); } } else { // 得到结果 conn.query(sql, (queryErr, result) => { if (queryErr) { reject(queryErr); } else { resolve(result); } // 释放连接 conn.release(); }) } }) })}在异常处理函数中使用 handleException (req, res, e) { // 写入日志 NodeLog.writeLog(e) res.json({ code: e.errno || 20501, success: false, content: e, message: '服务器内部错误' }) }最后日志完整代码 ...

June 14, 2019 · 1 min · jiezi

node-npm-环境配置-从头来过

在 Node 使用中会安装各种模块儿,小编我作为技术小白并不知道npm cpm 安装在哪儿,全局安装的东西在哪儿。总会出现 '*不是内部或外部命令,也不是可运行的程序'这种提示,也不知道安装的包为什么会在“C:UsersAdminAppDataRoaming”,今天搜了好多,终于把环境配置弄好了,鉴于查到的资料很少,觉得自己的解决过程还是有参考意义的。 问题: webpack 不是内部命令等做法: 卸载node.js ,删除所有相关的环境变量,重新安装。描述:默认安装的情况下,会自动安装npm,系统环境会创建两个环境变量。 1 用户变量 PATH : C:UsersAdminAppDataRoamingnpm ;2系统变量:PATH:D:Program Filesnodejs; 如果不做改动,全局安装的东西会到C:UsersAdminAppDataRoamingnpm 下,如果有用淘宝镜像,那会到目录下的cnpm中。对于没有强迫症也要弄个明白的同学可能会希望全局安装到指定位置,那继续看。 解决过程: 1,安装node.js . 我的安装目录:D:Program Filesnodejs;2,在D:Program Filesnodejs目录下新建两个文件夹,用于存放全局安装包(node_global)和临时缓存位置(npm-cache); 3,查看你系统中全局的路径 npm root -g 4,设置全局路径(安装路径 缓存路径) npm config set prefix "D:Program Filesnodejsnode_global" npm config set cache "D:Program Filesnodejsnpm-cache" 5,重复3,查看你系统中全局的路径 npm root -g 如果是D:Program Filesnodejsnode_global就对了(我执行第五步执行了两次才设置好全局路径。(执行完毕后,环境变量 用户变量是不发生变化的)6,修改两个环境变量。6.1 用户变量 PATH : D:UsersAdminAppDataRoamingnpm 修改为 PATH : D:Program Filesnodejsnode_global6.2 系统变量:PATH:D:Program Filesnodejs; 修改为 PATH: D:Program Filesnodejs;D:Program Flesnodejsnode_global(多个配置,用英文;隔开) ...

June 14, 2019 · 1 min · jiezi

lintstaged如何做到只lint-staged

介绍lint-staged针对暂存的git文件运行linters并且不要让 ???? 进入你的代码库!开始去年的时候分享过一个主题——规范化工作流之约定式提交,主要内容是提交代码时对暂存区代码格式的校验和提交信息规范校验。当时就接触到了lint-staged,只知道这个工具能针对暂存区的文件处理,并未深入了解, 那时候就有一些疑问埋在心里,最近得空,特来解疑。 疑问点: git分为暂存区和工作区,如果一个文件同时存在在两个区(某文件git add后又再次修改,如下图test2.js ),此时本地的文件内容实际上是等同未暂存区的,根据介绍lint-staged会lint暂存区的那个版本,那么这是怎么做到的呢? 先猜测: 使用SourceTree提交代码偶尔会比较卡,稍微窥得点儿(未暂存文件消失再重现),因此猜测可能是用了什么方法先清除未暂存文件然后再恢复。 猜测归猜测,还是要验证一下。 结论经过分析,lint-staged在执行检查前会对保存当前文件状态,然后清除掉修改,再执行lint任务,执行完毕再恢复。 重点就是:如何保存?如何恢复? 我总结出lint-staged的流程大致如下 这样就很清晰了,由图可知,上述疑问点为红色流程部分,下面我们来分析一下流程中的具体实现。 分析流程大致分为四部分: Stashing changesRunning lintersUpdating stashRestoring local changes我们来分别看一下每一步做了什么 保留案发现场并清除干扰(Stashing changes...)git write-tree // 得到 indexTreegit add .git write-tree // 得到 workingCopyTreegit read-tree $indexTreegit checkout-index -af // 清除文件修改(未暂存的test2.js被清除)根据以上操作步骤得知,lint-staged通过tree对象来保存暂存区目录和工作区目录,并清除掉工作区修改文件,操作完成后,可以看到,被修改的test2.js已经被清除(如下图)。 执行代码检查任务(Running linters...)按照配置的命令走,比如配置了 "*.js": "eslint" eslint test2.js test.js 更新(Updating stash...)上一步(Running linters)如果有检查到错误,直接跳过走下一步(Restoring local changes) git write-tree // 得到 formattedIndexTree 这里需要特别声明一下, 如果上一步(Running linters)未检测到错误,那么这里得到的formattedIndexTree 会和第一步的indexTree一样,如果检测到错误并将修复后文件添加到暂存区,如配置命令是eslint --fix , git add的话,那么代码被修复过,formattedIndexTree 与indexTree不同 ...

June 13, 2019 · 1 min · jiezi

详解Vue计算属性和侦听属性

前言一些初学者可能对计算属性和侦听属性的使用场景感到困惑不解,本文主要介绍两者的用法、使用场景及其两者的区别。本文的代码请猛戳github博客,纸上得来终觉浅,大家动手多敲敲代码! 计算属性1.介绍计算属性是自动监听依赖值的变化,从而动态返回内容,监听是一个过程,在监听的值变化时,可以触发一个回调,并做一些事情。它有以下几个特点: 数据可以进行逻辑处理,减少模板中计算逻辑。对计算属性中的数据进行监视依赖固定的数据类型(响应式数据)计算属性由两部分组成:get和set,分别用来获取计算属性和设置计算属性。默认只有get,如果需要set,要自己添加。另外set设置属性,并不是直接修改计算属性,而是修改它的依赖。 computed: { fullName: { // getter get: function () { return this.firstName + ' ' + this.lastName }, // setter set: function (newValue) { //this.fullName = newValue 这种写法会报错 var names = newValue.split(' ') this.firstName = names[0]//对它的依赖进行赋值 this.lastName = names[names.length - 1] } }}现在再运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstName 和 vm.lastName 也会相应地被更新。 2.计算属性 vs 普通属性可以像绑定普通属性一样在模板中绑定计算属性,在定义上有区别:计算属性的属性值必须是一个函数。 data:{ //普通属性 msg:'浪里行舟',},computed:{ //计算属性 msg2:function(){ //该函数必须有返回值,用来获取属性,称为get函数 return '浪里行舟'; }, reverseMsg:function(){ //可以包含逻辑处理操作,同时reverseMsg依赖于msg,一旦msg发生变化,reverseMsg也会跟着变化 return this.msg.split(' ').reverse().join(' '); }} 3.计算属性 vs 方法两者最主要的区别:computed 是可以缓存的,methods 不能缓存;只要相关依赖没有改变,多次访问计算属性得到的值是之前缓存的计算结果,不会多次执行。网上有种说法就是方法可以传参,而计算属性不能,其实并不准确,计算属性可以通过闭包来实现传参: ...

June 13, 2019 · 2 min · jiezi

keyvalueequal

https://github.com/cq0122/key-value-equal Declare an object with the same key value.Declare an object with the same key value, you need to use Ctrl+C many times.const config = { USER: "USER", MENU: "MENU", PROVINCES: "PROVINCES", CITYS: "CITYS", ...};Installation kve.$npm install key-value-equal --saveUse kve, no Ctrl+C necessary.import { kve } from "key-value-equal";const config = kve("USER", "MENU", "PROVINCES", "CITYS", ...);//=> {USER:"USER", MENU:"MENU", PROVINCES:"PROVINCES", CITYS:"CITYS", ...}Making the code easier to read, kve also supports grouping, the multidimensional array will be flattened.import { kve } from "key-value-equal";const config = kve(["USER", "MENU"], ["PROVINCES", "CITYS"], ...);//=> {USER:"USER", MENU:"MENU", PROVINCES:"PROVINCES", CITYS:"CITYS", ...}There are also little surprises.kfn.upperimport { kve, kfn } from "key-value-equal";const config = kve("user", "menu", "provinces", "citys", ... , kfn.upper);//const config = kve("USER", "MENU", "PROVINCES", "CITYS", ...);//=> {USER:"USER", MENU:"MENU", PROVINCES:"PROVINCES", CITYS:"CITYS", ...}kfn.lowerimport { kve, kfn } from "key-value-equal";const config = kve("USER", "MENU", "PROVINCES", "CITYS", ... , kfn.lower);//const config = kve("user", "menu", "provinces", "citys", ...);//=> {user:"user", menu:"menu", provinces:"provinces", citys:"citys", ...}kfn.humpimport { kve, kfn } from "key-value-equal";const config = kve("user_setting", "menu_conf", "province_list", "city_list", ... , kfn.hump);//const config = kve("userSetting", "menuConf", "provinceList", "cityList", ...);//=> {userSetting:"userSetting", menuConf:"menuConf", provinceList:"provinceList", cityList:"cityList", ...}kfn.lineimport { kve, kfn } from "key-value-equal";const config = kve("userSetting", "menuConf", "provinceList", "cityList", ... , kfn.line);//const config = kve("user_setting", "menu_conf", "province_list", "city_list", ...);//=> {user_setting:"user_setting", menu_conf:"menu_conf", province_list:"province_list", city_list:"city_list", ...}Hope you will like !

June 13, 2019 · 1 min · jiezi

provincecityarea省市区数据工具包

https://github.com/cq0122/province-city-area 省市区数据工具包,含有省市区三级数据,支持排序和自定义返回的数据属性。安装$npm install province-city-area --save数据对象属性类型说明idstringidpidstringpidnamestring名称full_namestring全称short_namestring简称pinyinstring拼音pinyin_initialstring拼音首字母area_codestring区号数据统计idprovincecitycity_remarkarea11北京东城、西城、朝阳、丰台、石景山、海淀、门头沟、房山、通州、顺义、昌平、大兴、怀柔、平谷、密云、延庆16 个市辖区-12天津和平、河东、河西、南开、河北、红桥、东丽、西青、津南、北辰、武清、宝坻、滨海、宁河、静海、蓟州16 个市辖区-13河北石家庄、唐山、秦皇岛、邯郸、邢台、保定、张家口、承德、沧州、廊坊、衡水11 个地级市18814山西太原、大同、阳泉、长治、晋城、朔州、晋中、运城、忻州、临汾、吕梁11 个地级市12215内蒙古呼和浩特、包头、乌海、赤峰、通辽、鄂尔多斯、呼伦贝尔、巴彦淖尔、乌兰察布、兴安盟、锡林郭勒盟、阿拉善盟9 个地级市、3 个盟10921辽宁沈阳、大连、鞍山、抚顺、本溪、丹东、锦州、营口、阜新、辽阳、盘锦、铁岭、朝阳、葫芦岛14 个地级市10022吉林长春、吉林市、四平、辽源、通化、白山、松原、白城、延边8 个地级市、1 个自治州6923黑龙江哈尔滨、齐齐哈尔、鸡西、鹤岗、双鸭山、大庆、伊春、佳木斯、七台河、牡丹江、黑河、绥化、大兴安岭12 个地级市、1 个地区13431上海黄浦、徐汇、长宁、静安、普陀、虹口、杨浦、闵行、宝山、嘉定、浦东、金山、松江、青浦、奉贤、崇明16 个市辖区-32江苏南京、无锡、徐州、常州、苏州、南通、连云港、淮安、盐城、扬州、镇江、泰州、宿迁13 个地级市10733浙江杭州、宁波、温州、嘉兴、湖州、绍兴、金华、衢州、舟山、台州、丽水11 个地级市9034安徽合肥、芜湖、蚌埠、淮南、马鞍山、淮北、铜陵、安庆、黄山、滁州、阜阳、宿州、六安、亳州、池州、宣城16 个地级市12035福建福州、厦门、莆田、三明、泉州、漳州、南平、龙岩、宁德9 个地级市8436江西南昌、景德镇、萍乡、九江、新余、鹰潭、赣州、吉安、宜春、抚州、上饶11 个地级市10037山东济南、青岛、淄博、枣庄、东营、烟台、潍坊、济宁、泰安、威海、日照、临沂、德州、聊城、滨州、菏泽16 个地级市15441河南郑州、开封、洛阳、平顶山、安阳、鹤壁、新乡、焦作、濮阳、许昌、漯河、三门峡、南阳、商丘、信阳、周口、驻马店、济源17 个省辖市、1 个省直管市18142湖北武汉、黄石、十堰、宜昌、襄阳、鄂州、荆门、孝感、荆州、黄冈、咸宁、随州、恩施、仙桃、潜江、天门、神农架12 个地级市、1 个自治州、3 个省直管县级市、1 个林区10143湖南长沙、株洲、湘潭、衡阳、邵阳、岳阳、常德、张家界、益阳、郴州、永州、怀化、娄底、湘西13 个地级市、1 个自治州13944广东广州、韶关、深圳、珠海、汕头、佛山、江门、湛江、茂名、肇庆、惠州、梅州、汕尾、河源、阳江、清远、东莞、中山、潮州、揭阳、云浮21 个地级市18145广西南宁、柳州、桂林、梧州、北海、防城港、钦州、贵港、玉林、百色、贺州、河池、来宾、崇左14 个地级市11146海南海口、三亚、三沙、儋州、五指山、琼海、文昌、万宁、东方、定安、屯昌、澄迈、临高、白沙、昌江、乐东、陵水、保亭、琼中4 个地级市、15 个省直辖县级行政单位2950重庆万州、涪陵、渝中、大渡口、江北、沙坪坝、九龙坡、南岸、北碚、綦江、大足、渝北、巴南、黔江、长寿、江津、合川、永川、南川、璧山、璧山、潼南、荣昌、开州、梁平、武隆、城口、丰都、垫江、忠县、云阳、奉节、巫山、巫溪、石柱、秀山、酉阳、彭水26 个区、8 个县、4 个自治县-51四川成都、自贡、攀枝花、泸州、德阳、绵阳、广元、遂宁、内江、乐山、南充、眉山、宜宾、广安、达州、雅安、巴中、资阳、阿坝、甘孜、凉山18 个地级市、3 个自治州18652贵州贵阳、六盘水、遵义、安顺、毕节、铜仁、黔西南、黔东南、黔南6 个地级市、3 个自治州8853云南昆明、曲靖、玉溪、保山、昭通、丽江、普洱、临沧、楚雄、红河、文山、西双版纳、大理、德宏、怒江、迪庆8 个地级市、8 个自治州12954西藏拉萨、日喀则、昌都、林芝、山南、那曲、阿里6 个地级市、1 个地区7861陕西西安、铜川、宝鸡、咸阳、渭南、延安、汉中、榆林、安康、商洛10 个地级市10762甘肃兰州、嘉峪关、金昌、白银、天水、武威、张掖、平凉、酒泉、庆阳、定西、陇南、临夏、甘南12 个地级市、2 个自治州9363青海西宁、海东、海北、黄南、海南、果洛、玉树、海西2 个地级市、6 个自治州4564宁夏银川、石嘴山、吴忠、固原、中卫5 个地级市2265新疆乌鲁木齐、克拉玛依、吐鲁番、哈密、昌吉、博尔塔拉、巴音郭楞、阿克苏、克孜勒苏、喀什、和田、伊犁、塔城、阿勒泰、石河子、阿拉尔、图木舒克、五家渠、北屯、铁门关、双河、可克达拉、昆玉4 个地级市、5 个地区、5 个自治州、9 个自治区直辖县级市11971台湾台北市、新北市、桃园市、台中市、台南市、高雄市、基隆市、新竹市、嘉义市、新竹县、苗栗县、彰化县、南投县、云林县、嘉义县、屏东县、宜兰县、花莲县、台东县、澎湖县、金门县、连江县6 个直辖市、3 市、13 县-81香港中西区、湾仔区、东区、南区、油尖旺区、深水埗区、九龙城区、黄大仙区、观塘区、北区、大埔区、沙田区、西贡区、荃湾区、屯门区、元朗区、葵青区、离岛区18 个区-82澳门花地玛堂区、圣安多尼堂区、大堂区、望德堂区、风顺堂区、嘉模堂区、圣方济各堂区7 个堂区-province、area 的区号为 null,city 的区号不为空。台湾、香港、澳门的 city 数据中 name、full_name、short_name 相同。数据为个人整理,难免有遗漏和疏忽,如有错误,欢迎指正,谢谢。使用说明getProvinces(field, sort)filed 返回数组元素的属性,可选参数。默认返回数据对象的全部属性,支持传数组或对象,数组元素或对象属性必须为数据对象中的属性。例如:["id", "name", "pinyin"],则返回[{id:"xx1",name:"xx1",pinyin:"xx1"},{id:"xx2",name:"xx2",pinyin:"xx2"}...]。同时也支持传对象参数更改返回值的对象属性,例如:{id:"value",name:"label"},则返回[{value:"xx1",label:"xx1"},{value:"xx2",label:"xx2"}...]。sort 字段和排序规则对象,可选参数。默认按 id 升序。例如:{id:"desc",pinyin:"desc"},排序对象的属性必须为数据对象中的属性,属性定义的顺序影响排序的结果,值为 asc 或 desc。getCitys(pid, field, sort)pid 省 id,必填参数。filed,参考 getProvinces。sort,参考 getProvinces。getAreas(pid, field, sort)pid 地市 id,必填参数。filed,参考 getProvinces。sort,参考 getProvinces。使用示例import { getProvinces, getCitys, getAreas } from "province-city-area";getProvinces({ id: "key", name: "label" }, { pinyin: "asc" });//=> [{"key":"34","label":"安徽"},{"key":"82","label":"澳门"},{"key":"11","label":"北京"},{"key":"50","label":"重庆"},{"key":"35","label":"福建"},{"key":"62","label":"甘肃"},{"key":"44","label":"广东"},{"key":"45","label":"广西"},{"key":"52","label":"贵州"},{"key":"46","label":"海南"},{"key":"13","label":"河北"},{"key":"41","label":"河南"},{"key":"23","label":"黑龙江"},{"key":"42","label":"湖北"},{"key":"43","label":"湖南"},{"key":"22","label":"吉林"},{"key":"32","label":"江苏"},{"key":"36","label":"江西"},{"key":"21","label":"辽宁"},{"key":"15","label":"内蒙古"},{"key":"64","label":"宁夏"},{"key":"63","label":"青海"},{"key":"37","label":"山东"},{"key":"14","label":"山西"},{"key":"61","label":"陕西"},{"key":"31","label":"上海"},{"key":"51","label":"四川"},{"key":"71","label":"台湾"},{"key":"12","label":"天津"},{"key":"54","label":"西藏"},{"key":"81","label":"香港"},{"key":"65","label":"新疆"},{"key":"53","label":"云南"},{"key":"33","label":"浙江"}]getCitys(42, { id: "i", name: "n" });//=> [{"i":"4201","n":"武汉"},{"i":"4202","n":"黄石"},{"i":"4203","n":"十堰"},{"i":"4205","n":"宜昌"},{"i":"4206","n":"襄阳"},{"i":"4207","n":"鄂州"},{"i":"4208","n":"荆门"},{"i":"4209","n":"孝感"},{"i":"4210","n":"荆州"},{"i":"4211","n":"黄冈"},{"i":"4212","n":"咸宁"},{"i":"4213","n":"随州"},{"i":"4228","n":"恩施"},{"i":"429004","n":"仙桃"},{"i":"429005","n":"潜江"},{"i":"429006","n":"天门"},{"i":"429021","n":"神农架"}]getCitys(42, ["id", "name"]);//=> [{"id":"4201","name":"武汉"},{"id":"4202","name":"黄石"},{"id":"4203","name":"十堰"},{"id":"4205","name":"宜昌"},{"id":"4206","name":"襄阳"},{"id":"4207","name":"鄂州"},{"id":"4208","name":"荆门"},{"id":"4209","name":"孝感"},{"id":"4210","name":"荆州"},{"id":"4211","name":"黄冈"},{"id":"4212","name":"咸宁"},{"id":"4213","name":"随州"},{"id":"4228","name":"恩施"},{"id":"429004","name":"仙桃"},{"id":"429005","name":"潜江"},{"id":"429006","name":"天门"},{"id":"429021","name":"神农架"}]getAreas(4213);//=> [{"fullName":"曾都区","id":"421303","name":"曾都","pinyin":"zeng_du","pinyinInitial":"Z"},{"fullName":"随县","id":"421321","name":"随县","pinyin":"sui_xian","pinyinInitial":"S"},{"fullName":"广水市","id":"421381","name":"广水","pinyin":"guang_shui","pinyinInitial":"G"}]价值¥ 1?? 的排序方法orderBy(collection, sort):数组对象排序方法,支持多个属性排序,支持中文排序。 ...

June 13, 2019 · 1 min · jiezi

spydebugger-Charles-移动端调试

简介 移动端调试一直都是一个痛点,因为移动终端对于我们来说是一个黑盒,它无法像PC端一样,我们可以通过F12很方便的调出开发者工具。在开发中经常会遇到同样一份代码在某个型号的手机上运行出现错误,其他手机都好好的,开发的时候Chrome上也没有报错。如果没有调试工具这种情况下我们就很难定位问题,接下来的主题就是介绍如何使用spy-debugger + Charles进行移动端调试。 安装第1步:全局安装 spy-debugger npm install spy-debugger -gspy-debugger 证书其实spy-debugger的代理是基于node-mitmproxy模块实现的,这里安装的证书其实是node-mitmproxy的证书,标题写spy-debugger证书是为了和Charels证书区分开来避免混淆。 电脑安装证书第1步:在命令行中执行spy-debugger启动spy-debugger服务,启动成功后,检查你的用户目录(home目录),会发现多了一个node-mitmproxy文件夹,这个文件夹内放的就是代理需要的证书。 我Mac电脑完整的路径是:/Users/jameswain/node-mitmproxy 第2步:在启动spy-debugger服务的电脑上安装证书,双击node-mitmproxy.ca.crt文件 第3步:双击node-mitmproxy CA 选择为 始终信任 第4步:输入你电脑的用户密码 出现这个+号表示证书已经安装成功 IOS手机安装证书第1步:首先需要将node-mitmproxy.ca.crt上传到手机上,可以通过live-server 在node-mitmproxy.ca.crt文件所在的目录下启动这个服务。如果你还没有live-server命令,可以通过以下命令进行安装: npm i -g live-server在/Users/jameswain/node-mitmproxy目录下执行live-server命令 第2步:在手机浏览器上访问这个服务,输入我电脑的IP地址和端口进行访问,⚠️手机和电脑必须是连接同一个WiFi网络才可以访问。 点击node-mitmproxy.ca.crt文件进行下载安装 第3步:在手机的 设置 > 通用 > 描述文件与设备管理 找到node-mitmproxy CA 证书,并点击安装 输入手机锁屏密码: 选择安装 第4步:在手机的 设置 > 通用 > 关于本机 > 证书信任设置 将 node-mitmproxy CA 打开 此时,spy-debugger的前期准备工作就已经全部完成了 Charles 证书这里简单介绍一下Charles的证书安装,如果你已经是Charles的老手了,可以直接跳过。Charles如果不安装证书的话是无法抓https的请求的。 ...

June 13, 2019 · 1 min · jiezi

手把教你搭建SSRvuevuecli-express

最近简单的研究了一下SSR,对SSR已经有了一个简单的认知,主要应用于单页面应用,Nuxt是SSR很不错的框架。也有过调研,简单的用了一下,感觉还是很不错。但是还是想知道若不依赖于框架又应该如果处理SSR,研究一下做个笔记。 什么是SSR把Vue组件渲染为服务器端的HTML字符串,将他们直接发送到浏览器,最后将静态标记混合为客户端上完全交互的应用程序。 为什么要使用SSR更好的SEO,搜索引擎爬虫爬取工具可以直接查看完全渲染的页面更宽的内容达到时间(time-to-content),当权请求页面的时候,服务端渲染完数据之后,把渲染好的页面直接发送给浏览器,并进行渲染。浏览器只需要解析html不需要去解析js。SSR弊端开发条件受限,Vue组件的某些生命周期钩子函数不能使用开发环境基于Node.js会造成服务端更多的负载。在Node.js中渲染完整的应用程序,显然会比仅仅提供静态文件server更加占用CPU资源,因此如果你在预料在高流量下使用,请准备响应的服务负载,并明智的采用缓存策略。准备工作在正式开始之前,在vue官网找到了一张这个图片,图中详细的讲述了vue中对ssr的实现思路。如下图简单的说一下。 下图中很重要的一点就是webpack,在项目过程中会用到webpack的配置,从最左边开始就是我们所写入的源码文件,所有的文件都有一个公共的入口文件app.js,然后就进入了server-entry(服务端入口)和client-entry(客户端入口),两个入口文件都要经过webpack,当访问node端的时候,使用的是服务端渲染,在服务端渲染的时候,会生成一个server-Bender,最后通过server-Bundle可以渲染出HTML页面,若在客户端访问的时候则是使用客户端渲染,通过client-Bundle在以后渲染出HTML页面。so~通过这个图可以很清晰的看出来,接下来会用到两个文件,一个server入口,一个client入口,最后由webpack生成server-Bundle和client-Bundle,最终当去请求页面的时候,node中的server-Bundle会生成HTML界面通过client-Bundle混合到html页面中即可。 对于vue中使用ssr做了一些简单的了解之后,那么就开始我们要做的第一步吧,首先要创建一个项目,创建一个文件夹,名字不重要,但是最好不要使用中文。 mkdir domecd domenpm initnpm init命令用来初始化package.json文件: { "name": "dome", // 项目名称 "version": "1.0.0", // 版本号 "description": "", // 描述 "main": "index.js", // 入口文件 "scripts": { // 命令行执行命令 如:npm run test "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Aaron", // 作者 "license": "ISC" // 许可证}初始化完成之后接下来需要安装,项目所需要依赖的包,所有依赖项如下: npm install express --save-devnpm install vue --save-devnpm install vue-server-render --save-devnpm install vue-router --save-dev如上所有依赖项一一安装即可,安装完成之后就可以进行下一步了。前面说过SSR是服务端预渲染,所以当然要创建一个Node服务来支撑。在dome文件夹下面创建一个index.js文件,并使用express创建一个服务。 ...

June 13, 2019 · 4 min · jiezi

实现跨域通信的9种方法

首先明确,跨域请求只是浏览器对请求的限制,虽然跨域,服务器仍然能收到客户端请求,服务器之间通信不存在跨域问题。1. 什么是跨域?协议域名端口只要协议、域名、端口号其中任意一者不同,均属跨域。2. 实现跨域的9种方法1 jsonp 2 cors 3 nginx 4 websocket 5 postMessage 6 document.domain 7 window.name 8 location.hash 9 http-proxy jsonpcorsAccess-Control-Allow-Origin: * 就不允许跨域携带cookienginx<!-- 访问.json文件时,会去root 下面json文件中查找 -->location ~.*\.json { root json;}postMessagedocument.domain一级域名和二级域名之间的通信window.namelocation.hashhttp-proxywebsocketsocket.io库结论根据不同的应用场景,选择相应的跨域通信方式。

June 12, 2019 · 1 min · jiezi

webpack4大结局加入腾讯IM配置策略实现前端工程化环境极致优化

webpack,打包所有的资源不知道不觉,webpack已经偷偷更新到4.34版本了,本人决定,这是今年最后一篇写webpack的文章,除非它更新到版本5,本人今年剩下的时间都会放在Golang和二进制数据操作以及后端的生态上在看本文前,假设你对webpack有一定了解,如果不了解,可以看看我之前的手写React和Vue脚手架的文章手写优化版React脚手架手写Vue的脚手架前端性能优化不完全手册跨平台webpack配置都是百星star的优质文章在此对webpack的性能优化进行几点声明:在部分极度复杂的环境下,需要双package.json文件,即实行三次打包在代码分割时,低于18K的文件没必要单独打包成一个chunk,http请求次数过多反而影响性能prerender和PWA互斥,这个问题暂时没有解决babel缓存编译缓存的是索引,即hash值,非常吃内存,每次开发完记得清理内存babel-polyfill按需加载在某些非常复杂的场景下比较适合prefetch,preload对首屏优化提升是明显代码分割不管什么技术栈,一定要做,不然就是垃圾项目多线程编译对构建速度提升也很明显代码分割配合PWA+预渲染+preload是首屏优化的巅峰,但是pwa无法缓存预渲染的html文件本文的webpack主要针对React技术栈,实现功能如下:开发模式热更新识别JSX文件识别class组件代码混淆压缩,防止反编译代码,加密代码配置alias别名,简化import的长字段同构直出,SSR的热调试(基于Node做中间件)实现javaScript的tree shaking 摇树优化 删除掉无用代码实现CSS的tree shaking识别 async / await 和 箭头函数react-hot-loader记录react页面留存状态statePWA功能,热刷新,安装后立即接管浏览器 离线后仍让可以访问网站 还可以在手机上添加网站到桌面使用preload 预加载资源 prefetch按需请求资源CSS模块化,不怕命名冲突小图片的base64处理文件后缀省掉jsx js json等实现React懒加载,按需加载 , 代码分割 并且支持服务端渲染支持less sass stylus等预处理code spliting 优化首屏加载时间 不让一个文件体积过大加入dns-prefetch和preload预请求必要的资源,加快首屏渲染(京东策略)加入prerender,极大加快首屏渲染速度提取公共代码,打包成一个chunk每个chunk有对应的chunkhash,每个文件有对应的contenthash,方便浏览器区别缓存图片压缩CSS压缩增加CSS前缀 兼容各种浏览器对于各种不同文件打包输出指定文件夹下缓存babel的编译结果,加快编译速度每个入口文件,对应一个chunk,打包出来后对应一个文件 也是code spliting删除HTML文件的注释等无用内容每次编译删除旧的打包代码将CSS文件单独抽取出来让babel不仅缓存编译结果,还在第一次编译后开启多线程编译,极大加快构建速度等等....本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundlewebpack打包原理识别入口文件通过逐层识别模块依赖。(Commonjs、amd或者es6的import,webpack都会对其进行分析。来获取代码的依赖)webpack做的就是分析代码。转换代码,编译代码,输出代码最终形成打包后的代码这些都是webpack的一些基础知识,对于理解webpack的工作机制很有帮助。舒适的开发体验,有助于提高我们的开发效率,优化开发体验也至关重要组件热刷新、CSS热刷新自从webpack推出热刷新后,前端开发者在开环境下体验大幅提高。没有热刷新能力,我们修改一个组件后 加入热刷新后 主要看一下React技术栈,如何在构建中接入热刷新无论什么技术栈,都需要在dev模式下加上 webpack.HotModuleReplacementPlugin插件 devServer: { contentBase: '../build', open: true, port: 5000, hot: true },注:也可以使用react-hot-loader来实现,具体参考官方文档 在开发模式下也要代码分割,加快打开页面速度optimization: { runtimeChunk: true, splitChunks: { chunks: 'all', minSize: 10000, // 提高缓存利用率,这需要在http2/spdy maxSize: 0,//没有限制 minChunks: 3,// 共享最少的chunk数,使用次数超过这个值才会被提取 maxAsyncRequests: 5,//最多的异步chunk数 maxInitialRequests: 5,// 最多的同步chunks数 automaticNameDelimiter: '~',// 多页面共用chunk命名分隔符 name: true, cacheGroups: {// 声明的公共chunk vendor: { // 过滤需要打入的模块 test: module => { if (module.resource) { const include = [/[\\/]node_modules[\\/]/].every(reg => { return reg.test(module.resource); }); const exclude = [/[\\/]node_modules[\\/](react|redux|antd)/].some(reg => { return reg.test(module.resource); }); return include && !exclude; } return false; }, name: 'vendor', priority: 50,// 确定模块打入的优先级 reuseExistingChunk: true,// 使用复用已经存在的模块 }, react: { test({ resource }) { return /[\\/]node_modules[\\/](react|redux)/.test(resource); }, name: 'react', priority: 20, reuseExistingChunk: true, }, antd: { test: /[\\/]node_modules[\\/]antd/, name: 'antd', priority: 15, reuseExistingChunk: true, }, }, } }简要解释上面这段配置将node_modules共用部分打入vendor.js bundle中;将react全家桶打入react.js bundle中;如果项目依赖了antd,那么将antd打入单独的bundle中;(其实不用这样,可以看我下面的babel配置,性能更高)最后剩下的业务模块超过3次引用的公共模块,将自动提取公共块注意 上面的配置只是为了给大家看,其实这样配置代码分割,性能更高optimization: { runtimeChunk: true, splitChunks: { chunks: 'all', }}react-hot-loader记录react页面留存状态stateyarn add react-hot-loader // 在入口文件里这样写 import React from "react";import ReactDOM from "react-dom";import { AppContainer } from "react-hot-loader";-------------------1、首先引入AppContainreimport { BrowserRouter } from "react-router-dom";import Router from "./router"; /*初始化*/renderWithHotReload(Router);-------------------2、初始化 /*热更新*/if (module.hot) {-------------------3、热更新操作 module.hot.accept("./router/index.js", () => { const Router = require("./router/index.js").default; renderWithHotReload(Router); });} function renderWithHotReload(Router) {-------------------4、定义渲染函数 ReactDOM.render( <AppContainer> <BrowserRouter> <Router /> </BrowserRouter> </AppContainer>, document.getElementById("app") );}然后你再刷新试试React的按需加载,附带代码分割功能 ,每个按需加载的组件打包后都会被单独分割成一个文件 import React from 'react' import loadable from 'react-loadable' import Loading from '../loading' const LoadableComponent = loadable({ loader: () => import('../Test/index.jsx'), loading: Loading, }); class Assets extends React.Component { render() { return ( <div> <div>这即将按需加载</div> <LoadableComponent /> </div> ) } } export default Assets* 加入html-loader识别html文件 { test: /\.(html)$/, loader: 'html-loader' }配置别名 resolve: { modules: [ path.resolve(__dirname, 'src'), path.resolve(__dirname,'node_modules'), ], alias: { components: path.resolve(__dirname, '/src/components'), }, } 加入eslint-loader { enforce:'pre', test:/\.js$/, exclude:/node_modules/, include:resolve(__dirname,'/src/js'), loader:'eslint-loader' }resolve解析配置,为了为了给所有文件后缀省掉 js jsx json,加入配置resolve: { extensions: [".js", ".json", ".jsx"]}加入HTML文件压缩,自动将入门的js文件注入html中,优化HTML文件 new HtmlWebpackPlugin({ template: './public/index.html', minify: { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true, } }),SSR同构直出热调试, 采用 webpack watch+nodemon 结合的模式实现对SSR热调试的支持。node 服务需要的html/js通过webpack插件动态输出,当nodemon检测到变化后将自动重启,html文件中的静态资源全部替换为dev模式下的资源,并保持socket连接自动更新页面。实现热调试后,调试流程大幅缩短,和普通非直出模式调试体验保持一致。下面是SSR热调试的流程图: ...

June 12, 2019 · 4 min · jiezi

配置TS-node-的开发环境

直接配置ts的开发环境还是挺麻烦的,这里我总结了一套开发模板,可以在用的时候可以考虑直接clone这个项目,项目地址https://github.com/fish-node/...以这个项目 为基础模板来开发就行。 $ tree.├── README.md├── jest.config.js├── package.json├── src│   ├── index.ts│   ├── math.ts│   └── server.ts├── test│   ├── math.test.ts│   └── server.test.ts├── tsconfig.json└── tslint.jsonts-node因为ts是建立在js的基础之上的,但是node又不能直接运行ts代码,实际使用是往往需要使用tsc将ts代码编译成js代码,这无疑是很麻烦的。 而ts-node则包装了node,它可以直接的运行ts代码,使用起来很方便,它的官方仓库在这里 https://github.com/TypeStrong... ,基本用法请自行查看它的readme。 我这里就是把ts-node写到package.json,大家在npm install之后,就可以通过 $ npx ts-node src/index.ts这种方式来运行ts代码,很方便。 然后我们知道node中有个nodemon,可以在开发时自动的重启我们的node程序,而在ts-node中,对应的就是ts-node-dev $ npx ts-node-dev src/index.ts示例代码在src目录下,我写了三个基本的ts代码 math.ts就是最基本的ts的用法然后server.js展示了如何使用ts编写一个http服务器index.ts则展示了ts中的模块化的使用测试框架然后我使用Jest 作为测试框架,在test目录下展示了用Jest + Ts 编写我们的测试程序。运行测试文件的话,可以使用 $ npm run test@types@types/node @types/jest 是ts中类型声明文件,它可以给js编写的模块赋予类型系统,让我们享受类型系统的好处。 配置文件.editorconfig是我们配置IDE的阅读配置文件tslint.json使我们的代码检查配置文件这个文件主要是继承了官方推荐的代码风格。不过为了调试方便,把'no-console'这个条件设置为false了tsconfig.json然后重点是我们的ts配置文件。 一般来说,json文件是不支持注释的,但是ts官方为了方便,对这个文件单独做了加强,使得我们可以使用注释,注释语法和js中的一样。 { "compilerOptions": { "target": "es2018", "module": "commonjs", "outDir": "./dist/", "rootDir": "./src/", "strict": true, "moduleResolution": "node", "esModuleInterop": true, "experimentalDecorators": true }, "exclude": ["./test"]}其中的target就是编译后js代码的版本,因为我们是node环境,没必要考虑浏览器的兼容问题,所以大家还是尽量的使用更新的版本吧。 ...

June 12, 2019 · 1 min · jiezi

基于-ThinkJS-的-WebSocket-通信详解

基于 ThinkJS 的 WebSocket 通信详解 前言我们的项目是基于 ThinkJS + Vue 开发的,最近实现了一个多端实时同步数据的功能,所以想写一篇文章来介绍下如何在 ThinkJS 的项目中利用 WebSocket 实现多端的实时通信。ThinkJS 是基于 Koa 2 开发的企业级 Node.js 服务端框架,文章中会从零开始实现一个简单的聊天室,希望读者们能有所收获。 WebSocketWebSocket 是 HTML5 中提出的一种协议。它的出现是为了解决客户端和服务端的实时通信问题。在 WebSocket 出现之前,如果想实现实时消息传递一般有两种方式: 客户端通过轮询不停的向服务端发送请求,如果有新消息客户端进行更新。这种方式的缺点很明显,客户端需要不停向服务器发送请求,然而 HTTP 请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多带宽资源HTTP 长连接,客户端通过 HTTP 请求连接到服务端后, 底层的 TCP 连接不会马上断开,后续的信息还是可以通过同一个连接来传输。这种方式有一个问题是每个连接会占用服务端资源,在收到消息后连接断开,就需要重新发送请求。如此循环往复。可以看到,这两种实现方式的本质还是客户端向服务端“Pull”的过程,并没有一个服务端主动“Push”到客户端的方式,所有的方式都是依赖客户端先发起请求。为了满足两方的实时通信, WebSocket 应运而生。 WebSocket 协议首先,WebSocket 是基于 HTTP 协议的,或者说借用了 HTTP 协议来完成连接的握手部分。其次,WebSocket 是一个持久化协议,相对于 HTTP 这种非持久的协议来说,一个 HTTP 请求在收到服务端回复后会直接断开连接,下次获取消息需要重新发送 HTTP 请求,而 WebSocket 在连接成功后可以保持连接状态。下图应该能体现两者的关系: 在发起 WebSocket 请求时需要先通过 HTTP 请求告诉服务端需求将协议升级为 WebSocket。 浏览器先发送请求: GET / HTTP/1.1Host: localhost:8080Origin: [url=http://127.0.0.1:3000]http://127.0.0.1:3000[/url]Connection: UpgradeUpgrade: WebSocketSec-WebSocket-Version: 13Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==服务端回应请求: ...

June 12, 2019 · 4 min · jiezi

初学NodeJS二-Ajax

关于Ajax封装好的Ajax 前端往后端传递数据需要用到Ajax来传递。 首先需要在网页链入这个已经封装好的Ajax<script src="..."></script>,因为如果直接在html页面写这个的话不好看之后就可以利用Ajax来传递了 // 前端页面文件名:<input type='text' id="fileName"/>文件内容:<textarea cols="30" rows="10" id="inner"></textarea><input type='button' value='写入文件' id='btn'/><script>btn.onclick=function() { ajax({ url:'localhost:1234', type:'get', data:{ fileName:fileName.value, inner:inner.value }, success:function(data){ console.log('文件写入完毕'); }}) } </script>首先要知道,封装好的ajax需要作者传递个json格式的参数进去其次,必须要有的是后端接口的地址url,传输数据的方式type,前端传给后端的数据内容都写在data内,success方法会自动传入个data参数,这个方法将处理成功接受到后端返回的数据假设我们后端想要写个文本文件 //后端页面var http = require('http'),urlLib = require('url'),fs = require('fs');//利用http模块创建本地服务固定格式http.createServer(function(req, res) { //跨域,‘*’号表示所有 res.setHeader('Access-Control-Allow-Origin', '*'); //利用url模块解析网页传递的地址数据 var json = urlLib.parse(req.url, true).query; //利用fs模块的writeFile方法写入文件 fs.writeFile(json.file, json.inner, function(err) { if(err)console.log(err); console.log('生成完毕'); });}).listen(1234);http模块的作用是让后台不用在html文件启动,直接启动本地服务器,在浏览器搜索栏输入localhost:1234即可,1234是后端自己设置的监听接口url模块的作用是如果用get方式传输数据的话,数据是可以在地址栏看到的,所以直接解析地址就可以得到前端传输的数据了利用http模块创建本地服务就可以不用在打开html文件,设置好的话后端会直接访问,用户访问网页只需要在浏览器输入localhost:+监听接口号。跨域:跨域大致可以理解为在这个页面访问另一个文件。Access-Control-Allow-Origin表示允许的域,*表示所有,也就是说给予最高访问权限差不多意思。代码写好还不止,因为我们的NodeJS是后端语言,是网页脚本,所以需要‘‘启动’’。 编写好代码之后需要进入脚本文件的文件夹内启动控制台cmd,输入node xxx.js这个xxx是你的脚本js文件的名字注意检查模块是否 已经下载好,是否进入正确的文件夹内。

June 12, 2019 · 1 min · jiezi

Nodejs-日志输出指南

原文地址: https://www.twilio.com/blog/g...原文作者: DOMINIK KUNDEL 翻译作者: icepy 翻译出处: https://github.com/lightningm... 当你开始使用 JavaScript 做开发时,你可能学习到的第一件事情就是如何使用 console.log 将内容打印到控制台。如果你搜索如何调试 JavaScript,你会发现数百个博客文章和 StackOverflow 的文章都指向简单的 console.log 。因为这是一种常见的做法,我们甚至可以使用 no-console 这样的规则来确保生产环境不会留下日志。但是,如果我们真的想要记录这些信息呢? 在这篇博文中,我们将介绍你想要记录信息的各种情况,Node.js 中的 console.log 和 console.error 之间的区别是什么,以及如何在不使用户控制台混乱的情况下在库中发送日志记录。 console.log(`Let's go!`);Theory First: Important Details for Node.js虽然你可以在浏览器和 Node.js 环境中使用 console.log 和 console.error,但在 Node.js 中使用时一定要记住一件重要的事情。 将如下代码写入到 index.js 文件中,并在 Node.js 环境里执行: console.log('Hello there');console.error('Bye bye');如图: 虽然这两个输出看起来可能一样,但系统实际上对它的处理方式有不同。如果你检查一下 console section of the Node.js documentation 你会发现 console.log 使用 stdout 打印而 console.error 则使用 stderr。 每一个进程都有三个可以使用的默认 streams,它们是 stdin ,stdout 和 stderr。 stdin 可以处理进程的输入,例如按下按钮或重定向输出。stdout 可以用于处理进程的输出。最后 stderr 则用于错误消息。如果你想了解 stderr 为什么存在以及何时使用它,可以访问:When to use STDERR instead of STDOUT。 ...

June 12, 2019 · 3 min · jiezi

TypeScript学习清单

ES6必看部分:** 红色勾选为重点** TypeScript文档必看部分:中文版:https://www.tslang.cn/docs/ha... 英文版(推荐,有更新):http://www.typescriptlang.org... 其它:Array的 map reduce find filter 方法 必看

June 12, 2019 · 1 min · jiezi

compressPng图片压缩

compressPng前端线上自动化图片压缩工具,基于tinypng接口开发,通过命令行控制 https://tinypng.com/developers "tinypng.com" ** 一,Quick Start**1,全局安装compresspngnpm i compresspng -g2,打开shell窗口,输入cpng 命令cpng //查看帮助 cpng path <源目录路径> [目标路径] 二,说明建议全局安装,安装之后可以随时通过shell压缩图片,并拷贝非图片文件。当目录路径是相对路径时,相对于当前命令窗口。可以放在package.json里使用,避免重复输入。例如:"scripts": { "img": "cpng path .\res\"}只有源目录路径一个参数时,或者源路径与目标路径相同时,默认覆盖当前文件,不拷贝非图片文件。**在源目录路径下生成compressed.log文件,用来记录当前目录图片压缩状态。已经压缩过一次之后,想在源目录路径下压缩图片覆盖源文件,请先手动删除compressed.log文件,反之同理。除此之外,为了避免不必要的压缩开销,请不要随意删除。**

June 12, 2019 · 1 min · jiezi

Nodejs官方文档到底什么是阻塞Blocking与非阻塞NonBlocking

译者按: Node.js文档阅读系列之一。 原文: Overview of Blocking vs Non-Blocking译者: Fundebug为了保证可读性,本文采用意译而非直译。 这篇博客将介绍Node.js的阻塞(Blocking)与非阻塞(Non-Blocking)。我会提到Event Loop与libuv,但是不了解它们也不会影响阅读。读者只需要有一定的JavaScript基础,理解Node.js的回调函数(callback pattern)就可以了。 博客中提到了很多次I/O,它主要指的是使用libuv与系统的磁盘与网络进行交互。 阻塞(Blocking)阻塞指的是一部分Node.js代码需要等到一些非Node.js代码执行完成之后才能继续执行。这是因为当阻塞发生时,Event Loop无法继续执行。 对于Node.js来说,由于CPU密集的操作导致代码性能很差时,不能称为阻塞。当需要等待非Node.js代码执行时,才能称为阻塞。Node.js中依赖于libuv的同步方法(以Sync结尾)导致阻塞,是最常见的情况。当然,一些不依赖于libuv的原生Node.js方法有些也能导致阻塞。 Node.js中所有与I/O相关的方法都提供了异步版本,它们是非阻塞的,可以指定回调函数,例如fs.readFile。其中一些方法也有对应的阻塞版本,它们的函数名以Sync结尾,例如fs.readFileSync。 代码示例阻塞的方法是同步执行的,而非阻塞的方法是异步执行。 以读文件为例,下面是同步执行的代码: const fs = require('fs');const data = fs.readFileSync('/file.md'); // 文件读取完成之前,代码会阻塞,不会执行后面的代码console.log("Hello, Fundebug!"); // 文件读取完成之后才会打印对应的异步代码如下: const fs = require('fs');fs.readFile('/file.md', (err, data) => { if (err) throw err;}); // 代码不会因为读文件阻塞,会继续执行后面的代码console.log("Hello, Fundebug!"); // 文件读完之前就会打印第一个示例代码看起来要简单很多,但是它的缺点是会阻塞代码执行,后面的代码需要等到整个文件读取完成之后才能继续执行。 在同步代码中,如果读取文件出错了,则错误需要使用try...catch处理,否则进程会崩溃。对于异步代码,是否处理回调函数的错误则取决于开发者。 我们可以将示例代码稍微修改一下,下面是同步代码: const fs = require('fs');const data = fs.readFileSync('/file.md'); console.log(data);moreWork(); // console.log之后再执行异步代码如下: const fs = require('fs');fs.readFile('/file.md', (err, data) => { if (err) throw err; console.log(data);});moreWork(); // 先于console.log执行在第一个示例中,console.log将会先于moreWork()执行。在第二个示例中,由于fs.readFile()是非阻塞的,代码可以继续执行,因此moreWork()会先于console.log执行。 ...

June 12, 2019 · 1 min · jiezi

Node中间层服务编写框架中的思考

框架编写核心思想高内聚低耦合核心方法封装npm包,核心库封装(函数式编程)核心方法只提供调用途径,像使用第三方的工具包一样(传参和结果都被制定好了)开发只关心业务逻辑即可基于koa2 封装路由方法,让路由、数据访问、业务、外部接口各自分层npm包的选择非业务组件库开源: 开放而非封闭,开源的好处: 分享 idea获取交流的机会(人外有人,天外有天。总有人可以教我们更好的做法)技术应该是持续更新,而不是一潭死水
业务组件库,和数据正相关: 封闭,发布私有包

June 12, 2019 · 1 min · jiezi

初学NodeJS三

nodeJS中的模块在nodeJS中有模块的概念,类似于C语言中的头文件,都是函数库。灵活运用模块可以非常便捷的操作后端操作前端。而在nodeJS中引入模块要使用require();这里举例几个常用的模块。 使用模块前要先利用npm或cnpm下载,npm是在nodeJS安装包里的,安装nodeJS自带的,而cnpm是阿里巴巴做的中国镜像,利用npm下载因为其服务器在国外所以可能会影响到下载速度,因此推荐利用cnpm下载。最好在运行文件的同个文件夹下下载方便使用,也可以在上级文件夹下载全局调用。下载完nodeJS后在cmd中输入node -v可以查看node的版本号。 同理npm -v可以查看npm版本号下载完成nodeJS之后cmd输入npm install -g cnpm --registry=https://registry.npm.taobao.org就安装cnpm了,同理cnpm -v查看cnpm版本号 fs 模块利用cnpm下载,cnpm i fs 或 cnpm install fs在cnpm与npm中i默认为是installfs模块介绍:nodeJS官方API文档介绍fs模块 简单来说,fs模块是用于操作文档文件的。他可以为文档增删改查。 增: var fs = require('fs');fs.writeFile('aaa.txt', '今天天气不错', function(err) { if(err) console.log('error is: '+ err); console.log('写入完毕,请查看');});var fs=require('fs');引入了下载好的fs模块fs.writeFile('文件名字','内容','错误处理函数');在错误处理函数中,writeFile会传进一个err报告错误内容是什么,一般很多模块的方法都会要求传递一个函数参数是方法传递进去的信息,作者自己写处理方法最后在处理方法里打印出错误条件,在前台打印出来删: var fs = require('fs');fs.unlink('aaa.txt',function(err) { if(err)console.log('删除错误'+err); console.log('删除成功');});与增一样,第一句引入fs模块fs.unlink('名字','错误处理函数'),这个方法只有两个参数改: var fs = require('fs');fs.rename('aaa.txt', 'bbb.txt' ,function(err) { if(err)console.log('更改错误'+err); console.log('更改成功');});fs.rename('目标名字','修改名字',fn)查: var fs = require('fs');fs.readFile('./aaa.txt',function(err,data){ if(err)console.log('读取错误'+err); console.log(data);}fs.readFile('文件路径','成功/失败处理函数')

June 12, 2019 · 1 min · jiezi

Nodejs常用工具util

讲讲node常用工具util。 util是一个Node.js的核心模块,提供常用函数的集合,用于弥补核心Javascript的功能过于精简的不足。 util.inherits(constructor, superConstructor)是一个实现对象间继承的函数。 JavaScript的面向对象特性是基于原型(如果不了解原型自己在去找找相关文章补习一下),与常用的基于类不同。JavaScript没有提供对象继承的语言级别特性,而是通过原型复制来实现的。 先说说uril.inherits的用法,示例: const util = require('util')function Base() { this.name = 'base' this.base = 1991 this.sayHello = function() { console.log('Hello ' + this.name) }}Base.prototype.showName = function(){ console.log(this.name)}function Sub() { this.name = 'Sub'}util.inherits(Sub,Base)const objBase = new Base()objBase.showName()objBase.sayHello()console.log(objBase)const objSub = new Sub()objSub.showName()console.log(objSub)我们定义了一个基础对象 Base 和一个继承自 Base 的 Sub,Base 有三个在构造函数内定义的属性和一个原型中定义的函数,通过util.inherits 实现继承。运行结果如下: 可以看到,Sub仅仅继承了Base在原型中定义的函数,而贡枣函数内部创造的Base属性和sayHello函数都没有被Sub继承。同时,在原型中定义都属性不回被console.log作为对象都属性输出。 util.inspect(object,[showHidden],[depth],[colors]):是一个将任意对象转换为字符串的方法,通常用于调试和错误输出。它至少接收一个参数object,即要转换的对象。showHidden是一个可选参数,如果值为true,将会输出更多隐藏信息,depth表示最大递归的层数,如果对象很复杂,你可以指定层数,如果不指定,默认会递归2层指定为null表示将不限递归层数完整的遍历对象。如果color的值为true,输出格式将会以ANSI颜色编码,通常用于在终端现实出更漂亮的效果。特别要指出的是,util.inspect并不会简单地直接把对象转换为字符串,即使该对象定义了toString方法也不会调用。 const util = require('util')function Person() { this.name = 'Sifou' this.toString = function() { return this.name }}const obj = new Person()console.log(util.inspect(obj))console.log(util.inspect(obj, true))结果为: ...

June 11, 2019 · 1 min · jiezi

NodeJs生成sitemap站点地图

如果博客是使用Hexo管理的,sitemap可以使用插件来生成。但对于一个内容管理网站,后端可能是express、koa之类的框架,这时sitemap就需要自己来生成了 什么是sitemap Sitemap可方便网站管理员通知搜索引擎他们网站上有哪些可供抓取的网页。最简单的Sitemap形式,就是XML文件,在其中列出网站中的网址以及关于每个网址的其他元数据(上次更新的时间、更改的频率以及相对于网站上其他网址的重要程度为何等),以便搜索引擎可以更加智能地抓取网站。 sitemap结构<url> <loc>http://www.jouypub.com/</loc> <lastmod>2019-05-01</lastmod> <changefreq>daily</changefreq> <priority>0.5</priority></url>loc:文章链接地址lastmod:最后更新时间changefreq:更新频率,daily/monthlypriority:权重 生成sitemap,基于express项目开源包:sitemap,地址: https://github.com/ekalinin/s... > npm install --save sitemap代码中使用 const express = require('express')const sm = require('sitemap');router.get('/sitemap.xml', function (req, res) { let pageRequest = Object.create({}); pageRequest.pageSize = -1; pageRequest.pageNum = 1; api.post('/article/list', pageRequest, function (result) { let urls = []; for (let article in result) { urls.push({ url: article.url, changefreq: 'daily', lastmodrealtime: true, priority: 1, lastmod: article.updateTime }); } let sitemap = sm.createSitemap({ hostname: 'http://invest.jouypub.com', cacheTime: 600000, // 600sec, cache purge period urls: urls }); sitemap.toXML(function (err, xml) { if (err) { console.log(err); return res.status(500).end(); } res.header('Content-Type', 'application/xml'); res.send(xml); }); });});sitemap优化上面那种方法在文章数少时还能使用,如果有几千甚至几万篇文章,一次拉取的方式就不适合了,就需要把返回结果写入到文件中,一天更新一次。只需要只需要把sitemap.toXML()改成fs.writeFileSync("app/assets/sitemap.xml", sitemap.toString());即可。每次请求sitemap时读文件即可 ...

June 11, 2019 · 1 min · jiezi

Jenkins自动部署NodeJs

Jenkins配置1、打开系统设置中的全局工具配置,选择新增NodeJs,选择自己对应的版本后保存即可,我这里别名直接用版本号代替了。 2、打开系统设置中的配置,选择增加SSH-Server,我选择直接用用户名登录的, 勾选Use password authentication, or use a different key,填写对应账号的密码即可 构建任务1、新建任务,选择构建自由风格的项目 2、源码管理中填写git地址并选择自己对应的分支 3、构建环境中勾选Provide Node & npm bin/ folder to PATH,选择自己对应的NodeJs 4、构建,选择执行shell 选择通过命令行发送文件 Source files:需要发送的文件,该路径是本项目在Jenkins工作空间(workspace)下的相对路径 Remove prefix:移除前缀,意思是去除路径中的文件夹,只留下文件 Remote directory:目标文件夹,该路径是目标Tomcat所在服务器的root文件夹下的相对路径 5、保存,构建看看结果,成功的话结果如下: 报错一开始总是下面这样报错 sh: /var/lib/jenkins/workspace/web_erp/node_modules/.bin/cross-env: Permission deniednpm ERR! code ELIFECYCLEnpm ERR! errno 126npm ERR! sdhadmin@1.0.0 build:qa: `cross-env NODE_ENV=qa env_config=qa node build/build.js`npm ERR! Exit status 126npm ERR! npm ERR! Failed at the sdhadmin@1.0.0 build:qa 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! /var/lib/jenkins/.npm/_logs/2019-06-04T02_24_47_411Z-debug.logBuild step 'Execute shell' marked build as failure网上找了一圈资料没找到什么信息,试着把node_modules文件夹删掉再重新构建后就ok了。 ...

June 11, 2019 · 1 min · jiezi

初学NodeJS一

Ajax 介绍在node.js中前后台交互数据经常会用到这个东西,Ajax不是新的编程语言,而是一种使用现有标准的新方法,他可以实现不刷新网页部分更新数据。 Ajax数据格式ajax封装 使用封装Ajax需要了解什么是XMLHttpRequest; 什么是XMLHttpRequestXMLHttpRequest 用于在后台与服务器交换数据。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新 创建XMLHttpRequest对象实例:var XMLHttpRequest=new XMLHttpRequest(); Ajax的传输方式传输数据方式有两种,一种get,一种post。 get: 地址栏 - 数据都可视, 不安全, 方便 可传输数据大小32kb post: 加密 - 数据不可视,安全, 不方便 可传输数据大小2gb 向后端发送数据利用XMLHttpRequest对象的open方法open方法用于解析数据传输方式,数据内容,是否异步调用 get方法var XMLHttpRequest = new XMLHttpRequest();XMLHttpRequest.open('GET','数据',true);XMLHttpRequest.send();var XMLHttpRequest = new XMLHttpRequest();XMLHttpRequest.open('POST','数据',true);XMLHttpReuqest.send();异步 - True 或 False同步:后台进程一步一步完成异步:后台进程同时调用运行 XMLHttpRequest的open方法第三个参数‘true’ or ‘false’就是选择是否异步,当然选择true啦,高效。 XMLHttpRequest.onreadystatechange=function() { if(XMLHttpRequest.readyState == 4) { if(XMLHttpRequest.status >= 200 && XMLHttpRequest.status < 300 || XMLHttpReuqest.status == 304) { //console.log(XMLHttpRequest.responseText); json.success(XMLHttpReuqest.responseText); }else{ //console.log('服务器错误'); } } }onreadystatechange方法:当状态码改变时触发,readyState 当前状态码 ...

June 11, 2019 · 1 min · jiezi

为什么我要构建这个脚手架

本文不是什么技术性介绍文章,准确地说算是自己的成长记录吧。刚参加工作时,组里使用的脚手架是由 leader 使用 webpack, gulp 搭建的多页面应用脚手架 fex。当时只需知道怎么使用就行了,不过为了能更好地工作,对 fex 怎么构建一直很好奇,也一直关注相关的技术栈。经过一年多磨练后,对 fex 怎么搭建的有了个大概地认识。常言道:"没有对比就没有伤害"。 在使用 vue-cli 构建第一个 vue 项目后,对脚手架构建有了个全新的认识。发现 fex 存在很多不足: 在打包时,只对 JavaScript 和 CSS 脚本文件进行打包压缩处理。不能对资源文件(如 img,字体等)进行依赖处理。导致在打包时: 不能按需打包(即实际用到资源,才将其进行打包)不能进行 MD5 处理不能输出压缩版的 html手动注入 JavaScript 和 CSS 脚本文件,如果需要做优化,会很不方便,特别在多页面情况下。dev 与 build 使用不同的技术方案,增加定制的成本。基于 nodemon 对开发目录进行 watch,当执行修改操作时,会重启整个服务。会存在重启服务耗时比较长的情况,导致刷新页面出现空页面的情况,开发体验不是很好。缺少 code-splitting、HMR、端口检测、Babel 等功能。当然,fex 也有自己的优点。基于自建服务提供前后端复用模板功能。前端后端使用相同的模板语言,前端拼接的模板可以直接输出给后端使用。 第二年年初,组里项目不是太多,刚好有时间折腾一下,于是决定构建一个全新的脚手架 fes。为了尝试一些新东西,在技术栈上,都使用了当时最新的技术框架 webpack4、koa2、babel6 来搭建。为了了解 webpack 如何工作,对 webpack 就做了 8 次调试,才稍微对 webpack 整个架构有个初步认识。 singsong: 在真正去了解 webpack 时,才知道它有多复杂。当然也参考了网上一些大神分享关于 webpack 源码分析的文章。反正整个过程还是挺熬心的????同时,还对 koa2、babel6 做了相关的研究。附一张 koa2 分析图吧????: 为了提高 fes 开发体验,除了继承 fex 的模板复用功能外,还集成了 vue-cli 中不错的功能。 ...

June 11, 2019 · 5 min · jiezi

Nodejs定时导出Highchart图表

一、背景需求1、因为数据包含机密信息,所以得自己搭建图表导出服务器;在后台生成对应Highcharts图表、以图片的形式导出保存。2、图表个性化程度较高,如一些图列是Highchart没有的,但在前端可以利用css实现。3、每周定时执行上述生成图表的任务,保存到指定位置。4、需求已经上线一个月,生产上运行良好,时间有限,只能在这简单记录下,理一下思路也方便以后查阅。 二、实现思路1、经过一番了解,发现Puppeteer,PhantomJs等可以实现上述Highchart图表以图片形式导出的功能,它们也常常用于: 爬虫生成网页截图/PDF测试等2、这个需求选用了Puppeteer去完成,原因如下: 官方的文档也较为详细,相关API,另外其他项目用它实现PDF生成的实践,迁移到本项目爬的坑也会少点。截出来的图片质量清晰,也满足业务的要求。3、定时任务的需求则用了Cron实现;在设定的时间点,在后台用Puppeteer打开我们网页,实现特定区域截图。 三、Puppeteer使用1、 安装,安装puppeteer同时,也会下载Chromium,安装地址为外国网站,下载失败的话多尝试几次、切换到cnpm或者手动下载。如果是部署再Linux上的话,还得安装依赖包才能启动Chromium,具体操作可以查看下面。 # 安装npm i puppeteer --save2、任意区域截图 第一张图为图表的HTML页面,第二张图是指定区域的截图,代码如下: const express = require('express');const puppeteer = require('puppeteer');const app = express();app.use(express.static('public'));async function screenshot() { try { // 添加启动参数'--no-sandbox', '--disable-setuid-sandbox' // 解决Linux环境下"no use sandbox"报错 const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'], headless: true }); const page = await browser.newPage();     await page.goto('http://localhost:3008/'); let clip = await page.evaluate(() => { // 获取指定容器的坐标信息 let { x, y, width, height } = document.getElementById('container').getBoundingClientRect(); return { x, y, width, height }; }); await page.screenshot({ path:'chart.png', clip:clip //设置clip 属性 }); await page.close(); await browser.close(); } catch (error) { throw error; }}// 访问http://localhost:3008/screenshot进行截图app.get('/screenshot', (req, res) => { screenshot() .then(data => res.json('clip successed')) .catch(err => res.json('clip failed')); });app.listen(3008);四、定时任务const CronJob = require('cron').CronJob;// 每天9点钟执行定时任务,其他时间可查找corn表达是或者使用corn表达式生成工具new CronJob({ cronTime: '0 0 9 * * *', onTick: function () { screenshot(); }, start: true});五、Linux上部署问题至此,我们实现了导出Highcharts图表的功能,但是这只是在windows系统的开发机上把这个流程跑通,部署到linux机器上是仍需解决以下几点问题 ...

June 11, 2019 · 1 min · jiezi

packagejson-中-你还不清楚的-browsermodulemain-字段优先级

browser VS module VS main前端开发中使用到 npm 包那可算是家常便饭,而使用到 npm 包总免不了接触到 package.json 包配置文件。 那么这里就有一个问题,当我们在不同环境下 import 一个 npm 包时,到底加载的是 npm 包的哪个文件? 老司机们很快地给出答案:main 字段中指定的文件。 然而我们清楚 npm 包其实又分为: 只允许在客户端使用的,只允许造服务端使用的,浏览器/服务端都可以使用。如果我们需要开发一个 npm 包同时兼容支持 web端 和 server 端,需要在不同环境下加载npm包不同的入口文件,显然一个 main 字段已经不能够满足我们的需求,这就衍生出来了 module 与 browser 字段。 本文就来说下 这几个字段的使用场景,以及同时存在这几个字段时,他们之间的优先级。 文件优先级在说 package.json 之前,先说下文件优先级 由于我们使用的模块规范有 ESM 和 commonJS 两种,为了能在 node 环境下原生执行 ESM 规范的脚本文件,.mjs 文件就应运而生。 当存在 index.mjs 和 index.js 这种同名不同后缀的文件时,import './index' 或者 require('./index') 是会优先加载 index.mjs 文件的。 也就是说,优先级 mjs > js ...

June 11, 2019 · 2 min · jiezi

轮椅上的程序员夜店传说

2019年6月9号,星期天,晴,33度 今天的bug格外的难解 对面商店的小姐姐,依然是我不敢奢求的梦 我是小蝌蚪,一名低级前端程序员搬砖六年,一事无成 经过这些年的努力拼搏,终于向那些曾经看不起我的人证明 ,他们是对的 今晚又加班到了十二点,身心俱疲公司楼下的夜店又响起了战歌我就像一个小丑,穿梭在灯红酒绿的街路边的美女和豪车都与我无关因为程序员不允许沾染世俗的烟火 话还没说完就对一家夜店着了迷因为店门口站着一排黑丝大长腿一直在加班,我好像三个月没见过异性了吧不得不感慨,穿着黑丝的她们,真的好美黑丝对程序员来说是一种圣物,同时也是个迷黑丝套在头上,你就是劫匪黑丝套在腿上,妳就是神明同样是一双人类的腿,加了一层黑布,就能对程序狗产生致命的吸引力上帝在设计男人的时候,还是留下了bug 就在这时,天边突然响起了师傅的佛音:“小蝌蚪,美色是你职业道路上的绊脚石还在意女人,你就成为不了江湖第一的程序员 ”我瞬间恢复了神智,赶紧把头扭开狠狠扇了自己一巴掌我果然不是一个好的程序员一个真正厉害的程序员应该是心无旁骛 ,只有代码 心中有码,到哪里都是比基尼 真的很感谢师傅每次受到黑丝的诱惑师傅的教诲总是能帮助我走出困境,打败心魔 我的师傅是一个高级前端工程师离至尊级程序员就差一步在一次修炼中,为了突破到至尊级凌晨一点钟,孤身一人进入夜店后来就再也没有回来消失前一小时他在微信工作群留下了两个字:“黑丝” 从那以后,黑丝就变成了我心中的魔鬼,既着迷又害怕导致敲代码的技术一直没法突破 可是那天晚上,师傅在夜店到底发生了什么呢看了一下时间,晚上12点59分夜店就在面前,穿着黑丝的妖孽就站在我旁边天时地利人和我不入地狱,谁入地狱 背起自己的大书包,一瘸一拐的进了店平时习惯了昏暗的办公室,见不得光被夜店里的聚光灯一照,差点亮瞎了我的狗眼 深夜中的男男女女,在舞池中央群魔乱舞我的身上穿着公司十周年发的屎黄色战服在舞池里蹦起来就好像一个小儿麻痹与周围的环境格格不入 随着音乐摇到一半 突然看到夜店后排蜷缩着一个面黄肌瘦的男人小蝌蚪:师傅!是你吗师傅:小。。。小蝌蚪?你为什么会在这里小蝌蚪:师傅~!我找了你好多年,终于找到你了。快跟我回去师傅:走不了,我的灵魂中毒了。小蝌蚪:中毒?师傅:我要找到一个女孩,她是我唯一的解药小蝌蚪:怎样才能找到她师傅:她的代号叫“黑丝”,传说中的夜店女皇,她藏的很深,几乎没有人能找到她。小蝌蚪:她是个什么样的女人师傅:黑丝是一个极度危险的女人,所有被她撩过的男人,都会瞬间沉沦,然后日渐消瘦,思念至死。师傅:三年前,她亲吻了我一下,然后、然后、然后。。。。。。黑丝、黑丝、黑丝、黑丝、黑丝、黑丝、黑丝、黑丝、黑丝、黑丝。。。话还没说完,师傅就像毒瘾发作疯狂的抓自己头发,口里不断重复着“黑丝”两个字师傅身边的一群男人,也是灵魂被操控,不断念着“黑丝”两个字 我终于意识到问题的严重性。哭着告诉自己不要慌,一定要找到黑丝,把师傅救出来。可是,这茫茫人海,黑丝在哪呢?看来只能用程序员的神技------心眼心眼是程序员的一个高阶技巧程序员平时在找bug的时候,几十万行代码如果用眼睛去找,那是会瞎的一旦你启用了心眼,就能一目千行瞬间找到几十万行代码里的bug所在 心眼讲究用心去感受周围的环境,而不是用眼睛我闭上眼睛,气运丹田,周围吵杂的环境逐渐安静了下来用心眼感受身边每个人的细微蠕动寻找每一个穿黑丝的女人 最终,锁定人群中的九点钟方向那里坐着一个清纯型妹纸 我很震惊,以为黑丝一定是个妖艳贱货型女子没想到她却是一副清纯学生的样子别的女人在夜店都是穿着黑丝上身的穿着尽可能妖娆和邪魅可她却只穿一件简单的白色短袖和一件粉红色超短裙与周围意乱情迷的环境形成强烈反差给人一种“出淤泥而不染,濯清涟而不妖”的既视感 她白皙的皮肤上没有任何的装饰和打扮将我们程序员敲代码的“the less is more”原则诠释到极致 一个真正的高富帅,就偏偏喜欢这样清纯简单的女人因为高富帅已经厌倦了庸脂俗粉和妖艳贱货鲁迅曾点评过男女之间的情爱秘籍:“喜欢一个女孩:若她涉世未深,就带她看尽人间繁华;若她心已沧桑,就带她坐旋转木马。 喜欢一个男孩:若他情窦初开,你就宽衣解带;若他阅人无数,你就灶边炉台.” 显然,黑丝她只是看起来纯洁,其实她不纯洁,她只是利用了纯洁 一个真正的高手,她的外表看起来永远都不像一个高手清纯只是她的障眼法我们程序员善于突破思维的禁锢,用辩证的思想看问题不被问题的表象所迷惑,透过现象看本质。这是一个优秀的程序员必须具备的品质。 我的心眼感受到了她娇羞清纯的容颜下藏着一颗高傲冷艳的野心既然已找到了黑丝,我要怎么撩她呢首先必须要让自己帅起来 小蝌蚪急中生智,在屎黄色的公司战服上,用圆珠笔画了一个“supreme”,逼格瞬间爆炸 用口水湿润头发,将低沉的发型上扬露出额头,眼镜一摘,混入舞池 进入蹦迪的舞池后,不会摇摆怎么办?师傅曾说过:“如果你在夜店里不会摇摆,那就用脸在天上画一个<粪>字”平时刻苦学习的知识今天终于派上了用场 头在摇摆的时候,手应该怎么晃动呢?师傅又曾经说过:“如果在夜店里你的手不知道怎么晃,那就想象天花板是一个巨大的键盘,将手举过头顶动用单身三十年的手速,对着天空一顿盲敲想象自己在用代码敲一条龙那样你就是夜店里最厉害的仔” 为了让自己看起来更像一个夜店高手小蝌蚪举一反三,融会贯通佯装自己是一个会rap的饶舌歌手小蝌蚪一面摇晃着脑袋一面将程序员的加班战歌《no-sex(无性繁殖)》大声唱了出来歌词大意如下:“来 跟我一起念药药 切克闹 哟哟 嘿V够 i don't need sex, the code 发可 me everyday大声跟我一起唱 i don't need sex, the code 发可 me everyday发可!发可!me!me!eve!ry!Day!”小蝌蚪一边rap一边嘲讽旁边的年轻人,不懂得什么才是真正的music 旁边的年轻人听不懂小蝌蚪在唱什么,他们也不敢问反正一切听不懂的,都一定是比他们厉害的东西 才用不到半小时,小蝌蚪已经是整个夜店里最狂的仔所有女人都为之倾倒。 经过一个小时的摇摆,小蝌蚪体力不支瘫软在吧台上把汗一擦,背挺直,假装自己一副人帅腰好的样子 小蝌蚪的骚操作,终于引起了黑丝的注意黑丝主动坐到了小蝌蚪身边这一切都在意料之中,就好像代码的运行流程,每一行都胸有成竹 桌上早已为黑丝点了杯名叫“烈焰红唇”的高端鸡尾酒小蝌蚪在杯壁上用口红写了首情诗 “ ...

June 10, 2019 · 1 min · jiezi

Javascript-打包工具

本文当时写在本地,发现换电脑很不是方便,在这里记录下。 前端的打包工具打包工具可以更好的管理html,css,javascript,使用可以锦上添花,不使用也没关系。1. 前言1.1 前端前端三剑客:结构层 html,表现层 css,行为层 javascript。 html好比是房子的地基,css和 javascript是房子的建筑材料,这三个部分一起组成个漂亮的房子。我们不能把他们分开说,某某部分是个房子,只有三个一起才能组成一个漂亮的房子 。 1.2 JavaScript 的简介(参考阮一峰的ES6入门简介)这几年,javascript 发展非常快速,特别是在2015年,更是有一个质的飞跃。1.2.1 ECMA说到 JavaScript,就要说下Web标准的组织协会,ECMA,它是“European Computer Manufactures Association”的缩写,中文称 欧洲计算机制造联合会,1961年成立,旨在建立统一的电脑操作格式标准--包括程序语言和输入输出的组织。 1.2.2 JavaScript2015年,JavaScript 引入许多新的语法糖,而且制定过程当中,还有很多组织和个人不断提交新功能。事情很快就变得清楚了,不可能在一个版本里面包括所有将要引入的功能。 常规的做法是先发布 6.0 版,过一段时间再发 6.1 版,然后是 6.2 版、6.3 版等等 ,这个2015年之前 JavaScript 现在习惯称为ECMAScript5,而之后称为ECMAScript6。 标准委员会商定后最终决定,标准在每年的 6 月份正式发布一次,作为当年的正式版本。接下来的时间,就在这个版本的基础上做改动,直到下一年的 6 月份,草案就自然变成了新一年的版本。这样一来,就不需要以前的版本号了,只要用年份标记就可以了。 因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”。 问题一:关于ECMAScript 和 JavaScript 是什么关系 ? 回答:从现在的角度来看,二者是可以互换的。即ECMAScript是JavaScript ,JavaScript 是ECMAScript。 问题二:ECMAScript 6 和 ECMAScript 2015 是什么关系 ?回答:ECMAScript 6泛指下一代 JavaScript 语言,ECMAScript 2015指的是 2015年的 JavaScript 标准; ...

June 10, 2019 · 2 min · jiezi

什么时候不能在-Nodejs-中使用-Lock-Files

翻译:疯狂的技术宅原文:https://www.twilio.com/blog/l... 未经允许严禁转载 “可是在我的机器上能工作啊!”这种场景可能是调试 bug 时最常见的问题。这通常是由于出错的机器和你自己的机器上系统的底层依赖性不同的结果。所以 yarn 和 npm 在引入了所谓的“lock file”,来跟踪你依赖项确切的版本。但是当你在开发要发布到 npm 的包时,应避免使用这类 lock file 。在本文中,我们将讨论为什么要这样。 快速摘要(tl; dr)如果你开发像 Web 服务器之类的程序,那么 lock file 是非常有用的。但是如果将库或 CLI 发布到 npm,则永远不要发布 lock file。因为如果你使用它,则意味着你和你的用户可能在使用不同版本的依赖项。 什么是Lock File?lock file 描述了整个依赖关系树,它在创建时被解析,包括具有特定版本的嵌套依赖关系。在 npm 名为 package-lock.json ,在 yarn 中名为 yarn.lock。在这两个npm和yarn它们被放置旁边你的package.json。 package-lock.json 的内容应该是这样: { "name": "lockfile-demo", "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { "color-convert": "^1.9.0" } }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } } }}yarn.lock 的格式不同,但也包含类似的信息: ...

June 10, 2019 · 3 min · jiezi

Linux安装nodejs

Linux安装nodejs安装说明安装的环境为CenterOS7 # 本文以node-v10.16.0为说明# 下载node.js的安装包,下载官网:https://nodejs.org/en/download/## 可以下载tar.gz 也可以下载 tar.xzwget https://nodejs.org/dist/v10.16.0/node-v10.16.0-linux-x64.tar.gz## 或者wget https://nodejs.org/dist/v10.16.0/node-v10.16.0-linux-x64.tar.xz# 解压tar -zxvf node-v10.16.0-linux-x64.tar.gz## 或tar -xf node-v10.16.0-linux-x64.tar.xz# 软连接,软连接后就可以全局访问,需要软连接node和npm## npm 可以直接软连接到 /usr/local/bin/下ln -s /**下载nodejs的根目录**/bin/npm /usr/local/bin/npm## 由于自己的电脑软连接到 /usr/local/bin/下无效因此指定到 /usr/bin/ 目录下ln -s /**下载nodejs的根目录**/bin/node /usr/bin/node# 检查安装是否成功npm -vnode -v遇到的问题如果软连接后提示报下面的错误,需要软连接指向/usr/bin/路径下,命令参考上面的安装说明: # 错误信息-bash: /usr/bin/node: No such file or directory

June 10, 2019 · 1 min · jiezi

StepByStep一周面试题深入解析-周刊-03

关于【Step-By-Step】Step-By-Step (点击进入项目) 是我于 2019-05-20 开始的一个项目,每个工作日发布一道面试题。每个周末我会仔细阅读大家的答案,整理最一份较优答案出来,因本人水平有限,有误的地方,大家及时指正。 如果想 加群 学习,关注公众号,添加我为好友,我拉你进群。 本周面试题一览:什么是XSS攻击,XSS 攻击可以分为哪几类?我们如何防范XSS攻击?如何隐藏页面中的某个元素?浏览器事件代理机制的原理是什么?setTimeout 倒计时为什么会出现误差?11. 什么是XSS攻击,XSS攻击可以分为哪几类?我们如何防范XSS攻击?1. XSS攻击XSS(Cross-Site Scripting,跨站脚本攻击)是一种代码注入攻击。攻击者在目标网站上注入恶意代码,当被攻击者登陆网站时就会执行这些恶意代码,这些脚本可以读取 cookie,session tokens,或者其它敏感的网站信息,对用户进行钓鱼欺诈,甚至发起蠕虫攻击等。 XSS 的本质是:恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。由于直接在用户的终端执行,恶意代码能够直接获取用户的信息,利用这些信息冒充用户向网站发起攻击者定义的请求。 XSS分类根据攻击的来源,XSS攻击可以分为存储型(持久性)、反射型(非持久型)和DOM型三种。下面我们来详细了解一下这三种XSS攻击: 1.1 反射型XSS当用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。Web服务器将注入脚本,比如一个错误信息,搜索结果等,未进行过滤直接返回到用户的浏览器上。 反射型 XSS 的攻击步骤:攻击者构造出特殊的 URL,其中包含恶意代码。用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等。由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。 POST 的内容也可以触发反射型 XSS,只不过其触发条件比较苛刻(需要构造表单提交页面,并引导用户点击),所以非常少见。 如果不希望被前端拿到cookie,后端可以设置 httpOnly (不过这不是 XSS攻击 的解决方案,只能降低受损范围) 如何防范反射型XSS攻击对字符串进行编码。 对url的查询参数进行转义后再输出到页面。 app.get('/welcome', function(req, res) { //对查询参数进行编码,避免反射型 XSS攻击 res.send(`${encodeURIComponent(req.query.type)}`); });1.2 DOM 型 XSSDOM 型 XSS 攻击,实际上就是前端 JavaScript 代码不够严谨,把不可信的内容插入到了页面。在使用 .innerHTML、.outerHTML、.appendChild、document.write()等API时要特别小心,不要把不可信的数据作为 HTML 插到页面上,尽量使用 .innerText、.textContent、.setAttribute() 等。 ...

June 10, 2019 · 3 min · jiezi

nodejs提示-crossdevice-link-not-permitted-rename-错误解决方法

node.js提示错误: Error: EXDEV: cross-device link not permitted, rename 'C:\Users\THEDIS~1\AppData\Local\Temp\upload_9b46f1afc2f9ade074037c3fb707d271' -> 'E:/node-rumen/tmp/test.png'文件上传的功能时候,调用fs.renameSync方法错误 这个提示是跨区重命名文件出现的权限问题。 先从源文件拷贝到另外分区的目标文件,然后再unlink,就可以了。 form.parse(request, function(error, fields, files) { console.log(“parsing done”); fs.renameSync(files.upload.path, “/tmp/test.png”);});改成 form.parse(request, function(error, fields, files) { console.log(“parsing done”); // fs.renameSync(files.upload.path, “/nodejs/case/two/img/test.png”); var readStream=fs.createReadStream(files.upload.path); var writeStream=fs.createWriteStream("./tmp/test.jpg"); readStream.pipe(writeStream); readStream.on('end',function(){ fs.unlinkSync(files.upload.path); });});PS:node版本是0.10.69可以使用上面的方法,如果使用的是0.6以下的版本,可以使用util.pump 相应代码只需将上面的代码中readStream.on处改成:(注意引入util模块) util.pump(readStream,writeStream, function() { fs.unlinkSync('files.upload.path');});参考地址:https://stackoverflow.com/que... https://stackoverflow.com/que... 方法二: 这种就简洁很多了 添加一个 form.uploadDir=’tmp’ 即可(写一个临时路径) function upload(response,request){ console.log(“upload called”); var form = new formidable.IncomingForm(); form.uploadDir=’tmp’; console.log(“about to parse”); form.parse(request, function(error, fields, files) { console.log(“parsing done”); fs.renameSync(files.upload.path, “./tmp/test.jpg”); response.writeHead(200, {“Content-Type”: “text/html”}); response.write(“received image:<br/>”); response.write(“<img src=’/show’ />”); response.end(); });}

June 9, 2019 · 1 min · jiezi

Nodejs运行原理高并发性能测试对比及生态圈汇总-全栈工程师入门

Node.js是从纯前端走向更高阶层的前端,以及全栈工程师的唯一快速途径简单的说Node.js 就是运行在服务端的 JavaScriptNode.js 是一个基于Chrome JavaScript 运行时建立的一个平台Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好如果你是一个前端程序员,你不懂得像PHP、Python或Ruby等动态编程语言,然后你想创建自己的服务,那么Node.js是一个非常好的选择Node.js 是运行在服务端的 JavaScript,如果你熟悉Javascript,那么你将会很容易的学会Node.js当然,如果你是后端程序员,想部署一些高性能的服务,那么学习Node.js也是一个非常好的选择Node.JS适合运用在高并发、I/O密集、少量业务逻辑的场景Node.js的模块组成如下: Node.js的运行机制V8引擎解析JavaScript脚本解析后的代码,调用Node APIlibuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个EventLoop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。V8引擎再将结果返回给用户。事件循环(Event Loop)Nodejs 执行之后会初始化一个事件循环,执行代码程序(这些程序可能会造成异步调用、定时器或者process.nextTick()),然后开始执行事件循环。事件循环的执行循序: 上边的每一个模块都是事件循环的一个阶段,每个阶段都有一个要执行的回调的FIFO队列。虽然每个阶段都不同,一般来说,当事件执行到一个阶段,先执行这个阶段特有的操作,然后操作这个阶段的队列,当队列执行完或者达到了回调上限,事件循环就会执行下一个阶段。各个阶段执行的任务如下: timers 阶段: 这个阶段执行setTimeout和setInterval预定的callback;I/O callbacks 阶段: 执行除了close事件的callbacks、被timers设定的callbacks、setImmediate()设定的callbacks这些之外的callbacks;idle, prepare 阶段: 仅node内部使用;poll 阶段: 获取新的I/O事件, 适当的条件下node将阻塞在这里;check 阶段: 执行setImmediate() 设定的callbacks;close callbacks 阶段: 执行socket.on('close', ...)这些 callbackprocess.nextTick()不属于上面的任何一个phase,它在每个phase结束的时候都会运行。也可以认为,nextTick在下一个异步方法的事件回调函数调用前执行。TIPS: Node.js中的事件循环机制不会掉头,只会由上往下,循环执行。完整的一次执行机制可以这样描述 在Node.js中,绝大部分API都是异步的,有一个很形象的故事描述了JAVA和Node.js的区别,JAVA是一个餐厅100个服务员对应100客户,Node.js是一个服务员玩命干,也对应100个客户,上菜的速度很大一部分取决于厨师的做菜速度Node.js的单线程并不是真正的单线程,只是开启了单个线程进行业务处理(cpu的运算),同时开启了其他线程专门处理I/O。当一个指令到达主线程,主线程发现有I/O之后,直接把这个事件传给I/O线程,不会等待I/O结束后,再去处理下面的业务,而是拿到一个状态后立即往下走,这就是“单线程”、“异步I/O”。I/O操作完之后呢?Node.js的I/O 处理完之后会有一个回调事件,这个事件会放在一个事件处理队列里头,在进程启动时node会创建一个类似于While(true)的循环,它的每一次轮询都会去查看是否有事件需要处理,是否有事件关联的回调函数需要处理,如果有就处理,然后加入下一个轮询,如果没有就退出进程,这就是所谓的“事件驱动”。这也从Node的角度解释了什么是”事件驱动”。在node.js中,事件主要来源于网络请求,文件I/O等,根据事件的不同对观察者进行了分类,有文件I/O观察者,网络I/O观察者。事件驱动是一个典型的生产者/消费者模型,请求到达观察者那里,事件循环从观察者进行消费,主线程就可以马不停蹄的只关注业务不用再去进行I/O等待。 优点: Node 公开宣称的目标是 “旨在提供一种简单的构建可伸缩网络程序的方法”。我们来看一个简单的例子,在 Java和 PHP 这类语言中,每个连接都会生成一个新线程,每个新线程可能需要2MB的配套内存。在一个拥有8GBRAM的系统上,理论上最大的并发连接数量是4,000个用户。随着您的客户群的增长,如果希望您的Web应用程序支持更多用户,那么,您必须添加更多服务器。所以在传统的后台开发中,整个Web应用程序架构(包括流量、处理器速度和内存速度)中的瓶颈是:服务器能够处理的并发连接的最大数量。这个不同的架构承载的并发数量是不一致的。而Node的出现就是为了解决这个问题:更改连接到服务器的方式。在Node 声称它不允许使用锁,它不会直接阻塞 I/O 调用。Node在每个连接发射一个在 Node 引擎的进程中运行的事件,而不是为每个连接生成一个新的 OS 线程(并为其分配一些配套内存)。缺点:如上所述,nodejs的机制是单线程,这个线程里面,有一个事件循环机制,处理所有的请求。在事件处理过程中,它会智能地将一些涉及到IO、网络通信等耗时比较长的操作,交由worker-threads去执行,执行完了再回调,这就是所谓的异步IO非阻塞吧。但是,那些非IO操作,只用CPU计算的操作,它就自己扛了,比如算什么斐波那契数列之类。它是单线程,这些自己扛的任务要一个接着一个地完成,前面那个没完成,后面的只能干等。因此,对CPU要求比较高的CPU密集型任务多的话,就有可能会造成号称高性能,适合高并发的node.js服务器反应缓慢。Node.js高并发使用Nginx+pm2,pm2中可以开启多线程负载均衡,模式分两种:pm2简介: PM2是node进程管理工具,可以利用它来简化很多node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等,而且使用非常简单。下面就对PM2进行入门性的介绍,基本涵盖了PM2的常用的功能和配置。 fork模式,单实例多进程,常用于多语言混编,比如php、python等,不支持端口复用,需要自己做应用的端口分配和负载均衡的子进程业务代码。缺点就是单服务器实例容易由于异常会导致服务器实例崩溃。 cluster模式,多实例多进程,但是只支持node,端口可以复用,不需要额外的端口配置,0代码实现负载均衡。优点就是由于多实例机制,可以保证服务器的容错性,就算出现异常也不会使多个服务器实例同时崩溃。 共同点,由于都是多进程,都需要消息机制或数据持久化来实现数据共享。pm2部署,默认开启负载均衡:npm i pm2 -g $ pm2 start app.js # 启动app.js应用程序$ pm2 start app.js -i 4 # cluster mode 模式启动4个app.js的应用实例 # 4个应用程序会自动进行负载均衡 pm2 start app.js -i max 根据你的cpu数量最大化启动多线程进行负载均衡如果要停止所有应用,可以pm2 stop all查看进程状态 pm2 listpm2真心很好很强大,可以在线热更新代码,更多的指令需要上官网看pm2和Nginx配合pm2 + nginx无非就是在nginx上做个反向代理配置,直接贴配置。 upstream my_nodejs_upstream { server 127.0.0.1:3001;}server { listen 80;server_name my_nodejs_server;root /home/www/project_root; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";proxy_max_temp_file_size 0; proxy_pass http://my_nodejs_upstream/;proxy_redirect off; proxy_read_timeout 240s; } 特别说明,我们不建议使用Node.js作为底层服务器,更多时候作为中间件和接入层使用,例如Electron开发跨平台应用Nginx开启多线程,负载均衡负载均衡的作用负载均衡:分摊到多个操作单元上进行执行,和它的英文名称很匹配。就是我们需要一个调度者,保证所有后端服务器都将性能充分发挥,从而保持服务器集群的整体性能最优,这就是负载均衡。负载均衡这里面涉及的东西相对也是比较多的,理论就不说太多了,网上,书上很多,今天我们就利用Nginx服务器来实现一个简单的负载均衡负载均衡算法源地址哈希法:根据获取客户端的IP地址,通过哈希函数计算得到一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。轮询法:将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务器实际的连接数和当前的系统负载。随机法:通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。加权轮询法:不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。加权随机法:与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。最小连接数法:由于后端服务器的配置不尽相同,对于请求的处理有快有慢,最小连接数法根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服务的利用效率,将负责合理地分流到每一台服务器。下载Nginx,找到config文件夹下面的nginx.conf,修改下面配置文件每个upstream test{ server 11.22.333.11:6666 weight=1; server 11.22.333.22:8888 down; server 11.22.333.33:8888 backup; server 11.22.333.44:5555 weight=2; }//down 表示单前的server临时不參与负载.//weight 默觉得1.weight越大,负载的权重就越大//backup: 其他全部的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻 nginx命令汇总 :nginx 服务器重启命令,关闭nginx -s reload :修改配置后重新加载生效nginx -s reopen :重新打开日志文件nginx -t -c /path/to/nginx.conf 测试nginx配置文件是否正确关闭nginx:nginx -s stop :快速停止nginxquit :完整有序的停止nginx其他的停止nginx 方式:ps -ef | grep nginxkill -QUIT 主进程号 :从容停止Nginxkill -TERM 主进程号 :快速停止Nginxpkill -9 nginx :强制停止Nginx 启动nginx:nginx -c /path/to/nginx.conf平滑重启nginx:kill -HUP 主进程号在开启Nginx多线程负载均衡和部署pm2负载均衡后的架构图:第一种,Node.js作为底层服务器,直接操作数据库的方式: ...

June 9, 2019 · 2 min · jiezi

nodejs-httpproxy-开发反向代理服务器防火墙过滤常见的web渗透

事出有因最近web系统引来了黑客的攻击,经常被扫描,各种漏洞尝试。分析攻击日志,有几种常见的攻击手段: 上传webshell远程执行命令漏洞sql注入xxs 攻击试探各种开源框架爆出来的漏洞分析攻击信息的特点说白了就是采用web渗透技术,利用http请求,黑客想尽办法,在http header ,body,等部分植入非法的命令,非法字符常见的有:exe,cmd,powershell,download,select,union,delete等等。 解决问题思路我们能不能开发个代理服务器,来分析http请求header,body里面的信息,如果有非法字符,就截断,拒绝服务。配置允许请求的白名单,拒绝非法Url.网络拓扑http proxy 拦截非法请求,拒绝服务。 技术选型常见的代理服务器有nginx,apache,不知道这2个代理服务器能不能灵活的配置,过滤,转发,没有深入了解。因此选用nodejs http-proxy。 nodejs优点轻量级快速部署灵活开发高吞吐,异步io编码实现逻辑图 绝对干货,分享代码代码依赖http-proxy 1.17.0https://github.com/nodejitsu/... 代码地址 "colors": "~0.6.2",var util = require('util'), colors = require('colors'), http = require('http'), httpProxy = require('./node_modules/http-proxy'); fs = require("fs");var welcome = [ '# # ##### ##### ##### ##### ##### #### # # # #', '# # # # # # # # # # # # # # # # ', '###### # # # # ##### # # # # # # ## # ', '# # # # ##### ##### ##### # # ## # ', '# # # # # # # # # # # # # ', '# # # # # # # # #### # # # '].join('\n');Date.prototype.Format = function(fmt) { //author: meizz var o = { "M+": this.getMonth() + 1, //月份 "d+": this.getDate(), //日 "h+": this.getHours(), //小时 "m+": this.getMinutes(), //分 "s+": this.getSeconds(), //秒 "S": this.getMilliseconds() //毫秒 }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); for (var k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); return fmt;}// 非法字符var re = /php|exe|cmd|shell|select|union|delete|update|insert/;/** 这里配置转发 */var proxyPassConfig = { "/hello": "http://www.qingmiaokeji.cn ", "/": "http://127.0.0.1/"}var logRootPath ="g:/httpproxy/";console.log(welcome.rainbow.bold);function getCurrentDayFile(){ // console.log(logRootPath+"access_"+(new Date()).Format("yyyy-MM-dd")+".log"); return logRootPath+"access_"+(new Date()).Format("yyyy-MM-dd")+".log";}//// Basic Http Proxy Server//var proxy = httpProxy.createProxyServer({});var server = http.createServer(function (req, res) { appendLog(req) var postData = ""; req.addListener('end', function(){ //数据接收完毕 console.log(postData); if(!isValid(postData)){//post请求非法参数 invalidHandler(res) } }); req.addListener('data', function(postDataStream){ postData += postDataStream }); var result = isValid(req.url) //验证http头部是否非法 for(key in req.headers){ result = result&& isValid(req.headers[key]) } if (result) { var patternUrl = urlHandler(req.url); console.log("patternUrl:" + patternUrl); if (patternUrl) { proxy.web(req, res, {target: patternUrl}); } else { noPattern(res); } } else { invalidHandler(res) }});proxy.on('error', function (err, req, res) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Something went wrong.');});/** * 验证非法参数 * @param value * @returns {boolean} 非法返回False */function isValid(value) { return re.test(value) ? false : true;}/** * 请求转发 * @param url * @returns {*} */function urlHandler(url) { var tempUrl = url.substring(url.lastIndexOf("/")); return proxyPassConfig[tempUrl];}function invalidHandler(res) { res.writeHead(400, {'Content-Type': 'text/plain'}); res.write('Bad Request '); res.end();}function noPattern(res) { res.writeHead(404, {'Content-Type': 'text/plain'}); res.write('not found'); res.end();}function getClientIp(req){ return req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress;}function appendLog(req) { console.log("request url:" + req.url); var logData = (new Date()).Format("yyyy-MM-dd hh:mm:ss")+" "+getClientIp(req)+" "+req.method+ " "+req.url+"\n"; fs.exists(logRootPath,function(exists){ if(!exists){ fs.mkdirSync(logRootPath) } fs.appendFile(getCurrentDayFile(),logData,'utf8',function(err){ if(err) { console.log(err); } }); })}console.log("listening on port 80".green.bold)server.listen(80);思路扩展拦截非法字符后可以发邮件通知管理员可以把日志发送到日志系统,进行大数据分析增加频繁访问,拒绝Ip功能。 可以利用redis 过期缓存实现。

June 9, 2019 · 2 min · jiezi

GraphQL从入门到实战

前言本来这篇文章准备51假期期间就发出来的,但是因为自己的笔记本电脑出了一点问题,所以拖到了现在????。为了大家更好的学习GraphQL,我写一个前后端的GraphQL的Demo,包含了登陆,增加数据,获取数据一些常见的操作。前端使用了Vue和TypeScript,后端使用的是Koa和GraphQL。 这个是预览的地址: GraphQLDeom 默认用户root,密码root 这个是源码的地址: learn-graphql GraphQL入门以及相关概念什么是GraphQL?按照官方文档中给出的定义, "GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具"。但是我在使用之后发现,gql需要后端做的太多了,类型系统对于前端很美好,但是对于后端来说可能意味着多次的数据库查询。虽然gql实现了http请求上的优化,但是后端io的性能也应当是我们所考虑的。 查询和变更GraphQL中操作类型主要分为查询和变更(还有subscription订阅),分别对应query,mutation关键字。query,mutation的操作名称operation name是可以省略的。但是添加操作名称可以避免歧义。操作可以传递不同的参数,例如getHomeInfo中分页参数,AddNote中笔记的属性参数。下文中,我们主要对query和mutation进行展开。 query getHomeInfo { users(pagestart: ${pagestart}, pagesize: ${pagesize}) { data { id name createDate } }}mutation AddNote { addNote(note: { title: "${title}", detail: "${detail}", uId: "${uId}" }) { code }}Schema全称Schema Definition Language。GraphQL实现了一种可读的模式语法,SDL和JavaScript类似,这种语法必须存储为String格式。我们需要区分GraphQL Schema和Mongoose Schema的区别。GraphQL Schema声明了返回的数据和结构。Mongoose Schema则声明了数据存储结构。 类型系统标量类型GraphQL提供了一些默认的标量类型, Int, Float, String, Boolean, ID。GraphQL支持自定义标量类型,我们会在后面介绍到。 对象类型对象类型是Schema中最常见的类型,允许嵌套和循环引用 type TypeName { fieldA: String fieldB: Boolean fieldC: Int fieldD: CustomType}查询类型查询类型用于获取数据,类似REST GET。Query是Schema的起点,是根级类型之一,Query描述了我们可以获取的数据。下面的例子中定义了两种查询,getBooks,getAuthors。 ...

June 9, 2019 · 5 min · jiezi

webpack4322系列教程03-devtool

简介 devtool选项用于控制是否需要生成source map,以及如何生成source map。源码地址 什么是source map? source map 一个存储源代码与编译代码对应位置的映射信息文件,它是专门给调试器准备的,它主要用于debug,目前我所知的只有Google Dev Tools 和 Fire Fox Debugger 支持source map。 Google Dev Tools 可以通过以下方式打开JavaScript的source map 和 CSS的source map: source map主要用于将压缩混淆后的JavaScript代码和CSS代码映射到源码中,方便debug调试。更多关于source map的知识,大家可以参考阮一峰大神的文章:JavaScript Source Map 详解 演示 最新的webpack官网中一共有13种devtool可选模式,不同的模式打包输出的代码和source map以及构建的速度都不一样,下面我演示几种比较常用的devtool模式。 eval 表示把每一个模块文件都转换为字符串,并且在每一个模块代码的尾部添加 //# sourceURL=webpack:/// 文件名.js,并使用eval执行。 1、编写入口文件和依赖代码 2、编写webpack配置 & 启动webpack const webpack = require('webpack');// 创建编译器对象const compiler = webpack({ mode: 'development', devtool: 'eval'});// 启动webpackcompiler.run((err, stats) => { if (err) { console.error(err); return; } // 输出编译成功信息 console.log(stats.toString({ colors: true }));});webpack运行结果: ...

June 9, 2019 · 1 min · jiezi

VueNode高德地图Echart做一款出行可视化全栈webapp

咔咔出行(出行可视化)项目简介解决出行问题,用于出行行程记录,路线规划,数据可视化分析的移动端webapp 点击这里查看该项目 项目截图私人出行公共交通历史列表我的信息 技术栈前端:移动端,vue全家桶,Mand组件库,Echarts.js,Scss后端:Node,Express框架,高德地图API数据库:Mysql功能模块个人出行用户个人出行,不确定路程、目的地等信息,选择出行工具,点击开始,系统实时监听用户手机位置得到WGS84经纬度坐标(w3c HTML5 Geolocation地理定位标准),行程结束,记录本次出行信息,经纬度坐标转换GCJ-02坐标体系,通过高德地图提供三方API绘制出行轨迹。 公共交通用户确定出发地、目的地、交通工具,选择公共交通出行,用户输入位置关键字,选择合适出发/目的位置,选择乘坐交通工具,规划出行路线,选择某一条路线,确定后点击保存上传本次出行记录 历史列表按时间顺序查看所有出行的历史记录,可查看出行的详情信息、行程轨迹、路线规划 我的信息(未完成)查看我的详情信息,通过出行数据分析得到的出行趋势折线图,与出行数据相关的数据分析图表,其他功能未写 项目构建前端前端在vue-cli3基础上开发,在此之上根据项目需求对项目工程作出几点修改,前端代码在view/文件夹中 移动端适配:之前做移动端开发一直使用手淘的分辨率适配方案,本项目根据大漠的《如何在Vue项目中使用vw实现移动端适配》,对移动端分辨率用webpack在工程中配置。请求拦截器:在view/src/request/中,基于axios提供的interceptors对所有ajax的请求和响应添加相应操作,如请求头添加,token添加,响应后台错误状态码的识别与报错;简单封装了下axios请求,主要为get,post两种。导航守卫:在view/src/router/中,做了全局导航守卫,未登录用户只能访问项目登录页面。工具类:在view/src/utils/中,对常用枚举值、全局组件注册、常用类封装等功能做模块化封装。css样式:在view/src/style/中,全局公共样式,初始化样式。svg组件:在view/src/icons/中,封装用于svg展示组件,用做小icon的展示,svg保存该文件夹中。模块化:对路由与vuex做模块化封装。地图:所有地图、地理信息、轨迹、路线规划功能有高德地图第三方API提供后端使用Node的express框架,连接Mysql数据库,做数据接口开发,数据的增删改查与简单封装。小结项目简结难度:简单开发时长:前期调研,编码一周关键字:移动端,出行,可视化,高德地图,Echart图表过程总结想法产出:因为在滴滴出行的实习经历,准备做款有关出行平台的,有关前端可视化的产品。需求调研:结合出行 可视化 关键字做需求分析,调研悦动圈、悦跑圈、滴滴出行、百度地图、高德地图确定几个主要功能 实时定位,绘制出行轨迹(悦跑圈,已完成)路线规划,规划路线绘制路径图(百度地图,已完成)网约车,快车,专车,顺风车在线叫车(滴滴出行,未完成)可视化分析,出行数据的可视化分析(已完成)技术调研: 选取HTML5 Geolocation提供的物理位置实时监听功能,获取到WGS84经纬坐标选取高德地图第三方API提供地图,地理位置,轨迹绘制,路线规划等功能选择开发移动端项目,用滴滴的Mand作为移动端UI组件库选取Node为服务端,Express为后端框架,Mysql为数据库难点总结产品从无到有是最困难的,项目虽然不难,可前期产品调研,技术调研,项目构建确花费了大量时间,相比照着成品写多了很多不一样的体验 Github源码 https://github.com/wwenj/tripRecord

June 8, 2019 · 1 min · jiezi

Nodejs异步处理的各种写法

异步的“坑”最近一段时间参与开发了一个Node.js后台项目,作为一个PHP开发者,上手项目本身并不难,但是开发的过程却并不顺利,不顺利的主要原因在于思路上没有转变,没有从同步的思维转换到异步的思维。 所谓同步,就是程序(线程)在一个任务的处理过程中,不会插入处理其他任务,即使遇到IO等不占CPU的操作,也会一直等待其结束才会继续往下处理。 所谓异步,就是程序(线程)在一个任务的处理过程中,会插入处理其他任务,如遇到IO操作,当前任务会将程序(线程)的控制权释放给其他任务,等IO操作结果返回后再继续往下处理。 众所周知,Node.js采用的是单线程的异步模型,在具体代码的写法上自然和PHP等同步模型不一样。在具体项目开发的过程中,各种异步操作相关的关键字层出不穷,如:.then()、function* ... yield、async...await等等。为了写一个类同步的操作,比如:“在执行完A步骤拿到结果之后再执行B步骤”这么一个简单的需求,却要经过大量的反复调试验证才能解决。究其原因,就是对于这些异步操作的场景和关键字的含义理解不到位,异步操作所提供的选择太多了。 下面就结合代码实例,理一理这些异步操作的参数具体怎么使用。 异步的各种写法任务说明:项目根目录下有三个文件Jay.txt、Angela.txt、Henry.txt,依次读取这三个文件的内容并打印。 下面使用各种异步处理的方法来完成此任务。 回调函数

June 8, 2019 · 1 min · jiezi

JeecgBoot-极简部署方案

基于 spring boot 特性 1、首先修改配置,去掉项目名 jeecg-boot ant-design-jeecg-vue/src/utils/request.jsant-design-jeecg-vue/public/index.html 2、修改路由History 模式为“hash” src/router/index.js 然后 ant-design-jeecg-vue 运行 build npm run build3、然后将编译之后dist下的文件复制到 jeecg-boot-module-system 项目的 /src/main/resources/static 目录下。4、修改springboot项目的启动名字,去掉项目名 jeecg-boot jeecg-boot-module-system/src/main/resources/application-dev.yml 5、重新启动项目,访问 http://localhost:8080/ 就可以看到效果

June 8, 2019 · 1 min · jiezi

JAVA前后端分离开发环境搭建详细教程JeecgBoot快速开发平台

目录索引: 后端开发工具前端开发工具Nodejs镜像WebStorm入门配置 JeecgBoot采用前后端分离的架构,官方推荐开发工具 前端开发: Webstrom 或者 IDEA 后端开发: Eclipse安装lombok插件 或者 IDEA 开发工具下载:https://pan.baidu.com/s/1tZmFuViGz5IwHhzmA-FN6A 提取码:frya 后端开发工具序号工具参考1eclipse安装lombok插件https://blog.csdn.net/qq_2564...2Eclipse自定义皮肤主题https://blog.csdn.net/StillOn...3Eclipse常用快捷键https://blog.csdn.net/zhangda...前端开发工具序号工具描述参考1Nodejs/Npm安装JavaScript运行环境,此处使用到它的包管理器npmhttp://www.jianshu.com/p/03a7...2Yarn安装下载包工具https://yarnpkg.com/zh-Hans/d...3WebStorm安装与使用WEB前端开发工具https://blog.csdn.net/u011781...配置Nodejs镜像npm config set registry https://registry.npm.taobao.org --globalnpm config set disturl https://npm.taobao.org/dist --globalyarn config set registry https://registry.npm.taobao.org --globalyarn config set disturl https://npm.taobao.org/dist --globalWebStorm-2018.1.3 开发工具入门配置序号标题链接1WebStorm安装与使用https://blog.csdn.net/u011781...2webstorm 2018 激活破解https://blog.csdn.net/q358591...3修改webstorm主题https://blog.csdn.net/master_...4Webstorm切换快捷键风格(Webstorm快捷键与eclipse对比介绍)https://blog.csdn.net/gsying1...5WebStorm SVN用法https://blog.csdn.net/hysh_ke...6‘svn’不是内部或外部命令问题解决https://blog.csdn.net/mitea90...7设置webstorm的vue新建文件模板(后面篇章)https://blog.csdn.net/diligen...8WebStorm卡顿拉取svn慢解决https://blog.csdn.net/WYA1993...前端Webstorm开发界面:后端Eclipse开发界面:

June 8, 2019 · 1 min · jiezi

全栈进阶课程-React168NextjsKoa2一步到位开发Github

全栈进阶课程 React16.8+Next.js+Koa2一步到位开发Github 课程使用Next.js、Koa、Redis、Github API等搭建了一个全栈项目——第三方Github客户端。通过课程学习让同学们理解Next.js搭建全栈同构项目的过程以及其SSR原理,深度理解业界广泛使用的OAuth登录体系,并且能够在工作项目中熟练运用,提升个人竞争力。 微信 eweixin5566

June 8, 2019 · 1 min · jiezi

学习NodeJS链接MySql二

在Node中链接MySql的方法在Node中链接MySql有两种方法,一种是用Pool,一种是用Connection 首先放出个代码模板 /** 数据库查询 * 1、链接数据库 * 2、获取链接,连接可能失败 */var mysql = require('mysql');// console.log(mysql);//这是后端链接数据库的线// 数据库地址:本地 用户名:root 密码:123456 数据库名:20190603 -port(端口):可以改var pool = mysql.createPool({'host':'localhost','user':'root','password':'123456' ,'database':'20190605'}); // console.log(pool);// pool.connect();//异步链接数据库//获取链接,可能失败,在connection.query 中写sql 语句pool.getConnection(function(err, connection){ if(err) { console.log('连接失败'+err); }else { // 关键字 关键字 表名 处理方法 connection.query('SELECT * FROM `user`;', function(err, data){ if(err) { console.log(err); }else { console.log(data); connection.end(); } }) }})Connection方法var mysql = require('mysql');var connection = mysql.createConnection({ host : 'localhost', user : 'root', password : '123456', database : 'test'}); connection.connect(); connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) { if (error) throw error; console.log('The solution is: ', results[0].solution);});MySql的增删改查增: INSERT INTO `表名` (`key`,`key2`,...) VALUES("value","value2",...); 这里要注意的是,表名和key值括起来的不是单引号,而是右上角esc键下方的 `,需要在英文输入下输入 ...

June 7, 2019 · 1 min · jiezi

vuenodemysql撸一个带权限的后台管理系统

简介mvvm-rbac是一个简易的带权限的后台管理系统。它基于vue、egg、mysql(sequlize)实现,旨在用最少代码理解实现常见的业务功能,所以并没有使用ui库,页面并不是很漂亮。麻雀虽小,但是五脏俱全。该项目如果有什么不足错误,麻烦您指出。如果能帮助到你,欢迎star github在线预览前序准备本项目技术栈基于 ES2015+、vue、vuex、vue-router 、vue-cli 3.0 、axios 和 egg、mysql(sequlize),所有的请求都是真实的数据 功能登录管理 登录退出角色管理 增加查找修改删除用户管理 增加(根据角色)查找修改(根据角色)删除权限管理 角色授权页面权限导航菜单图片管理(用户管理) 图片上传图片修改缓存 redishttp部署 nginx二级域名(http://rbac.pengyongjie.top/)

June 7, 2019 · 1 min · jiezi

信息检索课程实验笔记

注:前端不相关---只是用node实现了一下如何爬网站数据(大学课程的实验) 网页索引与检索实验目的l 了解搜索引擎的工作原理及实现方法;l 熟悉倒排索引的创建;l 掌握查询处理技术。 实验要求l 独立或合作(1~2人)完成实验内容;l 独立完成实验报告;(简单要求如下)1) 实验目的、内容与要求及实验环境描述;2) 索引和检索系统设计思路及总体框架;3) (负责部分的)程序结构及具体实现的流程分析,提供主要数据结构、函数分析等;4) 实验结果分析;5) 系统的优缺点、以及待改进的地方;6) 在实验过程中遇到的问题,实验的心得体会。 实验内容3.1 倒排索引(1)网页预处理。对实验一采集到的网页数据进行预处理,包括:网页的去噪和正文信息提取、中文分词、停止词处理等。(2)设计和创建倒排索引。对每个索引的词,至少应该记录其文件频率(df)。设计置入文件的数据结构,至少记录每个词在各个文档中出现的次数,即词频(tf)。同时对每个文档,记录其文档长度。(3)对索引的过程,生成相关的统计信息,例如:创建索引所需的时间、索引的大小、词汇表长度、具有最大df值的词的置入列表的大小等。(可选) 3.2检索系统 (1)设计实现一个简单的检索系统,可输入检索词,并输出查询结果,按相关度排序。 (2)对指定的查询词(IR2019查询词.txt),给出每个查询结果排序,以及相似度得分。所提交的结果将被评估。提交的结果文件有查询结果的数据块构成。每个查询词对应一个结果数据块,每个查询词提交10条查询结果。每个结果数据块格式如下:第一行是查询词序号,如“TD01”每一行是一条查询结果记录,格式为: <URL Similarity>URL:网页的规范化URL,如“http://www.scut.edu.cn/new/90...”Similarity:相似度得分每个数据块的十条记录按相似度从高到低排序,每个数据块之间以一个空行隔开.(3)对结果进行人工判断相关或不相关,然后基于该判断用评测指标Precision@10和MAP计算系统的检索性能指标。(可选)(4)采用各种查询处理技术对查询进行优化处理。并对所采用的不同技术的应用效果进行比较分析。(可选) 提交内容l 程序:包括源程序及注释,程序安装使用说明;l 查询结果文件:查询词对应查询结果汇总l 实验报告:说明程序设计的思路,并对实验过程进行分析和总结。 参考资料l 参考课程讲义的倒排索引、查询处理与检索评估、搜索引擎等章节;l 开源索引系统Lucene:http://lucene.apache.org ----------工作内容分解1生成倒序索引的文档 2 生成实验要求的结果(实验关键词txt) 3 网页 可以实时查询并且生成关键结果 4在后台跑服务器的服务 读 数据 返回数据做的优化工作1 修改停用词表2 同义但是模糊的词3 没有用本地数据库保存词语4 自己获取了词频和词语出现的位置5 result 去重6 使用db.txt存储数据参考文章:1 http://nathanchen.github.io/1...2 nodejs 读写文件http://javascript.ruanyifeng....3 json格式化的网站https://www.bejson.com/逻辑备注:1writeFileSync这是会默认会覆盖原来的内容的

June 6, 2019 · 1 min · jiezi

初学node链接MySql遇到的报错一

cmd命令错误报告5这个错误报告是由于没有用管理员模式启动,权限不够导致的,用管理员模式启动就好了 cmd命令错误报告:error 1067这个错误报告是没有配置MySql环境导致的,在path环境变量下新建一个,将MySql的bin目录路径添加进去就好了 错误报告:error 1146 Table doesn’t exist这个错误报告首先检查下表单名是否正确,查看表单是否建立成功。用node.js链接的话建议用pool链接,用法如下 如果是拿了别人的数据库数据过来使用的话就可能是因为innodb崩了,需要重新配置参数才行具体操作参考:转载 系统错误报告:2 系统找不到指定文件这个错误报告就比较麻烦了,首先排除掉前面的错误报告,确保没有其他错误其次输入cd ../..进入C盘根目录,在输入MySql文件bin目录路径,因为安装与移除MySQL服务都是要在bin目录下完成的。进入后输入mysqld -remove 显示Service successfully removed移除成功如果没安装服务或移除完成的话直接输入mysqld -install显示Service successfully installed安装成功之后就可以启动了net start mysql 查看下链接是否成功mysql -uroot成功显示 Your MySQL connection id is 3Server version: 5.6.10 MySQL Community Server (GPL)Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.mysql>```如果有数据库设置了密码并且没配置初始化文件my.ini的话会遇到error 1045 ...

June 6, 2019 · 1 min · jiezi

JeecgBoot-20-入门视频教程

视频大纲 视频地址: https://pan.baidu.com/s/1Il0T... 提取码:hok5 源码下载: https://github.com/zhangdaisc... (喜欢请Star)

June 6, 2019 · 1 min · jiezi

js-函数参数推荐书写方式-param1-param2

js 函数参数推荐书写方式 ({ param1, param2, ... })编程语言函数(包括对象的方法)参数的取值方式大致可以分为两种:按序取值与按名取值。 一般编程语言都是按序取值,比如 C、Java、JavaScript 等,少数语言支持按名取值,比如 Groovy。 1. 按序取值按照顺序,挨个取值,每个参数的顺序是固定的。 const func = (param1, param2, ...) => { ... }func(1, 2, ...)2. 按名取值按照名称取值,可以任意安排各个参数的顺序。 以下语法并不存在,只是作为讲解生造的const func = (param1: value1, param2: value2, ...) => { ... }func(param1: 1, param2: 2); // ok func(param2: 2, param1: 1); // ok again 3. js 的按名取值JavaScript 语言本身并不支持按名取值,但结合 ES6 的解构赋值,可以模拟函数参数的按名取值。 const func = ({ param1, param2, ... }) => { ... }func({ param1: 1, param2: 2, ... });但这种方式如果不传参数调用 func() 就会报错,需要 func({}) 这样调用才表示什么参数都不传。 ...

June 6, 2019 · 2 min · jiezi

Nodejs异步流程框架async

Async的简单介绍Async是一个流程控制工具包,提供了直接而强大的异步功能。基于Javascript为Node.js设计,同时也可以直接在浏览器中使用。Async提供了大约20个函数,包括常用的 map, reduce, filter, forEach 等,异步流程控制模式包括,串行(series),并行(parallel),瀑布(waterfall)等。https://github.com/caolan/async

June 6, 2019 · 1 min · jiezi

如何Electron中调用Dll

如何Electron中调用Dll客户端有些硬件的接口需要调试,是在电脑上连了一些硬件的设备,比如打印机、扫描仪或者进行串口通信等等。单靠JS是完成不了了,我们决定通过把C++或者C#把这些功能打包成Dll,然后在Electron客户端中通过Node调用Dll来实现所需要的功能。 Dll类型先简单说一下什么是Dll,Dll是动态链接库文件,也是一种代码库的形式,与静态链接库相比,它是在每次程序运行的时候去调用,而静态链接库指令都会被打包到最后的exe文件里,所以如果函数有什么变化那就需要重新生成exe,那动态链接库就不需要这么做了。生成Dll可以通过VS来完成,可以选择使用C#或者C++开发,C#开发界面的比较方便,如果你的功能需要弹出一些界面,那就要用C#编写相应的Dll。不过这里要注意了,用C#语言编写生成的Dll和用C++语言编写生成的Dll是不一样的,通过C#生成的Dll需要.net的开发环境,而C++生成的Dll就没有限制。 Node如何调用DllElectron里调用Dll其实就是node调用Dll,刚才说了,生成的Dll不一样,那么调用方式也不一样。我是用到了这两个模块,ffi和edge,使用ffi调用C++生成的Dll,使用edge调用C#生成的Dll。 ffi调用Dll比如我这里有个ffiTest.dll的文件,里面有个导出的函数叫做joinStr,就是暴露的方法,给定两个字符串,然后会返回这两个参数的拼接结果。注意C++生成的Dll要使用C风格extern “C”否则可能找不到对应的方法名。 var ffi = require('ffi');var path = require('path');var dllPath = path.resolve('ffiTest.dll');var lib = ffi.Library(dllPath, { 'joinStr': ['string', ['string', 'string']],})var result = lib.joinStr('hello', 'world');console.log(result); //打印 helloworld更详细的示例可以参考它的教程。ffi.Library里第二个参数是一个Json结构,key表示是方法名,value示一个数组,数组的第一个参数是返回值类型,第二个参数是方法的列表,如果返回值是空的话,那数组第一个参数应该是void。如果返回值或者参数类型不知道是什么类型就写void*。要使用ffi中的类型表示C/C++语言中的类型,对照表如下 基本类型int8 Signed 8-bit Integeruint8 Unsigned 8-bit Integerint16 Signed 16-bit Integeruint16 Unsigned 16-bit Integerint32 Signed 32-bit Integeruint32 Unsigned 32-bit Integerint64 Signed 64-bit Integeruint64 Unsigned 64-bit Integerfloat Single Precision Floating Point Number (float)double Double Precision Floating Point Number (double)pointer Pointer Typestring Null-Terminated String (char *)常见的C语言类型byte unsigned charchar charuchar unsigned charshort shortushort unsigned shortint intuint unsigned intlong longulong unsigned longlonglong longulonglong unsigned long longsize_t platform-dependent, usually pointer size如果是指针类型,可以利用ref模块来表示 ...

June 6, 2019 · 2 min · jiezi

Vue核心50讲-第零回做足前戏让你平滑进入-Vue

既然说明了是前戏,那就是要诱惑你... ...不对,就是告诉你本套”书“讲的是什么。讲的是什么呢?讲的是前端框架 Vue。 前端江湖发展到今天,已是框架三分天下的局面。可能你会问了,哪三分呢?这三分乃是 Vue、React 和 Angular 这三个前端框架。虽说是三分天下,但也有强弱之分,最强的要数咱们的主角 Vue。 说到这啊,咱们得介绍介绍这个历史背景。早在公元两千零九年,为了克服 HTML 在构建 Web 应用上的不足,由 Misko Hevery 为头的一伙人创建了 AngularJS,后来被一家叫做 Google 的公司收购了。AngularJS 把曾经运用在后端开发的模式引入到了前端开发之中,这就是著名的开发模式 MVC。 由于那个时候的前端江湖还处在需要手动创建工程,使用各种库的荒蛮时代。AngularJS 就是在这样的历史背景下被创建出来的,然后迅速崛起。崛起之后啊,也暴露出了一些问题,比如框架庞大、臃肿,性能也是越来越差。这说话间,时间就来到了公元两千一十三年。Facebook 这家公司找准了机会,推出了 ReactJS 这个前端框架。 ReactJS 的设计极其独特,最厉害的是它首创了 Virtual Dom(虚拟DOM)。 由于 ReactJS 首创了 Virtual Dom 和 JSX 等新的概念和用法,导致学习成本居高不下。这个时候,有个人叫尤雨溪,他当时还在 Google 公司用着 AngularJS 呢。当时呢,尤雨溪想写个简单的框架来练练手,就创造了 Vue。直到公元两千一十五年,Vue 才真正出世。由于 Vue 简单易学,迅速收到各大公司支持。 至此,前端江湖中框架三分天下的格局已定。

June 6, 2019 · 1 min · jiezi

还可以这么玩超实用-Typescript-内置类型与自定义类型

背景大家用过 Typescript 都清楚,很多时候我们需要提前声明一个类型,再将类型赋予变量。 例如在业务中,我们需要渲染一个表格,往往需要定义: interface Row { user: string email: string id: number vip: boolean // ...}const tableDatas: Row[] = []// ...有时候我们也需要表格对应的搜索表单,需要其中一两个搜索项,如果刚接触 typescript 的同学可能会立刻这样写: interface SearchModel { user?: string id?: number } const model: SearchModel = { user: '', id: undefined }这样写会出现一个问题,如果后面id 类型要改成 string,我们需要改 2 处地方,不小心的话可能就会忘了改另外一处。所以,有些人会这样写: interface SearchModel { user?: Row['user'] id?: Row['id']} 这固然是一个解决方法,但事实上,我们前面已经定义了 Row 类型,这其实是可以更优雅地复用的: const model: Partial<Row> = { user: '', id: undefined }// 或者需要明确指定 key 的,可以const model2: Partial<Pick<Row, 'user'|'id'>>这样一来,很多情况下,我们可以尽量少地写重复的类型,复用已有类型,让代码更加优雅容易维护。 ...

June 6, 2019 · 5 min · jiezi

基于Egg框架的日志链路追踪实践分享

快速导航[Logger-Custom] 需求背景[Logger-Custom] 自定义日志插件开发[Logger-Custom] 项目扩展[Logger-Custom] 项目应用[ContextFormatter] contextFormatter自定义日志格式[Logrotator] 日志切割需求背景实现全链路日志追踪,便于日志监控、问题排查、接口响应耗时数据统计等,首先 API 接口服务接收到调用方请求,根据调用方传的 traceId,在该次调用链中处理业务时,如需打印日志的,日志信息按照约定的规范进行打印,并记录 traceId,实现日志链路追踪。 日志路径约定/var/logs/${projectName}/bizLog/${projectName}-yyyyMMdd.log日志格式约定日志时间[]traceId[]服务端IP[]客户端IP[]日志级别[]日志内容采用 Egg.js 框架 egg-logger 中间件,在实现过程中发现对于按照以上日志格式打印是无法满足需求的(至少目前我还没找到可实现方式),如果要自己实现,可能要自己造轮子了,好在官方的 egg-logger 中间件提供了自定义日志扩展功能,参考 高级自定义日志,本身也提供了日志分割、多进程日志处理等功能。 egg-logger 提供了多种传输通道,我们的需求主要是对请求的业务日志自定义格式存储,主要用到 fileTransport 和 consoleTransport 两个通道,分别打印日志到文件和终端。 自定义日志插件开发基于 egg-logger 定制开发一个插件项目,参考 插件开发,以下以 egg-logger-custom 为项目,展示核心代码编写 编写logger.jsegg-logger-custom/lib/logger.jsconst moment = require('moment');const FileTransport = require('egg-logger').FileTransport;const utils = require('./utils');const util = require('util');/** * 继承 FileTransport */class AppTransport extends FileTransport { constructor(options, ctx) { super(options); this.ctx = ctx; // 得到每次请求的上下文 } log(level, args, meta) { // 获取自定义格式消息 const customMsg = this.messageFormat({ level, }); // 针对 Error 消息打印出错误的堆栈 if (args[0] instanceof Error) { const err = args[0] || {}; args[0] = util.format('%s: %s\n%s\npid: %s\n', err.name, err.message, err.stack, process.pid); } else { args[0] = util.format(customMsg, args[0]); } // 这个是必须的,否则日志文件不会写入 super.log(level, args, meta); } /** * 自定义消息格式 * 可以根据自己的业务需求自行定义 * @param { String } level */ messageFormat({ level }) { const { ctx } = this; const params = JSON.stringify(Object.assign({}, ctx.request.query, ctx.body)); return [ moment().format('YYYY/MM/DD HH:mm:ss'), ctx.request.get('traceId'), utils.serviceIPAddress, utils.clientIPAddress(ctx.req), level, ].join(utils.loggerDelimiter) + utils.loggerDelimiter; }}module.exports = AppTransport;工具egg-logger-custom/lib/utils.jsconst interfaces = require('os').networkInterfaces();module.exports = { /** * 日志分隔符 */ loggerDelimiter: '[]', /** * 获取当前服务器IP */ serviceIPAddress: (() => { for (const devName in interfaces) { const iface = interfaces[devName]; for (let i = 0; i < iface.length; i++) { const alias = iface[i]; if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) { return alias.address; } } } })(), /** * 获取当前请求客户端IP * 不安全的写法 */ clientIPAddress: req => { const address = req.headers['x-forwarded-for'] || // 判断是否有反向代理 IP req.connection.remoteAddress || // 判断 connection 的远程 IP req.socket.remoteAddress || // 判断后端的 socket 的 IP req.connection.socket.remoteAddress; return address.replace(/::ffff:/ig, ''); }, clientIPAddress: ctx => { return ctx.ip; },}注意:以上获取当前请求客户端IP的方式,如果你需要对用户的 IP 做限流、防刷限制,请不要使用如上方式,参见 科普文:如何伪造和获取用户真实 IP ?,在 Egg.js 里你也可以通过 ctx.ip 来获取,参考 前置代理模式。 ...

June 5, 2019 · 3 min · jiezi

重磅重构开源-让H5标签代替C实时解码播放speex压缩协议的音频文件-IM的福音

这么牛逼的轮子,肯定要美图镇楼Speex是一套主要针对语音的开源免费,无专利保护的音频压缩格式。本轮子,适用超大型项目,因为库本身很大,当然本身IM项目就没有小项目吧Speex(音标[spi:ks])是一套开源免费的、无专利保护的、针对语音设计的音频压缩格式。Speex项目通过以提供昂贵的专用语音编解码器的免费替代方案为目标,来降低语音应用程序的进入门槛。此外,Speex非常适用于互联网应用程序,并提供了其他大多数编解码器中不存在的有用特性。最后,Speex是GNU项目的一部分,可以在修订后的BSD许可证下使用。编码流程使用Speex的API函数对音频数据进行压缩编码要经过如下步骤:定义一个SpeexBits类型变量bits和一个Speex编码器的内存指针变量enc。调用speex_bits_init(&bits)函数初始化bits。调用enc = speex_encoder_init(&speex_nb_mode)函数初始化enc。其中speex_nb_mode是SpeexMode类型的变量,表示的是窄带模式。还有speex_wb_mode表示宽带模式、speex_uwb_mode表示超宽带模式。调用函数 int speex_encoder_ctl(void * state, int request, void * ptr)来设定编码器的参数,其中参数state表示编码器的内存指针;参数request表示要定义的参数类型,如SPEEX_GET_FRAME_SIZE表示设置帧大小,SPEEX_SET_QUALITY表示编码的质量等级;参数ptr表示要设定的值。初始化完毕后,对每一帧声音作如下处理:调用函数speex_bits_reset(&bits)重置bits,然后调用函数speex_encode(enc_state,input_frame, &bits)进行编码,参数bits中保存编码后的Speex格式数据帧。编码结束后,调用函数speex_bits_destroy(&bits),speex_encoder_destroy(enc_state)来销毁SpeexBits和编码器。 解码流程对已经编码过的Speex格式音频数据帧进行解码要经过以下步骤:定义一个SpeexBits类型变量bits和一个Speex解码器的内存指针变量dec。调用speex_bits_init(&bits) 函数初始化bits。调用dec = speex_decoder_init(&speex_nb_mode) 函数初始化dec。调用函数speex_decoder_ctl(void * state, int request, void * ptr)来设定解码器的参数。调用函数 speex_decode(void * state, SpeexBits * bits, float * out)对参数bits中的Speex格式音频数据帧进行解码,参数out中存放解码后的音频数据帧。调用函数speex_bits_destroy(&bits), speex_decoder_destroy(void * state)来销毁SpeexBits和解码器说重点当做即时通信产品,像微信这种的手机端,它们接受到很有可能就是speex协议压缩后的音频文件。当然,文件后缀是wav或者ogg都无关紧要H5的audio标签可以播放 音频格式及浏览器支持目前, <audio>元素支持三种音频格式文件: MP3, Wav, 和 Ogg:浏览器 MP3 Wav Ogg Internet Explorer 9+ YES NO NOChrome 6+ YES YES YESFirefox 3.6+ NO YES YESSafari 5+ YES YES NOOpera 10+ NO YES YES音频格式的MIME类型Format MIME-typeMP3 audio/mpegOgg audio/oggWav audio/wav本开源库基于speex封装,抽取了必须要的文件后进一步封装,修改了在复杂环境下的兼容本源码支持环境 ...

June 5, 2019 · 1 min · jiezi

如何提升JSONstringify的性能

1. 熟悉的JSON.stringify()在浏览器端或服务端,JSON.stringify()都是我们很常用的方法: 将 JSON object 存储到 localStorage 中;POST 请求中的 JSON body;处理响应体中的 JSON 形式的数据;甚至某些条件下,我们还会用它来实现一个简单的深拷贝;……在一些性能敏感的场合下(例如服务端处理大量并发),或面对大量 stringify 的操作时,我们会希望它的性能更好,速度更快。这也催生了一些优化的 stringify 方案/库,下图是它们与原生方法的性能对比: 绿色部分时原生JSON.stringify(),可见性能相较这些库都要低很多。那么,在大幅的性能提升背后的技术原理是什么呢? 2. 比 stringify 更快的 stringify由于 JavaScript 是动态性很强的语言,所以对于一个 Object 类型的变量,其包含的键名、键值、键值类型最终只能在运行时确定。因此,执行JSON.stringify()时会有很多工作要做。在一无所知的情况下,我们想要大幅优化显然无能为力。 那么如果我们知道这个 Object 中的键名、键值信息呢 —— 也就是知道它的结构信息,这会有帮助么? 看个例子: 下面这个 Object, const obj = { name: 'alienzhou', status: 6, working: true};我们对它应用JSON.stringify(),得到结果为 JSON.stringify(obj);// {"name":"alienzhou","status":6,"working":true}现在如果我们知道这个obj的结构是固定的: 键名不变键值的类型一定那么其实,我可以创建一个“定制化”的 stringify 方法 function myStringify(o) { return ( '{"name":"' + o.name + '","status":' + o.status + ',"isWorking":' + o.working + '}' );}看看我们的myStringify方法的输出: ...

June 5, 2019 · 3 min · jiezi

APubPlat-一款Devops自动化部署持续集成堡垒机开源项目友好的Web-Terminal

嗨、很高心你能进入这里,我是zane,  在这里给你介绍一款完整的Devops自动化部署工具 APubPlat - 一款完整的Devops自动化部署、持续集成、堡垒机、并且友好的Web Terminal开源项目。 如果你对它感兴趣,就给一个小小的关注吧,一款好的产品更需要碰撞和火花。: github address : https://github.com/wangweianger/APubPlat document : http://apub-wiki.seosiwei.com 接下来我还会持续的更新和迭代。 功能描述资产管理: 方便快捷的管理资产,可为资产分组,为应用分配不同的资产,快捷控制台管理等。应用管理:可建立各种应用任务,前端,后端发布任务,可同时执行单机和多机任务,并实时显示任务日志。WEB控制台: 一套强大的Web Terminal,可直接替代Xshell等工具,可单个或批量打开窗口或执行命令(已支持linux系统,后期版本支持windows系统)。脚本管理:可为单个或者多个资产预装各种软装或者执行各种命令,可自由自定义各种预装脚本,例如安装nginx单|多机脚本生成:可同时为单机或者多机器同时生成shell脚本到指定的目录,方便统一管理和操作。备份还原:单多机可同时备份,并按详细日期进行备份,可随时随意一键恢复任意历史版本。应用场景各种前端静态发布(例如:vue,react,jquery之类的纯前端持续集成)前端中间层发布(例如:使用node.js开发的前端中间层之类的服务持续集成)后端发布 (不限制后端语言,只依赖于shell脚本)单机 | 多台机器 同时发布、备份、还原web版本的xshell,让你不管何时何地都能方便的管理服务器资源强大的权限管理能力,为不同角色分配不同的管理权限,让我们的持续集成更灵活更方便安装环境APubPlat依赖的环境并不复杂,对软硬件的要求也并不高,一台1G双核的服务器都能搞定。 APubPlat 开发技术基于egg.js、vue.js, 因此只需要安装node环境,node.js版本推荐 8.9.0 ~ 10.15.1 之间 数据库基于mongudb、环境数据库基于redis、web服务器基于nginx,所有的软件和服务你都可以安装在一台机器中。 如果想了解更多你可以选择去查看项目文档: http://apub-wiki.seosiwei.com 项目预览登录界面、第一次使用时请注册admin账号,其他账号在后台中进行新增和编辑管理 你可以自定义任何适合你的项目环境 资产管理是项目的一个核心能力,所有持续集成都依赖于资产,也是Web Terminal的入口之一 你可以新建任何需要发布和管理的应用,分配相应的资产,可以选择单机部署、部分部署或者全量部署 在这里你可以查看任何时候的应用构建状态、备份状态、生成配置状态 一切的部署都依赖于shell脚本,脚本的正确与否,决定了你的应用是否能部署成功 友好的web化界面部署日志,支持多机,你可以随时掌控部署状态,也可随时终端某台机器的发布 强大的Web Terminal能力,跟xshell工具一样的体验,随时随地管理你的资产吧 感兴趣如果你有那么一点感兴趣,别犹豫先star或者watch,我会持续的更新和迭代,让它成为你开发中的神器吧 github address: https://github.com/wangweianger/APubPlat 如果你也认可我,那也可以给我一个following额 你还可以加入QQ群来尽情的交流吧,一款好的产品更需要碰撞和火花。

June 5, 2019 · 1 min · jiezi

Nodejs-全局对象-process

process是一个全局变量,global对象的属性。它的作用是描述当前Node.js进程状态的对象,提供了一个与操作系统的简单接口。通常在你写本地命令程序的时候,process就会经常用到,下面说说process对象的一些常用的成员方法。 exit : 当进程准备退出时触发 process.on('exit', function(code) { setTimeout(function(){ console.log('该代码不会执行。') }) console.log('退出码为:' + code)})console.log('程序执行结束') 退出状态码1 Uncaught Fatal Exception有未捕获异常,并且没有被域或 uncaughtException 处理函数处理。2 Unused保留3 Internal JavaScript Parse ErrorJavaScript的源码启动 Node 进程时引起解析错误。非常罕见,仅会在开发 Node 时才会有。4 Internal JavaScript Evaluation FailureJavaScript 的源码启动 Node 进程,评估时返回函数失败。非常罕见,仅会在开发 Node 时才会有。5 Fatal ErrorV8 里致命的不可恢复的错误。通常会打印到 stderr ,内容为: FATAL ERROR6 Non-function Internal Exception Handler未捕获异常,内部异常处理函数不知为何设置为on-function,并且不能被调用。7 Internal Exception Handler Run-Time Failure未捕获的异常, 并且异常处理函数处理时自己抛出了异常。例如,如果 process.on('uncaughtException') 或 domain.on('error') 抛出了异常。8 Unused保留9 Invalid Argument可能是给了未知的参数,或者给的参数没有值。10 Internal JavaScript Run-Time FailureJavaScript的源码启动 Node 进程时抛出错误,非常罕见,仅会在开发 Node 时才会有。12 Invalid Debug Argument 设置了参数--debug 和/或 --debug-brk,但是选择了错误端口。128 Signal Exits如果 Node 接收到致命信号,比如SIGKILL 或 SIGHUP,那么退出代码就是128 加信号代码。这是标准的 Unix 做法,退出信号代码放在高位。 ...

June 5, 2019 · 2 min · jiezi

Nodejs-全局对象

Node.js 全局对象 JavaScript中有一个特殊的对象,称之全局对象,它及其所有属性都可以在程序都任何地方访问,即全局对象 JS的全局对象是window,而Node的全局对象是global,在node.js中,所有的全局变量都是global对象的属性。在Node.js我们可以直接访问到global的属性,而不需要在应用中包含它。 全局对象与全局变量 global嘴笨根的作用就是作为全局变量的宿主。和window一样 什么是全局变量呢,满足一下条件的变量即称之为全局变量: 1 在最外层定义的变量;2 全局对象的属性;3 隐式定义的变量 (为定义直接赋值的变量)。当我们定义了一个全局变量,这个变量同时就会成为global的属性。这里需要注意的是,在Node.js中你不可能在最外层定义变量。因为所有用户代码都是属于当前模块的,而模块本身不是最外层上下文。注意:永远使用 var 定义变量以避免引入全局变量,因为全局变量会污染 命名空间,提高代码的耦合风险。 接下来看看node当中的全局变量 __filename (两个下划线) : 表示的是当前正在执行的脚本的文件名。它将暑促文件所在位置的绝对路径,切和命令行参数所指定的文件名不一定想动。如果在模块中,返回的值是模块文件的路径。 例子 结果 __dirname : 表示当前执行脚本的所在目录 setTimeout(cb, ms) : 定时器 第一个参数为指定函数,第二个参数为毫秒 ,返回值为代表定时器的句柄值 function sayHello() { console.log('Hello World')}setTimeout(sayHello , 2000) //结果为两秒以后输出Hello WorldclearTimeout(t) : 清空定时器,用于停止之前通过setTimeout创建的定时器。参数t是通过setTimeout函数创建的定时器。 function sayHello() { console.log('Hello World')}let t = setTimeout(sayHello , 2000)clearTimeout(t)setInterval(cb, ms) : setInterval里第一个参数为函数,第二个参数为毫秒,返回一个代表定时器的句柄值,与setTimeout()方法区别在于setInterval会不停的调用函数,直到clearInterval()被调用或窗口关闭 function sayHello() { console.log('Hello World') // clearInterval(t) 如果不写clearInterval 程序会一直执行打印Hello World}let t = setInterval(sayHello,2000)接下来就是console方法,console用于提供控制台标准输出,多用于调试。 ...

June 5, 2019 · 1 min · jiezi

vue如何自动化打包测试环境和正式环境的disttest文件

使用vue现在已经差不多2年了,想起来两年前的一次和某阿里处理的技术大牛(当时我们的技术总监)一起开发一个SPA项目的时候被硬着头皮去解决的一个难题,因为技术老大是阿里出身的,所以很多东西都是比较倾向于自动化,从项目ui设计到项目管理,到打包测试,到发布全部都要求我们要实现自动化,尽可能的减少手动操作。 当时技术大佬要求的事在jenkins进行一键打包,就是他点击不同的按钮在同一套代码上面分别打包测试环境运行的包和正式环境运行的包,刚刚接触vue的我摸不着头脑,老大给了我一天时间研究这个玩意,没办法,只好硬着头皮做,后来想想改造一下,也比较简单。 Step1、package.json中新增命令行脚本test命令,并指向build文件夹下的test.js。 Step2、在在build文件夹中新建test.js,内容可以直接拷贝同目录build.js内容,修改一些参数 这样就多了个test环境 Step3、 在build文件夹中新建webpack.test.conf.js,内容可以直接拷贝同目录webpack.prod.conf.js内容,修改一些参数。 这样构建时就会去config文件夹下的test.env.js寻找环境变量。 Step4、在config下创建test.js文件 Step5、在封装的axios.js的文件夹下创建config.js Step6、在封装的axios引入config.js 封装的get 和post请求 Step7、在config文件下的index增加test模块(可复制build)并更改相应的参数。 在打包的时候执行:npm run test 就会自动的指向测试环境的域名dist文件,执行npm run build 就会打包指向正式环境的域名的dist文件,在Jenkins里面的分别连接至gitlab/github,并将命令分别分配给run test && run build,需要发布的时候就直接点击不同的按钮,然后再Linux下自动打包不同环境的dist,可以提高开发效率,减少开发和沟通成本。

June 5, 2019 · 1 min · jiezi

Nodejs-事件循环

Node.js免费课程:阿里云大学——开发者课堂Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高。Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发。Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现。Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数. 事件驱动程序Node.js 使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。这个模型非常高效可扩展性非常强,因为webserver一直接受请求而不等待任何读写操作。(这也被称之为非阻塞式IO或者事件驱动IO)在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。 整个事件驱动的流程就是这么实现的,非常简洁。有点类似于观察者模式,事件相当于一个主题(Subject),而所有注册到这个事件上的处理函数相当于观察者(Observer)。 Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件,如下实例: // 引入 events 模块var events = require('events');// 创建 eventEmitter 对象var eventEmitter = new events.EventEmitter();以下程序绑定事件处理程序: // 绑定事件及事件的处理程序eventEmitter.on('eventName', eventHandler);我们可以通过程序触发事件: // 触发事件eventEmitter.emit('eventName');实例创建 main.js 文件,代码如下所示: // 引入 events 模块var events = require('events');// 创建 eventEmitter 对象var eventEmitter = new events.EventEmitter();// 创建事件处理程序var connectHandler = function connected() { console.log('连接成功。'); // 触发 data_received 事件 eventEmitter.emit('data_received');}// 绑定 connection 事件处理程序eventEmitter.on('connection', connectHandler); // 使用匿名函数绑定 data_received 事件eventEmitter.on('data_received', function(){ console.log('数据接收成功。');});// 触发 connection 事件 eventEmitter.emit('connection');console.log("程序执行完毕。");接下来让我们执行以上代码: ...

June 5, 2019 · 1 min · jiezi

前端案例nvm在windows下的安装配置

前端案例-nvm在windows下的安装配置 最近有同学学习node的时候在安装nvm上出现了许多问题,下面我就给大家说一说nvm的安装配置。 在实际情况下我们可能同时在进行2个项目,而2个不同的项目所使用的node版本又是不一样的,或者是要用更新的node版本进行试验和学习。这种情况下,对于维护多个版本的node将会是一件非常麻烦的事情,而nvm就是为解决这个问题而产生的,他可以方便的在同一台设备上进行多个node版本之间切换,而这个正是nvm的价值所在。 1、NVM下载如果你已经单独安装了node,建议卸载,然后继续下面的操作。 直接进入安装包下载地址:https://github.com/coreybutle...,选择nvm-setup.zip,下载后直接安装。 F:htmlnvm是nvm的安装目录 F:htmlnodejs是当前使用的node目录(是一个快捷方式,链接到nvm文件夹对应的node版本,当你切换到使用的版本后,快捷方式的指向随之改变,后面再说) 2、配置环境变量 NVM_HOME :指向nvm安装目录(node.js所有版本都会在这个目录下) NVM_SYMLINK:nodejs安装目录 (当前使用nodejs版本) 我这边是安装完nvm后自动配置好了环境变量 3、验证是否安装成功 cmd,输入nvm -v,出现版本号,说明安装成功 4、 安装,卸载nodeJS使用nvm install <version> [<arch>]命令下载需要的版本。arch参数表示系统位数,默认是64位,如果是32位操作系统,需要执行命令:nvm install 8.11.0 32,出现下图表示安装、卸载完成,安装时会自动安装对应版本的npm 如果报:Could not retrieve https://nodejs.org/dist/lates...Get https://nodejs.org/dist/lates...: net/http: TLS handshake timeout这种错,说明出现远程连接被关闭的问题,这是由于国内网络限制导致的解决方法:可以将nvm中node和npm设置到国内源,在nvm的安装路径下找到settings.txt(如果没有,可新建一个)打开:添加一下代码 node_mirror:npm.taobao.org/mirrors/node/npm_mirror:npm.taobao.org/mirrors/npm/ 查看一共安装了多少node和使用指定版本node 当切换到使用8.11.0版本时,对应的nodejs快捷方式指向会发生改变 5、 npm的安装npm是什么?  npm有两层含义,第一是npm这个开源的模块登记和管理系统,也就是这个站点:https://www.npmjs.com。 第二个指的是 nodejs package manager 也就是nodejs的包管理工具。我们主要说的就是这一个。 在每个版本的nodejs中,都会自带npm,为了统一起见,我们安装一个全局的npm工具,这个操作很有必要,因为我们需要安装一些全局的其他包,不会因为切换node版本造成原来下载过的包不可用。 首先我们进入命令模式,输入  npm config set prefix "F:htmlnvmnpm"  npm config set cache "F:htmlnvmnpm_cache"  ...

June 5, 2019 · 1 min · jiezi