xixigiggling 发布的文章

commonjs

每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。

每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。

require方法用于加载模块

以上来自CommonJS规范-阮一峰

commonjs的详细:可以从node的使用中获悉

- 阅读剩余部分 -

注:有缺陷,没有进行scroll事件的防抖、节流处理

某项目需要一下抖音的下拉刷新效果,目前做出来的效果如下:

做的还是很勉强,简而言之:

  1. 手指按住下滑时,需要出现一个旋转的spinner
  2. 随着手指往下滑动,spinner要能跟着旋转
  3. 用户可以取消刷新,取消刷新的动作是再往上滑
  4. 在取消刷新的过程中,spinner需要能跟着倒着旋转回去,在超过一定距离后,即成功取消刷新,spinner消失
  5. 若直接下滑到一定距离,再松开,则开始刷新,此时的spinner开始无限旋转,直到页面刷新完成

spinner怎么做?

也就是一个带缺口的1/4圆怎么做?

参考:mint-ui/mint-spinner

也即:设一个宽高相等的div,再设 border-radius: 50%;,这样就把div变成了一个圆

再设border: 4px solid transparent;,这样就形成了一个圆环,但是这个圆环还是透明的,还看不出效果

1/4圆环的最重要一步就来了:

border-top-color: #f5f5f5;
border-left-color: #f5f5f5;
border-bottom-color: #f5f5f5;

大家可以看出这个缺口是哪一边吗? --对的,就是右边,不过还隐藏了一点就是div默认的background的就是background: transparent;,所以此时显示的圆环就是一个4px宽的背景色为#f5f5f5的1/4圆环

旋转的spinner怎么做?

一个属性搞定:transform: rotate(360deg);,此时表示将元素旋转360度

ransform 属性向元素应用 2D 或 3D 转换。该属性允许我们对元素进行旋转、缩放、移动或倾斜

手指下滑,spinner要能跟着旋转

也是这次的核心:

主要思路: touchstart事件得到用户滑动的开始坐标,touchmove中去判断用户滑动的的位移,touchend事件判断用户滑动结束时的坐标,有了这3对值,就可以进行很多操作了

  • touchstart: 得到startXstartY
  • touchmove: 得到moveXmoveY
  • touchend: 得到endXendY

要手指下滑,spinner跟着旋转,也就是要手指下滑的距离与spinnertransform: rotate(xxxdeg);成比例关系,手指下滑的距离就是moveY-startY可以用diff表示

// 使得转动的角度,随着diff的距离变动
spinner.style.transform = 'rotate('+diff*1.2+'deg)';

这样基本的思路就确定了,但还有一个隐藏的地方,怎么确定用户是在下滑?

参考:模仿抖音的全屏滚动效果

GetSlideAngle(dx, dy) {
    // atan2() 方法可返回从 x 轴到点 (x,y) 之间的角度
    return Math.atan2(dy, dx) * 180 / Math.PI;
},
GetSlideDirection(startX, startY, endX, endY) {
    let dy = startY - endY;
    let dx = endX - startX;
    let result = 0;
    if (Math.abs(dx) < 50 && Math.abs(dy) < 50) {
        return result;
    }
    let angle = this.GetSlideAngle(dx, dy);
    if (angle >= -45 && angle < 45) {
        result = 4;
    } else if (angle >= 45 && angle < 135) {
        result = 1; //向上
    } else if (angle >= -135 && angle < -45) {
        result = 2; // 向下
    } else if (
        (angle >= 135 && angle <= 180) ||
        (angle >= -180 && angle < -135)
    ) {
        result = 3;
    }
    return result;
}

之后在touchmove的时候将,startXstartYmoveXmoveY传入就可以判断此时是否在下滑

用户取消刷新,又倒着上滑的时候

上述的spinner与滑动距离的随动关系,仍可以用,主要是怎么判断用户是在取消刷新?

例如,用户下滑到一定程度,再上滑,但上滑不超过起始点,那么如果一开始用的是startXstartY来判断,此时用户仍会被判断会下滑啊...

解决方法:定义一个maxDown变量来存储用户下滑的最大距离,下滑过程中,touchmove事件会被不断的触发,于是diff的值就会不断被计算出来

// 存储用户下滑的最大距离
if(this.maxDown<diff) {this.maxDown = diff}

// 但用户上滑到一定距离的时候
if(this.maxDown-diff) > this.maxDown*2/3) {
    // 下滑标志置为false
    this.moveDownFlag = false;
    // 隐藏spinner
    this.spinner = false;
}

直接下滑到一定距离,再松开

此时用户触发刷新操作,spinner开始无限旋转,直到页面刷新完成

这里判断用户正常下滑比较容易,只要在touchend事件时,用户是在下滑而且之前的moveDownFlag是为true,这样就可以了

但怎么让spinner无限旋转,而且要充当页面刷新的loading效果呢?

我采用的是vue,所以可以用动态class来实现

vuetemplate部分:

<div class="main__indicator" ref="spinner" :class="{'active': isLoading}" v-show="spinner"></div>

为了性能,这里采用css动画,使用animation+keyframes的组合,CSS @keyframes Rule

sass

@keyframes kebab-spinner-rotate {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(360deg);

    }
}
&.active {
    animation:kebab-spinner-rotate 0.8s infinite linear;
}

js:

if(direction === 2 && this.moveDownFlag) {
    this.isLoading = true;
    // 发送ajax更新,此时充当loading效果
    // ajax code ...
    if(response.data.code === 200) {
         this.isLoading = false;
    }
}

但有一个缺陷就是,当spinner无限旋转时与之前的旋转角度有点衔接补上。。。

隐藏彩蛋,固定/绝对定位的水平居中问题

需要注意的是,spinner是固定定位的,而从一开始的预览图中可以看出,它是水平居中的,怎么实现固定定位的水平居中呢?

我的方法是参考的网上的,具体出处有点不清楚了...

left: 50%;
height: 30px;
width: 30px;
margin-left: -15px;

借用同学腾讯云的服务器,因为webview需要开启摄像头的缘故,只能运行在有https的环境下,于是开启了httphttps

证书

一开始因为是腾讯云,有官方的上https的解决办法,但是尴尬的是同学说在把证书配置负载均衡的那一步,就找不到实例...,应该是我们就没开启任何的nginxapache的服务器之类的吧,于是索性就直接用https://letsencrypt.org

参考资料:

你的网站HTTPS了吗 | Let’s Encrypt

ubuntu使用Let’s Encrypt设置https,最终是用的这个,但是要注意选择http跳转方式(2),而且注意的是网址一定不要输错

由于自己一开始没有选择http跳转方式(2)(怕影响到已存在的服务...此处需谨慎),因为怕在服务器上已经运行的tomcat8080会被屏蔽掉,但是却发现首页也没有加上https

后来在如何免费的让你的网站变得更加安全 - HTTPS,这篇文章中找到灵感,sudo certbot --nginx,再输一下这个命令不就有了吗? 后来才发现自己不仅一开始没选好,就连网址都输错了...

nginx

这个才是自己的痛苦之源,因为对nginx一点都不熟,简直崩盘...

阿里云配置 node.js + Nginx 反向代理,写nginx配置文件的时候参考的这个,但没有成功...,对nginx不熟啊...

How To Set Up a Node.js Application for Production on Ubuntu 16.04,stackoverflow上推荐的

Deploying NodeJS using Express with NginX and Let's Encrypt,比较详细,也一边参考了这个

[[新手]nginx反向代理负载均衡配置](https://segmentfault.com/a/1190000003824014)

CentOS 7 yum 安装 Nginx,另外另一个同学有cenos7的服务器,也试了试nginx,发现与ubuntu还是有一些不同,

nginx把配置中默认的root指向的静态资源后报错403

nginx默认配置的静态资源目录(root),直接换成dist目录(直接用xftp上传报错,没权限。用cp -r /home/uftp/xixigiggling/. /var/www/html解决),就可以变成直接运行单页面应用了(佩服自己的机智),但是打开首页却报错,403 forbidden查nginx日志

# 万一起不来 日志日志日志,重要的事情说三遍!!!
tail -f /var/log/nginx/error.log

可以发现是 Permission denied的权限错误,于是设置linux文件的权限,dist的文件有index.htmlstaticchmod 777 index.html,但是注意在文件夹开启权限要用-Rchmod 777 -R static

参考资源:

nginx静态资源文件无法访问,403 forbidden错误

总结

不熟悉nginx!!!

遇到一个跨域问题,说到这里大家就心里默然一笑了,因为不外乎就那样的balabala,但这次有点不一样的在于:后端确定自己已经开启了跨域!这...就有点尴尬了

于是问题变成,我是怎么判断跨域的?

首先是最简单的http://10.63.231.79:8080http://47.106.111.100:8088发post请求,无疑已经跨域了,

其次,因为我的是vue-cli搭建的(我负责webview部分),然后用的axios发请求,按api地址来发数据的时候,报错403,如下图:

看起来,我很有道理,但是有两个问题

第一,为什么postman可以成功呢?

第二,为什么安卓端的请求也可以成功呢?

安卓端的请求如下:

第一个问题

通过为什么使用postman发送请求时不会有跨域问题?得知,

准确的跨域定义:当一个资源从与该资源本身所在的服务器不同的域或端口请求一个资源时,资源会发起一个跨域 HTTP 请求,通俗一点,正常的跨域情况,是你访问了一个A网站,然后这个网站返回的资源里面,请求了B网站/端口的资源,于是就跨域了

跨域这个情况只会出现在浏览器页面里,因为实际上是浏览器由于安全原因限制了这些请求的访问。在postman里面,实际上每发出一个请求,都是在独立请求一个资源

还有很解惑的一句话:

应该是POSTMAN中有个类似java程序或者node 代理了跨域接口,先用node去请求跨域接口,得到数据,然后本地发起ajax请求,实际请求的是node代理过后的接口,就不存在跨域了

只有在浏览器端发 ajax 请求才会存在跨域

http请求不会存在跨域

先不说正确性,其中的那句,实际请求的是node代理过后的接口,立马联想到,这应该也是设置代理能解决跨域的原因吧(将ajax请求交给代理变成由代理发出http请求),也就是句子最后的,只有在浏览器端发ajax请求才会存在跨域,http请求不会存在跨域

第二个问题

的确不明白,但似乎也是这样的理由吧?

后询问安卓的同学是使用的okhttp,上面赫然写着这样一句话:

OkHttp is an HTTP client

这不就差不多又是一个postman吗...似乎到这里事情已经很明白了,但我们需要再看一下的是

浏览器的报错信息:

Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://10.63.231.79:8080' is therefore not allowed access. The response had HTTP status code 403.

什么是preflight request?

参考链接: HTTP访问控制(CORS)-MDN

规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是GET 以外的 HTTP请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求

"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。

简单请求

另联想到,遇到的axios跨域(也就是上述的报错),搜索到的axios可以解决跨域访问的问题吗?,说道:

server端不支持跨域,如果不是自己开发的,那么可以自己写个后端转发该请求,用代理的方式实现。(后来的自己就是这样解决跨域的)

server端支持跨域,但不能响应OPTIONS请求时,如果 server 端也支持简单请求(见下方定义),特别是 axios POST请求时,默认使用 JSON 格式,改成 string 问题就解决了!Using application/x-www-form-urlencoded format,需要转成application/x-www-form-urlencoded,可以用qs.stringify(data),那么这里又是为什么可以呢?

重点就在于简单请求,查看上述MDN的介绍:

某些请求不会触发 CORS 预检请求。本文称这样的请求为“简单请求”

若请求满足所有下述条件,则该请求可视为“简单请求”:

其中条件有(还有其他,请看文档):

Content-Type 的值仅限于下列三者之一:text/plainmultipart/form-dataapplication/x-www-form-urlencoded

原来这个原理就是,把跨域的请求,通过转换成简单请求,这样就不会触发了OPTIONS了,但我的确这样做了之后,发送的预检请求报200了,但是结果控制台还是报跨域的错...这?

注意这些跨域请求与浏览器发出的其他跨域请求并无二致。如果服务器未返回正确的响应首部,则请求方不会收到任何数据。因此,那些不允许跨域请求的网站无需为这一新的 HTTP 访问控制特性担心。

额...原来白高兴一场...

通过响应头部来判断是否开跨域

后续再看MDN文档时更找到一个有力的证据判断服务器是否开跨域:

假如站点 http://foo.example 的网页应用想要访问 http://bar.other 的资源,请求报文和响应报文:

GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
Origin: http://foo.example


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61 
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

请求首部字段 Origin 表明该请求来源于 http://foo.exmaple,响应中携带了响应首部字段 Access-Control-Allow-Origin。使用OriginAccess-Control-Allow-Origin 就能完成最简单的访问控制

Access-Control-Allow-Origin: * 表明,该资源可以被任意外域访问

Access-Control-Allow-Origin: http://foo.example 表明,仅允许来自 http://foo.example 的访问

这就指明了一个更强的方法判断服务器开没开跨域:在响应中,查看是否有Access-Control-Allow-Origin字段

想到怎么测试呢? 用githubapi啊:

可以看到githubapi完全支持跨域

事至此,可以看到这个证明题基本已经差不多了,那就是:服务器没开跨域!

接着就是,你是怎么解决的跨域?

首先一开始由于不确定是不是跨域的问题,因此换了三种发ajax请求的框架,axiosvue-resource重识vue-resource ),jquery,但结果其实都会报错,

其中用jquerypost请求到后端的时候,用jsonp来发post请求,来解决跨域:

$.ajax('http://47.106.111.100:8088/rest/app/register', {
    type: "POST",
    data: test,
    dataType: 'jsonp',
    crossDomain: true,
    success: function (data) {
        alert('hello');
        if (data && data.resultcode == '200') {
            console.log(data.result.today);
        }
    }
});

还是会出现Cross-Origin Read Blocking (CORB) blocked cross-origin response

那么这是为什么呢?

jsonp为什么不支持post请求?

首先定义jsonp: 创建一个 script 标签,将 src 设置为目标请求,插入到 dom 中,服务器接受该请求并返回数据,数据通常被包裹在 回调钩子

可以用 jsonp 发送 post 请求么?

显然不行,看过支持 post 请求的 script 么?

详细解释:

从前端和后端两个角度分析jsonp跨域访问(完整实例)

Why does my JavaScript get a “No 'Access-Control-Allow-Origin' header is present on the requested resource” error when Postman does not?

Response to preflight request doesn't pass access control check

总结:

也就是jsonp:

  1. 不支持post请求
  2. 需要后台配合(You need the server to return data in JSONP format too)

跨域的解决有:

If you do NOT want to:

  • Disable web security in Chrome
  • Use JSONP
  • Use a third party site to re-route your requests

或者

You are running into CORS issues.There are several ways to fix this.

于是凉凉...,post请求肯定触发跨域,而jsonp是不行了,联想到之前有过印象,webpack可设置代理来解决跨域问题(大概是上面postman提到的那种思想),于是查一下,get!

webpack 前后端分离开发接口调试解决方案,proxyTable解决方案

http-proxy-middleware

config/index.js里面找到proxyTable

proxyTable: {
    "/rest": "http://47.106.111.100:8088"
},

这样的话,请求到 /rest/app/register,现在会被代理到请求 http://47.106.111.100:8088/rest/app/register。 更多的用法查看上面的http-proxy-middleware文档即可

就是这样,all is done !

受同学所托,要爬一个网站

第一步很自然的,查看网页元素,看渲染的能不能直接抓下来,

但是用上requestcherrio,一查元素,为空?(页面上明明有),把requestbody打印出来,才发现是ajax,应对方法:打开chrome,用Network,终于查看到了请求,复制链接地址,竟然直接浏览器打开就可以获得到json数据...

接下来就是漫长的数据整理工作了以及一直要弄明白的回调地狱的问题

第一关:字符串处理

以为返回的是json,结果一直报错,才发现返回的内容是hxbase_json1({sum: 2660, list: [,…]}),也就是真正的json内容在两个括号里面...

换言之,怎么获得两个指定的符号中间的内容?

第一反应:正则,但基本忘光了(以前就只是生搬硬套的用...),之后搜索中看到有人提到了直接用字符串的方法,str.substring(str.indexOf('('),str.indexOf(')')),这方法好是好,但是:

input: "hxbase_json1({XXXXxx(xxx)xxxx})"
output: ({XXXXxx(xxx

也就是如果中间有括号就不行(弃),如果要用字符串的方法的话,直接提取子串不就可以了!str.slice(13, -1);,主要发现其他请求都是返回的这个形式,所以直接写死也行,好的,第一关成功!

另,就在写总结的时候,发现找到了以前第一次爬网站的时候各种堆砌出来的较通用的正则匹配函数:

// 匹配前后缀,提取中间的内容
function getInnerString(source, prefix, postfix) {
    if (prefix !== "<p>")
        var regexp = new RegExp(encodeReg(prefix) + '.+' + encodeReg(postfix), 'gi');
    else
        var regexp = new RegExp(encodeReg(prefix) + '(?:(?!<\/p>)[\\s\\S])*' + encodeReg(postfix), 'gi');
    var matches = String(source).match(regexp);
    if (matches == null) {
        let now = new Date();
        matches = ['this is failed', now + ''];
    }
    var formatedMatches = matches.map(value => {
        return value
            .replace(prefix, '')
            .replace(postfix, '');
    });
    return formatedMatches;
}
//转义影响正则的字符
function encodeReg(source) {
    return String(source).replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1');
}

第二关:字符串转换成json

以为提取出字符串之间的内容就可以了

// 提取中间内容
let str = body.slice(13, -1);
// 把字符串转化为json
let testJson = JSON.parse(str);
...
// 报错
SyntaxError: Unexpected token s in JSON at position 1

参考链接:字符串转换成JSON的三种方式,终于知道为什么,因为必须要遵守json规范,而字符串里面的属性并没有引号...

示例发现一个更神奇的现象,只有外面用单引号,而属性用双引号才可以

let jee = '{"sum":2660}'; // 正确:输出{ sum: 2660 }
let jee = "{'sum':2660}"; // SyntaxError: Unexpected token ' in JSON at position 1
console.log(JSON.parse(jee));

于是这里的解决方法只能用eval了(第一次觉得eval这么亲切!),完美!

第三关:回调地狱

现在整个流程差不多写成这样:

// 部分为伪代码
for(let i= 11; i<=17 ;i++) {
    // 先请求起始url,借此得到所有的页码数
    let first = "xxx";
    Request(first, (error, response, body)=> {
        let str = body.slice(13, -1);
        let testJson = eval("(" + str + ")");
        let sum = testJson.sum;
        // 获取到整个的页码
        let pageNum = Math.floor(sum / 20);
        for(let j=1; j<=pageNum; j++) {
            let url = "xxx"+j;
            Request(url, (error, response, body) => {
                // 获取数据(伪代码)
                list.push(data)
            })
        }
        //写数据
        fs.writeFile(i+"year.json", JSON.stringify(list),
            function (err) {
                if (err) throw err;
                console.log('It\'s saved!');
            }
        );
    })
}

这也就是传说中的回调金字塔(回调地狱),说说我自己遇到的情况,

第一,不一定每次请求都能正确无误,所以常常跑着跑着,突然报错了,然后这样的代码结构简直要命,实际中的我还在其中用了一个urls数组先存下所有的url再遍历发请求,也就是各种嵌套,各种异步,出错出在哪到找不到...

第二,遇到了爬虫(或者异步中)很经典的问题,怎么判断回调结束了?这里的问题就是在fs.writeFile的时候怎么确保list已经获取到了所有的值?(11年有133条url,每条url指向20条数据)

第一种问题,以前有过一次爬博客园的经历,那里是采用Promise化的方式解决的,request库还有promise的版本,promise就保证了要么then回调成功,要么err

/**
 * 第一次爬博客园时的经历
 * users 示例
 {
    raphael5200: {
        username: 'raphael5200',
        defaultPage: 'http://www.cnblogs.com/raphael5200/default.html?page=',
        pages: 'http://www.cnblogs.com/raphael5200/default.html?page=2'
    },
    joyeecheung: {
        username: 'joyeecheung',
        defaultPage: 'http://www.cnblogs.com/joyeecheung/default.html?page=',
        pages: 'http://www.cnblogs.com/joyeecheung/default.html?page=2'
    }
    ...
 }
*/
// 遍历每一个用户
for (let key in users) {
    // 请求每一个用户的主页
    rq(users[key].pages)
      .then((body)=>{
        let titles = [];
        let reads = 0;
        let replys = 0;
        for (let i = 0; i < getPageNum(body); i++) {
          // 请求用户发出来的每一个文章列表页
          rq(users[key].defaultPage + '' + i)
            .then((body) => {
              // 提取当前文章列表页的所有文章标题,存到users[key].articles, 每篇文章的阅读数、评论数相加放置到readNum,replyNum中

              // 写入 当时不知道怎么判断异步结束,直接用了一个比较hack的方法,不断的重写文件(最后能保存下来的就随缘了)
              // fs.writeFile: Asynchronously writes data to a file, replacing the file if it already exists
              fs.writeFile("users.json", JSON.stringify(users),
                  function (err) {
                      if (err) throw err;
                      console.log('It\'s saved!');
                  }
              );
            }).catch (function (err) {
              //console.log(err);
            });
        }
      })
      .catch(function (err) {
          console.log(err);
      });
};
// 写入 还尝试过另一个方法,等待1分钟之后写入...(随缘)
setTimeout((users) => {
fs.writeFile("users.json",JSON.stringify(users),
  function (err) {
    if (err) throw err;
    console.log('It\'s saved!');
  }
);
}, 1000*1*60);

这次实际上是通过,把不必要的循环去掉,直接把10年17年要的数据,就分为8条url,在直接请求这8条(好在只有8条啊)

插播一条小细节: 之前的let str = body.slice(13, -1);,常常报错,大致意思就是body出问题了,undefined或者什么的不能调用slice方法,这样的话就又遇到了一个经典的问题:js怎么判断变量的类型?

参考链接:JavaScript学习总结(六)——JavaScript判断数据类型总结

// 外层加一个判断就避免了
if (typeof body === "string") {
        let str = body.slice(13, -1);

第二种问题是采用的,settInterval计数解决

参考链接:NodeJS的异步编程风格

例如:fs去访问五个txt文件,1.txt...5.TXT

const fs = require('fs');
const list = [];

for (let i = 1; i <= 5; i++ ) {
    fs.readFile('./' + i + '.txt', 'utf-8' ,(err, data) => {
        if (err) throw err;
        list.push(data);
    });
}

// 在后台不断的轮训,当list的长度达到指定值(也就是回调全部结束),此时就可以进行清除定时器
let intervalId = setInterval(() => {
    console.log('waiting!...');
    if(list.length === 5) {
        console.log('SUCCESS');
        // 注意setInterval与clearInterval的配合
        clearInterval(intervalId);
    }
}, 1000);

插话:有点疑惑的是,clearInterval(intervalId)有种自身清除自身的感觉? 已知的,它们都是隶属于WindowOrWorkerGlobalScope的方法 (待填坑..)

但是在参考链接中的方法更佳,使用setInterval会加入新的事件循环? Javascript 单线程模型 Event Loop 机制

//记录总的文件大小,使用一个`count`计数来判断异步结束 
count = filenames.length;
for (i = 0; i < filenames.length; i++) {
  fs.stat("./" + filenames[i], function (err, stats) {
     totalBytes += stats.size;
     count--;
     if (count === 0) {
           console.log(totalBytes);
      }
   });
}
从这个例子中,我们可以学到一点:并发运行的相同异步函数如果协作完成任务,需要添加计数代码判断执行状态,并且把所有异步函数完成后执行的代码放在判断条件的语句块里

第三关: 中文乱码

前面的关卡好不容易闯过了后,兴高采烈的抓到了数据,写到了json里,可是一打开一开,gg,股票名称的字段,全是乱码?,网上提示,直接看一下源网站的编码,浏览器打开一看头部,charset="gb2312",难怪...

参考链接:

request库使用时, gb2312、GBK中文乱码解决方法

nodejs下request模块中文gb2312乱码问题

也就是引用iconv-lite包,对返回的gb2312body进行一下转码,Iconv.decode(body, 'gb2312').toString(),这样就可以了

数据库备份:

参考官方文档:

Upgrading MySQL on Windows,了解自己按哪种方式升级,我的是Upgrading MySQL Using the Windows ZIP Distribution

之后开始数据备份,Database Backup Methods

选择备份的方法, 我用的是Making Backups with mysqldump,去查看mysqldump的用法,mysqldump — A Database Backup Program文档比较多,我直接看的:Using mysqldump for Backups

// 直接使用命令行,报错 `Got error: 1045: Access denied for user 'ODBC'@'localhost' (using password: NO)`
mysqldump --all-databases > dump.sql

在网上找到Mysql备份还原数据库之mysqldump实例及参数详细说明

// 成功
mysqldump -u root -p --all-databases > dump.sql

新版本的安装

选用傻瓜版:msi,安装过程有一个注意事项,因为选的是Custom,但是在后来的界面中一直打开的mysql server都是最新版的8.0,而且安装到某个界面还没有next弹出...,后来才发现在Custom之后的选择界面有filter选项,点开,里面就可以选择各种版本的server了

数据的导入

进入mysql数据库,使用source命令:

source E:\xxx.sql

起因,妹子问的实习的公司连不上网,只有公司电脑连有线才可以,但是她在输ip地址从192.168.101.100192.168.101.254一个个试(已经试到206),还是连不上网怎么办?

其实我对这个连不上网的问题真的好菜啊...,仅有的一次还是利用192.168.1.1(还是什么去了..)登录上路由器,然后路由器密码还是贼简单,登上去了,就改了一个路由器的名字,开心了半天,然后就没有然后了...

回到正题,于是赶快google,知乎搜,还是不知道怎么做,就知道了一个DHCP静态ip的名词.. 以及看到网友推荐了一本书,网络是怎样连接的,mark一下了

接着只好很丢脸的说,我不会啊,但是妹子说的从192.168.101.100192.168.101.254一个个试,却留下了印象,就是,我们会用ping来测试网络是不是畅通,那么有没有一种方法能让自动的ping呢?

也就是今天自己学会的bat脚本:

刚开始搜到这个:shell脚本实现网络连接的检测,这里面的思路就用循环从0到254,依次ping,很好,不过这是shell脚本,也就是在我的电脑上装了git-bash是可以运行的,但是妹子的电脑是没有的,也就是要转化成bat脚本啊,于是坑就来了

bat脚本的格式语法问题:

@echo off ::开头,不显示后续命令行及当前命令行

set var_name = "hello" ::set来定义变量

echo %var_name%   ::echo用于输出,变量取值用%var%

for /l %%i in (1,1,254)  ::递增循环的示例 /l表示带开关,此处的变量是%%i
do (
    :: code here     
)

于是代码初步这样:

@echo off
::Ping网段所有IP
set ip="192.168.0."  
for /l %%i in (1,1,254)
do (
ping -c 2 %ip%%%i |grep -q 'ttl=' && echo "%ip%%%i yes"|| echo "%ip%%%i no"
)
::yes正常,no主机不存在或不正常   
pause

遇到的麻烦:

一闪而过...,应该是语法错误了,直接在cmd中输入ping,根本没有-c的参数,而且grepSearch for PATTERN in each FILE or standard input,都去掉,依然一闪而过...

后来改成for /l %%i in (1,1,254) do ( ping %ip%%%i && echo "%ip%%%i yes"|| echo "%ip%%%i no" ),写成一行可以,但报

错误的参数 1。
""192.168.0." 1 no"

在循环中拼接字符串,ping %ip%%%i 难道不可行?

批处理文件在循环中拼接字符串,为什么结果不对啊问题中发现也有人遇到过,是需要设置环境变量延迟,加上setlocal enabledelayedexpansion,并变成ping !ip!%%i,可还是报同样的错误

难道是%%i是数字类型,而ip是字符串类型根本不行?

在bat批处理脚本中,怎样将for语句中的%%i当作字符串处理?做类似于%str:~1,5%之类的操作?

可以用在循环中在设一个变量等于%%i的方法,即set num= %%i ping !ip!!num!,结果没了错误参数,变成

""192.168.0." 1 yes"

这到底好没好呢...

同时又有提到字符串拼接的批处理用的是set /a num+=1set str=!str! %%i ,再在cmd批处理中set /a和set /p的区别介绍,了解到

/P 命令行开关允许将变量数值设成用户输入的一行输入

/a 是指定一个变量等于一串运算字符

彩蛋是这里面的实例2竟然有ping的示例代码

@echo off
set a=1
:start
echo %a%
ping 172.19.5.%a% -w 1 -n 1|find /i "Lost = 1"&&set c=1||set c=0
if %c%==0 (echo 172.19.5.%a% >>IP.txt)
set /a a=%a%+1
if %a%==255 exit
goto :start

利用goto变成一个do...while的结构,而且巧妙的用172.19.5.%a%避免了之前的字符串拼接,最后还能把ping成功的ip写入txt

自己粘过来试,仍不成功...因为明明在我的电脑上172.19.5.x都是ping不通的,却也全部写入了txt文件

再次查看ping命令:发现-w为等待每次回复的超时时间(毫秒),这里设置为1,-n为要发送的回显请求数,而find,作者是想找到Lost=1的ping不通的,但是我的是中文...,设为丢失=1就好了:

1
数据包:已发送 = 1,已接受 = 0, 丢失 = 1 (100%丢失)

完美!

还有一种方式,之前看到设置超时为1,觉得是超时太断所致,即使是ping通的也会被判断为错误的,就直接改成,去掉find,并需要把判断条件改成%c%==1

ping 192.168.101.%a% -n 1 && set c=1 || set c=0
if %c%==1 (echo 192.168.101.%a% >>IP.txt)

这样也是可行的,但是每次都会输出一大堆的ping的回显消息了

其实过程中还有,各种报ECHO处于关闭状态的错误等,从下午到晚上,也算是第一次知道了shell编程是什么,以及借此好像阴差阳错的懂了一点点linux的强大

最后希望自己慢慢的不要那么菜吧哈哈

还有部分的参考连接:

.bat批处理(三):变量声明、设置、拼接、截取

BAT批处理为何报错echo处于关闭状态?

易百教程-批处理简介

网文记录_Winter 直播笔记

来自:计算机之子 Winter 直播笔记及我的学习方法论

  1. 对于框架的使用没必要花太多时间,应该多研究一下三大框架背后的设计思想
  2. 当一个程序员对算法、语言标准、底层、原生、英文文档这些词汇产生恐惧感的时候他的技术生命已经走到尽头。
  3. 前端架构主要解决的是高复用性,架构能力提升方向主要是组件库开发、前端框架实现等。
  4. 对于前端进阶这个问题,其实看书的作用和意义已经不太明显,需要寻找好的平台和合适的项目,在项目中不断克服难题并挑战自己,遇到问题再去查资料总结。如果只是闭门看书那很难成为高手,书只是基础而已,真正的应用还是在项目中。
  5. 寒冬中能做的只有提升自己,但是光靠技术是不行的。
  6. 推荐 TensorFlow、可视化切图、PWA、WebGL

Jeff Bezos' speech to Princeton

Tomorrow, in a very real sense, your life -- the life you another from scrath on your own --begins

How will you use your gifts? What choices will you make? What inertia be your guide, or Will you follow your passions?

Will you follow dogma, or Will you be original? Will you choose a life of ease, or a life of service and adventure?

Will you wilt under criticism, or Will you follow yoour convictions?

Will you bluff it out when you're wrong, or Will you apologize?

Will you guard your heart against rejection, or Will you act when you fall in love?

Will you play it safe, or Will you be a little bit swashbuckling?

When it's tough, Will you give up, or Will you be relentless?

Will you be a cynic, or Will you be a builder?

Will you be clever at the expense of others, or Will you be kind?

I will hazard a prediction, when you are 80 years old, and in a quiet moment of reflection narrating for only yourself, the most personal version of your life story.

the telling that will be most compact and meaningful will be the series of choices you have made,

in the end, We are our choices, Build yourself a great story.

TK-answer:

艺无止境,功不唐捐