GithubHelp home page GithubHelp logo

wscats / node-tutorial Goto Github PK

View Code? Open in Web Editor NEW
498.0 28.0 110.0 49.62 MB

:relaxed:Some of the node tutorial -《Node学习笔记》

Home Page: https://github.com/Wscats/node-tutorial

HTML 15.32% JavaScript 79.31% PHP 0.05% CSS 5.00% Vue 0.27% EJS 0.05%
node-tutorial nodejs node express http koa redis socket-io mitm cheerio

node-tutorial's Introduction

node-tutorial's People

Contributors

dependabot[bot] avatar wscats avatar yaoone avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

node-tutorial's Issues

node驱动终端命令

var exec = require('child_process').exec;
var cmdStr = 'cat test.js';
exec(cmdStr, function (err, stdout, stderr) {
    if (err) {
        console.log('error:' + stderr);
        return;
    } else {
        //var data = JSON.parse(stdout);
        console.log(stdout);
    }
});

JS调用摄像头和桌面通知

摄像头

<video id="camera" autoplay> </video>
<script>
	navigator.mediaDevices.getUserMedia({
			video: true
		})
		.then(function(stream) {
			console.log(stream);
			document.getElementById('camera').src = URL.createObjectURL(stream);
		}).catch(function() {
			alert('could not connect stream');
		});
</script>

将视频绘制到canvas画布上

<video id="camera" autoplay> </video>
<canvas id="canvas"></canvas>
<script>
	var video = document.querySelector('video');
	var canvas = document.getElementById('canvas');
	var context = canvas.getContext('2d');
	navigator.mediaDevices.getUserMedia({
			video: true
		})
		.then(function(stream) {
			console.log(stream);
			document.getElementById('camera').src = URL.createObjectURL(stream);
		}).catch(function() {
			alert('could not connect stream');
		});
	//将视频帧绘制到Canvas对象上,Canvas每60ms切换帧,形成肉眼视频效果
	function drawVideoAtCanvas(video, context) {
		window.setInterval(function() {
			context.drawImage(video, 0, 0, 90, 120);
		}, 60);
	}
	drawVideoAtCanvas(video, context);
</script>

桌面通知

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
		<meta name="viewport" content="width=device-width">
		<title>HTML5 Web Notification基本效果演示</title>
		<style type="text/css">
			button {
				font-size: 100%;
				height: 2em;
			}
		</style>
	</head>
	<body>
		<p>
			<button id="button">点击出现提示</button>
		</p>
		<p id="result"></p>
		<script>
			if(window.Notification) {
				// 获得权限
				Notification.requestPermission();
				// 点击按钮
				document.querySelector('#button').addEventListener('click', function() {
					new Notification("Hi,帅哥:", {
						body: '可以加你为好友吗?',
						icon: 'https://avatars1.githubusercontent.com/u/17243165?s=460&v=4'
					});
				});
			} else {
				document.getElementById('result').innerHTML = '浏览器不支持Web Notification';
			}
		</script>
	</body>
</html>

直播配合canvas

  • 首先利用navigator.mediaDevices.getUserMedia获取视频源
  • 利用定时器window.setInterval,逐帧将context.drawImage(video, 0, 0, 640, 480)视频源绘制在canvas
  • 再利用canvas.toDataURL('image/png')canvas生成的图片配合drawImgAtCanvas再绘制到第二个canvas
var video = document.querySelector('video');
var canvas = document.querySelectorAll('canvas')[0];

var canvas2 = document.querySelectorAll('canvas')[1];
var context = canvas.getContext('2d');
var context2 = canvas2.getContext('2d');
// console.log(canvas, canvas2)
navigator.mediaDevices.getUserMedia({
    video: true
})
    .then(function (stream) {
        console.log(stream);
        // video.src = URL.createObjectURL(stream);
        video.srcObject = stream;
    })
    .catch(function () {
        alert('could not connect stream');
    });
//将视频帧绘制到Canvas对象上,Canvas每60ms切换帧,形成肉眼视频效果
// video转canvas
function drawVideoAtCanvas(video, context) {
    window.setInterval(function () {
        context.drawImage(video, 0, 0, 640, 480);
        // canvas转为img的地址data:base64
        var dataurl = canvas.toDataURL('image/png');
        drawImgAtCanvas(dataurl);
        console.log(dataurl);
    }, 60);
}
drawVideoAtCanvas(video, context);

// img转canvas
function drawImgAtCanvas(dataurl) {
    let img = new Image();
    img.onload = function () {
        context2.drawImage(img, 0, 0, 640, 480);
    };
    img.src = dataurl;
}

Charles进行https抓包

安装证书

打开 Charles 的菜单

Help—>SSL Proxying Setting—>Install Charles Root Certificate

image

这里注意新版本的 IOS 还需要设置如下: 关于本机 -> 证书信任设置 -> 针对根证书启用完全信任 打开 Charles Proxy Ca 的证书

image

手机安装证书

在手机浏览器打开以下链接

charlesproxy.com/getssl

image

如果是whistle,配置代理之后,由于无法扫描二维码下载证书

所以用Safari打开网址http://rootca.pro下载rootca证书进行安装,并通过上面的证书认证即可进行代理

Proxy设置

把下面几个勾上就可以了
image

Proxy设置

image

Mac

如果是 Mac 系统,还需要在钥匙串里面设置证书的如下设置
2018-09-27 7 26 45
2018-09-27 7 28 45

劫持JS

启用Tools > Map Local

手机设置代理

通过以上步骤即可抓取 https 包了,手机不要忘记设置相应的代理

IOS Webview 手机调试

  • 【Mac】使用 Safari 浏览器的“开发”菜单

请选取Safari 浏览器>偏好设置,点按高级,然后选择“在菜单栏中显示开发菜单”。

  • 【iPhone】开启调试模式

要远程调试 IOS Safari ,必须启用 Web 检查 功能,打开 iPhone 依次进入 设置 > Safari > 高级 > Web 检查 > 启用。

IMG_0193

iPhone 链接到 mac 上,打开 Safari 浏览器,运行手机 app 里面的 web 页面,在 开发 菜单中选择连接的手机,找到调试的网页,就能在 Safari 里面调试了

截屏2020-03-18下午2 51 37

如果 Safari 浏览器在 开发 中找不到模拟器选项,则可以关闭浏览器和模拟器重新多开几次尝试。

记得在手机安装 https 证书

截屏2020-03-18下午3 49 42

并且在全局设置代理,才可以抓包或者调试

截屏2020-03-18下午3 49 12

参考文档

node抓包工具AnyProxy

安装AnyProxy

全局安装AnyProxy模块

npm i -g anyproxy

image

配置

生成证书

anyproxy-ca

image

以代理https的方式启动

anyproxy -i

image

在浏览器打开链接

http://localhost:8002/

image

安装证书

配置代理

用的是Charles,具体可以参考网上其他文章,当然这个我后来也没配置,直接在手机的代理设置好PC的IP和默认的8001端口就可以了

PC端

本地双击直接安装rootCA.crt

C:\Users\Administrator\.anyproxy\certificates\rootCA.crt

手机端

扫描http://localhost:8002/界面下的RootCA二维码下载到手机

  • 在手机直接安装证书(但有可能提示:无法安装该证书 因为无法读取)
  • 打开设置->更多设置->系统安全->从存储设备(SD卡)安装->选择文件(有可能会提示警告,或者要你设置密码才能安装)

这里有一点要注意,如果是IOS10+,安装完证书之后要在设置->通用->关于本机->证书信任设置,打开对应的针对根证书启用完全信任,才可以抓包成功

定义规则

新建rule.js文件

module.exports = {
    summary: 'a rule to hack response',
    * beforeSendResponse(requestDetail, responseDetail) {
        if (requestDetail.url === 'http://httpbin.org/user-agent') {
            const newResponse = responseDetail.response;
            newResponse.body += '- AnyProxy Hacked! Wscats Test~';
            return new Promise((resolve, reject) => {
                setTimeout(() => { // delay
                    resolve({
                        response: newResponse
                    });
                }, 5000);
            });
        }
    },
};

在终端执行一下命令,在手机端打开http://httpbin.org/user-agent链接测试,代理https请求记得加上--intercept参数

anyproxy --intercept --rule rule.js

image

然后在浏览器打开链接http://localhost:8002/可以看到页面会有一下内容,证明配置规则成功

{
  "user-agent": "Mozilla/5.0 (Linux; U; Android 6.0.1; zh-cn; Redmi 4A Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.146 Mobile Safari/537.36 XiaoMi/MiuiBrowser/9.3.10"
}
- AnyProxy Hacked! Wscats Test~

参考文档

AnyProxy

TypeScript

安装

NPM全局安装TypeScript模块

npm install typescript -g

编译

TypeScript文件均以ts为后缀的文件,所以可以命名xxx.ts文件,并用命令行执行tsc xxx.tx

tsc xxx.ts

类型

TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用

布尔值

最基本的数据类型就是简单的true/false值,在JavaScript和TypeScript里叫做boolean

let bool: boolean = false;
//=>
var bool = false;

数字

和JavaScript一样,TypeScript里的所有数字都是浮点数。 这些浮点数的类型是 number。 除了支持十进制和十六进制字面量,TypeScript还支持ECMAScript 2015中引入的二进制和八进制字面量

let num: number = 9;
//=>
var num = 9;

字符串

JavaScript程序的另一项基本操作是处理网页或服务器端的文本数据。 像其它语言里一样,我们使用 string表示文本数据类型。 和JavaScript一样,可以使用双引号( ")或单引号(')表示字符串

let str: string = `Wscats ${bool?"is":"isn't"} ${num} years old`;
//=>
var str = "Wscats " + (bool ? "is" : "isn't") + " " + num + " years old";

数组

TypeScript像JavaScript一样可以操作数组元素。 有两种方式可以定义数组。 可以在元素类型后面接上 [],表示由此类型元素组成的一个数组

let arr: string[] = ["Oaoafly", "Corrine", "Eno"];
let arr: Array<number> = [1, 2, 3];
//=>
var arr = ["Oaoafly", "Corrine", "Eno"];

元组

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为 string和number类型的元组

let tuple: [string, number];
tuple = ['hello', 10];
//=>
var tuple;
tuple = ['hello', 10];

Any

有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any类型来标记这些变量

let notSure: any = 8;
let list: any[] = [false, 9, "Wscats"];
//=>
var notSure = 8;
var list = [false, 9, "Wscats"];

Void

某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void

function warning(): void {
    console.log("This is my warning message");
}
//=>
function warning() {
    console.log("This is my warning message");
}

声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined和null

let warning: void = undefined;

Null和Undefined

TypeScript里,undefined和null两者各自有自己的类型分别叫做undefined和null。 和void相似,它们的本身的类型用处不是很大

let u: undefined = undefined;
let n: null = null;

默认情况下null和undefined是所有类型的子类型。 就是说你可以把 null和undefined赋值给number类型的变量

Never

never类型表示的是那些永不存在的值的类型。 例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never类型,当它们被永不为真的类型保护所约束时

never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}

// 推断的返回值类型为never
function fail() {
    return error("Something failed");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {
    }
}

接口

类型检查器会查看getInfo的调用。 getInfo有一个参数,并要求这个对象参数有一个名为name类型为string的属性。 需要注意的是,我们传入的对象参数实际上会包含很多属性,但是编译器只会检查那些必需的属性是否存在,并且其类型是否匹配

interface objValue {
	name: string;
}

function getInfo(obj: objValue) {
	console.log(obj.name);
}

let myObj = {
	age: 10,
	name: "Wscats"
};
getInfo(myObj);

接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型

interface addValue {
	sum: number,
	good: string
}
function add(x: number, y: number, good: string): addValue {
	return {
		sum: x + y,
		good: good
	};
}
//=>相当于
function add(x: number, y: number, good: string): {
	sum: number,
	good: string
} {
	return {
		sum: x + y,
		good: good
	};
}
//=>相当于
interface Fn {
    (x: number, y: number, good: string): {
        sum: number,
        good: string
    };
}

const add: Fn = function (x, y, good) {
    return {
        sum: x + y,
        good: good
    };
}

接口还可以继承

class Control {
    age: number;
}
interface AnimalType extends Control {
    name: String;
    eat(food: String);
}
// 或者
interface PersonType {
    height: number
}
interface AnimalType extends PersonType {
    name: String;
    eat(food: String);
}
const god: AnimalType = {
    name: 'god',
    height: 18,
    eat() { }
}

传统的JavaScript程序使用函数和基于原型的继承来创建可重用的组件,但对于熟悉使用面向对象方式的程序员来讲就有些棘手,因为他们用的是基于类的继承并且对象是由类构建出来的。 从ECMAScript 2015,也就是ECMAScript 6开始,JavaScript程序员将能够使用基于类的面向对象的方式。 使用TypeScript,我们允许开发者现在就使用这些特性,并且编译后的JavaScript可以在所有主流浏览器和平台上运行,而不需要等到下个JavaScript版本

class Greeter {
	greeting: string;
	constructor(message: string) {
		this.greeting = message;
	}
	greet(): string {
		return "Hello, " + this.greeting;
	}
}
let greeter = new Greeter("world");
//=>
var Greeter = /** @class */ (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
}());
var greeter = new Greeter("world");

类类型 && implements

配合interfaceclass的类型进行约束

// implements 就是:实现的意思。
// 类实现接口本质上也是一样的,即类遵循接口的约束,接口里面写了多少个函数、参数,实现的类里面也要写相同的函数、参数。
; (() => {
    interface AnimalType {
        name: String;
        eat(food: String);
    }
    // Cat类需要遵循AnimalType的类型
    class Cat implements AnimalType {
        constructor(name: string) {
            this.name = name
        }
        name: String;
        eat(food) {
            console.log(this.age)
            console.log(this.name + food);
        }
    }

    class Lion extends Cat implements AnimalType {
        constructor(name: string) {
            super(name)
        }
    }
    let Tom = new Cat('Tom');
    let King = new Lion('King');
    console.log(Tom.name);
    King.eat('Apple');
})()

函数

函数是JavaScript应用程序的基础。 它帮助你实现抽象层,模拟类,信息隐藏和模块。 在TypeScript里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义 行为的地方。 TypeScript为JavaScript函数添加了额外的功能,让我们可以更容易地使用

let f = (x: number, y: number): number => {
	console.log(1)
	return x + y
}
f(8, 9)
//=>
var f = function (x, y) {
    console.log(1);
    return x + y;
};
f(8, 9);

可选参数和默认参数

  • JavaScript里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是undefined。 在TypeScript里我们可以在参数名旁使用?实现可选参数的功能
  • 在TypeScript里,我们也可以为参数提供一个默认值当用户没有传递这个参数或传递的值是undefined时。 它们叫做有默认初始化值的参数
let f = (x: number, y: number, good ? : string, shop = "gz"): number => {
	console.log(good, shop)
	return x + y
}

f(8, 9, "mobile")
f(8, 9)
=>
var f = function (x, y, good, shop) {
    if (shop === void 0) { shop = "gz"; }
    console.log(good, shop);
    return x + y;
};
f(8, 9, "mobile");
f(8, 9);

接口也同样支持可选参数,但不支持默认参数

interface SquareConfig {
    color?: string;
    width?: number;
}

interface SquareResult {
    width?: number;
    color?: string;
    area?: number;
}

function createSquare(config: SquareConfig): SquareResult {
    return {
        color: config.color,
        area: 14
    }
}

let mySquare = createSquare({ color: "red" });

爬虫

fetch.js

var http = require("http");

// Utility function that downloads a URL and invokes
// callback with the data.
function download(url, callback) {
  http.get(url, function(res) {
    var data = "";
    res.on('data', function (chunk) {
      data += chunk;
    });
    res.on("end", function() {
      callback(data);
    });
  }).on("error", function() {
    callback(null);
  });
}

exports.download = download;

catch.js

安装choorio,抓取页面信息,引入上面写好的fetch模块

var cheerio = require("cheerio");
var server = require("./fetch");

var url = "http://image.baidu.com/search/index?tn=baiduimage&ps=1&ct=201326592&lm=-1&cl=2&nc=1&ie=utf-8&word=%E8%B6%B3%E7%90%83"

server.download(url, function(data) {
    if(data) {
        //console.log(data);
        var $ = cheerio.load(data);
        //调用 .each(function(index, element))函数来遍历每一个对象,返回的是HTML DOM Elements
        /*$("a").each(function(index, element) {
            console.log("第" + index + "个:" + $(element).attr("href"));
        });*/
        $("img").each(function(index,element){
            console.log($(element));
        });
        /*$("p").each(function(index, element) {
            console.log("第" + index + "个:" + $(element).val());
        });*/
        console.log("检索完毕");
    } else {
        console.log("检索出错");
    }
});

downloadImg.js

获取地址后下载图片

var http = require("http");
var fs = require("fs");
var server = http.createServer(function(req, res) {}).listen(50082);
console.log("http start");
var url = "http://s0.hao123img.com/res/img/logo/logonew.png";
http.get(url, function(res) {
    var imgData = "";
    res.setEncoding("binary"); //一定要设置response的编码为binary否则会下载下来的图片打不开
    res.on("data", function(chunk) {
        imgData += chunk;
    });
    res.on("end", function() {
        fs.writeFile("./logonew.png", imgData, "binary", function(err) {
            if(err) {
                console.log("down fail");
            }
            console.log("down success");
        });
    });
});

node的wifi模块

创建配置文件

导出已有配置文件,会显示你曾经输入过的WIFI密码的配置文件

# clear表示以明文方式显示密码
netsh wlan export profile key=clear
<?xml version="1.0"?>
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
    <name>10086</name>
    <SSIDConfig>
        <SSID>
            <!-- <hex>3130303836</hex> -->
            <name>10086</name>
        </SSID>
    </SSIDConfig>
    <connectionType>ESS</connectionType>
    <connectionMode>auto</connectionMode>
    <autoSwitch>true</autoSwitch>
    <MSM>
        <security>
            <authEncryption>
                <!-- WPA2PSK,WPAPSK,open -->
                <authentication>WPA2PSK</authentication>
                <!-- AES,TKIP,WEP -->
                <encryption>AES</encryption>
                <useOneX>false</useOneX>
            </authEncryption>
            <sharedKey>
                <keyType>passPhrase</keyType>
                <protected>false</protected>
                <keyMaterial>{password}</keyMaterial>
            </sharedKey>
        </security>
    </MSM>
</WLANProfile>

name和SSID可以不同(最好设为一致)

  • name是配置文件名称
  • SSID是要连接的wifi名

connectionMode可以为手动连接的manual或者自动连接的auto;
keyMaterial处填写密码,无密码状态如下:

<security>
    <authEncryption>
        <authentication>open</authentication>
        <encryption>none</encryption>
        <useOneX>false</useOneX>
    </authEncryption>
</security>

检查配置文件是否已经存在:

netsh wlan show profile

删除配置文件:

netsh wlan delete profile name="10086"
netsh wlan delete profile name="Eno"

添加配置文件:

netsh wlan add profile filename="WLAN-10086.xml"

注意这里面参数是文件名,默认路径为当前目录,添加成功后提示:已将配置文件 10086 添加到接口 WLAN
可以在命令行运行一下命令扫描WiFi信息:

netsh wlan show networks mode=Bssid

进行连接:

netsh wlan connect name="10086"

重要的事情说三遍:这里的name是刚刚添加过的配置文件中的name,而不是配置文件名!
连接成功的提示信息为已成功完成连接请求。

配合Node

我们可以使用Node编写JS去实现连接,读取配置文件并连接

var execFile = require('child_process').execFile;
var env = Object.assign(process.env, {
    LANG: 'en_US.UTF-8',
    LC_ALL: 'en_US.UTF-8',
    LC_MESSAGES: 'en_US.UTF-8'
});
// 执行命令
function execCommand(cmd, params) {
    return new Promise(function (resolve, reject) {
        execFile(cmd, params, {
            env
        }, function (err, stdout, stderr) {
            if (err) {
                // Add command output to error, so it's easier to handle
                err.stdout = stdout;
                err.stderr = stderr;
                reject(err);
            } else {
                resolve(stdout);
            }
        });
    });
}

execCommand('netsh', [
    'wlan',
    'add',
    'profile',
    // 读取wifi配置文件
    'filename="nodeWifiConnect.xml"'
]);

常用netsh命令总结:

  • 列出配置文件:netsh wlan show profile
  • 导出配置文件:netsh wlan export profile key=clear
  • 删除配置文件:netsh wlan delete profile name=""
  • 添加配置文件:netsh wlan add profile filename=""
  • 连接wifi:netsh wlan connect name=""
  • 列出接口:netsh wlan show interface
  • 开启接口:netsh interface set interface "Interface Name" enabled
  • 列出所有可连接wifi详细信息:netsh wlan show networks mode=bssid

node-wifi

var wifi = require('node-wifi');
 
// Initialize wifi module
// Absolutely necessary even to set interface to null
wifi.init({
    iface : null // network interface, choose a random wifi interface if set to null
});
 
// Scan networks
wifi.scan(function(err, networks) {
    if (err) {
        console.log(err);
    } else {
        console.log(networks);
        /*
        networks = [
            {
              ssid: '...',
              bssid: '...',
              mac: '...', // equals to bssid (for retrocompatibility)
              channel: <number>,
              frequency: <number>, // in MHz
              signal_level: <number>, // in dB
              security: 'WPA WPA2' // format depending on locale for open networks in Windows
              security_flags: '...' // encryption protocols (format currently depending of the OS)
              mode: '...' // network mode like Infra (format currently depending of the OS)
            },
            ...
        ];
        */
    }
});

// Connect to a network
wifi.connect({ ssid : "10086", password : "123456"}, function(err) {
    if (err) {
        console.log(err);
    }
    console.log('Connected');
});

参考文档

express,koa和egg的配置

Express

安装

可以参考Express官方文档
首先express环境

npm install express

image
编写配置文件index.js,并执行

node index/node index.js

处理请求

处理GET请求:配合req.query
处理POST请求:需要body-parser模块,配合req.body

GET POST JSONP COOKIE
req.query req.body req.query req.cookies
//npm install express
var express = require('express');
//npm install body-parser
var bodyParser = require("body-parser");
var app = express();
//配置静态文件夹,在本地public读取css,js,html等文件
app.use(express.static('public'));
//post请求需要body-parser模块处理
app.use(bodyParser.urlencoded({
	extended: false
}));
app.get('/', function(req, res) {
	res.send('Hello World!');
});
app.get('/home', function(req, res) {
	//get请求参数对象
	console.log('get请求参数对象:', req.query);
	res.send('get请求');
});
app.post('/home', function(req, res) {
	//post请求参数对象
	console.log('post请求参数对象:', req.body);
	res.send('post请求');
});
var server = app.listen(3000, function() {
	var host = server.address().address;
	var port = server.address().port;
	console.log('Example app listening at http://%s:%s', host, port);
});

匹配路由参数

app.get('/add/:id/:age', function(req, res) {
	//追加请求头
	res.append("Access-Control-Allow-Origin","*");
	//?id=xx&age=xxx
	console.log(req.query)
	//:id/:age
	console.log(req.params)
	res.send("Hello Oaoafly");
})

跨域

可在中间件中追加这句防止跨域

res.append("Access-Control-Allow-Origin","*");

模板文件

这个设置视图文件的放置地方,然后配置jade为其模板渲染引擎,这里也需要安装jade模块实现

//views, 放模板文件的目录,比如: 
app.set('views', './views')
//view engine, 模板引擎
app.set('view engine', 'jade')

然后安装对应的模板引擎npm包

npm install jade

然后创建一个views文件夹,并在里面新建一个xxxx.jade文件,内容如下

html
	head
	body
		h1 这是测试
		p 你好
			ul.hhh#ddd
				for n in news
					li=n.title

在中间件中添加如下关键代码,res.render("文件名可省略后缀",{需要渲染在模板上的数据})

app.get('/', function(req, res) {
	connection.query("select * from news",function(err,data){
	var content = "Hello Oaoafly";
	res.render("qianfeng",{
		//model
		name:'xie',
		name2:'lan',
		news:data
	    })
	})
	//res.send("<p style='color:red'>"+content+"</p>");
})

静态文件

Express提供了内置的中间件express.static来设置静态文件如:图片, CSS,JavaScript等

你可以使用express.static中间件来设置静态文件路径

例如,如果你将图片, CSS,JavaScript文件放在public目录下

app.js根目录下创建一个public文件夹,然后在代码中添加

app.use(express.static('public'));

设置完静态文件夹后我们可以用res.sendFile(文件路径)方法来把文件发送到前端

注意路径要用绝对路径__dirname + "/public/" + "upload.html"

app.get('/index.html', function (req, res) {
   res.sendFile(__dirname + "/public" + "index.html");
})

还有值得注意的一点就是,对于每个应用程序,可以有多个静态目录,比如你可以按上传的文件类型分目录,当我们找某张图片的时候就会从这几个静态文件夹中一起找取

app.use(express.static('public'));
app.use(express.static('uploads'));
app.use(express.static('files'));

连接数据库

连接数据库,可以安装mysql模块实现

var mysql = require("mysql");
var connection = mysql.createConnection({
		host: "localhost",
		user: "root",
		password: "",
		database: "asm"
})
//执行数据库连接 .close();
connection.connect();
app.post('/add', function(req, res) {
	//追加请求头
	res.append("Access-Control-Allow-Origin","*");
	console.log(req.body);
	connection.query("insert into news (title,text) values ('" + req.body.title + "','" + req.body.text + "')",function(err,data){
		console.log(data)
	})
	res.send("增加信息");
	
})

body-parser

npm install body-parser

然后通过app.use()方法调用

var express = require('express')
var bodyParser = require('body-parser')
var app = express()
// parse application/x-www-form-urlencoded 
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json 
app.use(bodyParser.json())

cookie-parser

npm install cookie-parser

通过app.use()方法调用

var cookieParser = require('cookie-parser')
app.use(cookieParser())

然后在中间件中通过req.cookies获取前端页面的cookie,是一个通过处理的对象

module description
querystring 将GET请求url中的字符串信息进行转换
chalk 把控制台输出信息的字符串颜色改变
body-parser 将客户端通过POST方法传过来的req.body数据解析成json数据
cookie-parser 处理cookie信息
svg-captcha 用来生成验证码
trek-captcha 用来生成验证码
emailjs 用来通过邮箱找回密码
validator 验证器
mongodb 连接mongodb数据库
crypto express自带的加密模块
express-session session的认证机制离不开cookie,需要同时使用cookieParser中间件,可以用来保存用户的登陆状态,免密码登陆
formidable 表单文件上传模块

上传文件

node上传文件

热启动

npm install supervisor -g

全局安装后,就会有supervisor命令,它会自动检测你的文件变化,一旦变化则会自动重启

supervisor app.js

过滤器

可以设置对路由的拦截,比如用在登录拦截等

filter.js

exports.authorize = function(req, res, next) {
	if(req.body.token) {
		//res.redirect('/beauty/test');
		console.log(1)
	} else {
		//res.redirect('/beauty/getFemaleList');
		console.log(2)
		next();
	}
}

路由逻辑

var express = require('express');
var router = express.Router(); //模块化
var filter = require('../filter.js');
router.get('/getFemaleList', filter.authorize, function(req, res) {
	res.send('hello world');
});

此时访问/getFemaleList路由的时候就会进入过滤器逻辑,从而实现拦截功能

ES6

要让Express在ES6下跑起来就不得不用转码器Babel了。首先新建一个在某目录下新建一个项目。然后跳转到这个目录下开始下面的操作

全局安装

npm install --save-dev babel-cli -g

然后,可以安装一些presets

cnpm install --save-dev babel-preset-es2015 babel-preset-stage-2

package.json里添加运行的脚本,这里就可以用ES6代码写程序,babel自动帮我们转ES5运行

"scripts": {
    "start": "babel-node index.js --presets es2015,stage-2"
}

可以用babel lib -d dist命令将router文件夹的所有js转码

"scripts": {
    "start": "babel-node --presets es2015,stage-2",
    "build": "babel router -d dist --presets es2015,stage-2",
     "serve": "node dist/index.js"
}

脚手架

全局安装

npm install -g express-generator@4

在一个文件夹里面用express命令创建应用架构

express test
cd test

进入test文件夹安装依赖,推荐cnpm安装所有依赖

npm install

启动应用

SET DEBUG=test:*
npm start

访问在浏览器3000端口号

http://localhost:3000

创建路由

进入到test目录的routes文件夹,然后复制users.js

你可以改变/home这里的路径

var express = require('express');
var router = express.Router();
router.get('/home', function(req, res, next) {
  res.send('hello world');
});
module.exports = router;

app.js添加以下两条,该路由就完成了

var homeRouter = require('./routes/home');
//code
app.use('/test', homeRouter);

访问该路径

http://localhost:3000/test/home

配合await和async

let db = require("../libs/db.js");
router.post('/findUser', async (req, res, next) => {
  let {
    id,
    name,
    skill
  } = req.body
  let data = await db.connect(`select * from students where ?`, [{
    id
  }])
  res.send(data);
});

Koa

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。——引用Koa的官方文档的原话

所以Koa和Express框架其实很像,个人感觉Koa更轻量

安装

Koa依赖node v7.6.0ES2015及更高版本和async方法支持

npm i koa
node my-koa-app.js

安装完之后可以新建my-koa-app.js,然后写以下代码,就可以简单创建一个服务器

const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
  ctx.body = 'Hello World';
});
app.listen(3000);

处理请求和响应

Koa Contextnoderequestresponse对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法,一般将它简写为ctx

ctx.request; // 这是 koa Request
ctx.req; // 这是 node Request
// 注意:绕过 Koa 的 response 处理是 不被支持的. 应避免使用以下 node 属性:res.statusCode, res.writeHead(), res.write(), res.end()

res.statusCode
res.writeHead()
res.write()
res.end()
ctx.request

ctx.response; // 这是 koa Response
ctx.res; // 这是 node Response

区别于express框架,是在回调函数里面分开写requestresponse

为方便起见许多上下文的访问器和方法直接委托给它们的ctx.requestctx.response,不然的话它们是相同的。 例如ctx.typectx.length委托给response对象ctx.pathctx.method委托给request。所以ctx上面综合封装了多个requestresponse的方法

下面这个负责响应请求体的数据

ctx.response.body=
ctx.body= // 简写

将响应体设置为以下之一:

string 写入
Buffer 写入
Stream 管道
Object || Array JSON-字符串化
null 无内容响应

也就是说如果传递数组或者字符串它会自动调用JSON.stringify()来序列化数据,并且response.status如未被设置, Koa将会自动设置状态为200204

Context

GET POST JSONP COOKIE
ctx.query ctx.request.body ctx.query ctx.cookies.get(name, [options])

注意post请求需要配合koa-bodyparser模块和x-www-form-urlencoded格式,如果是formdata 格式,可以用multer模块来解析

const bodyParser = require('koa-bodyparser'); // 需要先安装koa-bodyparser npm install koa-bodyparser
app.use(bodyParser());

Request别名
以下访问器和Request别名等效:

ctx.header
ctx.headers
ctx.method
ctx.method=
ctx.url
ctx.url=
ctx.originalUrl
ctx.origin
ctx.href
ctx.path
ctx.path=
ctx.query
ctx.query=
ctx.querystring
ctx.querystring=
ctx.host
ctx.hostname
ctx.fresh
ctx.stale
ctx.socket
ctx.protocol
ctx.secure
ctx.ip
ctx.ips
ctx.subdomains
ctx.is()
ctx.accepts()
ctx.acceptsEncodings()
ctx.acceptsCharsets()
ctx.acceptsLanguages()
ctx.get()

Response别名
以下访问器和Response别名等效:

ctx.body
ctx.body=
ctx.status
ctx.status=
ctx.message
ctx.message=
ctx.length=
ctx.length
ctx.type=
ctx.type
ctx.headerSent
ctx.redirect()
ctx.attachment()
ctx.set()
ctx.append()
ctx.remove()
ctx.lastModified=
ctx.etag=

Egg

安装

直接使用脚手架,可快速生成项目文件夹

npm i egg-init -g
egg-init egg-example --type=simple
cd egg-example
npm i

控制器

第一步需要编写的ControllerRouter

// app/controller/home.js(编写文件的位置)
const Controller = require('egg').Controller;
class HomeController extends Controller {
  async index() {
    this.ctx.body = 'Hello world';
  }
}
module.exports = HomeController;

配置路由映射

// app/router.js(编写文件的位置)
module.exports = app => {
  const { router, controller } = app;
  router.get('/', controller.home.index);
};

静态资源

Egg 内置了 static 插件,线上环境建议部署到 CDN,无需该插件
static 插件默认映射/public/* -> app/public/*目录
此处,我们把静态资源都放到app/public目录即可

跨域

config/config.default.js添加以下代码

config.security = {
  csrf: {
    enable: false,
    ignoreJSON: true, // 默认为 false,当设置为 true 时,将会放过所有 content-type 为 `application/json` 的请求
  },
  domainWhiteList: [ 'http://localhost:8000' ],
};
config.cors = {
  allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS',
};

并且在plugin.js添加以下代码

exports.cors = {
  enable: true,
  package: 'egg-cors',
};

服务

简单来说,Service 就是在复杂业务场景下用于做业务逻辑封装的一个抽象层,提供这个抽象有以下几个好处:

  • 保持 Controller 中的逻辑更加简洁。
  • 保持业务逻辑的独立性,抽象出来的 Service 可以被多个 Controller 重复调用。
  • 将逻辑和展现分离,更容易编写测试用例,测试用例的编写具体可以查看这里

所以我们可以把操作数据库的逻辑放在 Service 层

定义 Service 文件

// app/service/user.js
const Service = require('egg').Service;
class UserService extends Service {
  // 默认不需要提供构造函数。
  // constructor(ctx) {
  //   super(ctx); 如果需要在构造函数做一些处理,一定要有这句话,才能保证后面 `this.ctx`的使用。
  //   // 就可以直接通过 this.ctx 获取 ctx 了
  //   // 还可以直接通过 this.app 获取 app 了
  // }
  async find(uid) {
    // 假如 我们拿到用户 id 从数据库获取用户详细信息
    const user = await this.ctx.db.query('select * from user where uid = ?', uid);
    // 假定这里还有一些复杂的计算,然后返回需要的信息。
    const picture = await this.getPicture(uid);
    return {
      name: user.user_name,
      age: user.age,
      picture,
    };
  }
  async getPicture(uid) {
    const result = await this.ctx.curl(`http://photoserver/uid=${uid}`, { dataType: 'json' });
    return result.data;
  }
}
module.exports = UserService;

我们就可以在 Controller 层用this.ctx.service.服务名xxx.方法xxx来调用服务里面封装好的方法

// app/router.js
module.exports = app => {
  app.router.get('/user/:id', app.controller.user.info);
};
// app/controller/user.js
const Controller = require('egg').Controller;
class UserController extends Controller {
  async info() {
    const { ctx } = this;
    const userId = ctx.params.id;
    const userInfo = await ctx.service.user.find(userId);
    ctx.body = userInfo;
  }
}
module.exports = UserController;

服务器代理

可以使用curl来代替第三方request模块,或者内置的http.request模块来实现服务器代理通讯

class HomeController extends Controller {
  async news() {
    // 今日头条
    const { ctx } = this;
    const {
      data,
    } = await ctx.curl('https://m.toutiao.com/list/?tag=video&ac=wap&count=20&format=json_raw&as=A1457C764A41F74&cp=5C6AC19F07943E1&min_behot_time=0&_signature=1Y7F0AAAieymeM-.Mi2uANWOxc&i=', {
      dataType: 'json',
    });
    ctx.body = data;
  }
}

node的http.request和http.get方法

http.request

方法说明:

函数的功能室作为客户端向HTTP服务器发起请求。

语法:

http.get(options, callback)

由于该方法属于http模块,使用前需要引入http模块

var http= require("http") 

接收参数:

option 数组对象,包含以下参数
host 表示请求网站的域名或IP地址(请求的地址)。 默认为'localhost'
hostname 服务器名称,主机名是首选的值
port 请求网站的端口,默认为 80
localAddress 建立网络连接的本地
socketPath Unix Domain Socket(Domain套接字路径)
method HTTP请求方法,默认是 ‘GET'
path 请求的相对于根的路径,默认是'/'。QueryString应该包含在其中。例如:/index.html?page=12
headers 请求头对象
auth Basic认证(基本身份验证),这个值将被计算成请求头中的 Authorization 部分
callback 回调,传递一个参数,为 http.ClientResponse的实例。http.request 返回一个http.ClientRequest 的实例

例如我们可以实现一个机器人接口的代理

var http = require("http");
//http://www.tuling123.com/openapi/api?key=c75ba576f50ddaa5fd2a87615d144ecf&info=%E8%AE%B2%E4%B8%AA%E7%AC%91%E8%AF%9D
http.request({
	hostname: 'www.tuling123.com',
	port: '80',
	path: '/openapi/api?key=c75ba576f50ddaa5fd2a87615d144ecf&info=%E8%AE%B2%E4%B8%AA%E7%AC%91%E8%AF%9D',
	method: 'GET'
}, function(res) {
	res.setEncoding('utf8');
	var data = "";
	res.on('data', function(chunk){
		data += chunk
	});
	res.on('end', function(){
		console.log(data);
	});
}).on('error', function(e) {
	console.log('problem with request: ' + e.message);
}).end();

http.get

url为需要代理获取的页面链接,因为页面信息是一个流形式返回,所以需要res.on监听接受

http.get(url, function(res) {
var data = "";
	res.on('data', function(chunk) {
		data += chunk
	})
	res.on('end', function() {
		console.log(data)
	})
})

中间件代理

比如下面这段代码,我们首先引入express框架,http模块(用来代理请求收发),fs模块(读写文件)

var express = require('express');
var http = require("http");
var fs = require("fs")
var app = express();

编写第一个中间件,接受前端发送给我们的信息并写入html片段到log.html文件中,然后通过http.request发送消息到php服务器处理,那此时我们可以让nodejs帮我们读写文件,过滤数据,生成日志,存入测试服数据库等,那php服务器就专门就获取和存储数据到数据库,就可以减轻php后端的部分压力

//中间层
app.get('/', function(req, res) {
	//nodejs专门写文件
	fs.readFile("log.html", function(err, data) {
		var content = `<tr>
			<th>${data.toString()}</th>
			<th>${req.query.name}</th>
			</tr>`;
		fs.writeFile("log.html", content, function(err) {

		})
	});
	//通信php
	//php专门存数据库
	http.request({
		hostname: 'localhost',
		port: '81',
		path: '/1702/nodedemo/test.php?name=' + req.query.name,
		method: 'GET'
	}, function(res) {
		res.setEncoding('utf8');
		var data = "";
		res.on('data', function(chunk) {
			data += chunk
		});
		res.on('end', function() {
			console.log(data);
		});
	}).on('error', function(e) {
		console.log('problem with request: ' + e.message);
	}).end();
	res.send('Hello World');
})

通过/log直接打开log.html日志页面

app.get("/log", function(req, res) {
	fs.readFile("log.html", function(err, data) {
		res.send("<meta charset='utf-8' /><table>" + data + "</table>");
	})
})
app.listen(6789)

node的部署方案

pm2

安装pm2

npm install pm2 -g

2018-10-22 10 16 57

新建一份index.js测试,运行以下命令测试

pm2 start index.js

2018-10-22 10 26 21

运行

你可以执行以下命令来重启和暂停服务

pm2 stop     <app_name|id|'all'|json_conf>
pm2 restart  <app_name|id|'all'|json_conf>
pm2 delete   <app_name|id|'all'|json_conf>

比如pm2 stop index.js,暂停上面的index.js服务

2018-10-22 10 59 00

自动重启

当文件改动则自动重启服务

pm2 start app.js --watch

这里是监控整个项目的文件,如果只想监听指定文件和目录,建议通过下面配置文件的watch、ignore_watch字段来设置

配置文件

编写一份ecosystem.json文件,完整配置说明请参考官方文档

{
    "name": "test", // 应用名称
    "script": "./bin/www", // 实际启动脚本
    "cwd": "./", // 当前工作路径
    "watch": [ // 监控变化的目录,一旦变化,自动重启
        "bin",
        "routers"
    ],
    "ignore_watch": [ // 从监控目录中排除
        "node_modules",
        "logs",
        "public"
    ],
    "watch_options": {
        "followSymlinks": false
    },
    "max_memory_restart": "100M", //超过最大内存重启
    "error_file": "./logs/app-err.log", // 错误日志路径
    "out_file": "./logs/app-out.log", // 普通日志路径
    "env": {
        "NODE_ENV": "production" // 环境参数,当前指定为生产环境
    }
}

配置完后你可以执行以下命令

# Start all apps
pm2 start ecosystem.json

# Stop
pm2 stop ecosystem.json

# Restart
pm2 start ecosystem.json
## Or
pm2 restart ecosystem.json

# Reload
pm2 reload ecosystem.json

# Delete from PM2
pm2 delete ecosystem.json

这里注意的是配置文件改变了之后要先deletestart配置文件才能生效

2018-10-22 5 37 49

负载均衡

命令如下,表示开启三个进程。如果-i 0,则会根据机器当前核数自动开启尽可能多的进程

pm2 start app.js -i 3      //开启三个进程
pm2 start app.js -i max //根据机器CPU核数,开启对应数目的进程

日志查看

除了可以打开日志文件查看日志外,还可以通过pm2 logs来查看实时日志。这点对于线上问题排查非常重要

比如某个node服务突然异常重启了,那么可以通过pm2提供的日志工具来查看实时日志,看是不是脚本出错之类导致的异常重启。

pm2 logs

内存使用超过上限自动重启

如果想要你的应用,在超过使用内存上限后自动重启,那么可以加上--max-memory-restart参数。(有对应的配置项)

pm2 start big-array.js --max-memory-restart 20M

参考文档

node实现反向代理

安装

安装http-proxy-middleware模块

npm install http-proxy-middleware --save-dev

配置

当你请求localhost:1314/api的时候,会跳转到localhost:5000/api,由于配置了pathRewrite,则会处理连接跳到localhost:5000/

//反向代理
var proxy = require('http-proxy-middleware');
var options = {
	target: 'http://localhost:5000/', // 目标主机
	changeOrigin: true, // 需要虚拟主机站点
	pathRewrite: {
		'^/api/old-path': '/api/new-path', // rewrite path 
		'^/api': '/' // remove base path 把api的地址改为/
	},
};
var exampleProxy = proxy('/api', options); //开启代理功能,并加载配置
app.use('/api', exampleProxy); //对地址为/的请求全部转发
app.listen(1314)

参考文档

lodash

Lodash

Lodash是一套工具库,它内部封装了诸多对字符串、数组、对象等常见数据类型的处理函数,其中部分是目前ECMAScript尚未制定的规范,但同时被业界所认可的辅助函数

安装

浏览器端引用

<script src="lodash.js"></script>

Node.js服务端或者Webpack中的引入

//首先 npm i --save lodash
//引用模块
var _ = require('lodash');
//单独引用某一个模块
var array = require('lodash/array');
//单独引用某一个模块的方法
var chunk = require('lodash/array/chunk');

模块组成

Lodash 提供的辅助函数主要分为以下几类

模块 功能
Array 适用于数组类型,比如填充数据、查找元素、数组分片等操作
Collection 适用于数组和对象类型,部分适用于字符串,比如分组、查找、过滤等操作
Function 适用于函数类型,比如节流、延迟、缓存、设置钩子等操作
Lang 普遍适用于各种类型,常用于执行类型判断和类型转换
Math 适用于数值类型,常用于执行数学运算
Number 适用于生成随机数,比较数值与数值区间的关系
Object 适用于对象类型,常用于对象的创建、扩展、类型转换、检索、集合等操作
Seq 常用于创建链式调用,提高执行性能(惰性计算)
String 适用于字符串类型

参考文档

lodash4.x中文文档
lodash3.x中文文档

脚本

假设日期是2013年6月5日早上6:00:00

setTimeout(function () { 
    $('.chatSend')[0].click();
 },(new Date(2013,5,4,5,0,0)-new Date()));

循环发送(每隔3秒发送一次):

setInterval(function(){$('.edit_area').html('需要发送的文字');
   $(".edit_area").trigger($.Event("keydown", { keyCode: 13,ctrlKey: true}));
   $('.btn_send').click();},3000);

定时发送(需注意日期格式)

setInterval(function(){
          if(new Date().toLocaleString().indexOf('2015/8/31 下午4:02:00')===0) 
             {$('.edit_area').html('需要发送的文字');
              $(".edit_area").trigger($.Event("keydown", { keyCode: 13,ctrlKey: true}));
              $('.btn_send').click();}},1000);

huajiao

setInterval(function(){
	$(".tt-type-msg").val("hello")
	$(".tt-type-submit").click()
},3000)

github

var i = 0;
for(;i<document.querySelectorAll("[aria-label='Star this repository']").length;i++){
	document.querySelectorAll("[aria-label='Star this repository']")[i].click()
}

获取评论并且去除空格

$($(".tt-msg-content-h5.tt-msg-content-h5-chat")[$(".tt-msg-content-h5.tt-msg-content-h5-chat").length-1]).text().replace(/(^\s*)|(\s*$)/g, "");
console.log("comments:" + $($(".tt-msg-content-h5.tt-msg-content-h5-chat")[$(".tt-msg-content-h5.tt-msg-content-h5-chat").length - 1]).text().replace(/(^\s*)|(\s*$)/g, ""))
		$.ajax({
			type: "GET",
			url: "http://localhost:81/angular/0317/turing.php",
			data: {
				qu: $($(".tt-msg-content-h5.tt-msg-content-h5-chat")[$(".tt-msg-content-h5.tt-msg-content-h5-chat").length - 1]).text().replace(/(^\s*)|(\s*$)/g, "")
			},
			success: function(data) {
				$(".tt-type-msg").val(JSON.parse(data).text)
			}
		})

终极版

console.log("comments:" + $($(".tt-msg-content-h5.tt-msg-content-h5-chat")[$(".tt-msg-content-h5.tt-msg-content-h5-chat").length - 1]).text().replace(/(^\s*)|(\s*$)/g, ""))
		$.ajax({
			type: "GET",
			url: "http://www.tuling123.com/openapi/api?key=c75ba576f50ddaa5fd2a87615d144ecf&info="+$($(".tt-msg-content-h5.tt-msg-content-h5-chat")[$(".tt-msg-content-h5.tt-msg-content-h5-chat").length - 1]).text().replace(/(^\s*)|(\s*$)/g, ""),
			success: function(data) {
                                console.log(data)
				$(".tt-type-msg").val(data.text)
				//$(".tt-type-submit").click()
			}
		})

chrome插件自动测评核心文件

let stu = {
	userid: "xxx",
	password: "xxx",
	commit: ["666", "特别帅", "特别厉害", "非常棒", "无与伦比", "亮瞎双眼", "特别厉害", "暂时没有", "nice", "知识面广,课余知识和课堂知识很丰富", "受益匪浅,值得钦佩", "good", "教课之外也教了很多工作经验", "优秀", "段子手", "通俗易懂", "生动活泼", "比较幽默", "讲话太快了", "再接再厉", "完美", "负责任,耐心讲解", "出其不意", "大神级别"]
}
let host = "";
let href = location.href.indexOf("?") ? location.href.split("?")[0] : location.href;

switch(href) {
	/*case `${host}/student.php/Public/login`:
		document.querySelector("[name='Account']").value = stu.userid;
		document.querySelector("[name='PassWord']").value = stu.password;
		setTimeout(() => {
			document.querySelector("[type='submit']").click();
		}, 500);
		break;*/
	case `${host}/student.php/Index/index`:
		location.href = `${host}/student.php/Index/evaluate`;
	case `${host}/student.php/Index/evaluate`:
		setTimeout(() => {
			document.querySelector("[class='btn btn-xs btn-success']").click()
		})
		break;
	case `${host}/student.php/Index/start_evaluate`:
		let i = 0;
		let inputs = document.querySelectorAll("input");
		for(; i < inputs.length;) {
			document.querySelectorAll("input")[i].click();
			i += 4;
		}
		document.querySelectorAll("textarea")[0].value = stu.commit[Math.floor(Math.random() * stu.commit.length)];
		document.querySelectorAll("textarea")[1].value = stu.commit[Math.floor(Math.random() * stu.commit.length)];
		document.getElementById("addstudent").click();
}

百度爬图

kapture 2018-12-20 at 10 11 33

let y = 0;
let num = 0;
let imgArr = [];
setInterval(()=>{
	let imgs = document.querySelectorAll("img");
	let length = imgs.length;
	if(num!==length){
		num = length;
		imgArr = imgs;
		console.log(length,imgArr);
	}
	y = y + 1;
	scrollTo(0,y);
},1)

微信自动回复

wechat

var num = 0;
setInterval(function(){
	// 监听每一条新的聊天记录
	var messages = document.querySelectorAll(".js_message_plain")
	// 获取信息的长度
	var length = messages.length
	// 这个if里面的逻辑是有新消息我才进行回复
	if(num!=length){
		//num和length不一样的话,打印新的消息
		console.log(messages[length-1].innerHTML)
		if(messages[length-1].innerHTML=='你好'){
			var appElement = document.querySelector('[ng-controller=chatSenderController]');
			var $scope = angular.element(appElement).scope();
			$scope.editAreaCtn = "你也好啊";
			$scope.sendTextMessage();
		}else if(messages[length-1].innerHTML=='今晚搞不搞'){
			var appElement = document.querySelector('[ng-controller=chatSenderController]');
			var $scope = angular.element(appElement).scope();
			$scope.editAreaCtn = "搞啊";
			$scope.sendTextMessage();
		}
		num = document.querySelectorAll(".js_message_plain").length
	}
},1000)

监听浏览器键盘事件

全局监听

document.onkeyup = function (e) {
    console.log(e.keyCode)
    e = e || window.event;
    e.preventDefault();
    console.log(e.keyCode);
    switch (e.keyCode) {
        case 38:
            console.log('上键');
            break;
        case 40:
            console.log('下键');
            break;
    }
}

监听页面所有的输入框

console.log("监听所有input输入框")
// 获取所有输入框节点
let inputs = document.querySelectorAll("input");
// 遍历所有input节点
[].forEach.call(inputs, (input) => {
    input.addEventListener("input", (e) => {
        // 打印输入的值
        console.log(e.target)
        console.log(e.data)
    })
});

鼠标右击事件

document.onmousedown = function (event) {
    var event = event || window.event
    if (event.button == "2") {
        //这里做处理
        console.log("点解鼠标右键",event)
        event.returnValue = false;
    };
} 

vscode开发

安装code命令

打开VSC快捷键⇧⌘P打开命令行找到shell command选择Shell Command: Install 'code' command in PATH command安装

屏幕快照 2019-04-29 下午5 31 59

安装脚手架

执行完以下命令后,在VSC按F5,此时成功的话会打开一个新的调试窗口(扩展开发主机)

npm install -g yo generator-code
yo code
# ? What type of extension do you want to create? New Extension (TypeScript)
# ? What's the name of your extension? HelloWorld
### Press <Enter> to choose default for all options below ###

# ? What's the identifier of your extension? helloworld
# ? What's the description of your extension? LEAVE BLANK
# ? Enable stricter TypeScript checking in 'tsconfig.json'? Yes
# ? Setup linting using 'tslint'? Yes
# ? Initialize a git repository? Yes
# ? Which package manager to use? npm
code ./helloworld

Hello World

快捷键⇧⌘P打开命令行,执行Hello World

屏幕快照 2019-04-29 下午5 29 23

添加右键菜单和快捷键

打开package.json,按照下述方式添加:

extension.sayHello对应的是extension.js里面的方法

{
    "contributes": {
        "commands": [
            {
                "command": "extension.sayHello",
                "title": "Hello World"
            }
        ],
        // 快捷键绑定
        "keybindings": [
            {
                "command": "extension.sayHello",
                "key": "ctrl+f10",
                "mac": "cmd+f10",
                "when": "editorTextFocus"
            }
        ],
        // 设置菜单
        "menus": {
            "editor/context": [
                {
                    "when": "editorFocus",
                    "command": "extension.sayHello",
                    "group": "navigation"
                }
            ]
        }
    }
}

setting.json

Code->首选项设置->设置->文本编辑器->文件->Associations

{
    "git.ignoreMissingGitWarning": true,
    "explorer.confirmDelete": false,
    "javascript.updateImportsOnFileMove.enabled": "never",
    "files.associations": {
        "*.cjson": "jsonc",
        "*.wxss": "css",
        "*.wxs": "javascript",
        "*.omi": "html"
    }
}

打包 发布

无论是本地打包还是发布到应用市场都需要借助vsce这个工具

npm i vsce -g

打包成vsix文件

vsce package

登录marketplacejian上传

监听命令

var vscode = require('vscode');
function activate(context) {
    // when you click ctrl+s, fn will action
    vscode.workspace.onDidSaveTextDocument(function (document) {
        console.log(document)
    });
}
exports.activate = activate;

Webview

读取html需要vscode-resource配合

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';

/**
 * 从某个HTML文件读取能被Webview加载的HTML内容
 * @param {*} context 上下文
 * @param {*} templatePath 相对于插件根目录的html文件相对路径
 */
function getWebViewContent(context: vscode.ExtensionContext, templatePath: string) {
	const resourcePath = path.join(context.extensionPath, templatePath);
	const dirPath = path.dirname(resourcePath);
	let html = fs.readFileSync(resourcePath, 'utf-8');
	// vscode不支持直接加载本地资源,需要替换成其专有路径格式,这里只是简单的将样式和JS的路径替换
	html = html.replace(/(<link.+?href="|<script.+?src="|<img.+?src=")(.+?)"/g, (m, $1, $2) => {
		return $1 + vscode.Uri.file(path.resolve(dirPath, $2)).with({ scheme: 'vscode-resource' }).toString() + '"';
	});
	return html;
}

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {

	// Use the console to output diagnostic information (console.log) and errors (console.error)
	// This line of code will only be executed once when your extension is activated
	console.log('Congratulations, your extension "qf" is now active!');

	// The command has been defined in the package.json file
	// Now provide the implementation of the command with registerCommand
	// The commandId parameter must match the command field in package.json

	// 欢迎提示
	let disposable = vscode.commands.registerCommand('extension.qf', function () {
		// vscode.window.showInformationMessage('Hello World');
		const panel = vscode.window.createWebviewPanel(
			'testWelcome', // viewType
			"Welcome to Eno Snippets", // 视图标题
			vscode.ViewColumn.One, // 显示在编辑器的哪个部位
			{
				enableScripts: true, // 启用JS,默认禁用
				retainContextWhenHidden: true, // webview被隐藏时保持状态,避免被重置
			}
		);
		panel.webview.html = getWebViewContent(context, 'src/html/index.html');
	});
	context.subscriptions.push(disposable);
	// 如果设置里面开启了欢迎页显示,启动欢迎页
	const key = 'vscodePluginDemo.showTip';
	if (vscode.workspace.getConfiguration().get(key)) {
		vscode.commands.executeCommand('extension.qf');
	}
}

// this method is called when your extension is deactivated
export function deactivate() { }

配置参数

package.json配置文件如下:

"configuration": {
  "title": "Px to rem configuration",
  "properties": {
    "px-to-rem.px-per-rem": {
      "type": "integer",
      "default": 16,
      "description": "Number of pixels per 1rem."
    },
    "px-to-rem.only-change-first-ocurrence": {
      "type": "boolean",
      "default": false,
      "description": "Set value to only change first occurence of px/rem per selection."
    },
    "px-to-rem.notify-if-no-changes": {
      "type": "boolean",
      "default": true,
      "description": "Show a warning if no conversion could be made."
    },
    "px-to-rem.number-of-decimals-digits": {
      "type": "integer",
      "default": 4,
      "description": "Maximum number of decimals digits a px or rem can have."
    }
  }
}

在代码中获取默认的配置参数

const config = vscode.workspace.getConfiguration("px-to-rem");

FileSystemWatcher

用于监听文件是否发生了变化,可以监听到新建、更新、删除这 3 种事件,也可以选择忽略其中某个类型事件。创建watcher是利用vscode.workspace.createFileSystemWatcher: function createFileSystemWatcher(globPattern: GlobPattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): FileSystemWatcher;

例如监听所有js文件的变动:

const watcher = vscode.workspace.createFileSystemWatcher('*.js', false, false, false);
watcher.onDidChange(e => { // 文件发生更新
  console.log('js changed,' e.fsPath);
});
watcher.onDidCreate(e => { // 新建了js文件
  console.log('js created,' e.fsPath);
});
watcher.onDidDelete(e => { // 删除了js文件
  console.log('js deleted,' e.fsPath);
});

设置并持久保存token

https://gitee.com/g8up/vscode-gitee/blob/dev/src/tokens.ts

import { Memento } from 'vscode';

interface Tokens {
  [host: string]: {
    token: string;
  };
}

export function setToken(memento: Memento, provider: string, token: string): void {
  if (token) {
    const tokens = getToken(memento);
    tokens[provider] = {
      token,
    };
    memento.update('tokens', tokens);
  }
};

export function getToken(memento: Memento): Tokens {
  return memento.get<Tokens>('tokens', {});
}

export function removeToken(memento: Memento, provider: string): void {
  const tokens: Tokens | undefined = memento.get('tokens');
  if (tokens) {
    delete tokens[provider];
    memento.update('tokens', tokens);
  }
}

Chrome插件开发

5种类型的JS对比

Chrome插件的JS主要可以分为这5类:injected scriptcontent-scriptpopup jsbackground jsdevtools js

权限对比

JS种类 可访问的API DOM访问情况 JS访问情况 直接跨域
injected script 和普通JS无任何差别,不能访问任何扩展API 可以访问 可以访问 不可以
content script 只能访问 extension、runtime等部分API 可以访问 不可以 不可以
popup js 可访问绝大部分API,除了devtools系列 不可直接访问 不可以 可以
background js 可访问绝大部分API,除了devtools系列 不可直接访问 不可以 可以
devtools js 只能访问 devtools、extension、runtime等部分API 可以 可以 不可以

调试方式对比

JS类型 调试方式 图片说明
injected script 直接普通的F12即可 懒得截图
content-script 打开Console,如图切换
popup-js popup页面右键审查元素
background 插件管理页点击背景页即可
devtools-js 暂未找到有效方法 -

互相通信概览

注:-表示不存在或者无意义,或者待验证。

injected-script content-script popup-js background-js
injected-script - window.postMessage - -
content-script window.postMessage - chrome.runtime.sendMessage chrome.runtime.connect chrome.runtime.sendMessage chrome.runtime.connect
popup-js - chrome.tabs.sendMessage chrome.tabs.connect - chrome.extension. getBackgroundPage()
background-js - chrome.tabs.sendMessage chrome.tabs.connect chrome.extension.getViews -
devtools-js chrome.devtools. inspectedWindow.eval - chrome.runtime.sendMessage chrome.runtime.sendMessage

background和content_scripts的通信

方案1

因为background是插件一直运行的主程,而content_scripts是往内容页面注入的脚本,并且它拥有最多的权限(比如跨域,读写文件等),但是与内容页面只共享DOM,不能共享数据,所以我们需要配合background和content_scripts来做通信

background.js

chrome.contextMenus.create({
  "title": "启动", "type": "normal", //菜单项类型 "checkbox", "radio","separator"
  "onclick": function (info, tab) {
    chrome.tabs.executeScript(tab.id, {
      code: `
        // 插入JS脚本
    `}, () => {
        chrome.tabs.sendMessage(tab.id, {
          greeting: "hello to content script!"
        }, function (response) {
          console.log(response);
        });
      });
  }
});

content_scripts.js

chrome.extension.onMessage.addListener(
  function(request, sender, sendResponse) {
      console.log(request);// {greeting: "hello to content script!"}
      console.log("前端/后端/Popup收到");
      sendResponse("popup返回值");
  }
);

方案2

background.js

chrome.contextMenus.create({
  "title": "通信", "type": "normal", //菜单项类型 "checkbox", "radio","separator"
  "onclick": function (info, tab) {
    chrome.tabs.executeScript(tab.id, {
      code: `
        // 在隔离环境下清楚定时器
        window.postMessage({ type: "FROM_PAGE", text: "来自网页的问候!" }, "*");
      `})
  }, //单击时的处理函数
});

content_scripts.js

// 跟隔绝区域的JS通信
window.addEventListener("message", function (event) {
  // 我们只接受来自我们自己的消息
  if (event.source != window)
    return;

  if (event.data.type && (event.data.type == "FROM_PAGE")) {
    console.log("内容脚本接收到:" + event.data.text);
  }
}, false);

Tampermonkey

我们可以通过下载Tampermonkey实现插件功能,这是一个插件的管理工具,可以通过Greasyfork下载第三方脚本配合Tampermonkey实现一些功能

inject.js

在 content_scripts.js 可以写入以下函数来往页面注入 inject.js ,从而可以借 inject.js 之手直接操作 DOM 和获取 JS 因为 content_scripts.js 只能共享 DOM 不能共享 JS

// 向页面注入JS
function injectCustomJs(jsPath) {
  jsPath = jsPath || 'static/js/inject.js';
  var temp = document.createElement('script');
  temp.setAttribute('type', 'text/javascript');
  // 获得的地址类似:chrome-extension://ihcokhadfjfchaeagdoclpnjdiokfakg/js/inject.js
  temp.src = chrome.extension.getURL(jsPath);
  temp.onload = function () {
    // 放在页面不好看,执行完后移除掉
    this.parentNode.removeChild(this);
  };
  document.body.appendChild(temp);
}
injectCustomJs()

inject.js

console.log("监听所有input输入框")
// 获取所有输入框节点
let inputs = document.querySelectorAll("input");
// 遍历所有input节点
[].forEach.call(inputs, (input) => {
    input.addEventListener("input", (e) => {
        // 打印输入的值
        console.log(e.target)
        console.log(e.data)
    })
});

需要注意的是在 manifest.json 添加这句

{
    // 普通页面能够直接访问的插件资源列表,如果不设置是无法直接访问的
    "web_accessible_resources": ["static/js/inject.js"],
}

难点

页面为https协议时候发送http请求回产生跨域,所以可以尝试升级为https请求

// 将所有http请求升级为https请求
var meta = document.createElement("meta");
meta.httpEquiv = "Content-Security-Policy";
meta.content = "upgrade-insecure-requests";
document.head.appendChild(meta)

参考文档

关于Pornographic website的一些前端分析

讲解素材
网页如图,这里只是说明整个网站的一些技术点,所以不该看的地方我都打上马赛克了,让我们揭开这些网站的整个前端工作原理
image
首先刚进去的时候显示一堆乱七八糟的东西,点进去其中一个页面,下面各种虚假评论,然后每隔几秒钟弹出第几几几个会员充值成功,我们先把这个删掉
image
好了,我们继续研究这个网站内部结构了
正常情况每个页面的视频都是试看几十秒就会提醒你充值,当然这是这类网站的常规套路,我们在浏览器的Network观察加载的js,我们可以看到里面这段代码
image
这是一份叫做lsj.mp4.js的代码的其中一部分,我们可以看到里面有个关键的判断,具体就是判断你是否是会员,如果是那就根据类型是否是2返回play_url_arr_oumei数组和play_url_arr数组拼接一个url给你,如果你不是会员的话就进入试看的判断,很清晰的看到有一个url,那就是试看的短视频的路径

var isvip = getCookie('lsjyy');
if(isvip != undefined && isvip != ''  && isvip.match(rgExp)){}else{}

上面这两句还告诉我们第二个关键点就是我们可以根据isvip的cookie值来伪造身份,这个cookie就是保存到我们的本地浏览器上,所以如果我们能成功改写它的话我们就能变成会员绕过这个判断,这份代码已经封装好getCookie,所以我们可以在控制台直接调用来改写cookie值,但是问题来了,我们应该改成什么值才能绕过判断呢,我们继续看源代码搜索答案

function getMp4Url() {
        var play_name = getParam('play_name');
        var type = getParam('type');
        var rgExp =/^(v|V)[i|I|p|P|0-9]{2,3}$/g;
        var isvip = getCookie('lsjyy');
        var pic = $('.movie-pic img').attr('src');
        var html = '<img src="'+pic+'" style="height:auto; width:100%;display: none;">';
        html = '';
        var play_url = '';
        //vip会员
        if(isvip != undefined && isvip != ''  && isvip.match(rgExp))
        {
            var play_mp4 = getCookie('play_mp4');
            if(type == 2)
            {
                play_url = play_url_arr_oumei[play_rand2];

            }else{
                play_url = play_url_arr[play_rand];

            }
        }
        //试看
        else if(( play_name != '')&&(play_name != 'undefined'))
        {
            
            play_pic = 'http://oewx8de7n.qnssl.com/9e0120cv23/images/'+play_name+'.jpg';
            play_url = 'http://oewx8de7n.qnssl.com/9e0120cv23/images/mp4/'+play_name+'.mp4';
        }
        else
            play_url =  "http://su.cywl5.com/sy/h34.mp4";
        return play_url;
}

在lsj.pay.js?v=256这份代码的279行中我们又找到了一个线索,我们从代码看到,如果支付成功会在本地浏览器设置用户的cookie值,有两个关键的值出现了,分别是lsjlsjyy
lsj

setCookie('lsj', pay_tee, 'd730');

lsjyy

setCookie('lsjyy', 'vip', 'd730');

其实我想了很久这个名字跟vip有什么关联,估计这个网站的作者想写的是老司机和老司机YY的意思吧,不过不管它是什么意思,现在只要我们尝试用它封装好的setCookie方法自己改写这两个cookie值,就能在本地顺利成为会员,绕过刚才的判断条件了
lsj.pay.js

function checkOrderStatus() {
    $.post("./pay/ok.php?action=check&uid="+getCookie('user'), function(data) {
        var pay_tee = Number(data);
        if (pay_tee >= 1) 
        {
            ispay = 1;
            ispay = true;
            var lsj = getCookie('lsj');
            if(lsj == null) lsj = 0; 
            if(pay_tee > lsj)
            {    
                setCookie('lsj', pay_tee, 'd730');
            }
        }   
        if ((pay_tee > 0)&&(pay_tee <= s_v1_money)) 
        {
            setCookie('lsjyy', 'vip', 'd730');
        }
        else if ((pay_tee > s_v1_money)&&(pay_tee <= s_v2_money)) 
        {
            setCookie('lsjyy', 'vip2', 'd730');         
        }
        if (pay_tee >= 1) 
        {
            var dt1 =Math.round(new Date().getTime()/1000);
            setCookie('lsjtime', dt1, 'd730');
            setCookie("isPayClick",0, 'd1');
            var isPayClick = getCookie("isPayClick");
            if (isPayClick == 0)
                window.location.reload(); 
        }
       // el_checkpay.hide();
    })
} 

其实往下也可以看到有一个clearVIP函数,更肯定了刚才我们的结论,VIP和非VIP其实就是这两个值在作怪,所以我们默认的值应该就是clearVIP函数设置的值

function clearVIP()
{
    setCookie("lsjyy",0, 's1');
    setCookie("lsj",0, 's1');
}

如下图,我们在浏览器执行这两个函数,执行成功后我们就能看到cookie被成功改写
image
image
在这里我设置lsjyy为vip2,当然我在后面测试的时候发现这两个值貌似没什么区别
image

然后我们可以用lsj.pay.js第179行的showpay函数来检测是否已经是成功变成VIP

function showpay() {
    var rgExp =/^(v|V)[i|I|p|P|0-9]{2,3}$/g;
    var isvip = getCookie('lsjyy');
    if(isvip != undefined && isvip != ''  && isvip.match(rgExp))
    {
        alert('您已经是vip了哦.');
        return false;
    }
   //省略后面的代码
}

如果成功完成上面的步骤,我们在控制台输入showpay()就会看到下图这个弹窗,那证明我们已经成功升级为VIP...
image
我们再次进入其中一个带有视频的网页看看变成VIP会会真的有VIP的"待遇",我们发现页面内容块变成了空白,控制台报了下面这个错,那是提示有一个值叫play_rand没有定义,其实来到这里我们已经发现我们已经绕过了刚才会员判断,进入我们是会员的判断分支,只是因为一个未定义的值停止了代码的运行
image
image
由于这个值我们可以看到是一个数组的索引值,所以我们在控制台全局定义

var play_rand = 1

image
我们把id为play-html节点的元素样式设置diplay为显示,我们就可以看到正常的播放器出来了
image
分析源码得知,它大概的工作原理就是根据url的参数,比如play_nameplay_type,根据你是否VIP然后加载播放器播放拼接完的URL,play_name还含有undefined很不科学,说明有些视频根本只有图没有源
打开play.html查看里面的源代码,可以看到视频的链接源
play.html第21行

<script type="text/javascript">
    var cdn_url = 'http://oewx8de7n.qnssl.com/9e0120cv23';
</script>

往下面看我们可以看到刚才play_rand选取的对应的那个视频,事实上play_url_arr_oumeiplay_url_arr数组已经定义好了VIP可以观看的这十几个视频,所以不管怎么样付钱后都是看这十几个里面的其中一个,在这里其实已经可以自己拼接对应的链接进入相应的视频
play.html第324行

var play_url_arr_oumei=new Array();
    play_url_arr_oumei[1]=cdn_url+"/vip/2/om_1.mp4";
    play_url_arr_oumei[2]=cdn_url+"/vip/2/om_2.mp4";
    play_url_arr_oumei[3]=cdn_url+"/vip/2/om_3.mp4";
    play_url_arr_oumei[4]=cdn_url+"/vip/2/om_4.mp4";
    play_url_arr_oumei[5]=cdn_url+"/vip/2/om_5.mp4";
    play_url_arr_oumei[6]=cdn_url+"/vip/2/om_6.mp4";
    play_url_arr_oumei[7]=cdn_url+"/vip/try7.mp4";
    play_url_arr_oumei[8]=cdn_url+"/vip/try8.mp4";
    play_url_arr_oumei[9]=cdn_url+"/vip/try9.mp4";
    play_url_arr_oumei[10]=cdn_url+"/vip/try10.mp5";
    var play_url_arr=new Array();
    play_url_arr[1]=cdn_url+"/vip/try1.mp4";
    play_url_arr[2]=cdn_url+"/vip/try2.mp4";
    play_url_arr[3]=cdn_url+"/vip/try3.mp4";
    play_url_arr[4]=cdn_url+"/vip/try4.mp4";
    play_url_arr[5]=cdn_url+"/vip/try5.mp4";
    play_url_arr[6]=cdn_url+"/vip/try6.mp4";
    play_url_arr[7]=cdn_url+"/vip/try7.mp4";
    play_url_arr[8]=cdn_url+"/vip/try8.mp4";
    play_url_arr[9]=cdn_url+"/vip/try9.mp4";
    play_url_arr[10]=cdn_url+"/vip/try10.mp4";

当然我们为了证明我们刚才上面的推测,我们可以在控制台把lsj.mp4.jsgetMp4Url函数执行一次

image

我们可以看到一个可用的链接就这样出现了
当然你可以再执行如下代码,让它在网页端执行,关键就是用playHtml3函数加载播放器,并打开拼接好的视频链接,有时候加载的是一张图片,更有时候加载的视频链接也根本无法播放,其实不管是否会员,最后也只是播放在页面最后定义好的那十几个视频,支付变成会员只是它们把数据入库,然后帮你设置个cookie的过程而已
当然这些网站的前端人员的代码和注释还是挺可笑的
image

image
至此,就可以看懂这种网站的工作原理了,所以遇到小心上当了

阿里云,AWS,HTTPS和企业支付宝配置

申请证书

  • 登录:阿里云控制台,产品与服务,证书服务,购买证书。
  • 购买:证书类型选择 免费型DV SSL,然后完成购买。

要来回点击寻找一下,因为免费版隐藏了

image

配置

等待阿里云签发成功,然后在右边栏目里面选择下载

这里写图片描述

安装证书

文件说明:

  1. 证书文件1523928142501.pem,包含两段内容,请不要删除任何一段内容。
  2. 如果是证书系统创建的CSR,还包含:证书私钥文件1523928142501.key、证书公钥文件public.pem、证书链文件chain.pem。
    ( 1 ) 在Apache的安装目录下创建cert目录,并且将下载的全部文件拷贝到cert目录中。如果申请证书时是自己创建的CSR文件,请将对应的私钥文件放到cert目录下并且命名为1523928142501.key;
    ( 2 ) 打开 apache 安装目录下 conf 目录中的 httpd.conf 文件,找到以下内容并去掉“#”:
#LoadModule ssl_module modules/mod_ssl.so (如果找不到请确认是否编译过 openssl 插件)
#Include conf/extra/httpd-ssl.conf

( 3 ) 打开 apache 安装目录下 conf/extra/httpd-ssl.conf 文件 (也可能是conf.d/ssl.conf,与操作系统及安装方式有关), 在配置文件中查找以下配置语句:

# 添加 SSL 协议支持协议,去掉不安全的协议
SSLProtocol all -SSLv2 -SSLv3
# 修改加密套件如下
SSLCipherSuite HIGH:!RC4:!MD5:!aNULL:!eNULL:!NULL:!DH:!EDH:!EXP:+MEDIUM
SSLHonorCipherOrder on
# 证书公钥配置
SSLCertificateFile cert/public.pem
# 证书私钥配置
SSLCertificateKeyFile cert/1523928142501.key
# 证书链配置,如果该属性开头有 '#'字符,请删除掉
SSLCertificateChainFile cert/chain.pem

( 4 ) 重启 Apache。
( 5 ) 通过 https 方式访问您的站点,测试站点证书的安装配置,如遇到证书不信任问题

这里注意如果使用阿里云还要在安全组规则里面设置443端口的出入方向

Apache反向代理设置https

因为我是用express框架写的后端,apache默认占了80端口,express可以通过反向代理来享用80端口

首先在 httpd.conf 启用必要的模块,也就是如果去掉下面这几行前面的 #

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so

Include conf/extra/httpd-ssl.conf

httpd.conf 末端添加如下设置,注意 ProxyPassProxyPassReverse 设置为express框架暴露的端口,并且设置好 SSLCertificateFileSSLCertificateKeyFile 的路径,路径就是上面安装证书步骤里面的路径

<VirtualHost *:80>
	ServerAdmin www.smms.ink
	ServerName www.smms.ink
	ProxyRequests Off
	<Proxy *>
	Order deny,allow
	Allow from all
	</Proxy>
	ProxyPass / http://127.0.0.1:1314/
	ProxyPassReverse / http://127.0.0.1:1314/
</VirtualHost>

<virtualhost *:443>
	ServerName www.smms.ink
	<proxy>
	Order deny,allow
	Allow from all
	</proxy>
	SSLEngine On
	SSLProxyEngine On
	SSLProxyVerify none
	SSLProxyCheckPeerCN off
	SSLProxyCheckPeerName off
	SSLCertificateFile cert/public.pem
	SSLCertificateKeyFile cert/1523928142501.key
	 
	ProxyRequests Off
	ProxyPreserveHost On
	 
	ProxyPass / http://127.0.0.1:1314/
	ProxyPassReverse / http://127.0.0.1:1314/
</virtualhost>

此时当我们访问 https://www.xxx.com 就等于 访问到内网的 http://127.0.0.1:1314
这样对 https://www.xxx.com 的访问,返回的数据将是来自 http://127.0.0.1:1314

上面的这个配置思路总结为如下三步:

  1. 正常启动express项目在1314端口
  2. 然后用apache服务器实现反向代理
  3. 再用apache配置https

Apache和node共享端口

参考文档Apache和nodejs公用80端口的问题

思路是通过不同域名来区分进去是apache解析还是node解析文件,可以利用二级域名来划分

ProxyPass / http://127.0.0.1:9000/
ProxyPassReverse / http://127.0.0.1:9000/
/ 映射到  http://127.0.0.1:9000/

你可以根据自己的路由规则来实现不用的需求,例如:

ProxyPass / http://127.0.0.1:9000/
ProxyPassReverse / http://127.0.0.1:9000/

ProxyPass /nodejs http://127.0.0.1:1314
ProxyPassReverse /nodejs http://127.0.0.1:1314

此时我们可以根据不同的二级路由进入内网的不同端口,拨入我们可以把/nodejs分配给node开的1314端口,那么外网就可以通过https://xxx/nodejs/xxx进入到nodejs的服务器中,对应如果是其他路由就会进入到apache服务器中

企业支付宝接入

接入之前建议先看官方文档比较详细

创建应用

首先有企业支付宝账号,登录企业支付宝管理中心,登录不知道是不是安全校验太频繁,官网输入登录的时候特别卡特别慢,进入开发者中心,因为我选择的是网页&移动应用,要提供网址通过审核,审核通过就会出现下面的已上线应用

image

配置RSA密钥

下载官网中的RSA签名验签工具配置RSA2密钥,会生成应用公钥应用密钥,注意上面选择中有JAVA适用非JAVA适用的选项,因为我后端选择PHP所以我选择了非JAVA适用的PKCS1

image

应用公钥上传应用信息中的开发配置中,还有开发配置中的应用网关不是必填项,可以选择不填,然后将旁边的支付宝密钥复制到后端的SDK中的config.php中alipay_public_key字段后,注意应用公钥是跟支付宝密钥完全不同的,不要弄错,不然一会儿验签会失败

image

配置服务器

下载对应的SDK文件手机网站支付DEMO,这里根据对应的需求进行下载,有对应不同后端语言的,我下载的手机网站支付是有包含完整前后端的逻辑的DEMO

一般来说最简单的测试只需要配置config.php文件就可以了

<?php
$config = array (	
	//应用ID,您的APPID。
	'app_id' => "xxxx",

	//商户私钥,您的原始格式RSA私钥,也就是应用私钥
	'merchant_private_key' => "xxxx",
		
	//异步通知地址
	//这里最好用http 支付宝的异步的回调在免费https证书下会失败
	'notify_url' => "http://工程公网访问地址/alipay.trade.wap.pay-PHP-UTF-8/notify_url.php",
		
	//同步跳转
	//'return_url' => "http://mitsein.com/alipay.trade.wap.pay-PHP-UTF-8/return_url.php",
		
	//编码格式
	'charset' => "UTF-8",

	//签名方式
	'sign_type'=>"RSA2",

	//支付宝网关
	'gatewayUrl' => "https://openapi.alipay.com/gateway.do",

	//支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm
        //对应APPID下的支付宝公钥。注意是支付宝公钥,而不是应用公钥,支付宝公钥是基于上传的应用公钥生成的
	'alipay_public_key' => "xxxx",
);

这里注意两点支付宝公钥和应用公钥,不要粘贴错,这里需要的是支付宝公钥而不是应用公钥,还有点就是notify_url和return_url

return_url是支付成功后在客户端返回给客户看的回调地址,需要和开发配置中的授权回调地址对应,这是支付宝对支付的一种校验,这个地址建议不要写任何支付后的操作逻辑,因为用户有可能会在客户端切掉看不到而影响逻辑判断

image

notify_url是支付成功后支付宝通知给服务端的回调地址,这个地址注意要在公网可访问,因为支付宝会响应它,所以在本地测试中是不行的,必须上传到公网服务器,还有这里注意的是使用阿里云的免费https证书时候,这里如果地址是https会有一定失败率的,也就是说这里如果用了阿里云的免费https证书最好还是用回http地址响应,如果响应成功会echo出success这7个字符给支付宝,说明验签成功了,所以这个文件不要包含任何html代码,会影响结果输出,我们可以把支付后后端需要执行的逻辑放在这里,比如记录支付成功等

完整的交易逻辑

最重要就是 WIDout_trade_no 商品订单号,自己定义的,当我们发送支付到自己后端时候记录这个订单号,等支付宝验签回来成功后,对比信息中的订单号完成一个完整的支付逻辑,还要注意的是支付宝应该是只能用form表单提交支付请求,因为返回来的是一个form的代码,当然你也可以动态创建form来发送支付请求,但我个人建议还是直接静态form的形式来得方便

APP和网页支付

注意APP和网页都要各自通过签约才可以调用支付接口的,之前在这里浪费了很久时间一直测试都是报ALIN10146错误就是因为忘记还没有APP签约

2018-11-23 11 17 41

APP内支付要真机连接查看返回的参数,注意后台PHP返回给APP的签名是不需要htmlspecialchars,不然这里会帮你把&&amp,这里正确是&,不然APP支付会报ALIN10146

// 注意:这里不需要使用htmlspecialchars进行转义,直接返回即可
echo $response;
//echo htmlspecialchars($response);//就是orderString 可以直接给客户端请求,无需再做处理。

node原生模块爬虫和request爬虫方案

原生模块爬虫

//原生http模块,用于请求文件或者创建服务器
var http = require("http");
//原生fs模块,用于读写文件
var fs = require("fs")
//调用cheerio模块,类似于jquery
var cheerio = require("cheerio")
//调用mysql第三方模块
var mysql = require("mysql")
//进行数据库连接
var connection = mysql.createConnection({
	host: 'localhost', //localhost
	user: 'test',
	password: '123456789',
	database: 'laoyao'
});

//执行连接
connection.connect();
//此函数用于获取需要被爬虫的网页DOM结构
function download(url, callback) {
	http.get(url, function(res) {
		var data = "";
		res.on('data', function(chunk) {
			data += chunk
		})
		res.on('end', function() {
			callback(data);
		})
	})
}

download("http://www.mmjpg.com/", function(data) {
	//将网页信息交给cheerio处理,类似于jquery处理DOM结构
	var $ = cheerio.load(data);
	var imgArr = [];
	//遍历图片信息,并执行存储
	$('img').each(function(index, ele) {
		var src = $(ele).attr("src");
		//把数据插入到数据库
		connection.query('INSERT INTO `meizi`(`src`) VALUES ("' + src + '")', function(error, results, fields) {
			if(error) throw error;
		});
		imgArr.push(src);
	})
	//执行下载图片
	downloadImg(imgArr)
})
var i = 0;
function downloadImg(imgArr) {
	var lenth = imgArr.length;
	var writerStream = fs.createWriteStream('img/'+i+'.jpg');
	http.get(imgArr[i], function(res) {
		res.pipe(writerStream);
		if(i<lenth){
			i++;
			//递归执行图片下载,确保每一张图片下载完再下载下一张
			downloadImg(imgArr)		
		}else{
			return;
		}
	})
}

node压缩图片

安装

我选择的是node for gm库来处理图片上传后的压缩

首先下载GraphicsMagick,建议把ImageMagick也下载了

GraphicsMagick

image

ImageMagick

image

注意如果要勾选Install legacy utilities(e.g. convert)选项,不然会出现Error: Could not execute GraphicsMagick/ImageMagick: gm “identify” “-ping” “-format” “%[EXIF:Orientation
image

当然安装完后可以在cmd输入gm来测试是否成功

image

这里注意的是win系统在装完要重启一次电脑,不然有可能出现convert跟系统命令冲突的错误

再安装node的gm

npm install gm

使用

GraphicsMagick是从ImageMagick中分离出来的
加载GraphicsMagick(大小4.26 MB)

var gm = require('gm')

加载ImageMagick(大小26.2 MB),推荐ImageMagick

var gm = require('gm').subClass({imageMagick: true})// 注意使用的区别

除了加载有区别,其他使用方式完全一样

Mongodb

安装配置

Win

Mongodb官网下载最新版本的Mongodb下载地址

下载msiwindow安装包,可以装到C盘或者D盘目录下

Mac

Github或者官网下载到电脑里面

或者直接进入/usr/local文件夹下载

# 进入 /usr/local
cd /usr/local
# 下载
sudo curl -O https://fastdl.mongodb.org/osx/mongodb-osx-x86_64-x.x.x.tgz
# 解压
sudo tar -zxvf mongodb-osx-x86_64-x.x.x.tgz
# 重命名为 mongodb 目录
sudo mv mongodb-osx-x86_64-x.x.x mongodb

安装完成后,我们可以把MongoDB的二进制命令文件目录(安装目录/bin)添加到PATH路径中

export PATH=/usr/local/mongodb/bin:$PATH

如果是从Github或者官网下载的,要先把它文件夹改名为mongodb,然后放到/usr/local目录下,然后再运行上面那一句命令,把bin目录添加到PATH路径中

//创建默认数据库文件夹
sudo mkdir -p /data/db              
//连接数据库
sudo mongod --dbpath /data/db
//如果mongod默认连接/data/db这个位置,不需要带--dbpath
sudo mongod                                

配置

由于我是安装在D盘的环境下

D:\Program Files (x86)\MongoDB\Server\3.2\bin

所以在bin文件夹下找到mongod.exe命令,然后通过管理员执行mongod --dbpath x路径x,路径可以是任何地方,我这里选择在D盘的MongoDB目录下,当然路径不要包含特殊的字符串,比如Program Files (x86)也不行

mongod --dbpath D:\mongodb\data\db

image

命令行

经过上面的配置之后,就可以返回bin目录下找到mongo.exe命令,并管理员下执行,就可以出现mongodb的命令行模式

D:\Program Files (x86)\MongoDB\Server\3.2\bin

image

然后就可以使用下面的命令来测试了

db.help()//帮助
db.stats()//统计

显示数据库

show dbs

检查当前选择的数据库

db

添加数据库

数据库名为数据库创建的名字,使用该命令后会默认切换到对应的数据库,并且在数据库中添加选项,数据库信息才显示,如果默认就有该数据库,那就是切换到对应的数据库里面

use 数据库名

删除数据库

先切换到对应的数据库,然后再执行db.dropDatabase()删除该数据库

use 数据库名
//switched to db 数据库名
db.dropDatabase()

显示集合

用一下命令可以检查创建的集合

show collections

添加集合

在创建完数据库之后,我们就可以创建集合

db.createCollection(集合名字name,设置参数options[对象类型])

name是要创建的集合的名称。 options是一个文档,用于指定集合的配置

参数 类型 描述
name String 要创建的集合的名称
options Document (可选)指定有关内存大小和索引的选项

options参数是可选的,因此只需要指定集合的名称。 以下是可以使用的选项列表:

字段 类型 描述
capped Boolean (可选)如果为true,则启用封闭的集合。上限集合是固定大小的集合,它在达到其最大大小时自动覆盖其最旧的条目。 如果指定true,则还需要指定size参数。
autoIndexId Boolean (可选)如果为true,则在_id字段上自动创建索引。默认值为false。
size 数字 (可选)指定上限集合的最大大小(以字节为单位)。 如果capped为true,那么还需要指定此字段的值。
max 数字 (可选)指定上限集合中允许的最大文档数。

由于option是可选,我们也可以不带配置项创建集合

db.createCollection("mycollection")

删除集合

db.collection.drop()用于从数据库中删除集合

db.集合名.drop()

比如我们可以测试以下操作

db.createCollection("wscats")//创建名为wscats的集合
show collections//显示该数据库所有集合   wscats
db.wscats.drop()//删除名为wscats的集合

查看文档

最简单查看文档的方法就是find(),会检索集合中所有的文档结果

db.集合名.find()

要以格式化的方式显示结果,可以使用pretty()方法。

db.集合名.find().pretty()

1.固值寻找

寻找age集合里面所有含有属性值为wscats的文档结果,相当于where name = 'wscats'

db.age.find({name:"wscats"})

2.范值寻找

操作 语法 示例 等效语句
相等 {:} db.age.find({"name":"wscats"}).pretty() where name = 'wscats'
小于 {:{$lt:}} db.age.find({"likes":{$lt:50}}).pretty() where likes < 50
小于等于 {:{$lte:}} db.age.find({"likes":{$lte:50}}).pretty() where likes <= 50
大于 {:{$gt:}} db.age.find({"likes":{$gt:50}}).pretty() where likes > 50
大于等于 {:{$gte:}} db.age.find({"likes":{$gte:50}}).pretty() where likes >= 50
不等于 {:{$ne:}} db.age.find({"likes":{$ne:50}}).pretty() where likes != 50

3.AND和OR寻找

AND

在find()方法中,如果通过使用将它们分开传递多个键,则mongodb将其视为AND条件。 以下是AND的基本语法

寻找_id为1并且name为wscats的所有结果集

db.age.find(
   {
      $and: [
         {"_id": 1}, {"name": "wscats"}
      ]
   }
)

OR

在要根据OR条件查询文档,需要使用$or关键字。以下是OR条件的基本语法

寻找name为corrine或者name为wscats的所有结果集

db.age.find(
   {
      $or: [
         {"name": "corrine"}, {“name“: "wscats"}
      ]
   }
)

AND和OR等结合

相当于语句where title = "wscats" OR ( title = "corrine" AND _id < 5)

db.age.find({
  $or: [{
    "title": "wscats"
  }, {
    $and: [{
      "title": "corrine"
    }, {
      "_id": {
        $lte: 5
      }
    }]
  }]
})

插入文档

文档的数据结构和JSON基本一样。
所有存储在集合中的数据都是BSON格式。
BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON

要将数据插入到mongodb集合中,需要使用mongodb的insert()save()方法。

db.集合名.insert(document)

比如我们可以插入以下数据

db.wscats.insert({
   _id: 100,
   title: 'MongoDB Tutorials', 
   description: 'node_tutorials',
   by: 'Oaoafly',
   url: 'https://github.com/Wscats/node-tutorial',
   tags: ['wscat','MongoDB', 'database', 'NoSQL','node'],
   num: 100,
})

也可以支持插入多个,注意传入的是数组形式

db.wscats.insert([{
   _id: 100,
   title: ‘Hello’
},{
   _id: 101,
   title: ‘World’
}])

在插入的文档中,如果不指定_id参数,那么mongodb会为此文档分配一个唯一的ObjectId
要插入文档,也可以使用db.post.save(document)。如果不在文档中指定_id,那么save()方法将与insert()方法一样自动分配ID的值。如果指定_id,则将以save()方法的形式替换包含**_id**的文档的全部数据。

db.wscats.save({
   _id: 111,
   title: 'Oaoafly Wscats', 
})

更新文档

1.update()方法

寻找第一条title为wscats的值,并且更新值title为corrine和age为12

db.age.update({
  'title': 'wscats'
}, {
  $set: {
    'title': 'corrine',
    'age': 12
  }
})

默认情况下,mongodb只会更新一个文档。要更新多个文档,需要将参数multi设置为true,还可以配合find方法里面的各种复杂条件判断来筛选结果,然后更新多个文档

寻找所有title为wscats的值,并且更新值title为corrine和age为12

db.age.update({
  'title': 'wscats'
}, {
  $set: {
    'title': 'corrine',
    'age': 12
  }
}, {
  multi: true
})

2.save()方法

_id主键为3的文档,覆盖新的值,注意_id为必传

db.age.save({
  '_id':3,
  'title': 'wscats'
})

删除文档

删除主键_id为3的文档,默认是删除多条

db.age.remove({
  '_id':3
})

建议在执行remove()函数前先执行find()命令来判断执行的条件是否正确

如果你只想删除第一条找到的记录可以设置justOne为1,如下所示

db.age.remove({...},1)

全部删除

db.age.remove({})

Limit与Skip方法

Limit

如果你需要在mongodb中读取指定数量的数据记录,可以使用mongodb的Limit方法,limit()方法接受一个数字参数,该参数指定从mongodb中读取的记录条数。

db.age.find().limit(数量)

Skip

我们除了可以使用limit()方法来读取指定数量的数据外,还可以使用skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数。

db.age.find().limit(数量).skip(数量)
//skip()方法默认值为0

所以我们在实现分页的时候就可以用limit来限制每页多少条数据(一般固定一个值),用skip来决定显示第几页(一个有规律变动的值)

排序

在mongodb中使用使用sort()方法对数据进行排序,sort()方法可以通过参数指定排序的字段,并使用1和-1来指定排序的方式,其中1为升序排列,而-1是用于降序排列,注意记得是数字(1,-1)而非字符串。

1 升序排列
-1 降序排列
db.集合名.find().sort({键值(属性值):1})

age集合表重新根据_id主键进行降序排列

db.age.find().sort({
  "_id": -1
})

Node.js连接

安装mongodb的模块

npm install mongodb

1.连接数据库

var MongoClient = require('mongodb').MongoClient;
//结尾是选择数据库名
var DB_CONN_STR = 'mongodb://localhost:27017/wscats';
MongoClient.connect(DB_CONN_STR, function(err, db) {
  console.log("连接成功!");
});

2.查询数据

注意查询回来的结果需要toArray来遍历处理

var MongoClient = require('mongodb').MongoClient;
var DB_CONN_STR = 'mongodb://localhost:27017/wscats';

MongoClient.connect(DB_CONN_STR, function(err, db) {
  console.log("连接成功!");
  //选中age集合,并用find方法把结果集拿回来进行处理
  db.collection("age").find({title: "cba"}).toArray(function(err, result) {
    if (err) {
      console.log('Error:' + err);
      return;
    }
    console.log(result);
  });
});

经过测试,读取大于100条的时候会出现报错官网解释,可以尝试用forEach代替

db.collection('pokemon').find({})
  .forEach(function(item){
      console.log(item)
  })

查询ID

查询自动生成的ObjectId

var ObjectId = require('mongodb').ObjectId;
let _id = ObjectId("5bcae50ed1f2c2f5e4e1a76a");
db.collection('xxx').find({
    "_id": _id
}).forEach(function (item) {
    console.log(item)
})

3.插入数据

insert函数第一个参数是需要插入的值(可以一个也可以多个),第二个参数是接受一个回调函数,当值插入成功后回返回插入值得一些关键信息,比如_id

var MongoClient = require('mongodb').MongoClient;
var DB_CONN_STR = 'mongodb://localhost:27017/wscats';

MongoClient.connect(DB_CONN_STR, function(err, db) {
  console.log("连接成功!");
  db.collection("age").insert([
    {
      title: "插入的值A"
    }, {
      title: "插入的值B"
    }
  ], function(err, result) {
    if (err) {
      console.log('Error:' + err);
      return;
    }
    console.log(result)
  })
});

4.更新数据

注意如果不加$set就是完全替换原来的那份(没有设置的属性值将会丢失),加上$set则只是更新对应的属性值,其余不做改变

var MongoClient = require('mongodb').MongoClient;
var DB_CONN_STR = 'mongodb://localhost:27017/wscats';

MongoClient.connect(DB_CONN_STR, function(err, db) {
  console.log("连接成功!");
  db.collection("age").update({
    "_id": 1
  }, {
    $set: {
      title: "你好,世界",
      skill: "js"
    }
  }, function(err, result) {
    if (err) {
      console.log('Error:' + err);
      return;
    }
    //console.log(result);
  });
});

5.删除数据

var MongoClient = require('mongodb').MongoClient;
var DB_CONN_STR = 'mongodb://localhost:27017/wscats';

MongoClient.connect(DB_CONN_STR, function(err, db) {
  console.log("连接成功!");
  db.collection("age").remove({
    "_id": 1
  }, function(err, result) {
    if (err) {
      console.log('Error:' + err);
      return;
    }
    //console.log(result);
    //关闭数据库
    db.close();
  });
});

6.关闭数据库

db.close();

封装自定义模块

新建mongo.js写入以下代码,封装自定义模块,方便其他路由复用,注意assert是node自带的断言模块,用于测试代码,具体封装好的MongoDB增删查改的代码可以参考这里

参考

const MongoClient = require('mongodb').MongoClient;
const assert = require('assert');
const url = 'mongodb://localhost:27017';
const dbName = 'shop';
function query(callback) {
	MongoClient.connect(url, function(err, client) {
		assert.equal(null, err);
		console.log("Connected successfully to server");
		const db = client.db(dbName);
		callback(db);
		client.close();
	});
}
module.exports = {
	query
}

在路由文件中引入和使用

var mongo = require('./mongo.js')
router.post('/addproduct', function(req, res, next) {
	mongo.query(function(db) {
		db.collection("product").insertMany([req.body], function(err, result) {
			console.log("Inserted 1 document into the collection");
			res.send('respond with a resource');
		});
	})
});

数据库管理

bin文件里面自带了mongodb导出导入备份和恢复的命令,我们可以根据情况使用

导出

命令 作用
-d 数据库名
-c collection名
-o 输出的文件名
--type 输出的格式,默认为json
-f 输出的字段,如果-type为csv,则需要加上-f "字段名"
sudo mongoexport -d dbName -c students -o /usr/local/mongodb/users.json

导入

命令 作用
-d 数据库名
-c collection名
--type 导入的格式默认json
-f 导入的字段名
--headerline 如果导入的格式是csv,则可以使用第一行的标题作为导入的字段
--file 要导入的文件
sudo mongoimport -d dbName -c users --file /usr/local/mongodb/users.json --type json

备份

命令 作用
-h MongDB所在服务器地址,例如:127.0.0.1,当然也可以指定端口号:127.0.0.1:27017
-d 需要备份的数据库实例,例如:test
-o 备份的数据存放位置,例如:/home/mongodump/,当然该目录需要提前建立,这个目录里面存放该数据库实例的备份数据
sudo mongodump -h 127.0.0.1:27017 -d dbName -o /usr/local/mongodb/

恢复

命令 作用
-h MongoDB所在服务器地址
-d 需要恢复的数据库实例,例如:test,当然这个名称也可以和备份时候的不一样,比如test2
--dir 备份数据所在位置,例如:/home/mongodump/itcast/
--drop 恢复的时候,先删除当前数据,然后恢复备份的数据。就是说,恢复后,备份后添加修改的数据都会被删除,慎用!
sudo mongorestore -h 127.0.0.1:27017 -d test --dir /usr/local/mongodb/dbName

可视化

参考文档

node文字转语音

//http://tts.baidu.com/text2audio?lan=zh&ie=UTF-8&spd=2&text=你要转换的文字
var http = require('http'); // 通过http模块访问百度的接口
var querystring = require('querystring'); // 处理请求参数的querystring模块
var fs = require('fs'); // fs模块,用来保存语音文件
var path = require('path'); //path模块 用来配置路径
//准备http请求的一些参数
var postData = querystring.stringify({
  "lan": "zh", // zh表示中文
  "ie": "UTF-8", // 字符编码
  "spd": 5, // 表示朗读的语速,9代表最快,1是最慢(撩妹请用2,绕口令请用9)
  "text": '浦发银行有新信号' // 这句话就是要转换为语音的,可以表白一下,XXX我爱你
});
var options = {
  "method": "GET",
  "hostname": "tts.baidu.com",
  "path": "/text2audio?" + postData
};
// 调用http模块的request方法请求百度接口
var req = http.request(options, function (res) {
  var chunks = [];
  res.on("data", function (chunk) {
    chunks.push(chunk); // 获取到的音频文件数据暂存到chunks里面
  });
  res.on("end", function () {
    // 这里用到了Buffer模块,大概意思就是把获取到的语音文件流存入到body里面,body是一个Buffer
    var file = Buffer.concat(chunks);
    // 生成的mp3文件存储的路径,文件名叫做iloveu.mp3
    var filePath = path.normalize('./t.mp3');
    // fs模块写文件    
    fs.writeFileSync(filePath, file);
  });
});
req.end();

node的GBK转UTF8问题

GBK转UTF8

借助于iconv-lite模块

var fs = require("fs");
var iconv = require('iconv-lite');
var buffer = Buffer.from(fs.readFileSync('./703533.txt', {encoding: 'binary'}), 'binary');
var text = iconv.decode(buffer, 'GBK'); //使用GBK解码
console.log(text);

参考文档

Node.js环境中使用GBK编码

node-mysql

1.安装mysql模块

npm install mysql

image

2.新建sql.js,并写上以下代码

var mysql = require('mysql');
var connection = mysql.createConnection({
	host: 'localhost',
	user: 'wscats',
	password: '123456789',
	database: 'asm'
});
connection.connect();//连接数据库
connection.query('select * from news', function(err, rows, fields) {
	if(err) throw err;
	console.log('The news is: ', rows[0]);
});//执行SQL语句
connection.end();//关闭连接。

连接基本参数

参数 用法
host 主机名,localhost代表本地
user Mysql用户
password 密码
database 连接的数据库

由于我本地数据库名叫asm,操作表为news,取第一列数据
image

3.执行文件

执行sql.js,显示结果如图

node sql

image

注意如果我们要在每一次查询数据库后connection.end()关闭一次连接,那我们需要用mysql.createConnection()创建一个新的connection,也就是每一次的开关都是用唯一一个connection来实现

var connection;
function createConnection() {
	connection = mysql.createConnection({
		host: 'localhost',
		user: 'laoxie',
		password: '12345678',
		database: 'asm'
	});
}

4.增删查改分页

注意sql语句不要写错语法

//增加记录
connection.query('insert into news (title ,text) values ("wscats" , "eno")');
//删除记录
connection.query('delete from news where title = "wscats"');
// 修改记录
connection.query('update news set text = "eno" where title = "wscats"');
//查找记录
connection.query('select * from news', function(err, rows, fields) {
	if(err) throw err;
	console.log('The news is: ', rows[0]);
});
//查询记录
var arr = [];
connection.query("select * from news", function selectTable(err, rows, fields) {
	if(err) {
		throw err;
	}
	if(rows) {
		for(var i = 0; i < rows.length; i++) {
			console.log("第" + i + "条", "id: " + rows[i].id, "title: " + rows[i].title, "text: " + rows[i].text);
			//把数据组装成数组对象
			var obj = {};
			obj.id = rows[i].id;
			obj.title = rows[i].title;
			obj.text = rows[i].text;
			arr.push(obj);
		}
	}
	console.log(arr);
});
//查询记录
connection.query('select * from news where id = 2', function(err, rows, fields) {
	if(err) throw err;
	console.log('The news is: ', rows[0]);
});
//分页
//取前5条数据
select * from table limit 0,5
//or
select * from table limit 5
//取第11条到第15条数据,共5条
select * from table limit 10,5
//格式
select * from table limit offset,rows
offset指定要返回的第一行的偏移量,rows第二个指定返回行的最大数目。初始行的偏移量是0(不是1)

5.封装成模块

最后我们可以把它封装成一个模块导出,在其他主模块中调用
注意我在每个原型链的函数结尾处都会调用一个connection.end()方法,这个方法connection.connect()对应,一个开始,一个结束

配合await和async的封装具体可以参考这里

function curd() {
	var mysql = require('mysql');
	this.connection = mysql.createConnection({
		host: 'localhost',
		user: 'wscats',
		password: '123456789',
		database: 'asm'
	});
	//开始链接数据库
	this.connection.connect();
}
curd.prototype.insert = function() {
	//增加记录
	this.connection.query('insert into news (title ,text) values ("wscats" , "eno")');
	//结束链接
	this.connection.end();
}
curd.prototype.update = function() {
	// 修改记录
	this.connection.query('update news set text = "eno" where title = "wscats"');
}
curd.prototype.where = function() {
	this.connection.query('select * from news where id = 2', function(err, rows, fields) {
		if(err) throw err;
		console.log('The news is: ', rows[0]);
	});
	//结束链接
	this.connection.end();
}
curd.prototype.delete = function() {
	//删除记录
	this.connection.query('delete from news where title = "wscats"');
}
curd.prototype.find = function() {
	//查询记录
	var arr = [];
	this.connection.query("select * from news", function(err, rows, fields) {
		if(err) {
			throw err;
		}
		if(rows) {
			for(var i = 0; i < rows.length; i++) {
				console.log("第" + i + "条", "id: " + rows[i].id, "title: " + rows[i].title, "text: " + rows[i].text);
				//把数据组装成数组对象
				var obj = {};
				obj.id = rows[i].id;
				obj.title = rows[i].title;
				obj.text = rows[i].text;
				arr.push(obj);
			}
		}
		console.log(arr);
	});
	this.connection.end();
}
curd.prototype.find2 = function(
	//查找记录
	this.connection.query('select * from news', function(err, rows, fields) {
		if(err) throw err;
		console.log('The news is: ', rows[0]);
	});
)
var db = new curd();
exports.db = db;
//执行
//db.insert()
//外部引用方法
//var db = require('./sql');
//db.db.where();

6.断线重连

因为mysql连接时间长的话会自动断掉,可以封装一个断线重连的接口

const mysql = require("mysql");
function createConnection() {
	let connection = mysql.createConnection({
		// 域名
		host: 'localhost',
		// 用户名
		user: 'wscats',
		// 密码
		password: '12345678',
		// 数据库
		database: 'corrine'
	});
	//连接错误,2秒重试
	connection.connect((err) => {
		if(err) {
			console.log('error when connecting to db:', err);
			setTimeout(createConnection, 2000);
		}
	});
	connection.on('error', function(err) {
		console.log('db error', err);
		// 如果是连接断开,自动重新连接
		if(err.code === 'PROTOCOL_CONNECTION_LOST') {
			createConnection();
		} else {
			throw err;
		}
	});
	return connection
}
module.exports = createConnection();

7.自动断线

建议用下面这一段来实现mysql的自动连接和自动断开,那就不会出现too many connections的错误提醒了

var query = function(sql, params, callback) {
	var connection = mysql.createConnection({
		// 域名
		host: 'localhost',
		// 用户名
		user: 'wscats',
		// 密码
		password: '12345678',
		// 数据库
		database: 'corrine'
	});
	//连接错误,2秒重试  
	connection.connect(function(err) {
		if(err) {
			console.log("error when connecting to db:", err);
			setTimeout(query, 2000);
		} else {
			var q = connection.query(sql, params, function(error, results, fields) {
				//关闭连接  
				connection.end();
				//事件驱动回调  
				callback(error, results, fields);
			});
			console.log("sql:::" + q.sql);
		}
	});
	connection.on("error", function(err) {
		console.log("db error", err);
		// 如果是连接断开,自动重新连接  
		if(err.code === "PROTOCOL_CONNECTION_LOST") {
			query();
		} else {
			throw err;
		}
	});
}

参考文档

无依赖自定义组件的实践

组件

无依赖自定义组件的实践需要依赖三个技术(浏览器原生就支持组件化)

  • Custom elements(自定义元素)customElements.define('my-header', Xheader)
  • Shadow DOM(影子DOM)attachShadow
  • HTML templates(HTML模板)<template>和 <slot>
// HTML模板技术包含两个标签:<template>和 <slot>
// 当需要在页面上重复使用同一个 DOM结构时,可以用 template 标签来包裹它们,然后进行复用
// slot标签让模板更加灵活,使得用户可以自定义模板中的某些内容
const str =
    `
    <style>
        header {
            height: 50px;
            line-height: 50px;
            width: 100%;
            text-align: center;
            color: white;
            background-color: red;
        }
    </style>
    <header><slot name="my-title">头部</slot></header>
    `
class Xheader extends HTMLElement {
    constructor() {
        super();
        const template = document.createElement('template');
        template.innerHTML = str;
        const templateContent = template.content;
        // 创建一颗可见的DOM树,这棵树会附着到某个DOM元素上
        // 这棵树的根节点称之为shadow root,只有通过shadow root 才可以访问内部的shadow dom,并且外部的css样式也不会影响到shadow dom上
        // 相当于创建了一个独立的作用域
        this.attachShadow({
            mode: 'open'// 'open' 表示该shadow dom可以通过js 的函数进行访问
        }).appendChild(
            templateContent.cloneNode(true)// 操作shadow dom
        );
    }
}
// 允许自定义元素及其行为,然后可以在您的用户界面中按照需要使用它们
customElements.define('my-header', Xheader);

事件绑定

经过测试,发现需要在appendChild()之后,才能绑定事件

attachShadow.appendChild(
    templateContent.cloneNode(true) // 操作shadow dom
);
// 编译模板,要在appendChild之后
attachShadow.querySelector(xxx);
let nodes = attachShadow.childNodes;

node跨域

后端

var http=require('http');
var querystring=require('querystring');

http.createServer(function(req,res){
  var postData='';
  req.setEncoding('utf8');

  req.on('data',function(chunk){
    postData+=chunk;
  });
  req.on('end',function(){
    res.end(postData+"hehe");
  });
}).listen(3000);
console.log("服务启动。。。")

前端

$(function(){
  $("#test").click(function(){
    $.ajax({
      'url':'http://localhost:3000',
      'method':'POST',
      'data':{},
      'success':function(data){
        console.log(data);
      }
    });
  });
})

正常情况会发生跨域

XMLHttpRequest cannot load http://localhost:3000/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access

这测试环境下我们可以这样解决
在createServer方法里面第一行设置

res.setHeader('Access-Control-Allow-Origin', '*');

*号代表允许任何与的请求,当然实际生产环境不可能这么做
你可以通过报错提示找到被拒绝的域然后设置

res.setHeader('Access-Control-Allow-Origin', '域名');

比如我在HBulider里面打开html文件是这样设置

res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8020');

node入门

node

  1. 以前我们打代码只能在浏览器里面显示效果,谷歌浏览器的控制台事实上就是V8引擎的界面,浏览器有窗口去看到页面效果

  2. nodejs环境就是一个脱离浏览器运行的V8引擎,它是没有显示页面的窗口

客户端 浏览器端

HTML

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
	</body>
	<script src="nodejs.js"></script>
</html>

JS 它如果不在HTM中嵌入,那这个脚本作用为零L

var a = 1;
setInterval(function() {
	console.log(a);
	a++
}, 1000)

服务器端 nodejs

var a = 1;
setInterval(function() {
	console.log(a);
	a++
}, 1000)

在node命令行里面执行

node nodejs.js

浏览器引入多个JS的时候

	<script src="nodejs.js"></script>
	<script src="module.js"></script>

服务端因人员多个JS的时候

	require("./nodejs.js");
	require("./module.js");

原生模块

//带路径的自定义模块(服务)
var module = require("./module.js");
//不带路径的原生模块
var http = require("http");

创建服务器的步骤

接下来我们使用 http.createServer() 方法创建服务器,并使用 listen 方法绑定 8888 端口。 函数通过 request, response 参数来接收和响应数据。

  1. 引入http模块
var http = require("http");
  1. 接下来我们使用 http.createServer() 方法创建服务器,并使用 listen 方法绑定 8888 端口。 函数通过 request, response 参数来接收和响应数据。
http.createServer(function(request,response){
	//改写头部,允许跨域
	response.setHeader('Access-Control-Allow-Origin','*');
	var obj = {
		name:'wscats',
		age:88
	}
	response.end(JSON.stringify(obj))
}).listen(12345)
  1. 打开服务器
node xxx-server.js

nodejs获取前端传过来的数据

$_GET['']
$_POST['']
var url = require("url");
var querystring = require("querystring")
var paramsStr = url.parse(request.url).query
var params = querystring.parse(paramsStr)//$_GET['']

回调函数

回调函数其实是解决异步编程的重要方法
因为异步是脱离了主线程的,回调可以让异步程序在执行完的时候触发某个方法,从而把它的逻辑带回到主线程

  1. 浏览器端出现这三个都是异步方法 ajax setTimeout setInterval

ajax

console.log("start");

		function ajax(callback) {
			var xmlhttp = new XMLHttpRequest();
			xmlhttp.open("GET", "../test.php", true);
			xmlhttp.send();
			xmlhttp.onreadystatechange = function() {
				if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
					callback(xmlhttp.responseText);
				}
			}
		}
		ajax(function(data) {
			console.log(data)
			console.log("end")
		})

定时器

console.log("start")
var a = 1;

function cb(callback) {
	setTimeout(function() {
		a = 2;
		//console.log(a)
		callback()
	}, 1000)
}
cb(function(){
	
	console.log(a)
})

fs读写文件

读写文件的方法

read 异步
readSync 同步
xxxSync就是同步方法

同步阻塞

var fs = require("fs");
console.log("start")
//同步读取
var data = fs.readFileSync("demo.txt");
console.log(data.toString())
console.log("end")

这样会输出 start->adcd->end
这里面会一直等到txt文件完全读完,才往下执行

异步非阻塞

var fs = require("fs");
console.log("start")
//异步读取
fs.readFile("demo.txt", function(err, data) {
	console.log(data.toString())
})
console.log("end")

这样会输出 start->end->adcd
这里面会不会等到txt文件完全读完,会一直往下走,知道txt读完再执行相应回调执行对应的逻辑

事件驱动

其实就是为了解决多层回调(回调地狱)
把异步编程,用同步的方式去编写,便于维护
eventEmitter.on()方法注册事件
eventEmitter.emit()方法广播事件

// 引入 events 模块
var events = require('events');
// 创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();
eventEmitter.on('event1', function() {
	setTimeout(function() {
		console.log("1")
		eventEmitter.emit('event2');
	}, 1000)
});
eventEmitter.on('event2', function() {
	setTimeout(function() {
		console.log("2")
		eventEmitter.emit('event3');
	}, 1000)
});
eventEmitter.on('event3', function() {
	setTimeout(function() {
		console.log("3")
	}, 1000)
});
//触发事件1
eventEmitter.emit('event1');

读写流

跟fs readFile和writeFile相比,读写流是分一部分一部分地写,readFile和writeFile一次性写入,所以在处理大型文件的时候一般用流的形式读或者写

var data = '';
// 创建可读流
var readerStream = fs.createReadStream('test.txt');
// 处理流事件 --> data, end, and error
readerStream.on('data', function(chunk) {
   data += chunk;
});
readerStream.on('end',function(){
   console.log(data);
});
var data = '菜鸟教程官网地址:www.runoob.com';
// 创建一个可以写入的流,写入到文件 output.txt 中
var writerStream = fs.createWriteStream('news.txt');
writerStream.write(data,'UTF8');
// 处理流事件 --> data, end, and error
writerStream.on('finish', function() {
    console.log("写入完成。");
});

管道流

一般是用于copy文件,可以是img,video,txt等文件,一般用于下载

var fs = require("fs");
// 创建一个可读流
var readerStream = fs.createReadStream('./video/1.avi');
// 创建一个可写流
var writerStream = fs.createWriteStream('./video/11.avi');
readerStream.pipe(writerStream);

链式流

//链式流
fs.createReadStream('./video/1.avi')
//.pipe(fs.createWriteStream('./video/11.avi'))
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('./video/video.avi.gz'));

API文档

NodeJS Api文档

模块

引入模块的方法就是

var fs = require("fs");

定义模块的方法

var obj = {
	name:'w'
}
module.exports = obj;

三种模块类型

内置模块 fs http url querystring

fs:操作文件的 http:创建服务器或者发送请求

url:处理路由的 querystring:处理字符串

var fs = require("fs");
//第三方模块 
//cnpm install XXX
var jquery = require("jquery")
//自定义模块
var fss = require("./fs")

CMD命令

cd 定位到文件夹
dir 查看目录
mkdir 新建文件夹

express框架

新建一个package.json,存放着我们模块的信息

npm init

安装express框架

npm install express

jade模板引擎

//views, 放模板文件的目录,比如: 
app.set('views', './views')
//view engine, 模板引擎
app.set('view engine', 'jade')
npm install jade

###express前后端分离
后端
前后端分离那就是后端只负责提供数据

var express = require("express");
var app = express();
//REST post get put delete
app.get("/", function(req, res) {
	res.append("Access-Control-Allow-Origin", "*");
	res.send("hello")
})
app.listen(8899)

前后端分离关键的一环就是用ajax或者jsonp或者表单提交,来通信数据

前端负责拿数据并且渲染
前端

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<p></p>
	</body>
	<script src="../js/jquery.js"></script>
	<script>
		$.ajax({
			type:"get",
			url:"http://localhost:8899/",
			async:true,
			success:function(data){
				console.log(data)
				$("p").html(data)
			}
		});
	</script>
</html>

优点:方便维护,简化了后端的工作,会更专注各自的工作,细化了工作流程
缺点:不利于SEO的优化,不方便爬虫

express 前后端不分离

后端

var express = require("express");
var app = express();
//REST post get put delete
app.get("/", function(req, res) {
	res.append("Access-Control-Allow-Origin", "*");
	var html = `
	<!DOCTYPE html>
	<html>
		<head>
			<meta charset="UTF-8">
			<title></title>
		</head>
		<body>
			<header>hello后端渲染</header>
			<p></p>
		</body>
		<script src="http://localhost:81/angular/0410/express/js/jquery.js"></script>
	<script>
		$.ajax({
			type:"get",
			url:"http://localhost:8899/data",
			async:true,
			success:function(data){
				console.log(data)
				$("p").html(data)
			}
		});
	</script>
	</html>
	`
	res.send(html)
})
app.get("/data", function(req, res) {
	res.send("hello前端渲染")
})
app.listen(8899)

优点:有利于SEO的优化
缺点:不方便维护,后端工作量就很大,不利于实现一些用户的交互效果

websocket

ws websocket 基于TCP/IP全双共通信协议 长连接

http ajax 短链接

ftp file://文件协议

			ws = new WebSocket("ws://loacalhost:7272");
			//打开WebSocket进行握手
			ws.onopen = function(){
				//第一次连接时候触发的回调
			}
			//服务端通信给你的消息
			ws.onmessage = function(){
				//连接成功后一直监听的回调函数
			}
			//服务端关闭时候触发的回调
			ws.onclose = function(){
				// 关闭 客户端关闭/服务端关闭
			}
			//出现错误时候的回调
			ws.onerror = function(){
				
			}
			//向websocket服务端发送数据
			ws.send()

socket.io

安装socket.io第三方模块

npm install socket.io

客户端文件引入的JS

<script src="https://cdn.socket.io/socket.io-1.4.4.js"></script>

websocket是不会跨域的,ajax是会跨域的
websocket是长连接,如果你不关掉浏览器或者终止连接,或者不关掉服务器,它是永远保持着连接的
ajax是短连接,成功后就更服务器脱离关系

Promise, Await, Deferred, Event Loop 和 Worker

promise

每一个异步请求立刻返回一个Promise对象,由于是立刻返回,所以可以采用同步操作的流程。而Promise的then方法,允许指定回调函数,在异步任务完成后调用
下面的setTimeout()可以代替理解为一个ajax请求,所以ajax请求同理

function a() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log('执行任务a');
            resolve('执行任务a成功');
        }, 1000);
    });
}

function b(value) {
    console.log(value)
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log('执行任务b');
            resolve('执行任务b成功');
        }, 2000);
    });
}

function c() {
    console.log('最后执行c')
}
a().then(b).then(c);
  • 如果then里return的值是promise则将resolve的结果传入下一个then
  • 如果then里return返回的不是promise则将结果直接传入下一个then

类promise

很多像promise的异步封装方法,比如angular1.x内置封装的$http方法,如下,可以实现多个回调的链式调用,避免了金字塔式的回调地狱

//1
$http({
	method: 'GET',
	url: 'news.json',
}).then(function successCallback(response) {
	console.log(response)
}, function errorCallback(response) {
	console.log(response)
})
//2
.then(function() {
	return $http({
		method: 'GET',
		url: 'data.json',
	})
}).then(function(data) {
	console.log(data)
})
//3
.then(function() {
	setTimeout(function() {
		console.log("定时器")
	}, 1000)
})

await

await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西。

如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。

如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

function a() {
  return new Promise(function(resolve){
    setTimeout(()=>{
    	console.log("a")
    	resolve()
    },1000)
  });
}

function b() {
  return new Promise(function(resolve){
    setTimeout(()=>{
    	console.log("b")
    	resolve()
    },1000)
  });
}

function c() {
  return new Promise(function(resolve){
    setTimeout(()=>{
    	console.log("c")
    	resolve()
    },1000)
  });
}

//ES6
a()
  .then(b)
  .then(c);

//ES2017
await a();
await b();
await c();

await等待的虽然是promise对象,但不必写.then(..),直接可以得到返回值,所以使用await就没有了多个then的链式调用

var sleep = function (time) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve();
        }, time);
    })
};

var start = async function () {
    // 在这里使用起来就像同步代码那样直观
    console.log('start');
    await sleep(3000);
    console.log('end');
};
start();
  • async表示这是一个async函数,await只能用在这个函数里面。
  • await表示在这里等待promise返回结果了,再继续执行。
  • await后面跟着的应该是一个promise对象(当然,其他返回值也没关系,不过那样就没有意义了)

deferred

$.ajax()操作完成后,如果使用的是低于1.5.0版本的jQuery,返回的是XHR对象,你没法进行链式操作;如果高于1.5.0版本,返回的是deferred对象,可以进行链式操作,done()相当于success方法,fail()相当于error方法。采用链式写法以后,代码的可读性大大提高,deferred对象的一大好处,就是它允许你自由添加多个回调函数

$.when(function(dtd) {
	var dtd = $.Deferred(); // 新建一个deferred对象
	setTimeout(function() {
		console.log(0);
		dtd.resolve(1); // 改变deferred对象的执行状态 触发done回调
		//dtd.reject(); //跟resolve相反,触发fail回调
	}, 1000);
	return dtd;
}()).done(function(num) {
	console.log(num);
}).done(function() {
	console.log(2);
}).done(function() {
	console.log(2);
})

//ajax默认就是返回deferred对象
$.when($.post("index.php", {
		name: "wscat",
	}), $.post("other.php"))
	.done(function(data1, data2) {
		//两个ajax成功才可以进入done回调
		console.log(data1, data2);
	}).fail(function(err) {
		//其中一个ajax失败都会进入fail回调
		console.log(err)
	})

event loop

先处理微任务队列再处理宏任务队列

微任务 宏任务
then setTimeout
console.log('script start');
setTimeout(function() {
  console.log('setTimeout');
}, 0);//定时器为宏任务
Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {//then为微任务
  console.log('promise2');
});
console.log('script end');
//先同步后异步
//先清空微任务再清空宏任务

输出的顺序是:script start, script end, promise1, promise2, setTimeout

console.log('script start');
setTimeout(function() {
  console.log('timeout1');
}, 10);
new Promise(resolve => {
    console.log('promise1');
    resolve();
    setTimeout(() => console.log('timeout2'), 10);
}).then(function() {
    console.log('then1')
})
console.log('script end');

输出的顺序是:script start, promise1, script end, then1, timeout1, timeout2

配置await/async环境

安装一下依赖

npm i -D babel-core babel-polyfill babel-preset-es2015 babel-preset-stage-0 babel-loader

新建.babelrc文件,输入以下内容

{
    "presets": [
        "stage-0",
        "es2015"
    ]
}

新建一份index.js,把你的逻辑文件app.js,后面require的任何模块都交给babel处理,polyfill支持awaitasync

require("babel-core/register");
require("babel-polyfill");
require("./app.js");

参考Babel 6 regeneratorRuntime is not defined

await,async,promise三者配合

//async定义里面的异步函数顺序执行
((async() => {
	try {
		//await相当于等待每个异步函数执行完成,然后继续下一个await函数
		const a = await (() => {
			return new Promise((resolve, reject) => {
				setTimeout(() => {
					console.log(1)
					resolve(2);
					//reject(3)
				}, 1000)
			});
		})();
		const b = await (() => {
			return new Promise((resolve, reject) => {
				setTimeout(() => {
					console.log(1)
					//resolve(2);
					reject(3)
				}, 1000)
			});
		})();
		console.log(4)
		console.log(a) //3
		return b;
	} catch(err) {
		//上面try中一旦触发reject则进入这个分支
		console.log(err);
		return err;
	}
})()).then((data) => {
	console.error(data)
}).catch((err) => {
	console.error(err)
})
//分别输出213

注意点:

  • async用来申明里面包裹的内容可以进行同步的方式执行,await则是进行执行顺序控制,每次执行一个await,程序都会暂停等待await返回值,然后再执行之后的await。
  • await后面调用的函数需要返回一个promise,另外这个函数是一个普通的函数即可,而不是generator。
  • await只能用在async函数之中,用在普通函数中会报错。
  • await命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中。

当然我个人觉得下面写法比较清晰点

//利用try...catch捕捉Promise的reject
async function ajax(data) {
	try {
		return await new Promise((resolve, reject) => {
			setTimeout(() => {
				console.log(data)
				resolve(data); //成功
			}, 2000);
		});
	} catch(err) {}
}
async function io() {
	try {
		const response = await new Promise((resolve, reject) => {
			setTimeout(() => {
				reject("io"); //失败
			}, 1000);
		});
		//resolve执行才会执行下面这句return 
		return response
	} catch(err) {
		console.log(err);
	}
}
//异步串行
(async() => {
	await ajax("ajax1");
	await ajax("ajax2");
	await io();
})()

(async() => {
    let [ajax1, ajax2] = await Promise.all([ajax("ajax1"), ajax("ajax2"),io()]);
    return [ajax1,ajax2]
})()

worker

Web Worker 是 HTML5 标准的一部分,这一规范定义了一套 API,允许一段 JavaScript 程序运行在主线程之外的另外一个线程中。将一些任务分配给后者运行。在主线程运行的同时,子线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程通常负责 UI 交互就会很流畅,不会被阻塞或拖慢。

<input id="btn" type="button" value="通信" />
<script>
    let myWorker = new Worker('./worker.js');
    let button = document.querySelector('#btn');
    // myWorker.onmessage = function (event) { // 接收
    //     console.log('子线程通知主线程:' + event.data);
    //     myWorker.terminate(); // 暂停
    // }
    myWorker.addEventListener('message', function (e) {
        console.log('子线程通知主线程:' + event.data);
        myWorker.terminate(); // 暂停
    });
    // 监听 error 事件
    myWorker.addEventListener('error', function (e) {
        console.log('错误', e);
    });
    button.onclick = function () {
        myWorker.postMessage("主线程通知子线程");  // 启动消息发送线程发送消息
    }
</script>

worker.js

addEventListener('message', function (e) {
    postMessage('子线程通知主线程: ' + e.data);
}, false);

由于多线程需要在同域情况下进行,所以我们可以借助 blob 把它放到同一个文件下执行

let script = 'console.log("hello world!");'
let workerBlob = new Blob([script], { type: "text/javascript" });
let url = URL.createObjectURL(workerBlob);
let myWorker = new Worker(url);

堆和栈

image

区别 堆(heap) 栈(stack)
结构 heap是没有结构的,数据可以任意存放。heap用于复杂数据类型(引用类型)分配空间 stack是有结构的,每个区块按照一定次序存放(后进先出),stack中主要存放一些基本类型的变量和对象的引用,存在栈中的数据大小与生存期必须是确定的。可以明确知道每个区块的大小,因此,stack的寻址速度要快于heap
速度
图示 image image
类型 引用类型:对象,数组的内容 Boolean、Number、String、Undefined、Null,以及对象变量的指针
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便 栈由系统自动分配,速度较快。但程序员是无法控制的
对堆而言,数据项位置没有固定的顺序。你可以以任何顺序插入和删除,因为他们没有顶部数据这一概念 对栈而言,栈中的新加数据项放在其他数据的顶部,移除时你也只能移除最顶部的数据(不能越位获取)

使用new关键字初始化的之后是不存储在栈内存中的。为什么呢?new大家都知道,根据构造函数生成新实例,这个时候生成的是对象,而不是基本类型。再看一个例子

var a = new String('123')
var b = String('123')
var c = '123'
console.log(a==b, a===b, b==c, b===c, a==c, a===c)  
>>> true false true true true false
console.log(typeof a)
>>> 'object'

我们可以看到new一个String,出来的是对象,而直接字面量赋值和工厂模式出来的都是字符串。但是根据我们上面的分析大小相对固定可预期的即便是对象也可以存储在栈内存的,比如null,为啥这个不是呢?再继续看

var a = new String('123')
var b = new String('123')
console.log(a==b, a===b)
>>> false false

很明显,如果a,b是存储在栈内存中的话,两者应该是明显相等的,就像null === null是true一样,但结果两者并不相等,说明两者都是存储在堆内存中的,指针指向不一致

说到这里,再去想一想我们常说的值类型和引用类型其实说的就是栈内存变量和堆内存变量,再想想值传递和引用传递、深拷贝和浅拷贝,都是围绕堆栈内存展开的,一个是处理值,一个是处理指针

堆、栈、队列之间的区别是?

  • 堆是在程序运行时,而不是在程序编译时,申请某个大小的内存空间。即动态分配内存,对其访问和对一般内存的访问没有区别。
  • 栈就是一个桶,后放进去的先拿出来,它下面本来有的东西要等它出来之后才能出来。(后进先出)
  • 队列只能在队头做删除操作,在队尾做插入操作.而栈只能在栈顶做插入和删除操作。(先进先出)

内存分配和垃圾回收

一般来说栈内存线性有序存储,容量小,系统分配效率高。而堆内存首先要在堆内存新分配存储区域,之后又要把指针存储到栈内存中,效率相对就要低一些了

垃圾回收方面,栈内存变量基本上用完就回收了,而推内存中的变量因为存在很多不确定的引用,只有当所有调用的变量全部销毁之后才能回收

传值和传址

从一个向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终指向同一个对象。即复制的是栈中的地址而不是堆中的对象

从一个变量复向另一个变量复制基本类型的值,会创建这个值的副本

参考文章

node下forEach实现异步

因为forEach是一个异步方法,在我实践中发现,比如我要遍历一个数组,但是数组里面读取每一项都有异步操作,比如读写文件存数据库,所以我们需要每异步遍历而非同步遍历

使用async库

安装async模块

npm install async

注意callback(null)固定放在异步函数里面,这里会把所有异步操作执行完之后再执行回调函数输出console.log('done');

const async = require('async');
async.each(
    ['file1', 'file2', 'file3'],
    (item, callback) => {
        setTimeout(
            () => {
                console.log('item:', item);
                callback(null);
            },
            1000
        );
    },
    () => {
        console.log('done');
    }
);

使用promise

var arry = [...];
Promise.all(arry.map(function(elem){
  return new Promise(function(resolve, reject){
    ...
    resolve(result);
  })

})).then(function(data){
  //在这就可以等所有的返回结果可以得到
})

参考文章

express配置session

参考文档

token

跨域传cookies

很多浏览器默认是不传递cookies的,可以在前后端分别配置如下进行跨域传递

$.ajax({
	type: "get",
	url: "//localhost:3000/fe/getFemaleList",
	crossDomain: true,
	xhrFields: {
		withCredentials: true//允许cookie传递
	},
	async: true,
	data: {
		mobile: self.mobile,
		vcode: self.vcode
	},
	success(data) {
		console.log(data)
	}
});
app.use(function(req, res, next) {
	//res.append("Access-Control-Allow-Origin", "*");
	res.append("Access-Control-Allow-Origin", "http://localhost:12345");
	res.append("Access-Control-Allow-Credentials", true);// Allow Cookie
	//res.append("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
});

链式调用和生成器函数

链式调用

如果我们经常使用 jQuery ,我们写代码就会有时候这样写,这种就是我们最常用的链式调用

$(ele).show().find(child).hide()

实现这种链式调用的本质是,方法体内返回对象实例自身 this,让下一个对象的属性能够继续获取该对象的其他属性从而一直驱动着程序

var obj = {
    a: 1,
    func: function () {
        this.a += 1;
        return this
    }
}
obj.func().func();
console.log(Obj.a); // 3

但是如果链式调用中出现异步的函数的时候,我们就要用一个数组来进行集中式管理,类似一个队列的顺序规范,下面的 Queue 类就是一个重要的例子,关键点在于数组this.task = []还有next()的出队列的过程,让每一个函数在链式调用的时候进去数组this.task = []里面缓存起来,然后用用next()不断触发下一个函数的执行,这里面涉及数组的几个常用的方法,比如要置顶到队列前排,类似插队的草最就用unshift(),平时的按正常顺序进队列最后排的就用push(),而从头部出队列执行就用shift()

方法 作用
shift() 删除并返回数组的第一个元素
unshift() 向数组的开头添加一个或更多元素,并返回新的长度
push() 向数组的末尾添加一个或更多元素,并返回新的长度
class Queue {
    constructor() {
        this.task = [];
        setTimeout(() => {
            this.next();
        }, 0)
    }
    next() {
        // 先进先出 队列的过程
        let fn = this.task.shift(); // 删除并返回数组的第一个元素
        fn && fn();
    }
    // 置顶执行
    // 任务队列头部添加,然后执行头部
    first(callback, timer) {
        let fn = () => {
            setTimeout(() => {
                callback();
                this.next();
            }, timer * 1000)
        }
        this.task.unshift(fn); // 向数组的开头添加一个或更多元素,并返回新的长度
        return this;
    }
    // 延时执行
    // 任务队列尾部添加,然后执行头部
    time(callback, timer) {
        let fn = () => {
            setTimeout(() => {
                callback()
                this.next();
            }, timer * 1000)
        }
        this.task.push(fn); // 向数组的末尾添加一个或更多元素,并返回新的长度
        return this;
    }
    // 顺序执行
    // 和延时执行其实一样,只是延时会卡在哪里等待过完再往下走
    // 任务队列尾部添加,然后执行头部
    work(callback) {
        let fn = () => {
            callback();
            this.next();
        }
        this.task.push(fn);
        return this;
    }
}
new Queue()
    .first(() => {
        console.log(0)
    }, 2)
    .first(() => {
        console.log(1)
    }, 2)
    .work(() => {
        console.log(2)
    })
    .time(() => {
        console.log(3)
    }, 1)
// 1 0 2 3
// 两个置顶先执行,谁后置顶谁执行

生成器函数

function* 这种声明方式(function关键字后跟一个星号)会定义一个生成器函数(generator function),它返回一个 Generator 对象

function* fn() {
    console.log(1);
    //暂停!
    yield;
    //调用next方法继续执行
    console.log(2);
}
var iter = fn();
iter.next(); //1
iter.next(); //2

生成器函数在执行时能暂停,后面又能从暂停处继续执行,每 next 一次就执行每个 yield 间隔之间的代码,本质就是动态移动指针

  • 函数生成器特点是函数名前面有一个*
  • 通过调用函数生成一个控制器
  • 调用next()方法开始执行函数
  • 遇到yield函数将暂停
  • 再次调用next()继续执行函数

调用next()方法时,如果传入了参数,那么这个参数会作为上一条执行的yield语句的返回

function *gen(){
    yield 10;
    y=yield 'foo';
    yield y;
}

var gen_obj=gen();
console.log(gen_obj.next());// 执行 yield 10,返回 10
console.log(gen_obj.next());// 执行 yield 'foo',返回 'foo'
console.log(gen_obj.next(10));// 将 10 赋给上一条 yield 'foo' 的左值,即执行 y=10,返回 10
console.log(gen_obj.next());// 执行完毕,value 为 undefined,done 为 true

传递参数

function *createIterator() {
    let first = yield 1;
    let second = yield first + 2; // 4 + 2 
                                  // first =4 是next(4)将参数赋给上一条的
    yield second + 3;             // 5 + 3
}

let iterator = createIterator();
console.log(iterator.next());    // "{ value: 1, done: false }"
console.log(iterator.next(4));   // "{ value: 6, done: false }"
console.log(iterator.next(5));   // "{ value: 8, done: false }"
console.log(iterator.next());    // "{ value: undefined, done: true }"

注意显式返回和生成器函数不能当构造器使用

function* yieldAndReturn() {
  yield "Y";
  return "R";//显式返回处,可以观察到 done 也立即变为了 true
  yield "unreachable";// 不会被执行了
}
var gen = yieldAndReturn()
console.log(gen.next()); // { value: "Y", done: false }
console.log(gen.next()); // { value: "R", done: true }
console.log(gen.next()); // { value: undefined, done: true }
function* f() {}
var obj = new f; // throws "TypeError: f is not a constructor"

一个Node.js饭店的发展历程

一个Node.js饭店的发展历程

第一年

饭店开张,只有一个厨师(同时还兼任老板、服务员、打荷、收银员),当一个客人点餐之后,这个厨师就开始记录(服务员),然后他就开始备菜(打荷)、炒菜(厨师)、然后上菜(服务员)、收钱(收银员),这个时候即使有其他客人来了,等着吧还没忙完呢。这个厨师就这样兢兢业业,有条不紊的干着每一件事,因为每件事都是亲力亲为,都不能出错,虽然所有的事情都了然于心,但效率很低,一天只能卖出十多份饭菜。

这是饭店单线程的第一年:

  • 利:它没有线程上下文交换所带来性能上的开销(因为每件事都是亲力亲为);
  • 弊:无法利用多核CPU(厨房空间那么大,完全可以很多人一起干活),同时错误会引起整个应用退出,应用的健壮性值得考验(当这个厨师生病,或者有事了饭店就得停业)

第二年

这个厨师第一年赚了点钱,回到老家把表哥、表弟全拉过来,现在他们有5个人,可工作方式还是跟厨师第一年的时候一样。当客人来了,就会有一个人去记录,然后自己去厨房洗菜、切菜、炒菜、洗碗、上菜、收钱。当来了第六个客人的时候,就要等待前面的人做完所有事情才能空出一个人来接待。后来他们就想既然客户多了,厨师就得多,再回老家多叫几个兄弟吧。这时新的问题发生了,当每个厨师做完饭后就出去找客人,客人说我刚刚点餐了,然后厨师就去厨房问,刚刚是哪个表兄接待的那个客户,要是没有人接待的话,我来处理。就这样忙忙碌碌一年,他们比去年多做了好多生意,但是感觉每天客户多的时候,厨房里头乱糟糟的,总要询问这个询问那个。

这就是饭店多线程的一年:

  • 利:一个线程服务一个请求,线程之间可以共享数据,这样可以避免内存的浪费,可以同时处理多个请求;
  • 弊:操作系统内核在切换线程的同时也要切换线程的上下文,当线程数量过多时,时间将会被消耗在上下文切换上(厨房里头乱糟糟的,总要询问这个询问那个)。

第三年

老板娘过来了(Node.Js闪亮登场),她发现这帮厨师都在各自为战,自己拿到客户的点餐后去洗菜、切菜、洗碗、炒菜、上菜、收钱,一个人只能同时处理一个任务,而且作为厨师没必要去做洗菜、切菜、洗碗、收银之类费时的工作。

所以老板娘把所有人进行了分工:

老板作为厨师长(Node里头的主线程),他不再去洗碗、洗菜、切菜、炒菜、收银(我们可以把洗碗、洗菜、炒菜、切菜认为是比较耗时的I/O),他只负责将收银台小妹(观察者)拿过来的菜单分配给不同的厨师,打荷(这些人就是不同的I/O线程),吩咐下去之后他不会等菜出来再走(进入下一个轮询),又问收银台小妹还有没有菜单要做,如果有继续轮询,如果没有了休息(退出进程)。当菜做出来之后放在上菜区(回调),收银台就显示菜出来了(将回调放入事件队列),当老板查询收银员(观察者)的时候,收银员就告诉厨师长,厨师长就通知服务员(处理不同I/O的线程)上菜(完成回调),这样饭店有条不紊的运行下去,客人也越来越多了。

有些高端客户不想在这挤,所以老板娘就想,饭店房子那么大(多核CPU),可以叫她弟弟(子进程的主线程)在这开个子店(child_process),然后发现一个收银员不够用,那就多招几个收银员(多个观察者),就这样每个分店(进程)只有一个主管(主线程),主管的弱点就是无暇顾及洗碗等杂活(I/O),只能关注业务,至于饭店能开多大(应用程序能处理多大请求),要看主管的处理能力(主线程的编程强壮度)。最后,大饭店起了个时髦的名字叫Hotel NodeJs!

参考文档

Node.js应用实战和工作原理解析

计算两个经纬度之间的距离

function (lat1, lng1, lat2, lng2) {
    var radLat1 = lat1 * Math.PI / 180.0;
    var radLat2 = lat2 * Math.PI / 180.0;
    var a = radLat1 - radLat2;
    var b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0;
    var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
    s = s * 6378.137;
    s = Math.round(s * 10000) / 10000;
    return s
};

Egret

入口文件

Main.ts,清空createGameScene里面的方法,里面是所有场景的起点

protected createGameScene(): void {}

添加背景

var bg:egret.Shape = new egret.Shape();
bg.graphics.beginFill( 0x336699 );
bg.graphics.drawRect( 0, 0, this.stage.stageWidth, this.stage.stageHeight ); 
bg.graphics.endFill();
this.addChild(bg);

添加文字

var tx:egret.TextField = new egret.TextField();
tx.text = "I'm Jack, I will use Egret create a fantasy mobile game!"; 
tx.size = 32; 
this.addChild( tx );

添加事件

var tx: egret.TextField = new egret.TextField();
tx.text = "I'm Jack, I will use Egret create a fantasy mobile game!";
tx.size = 32;
tx.touchEnabled = true;//记得打开该对象的事件监听,所有对象默认都是不开启事件监听的,为了性能 
tx.addEventListener(egret.TouchEvent.TOUCH_TAP
    , function (evt: egret.TouchEvent): void {
        tx.textColor = 0x00ff00;
    }, this);
this.addChild(tx);

除了运用匿名函数,还可以这样添加事件

tx.touchEnabled = true; 
tx.addEventListener( egret.TouchEvent.TOUCH_TAP, this.touchHandler, this );
private touchHandler( evt:egret.TouchEvent ):void{
    var tx:egret.TextField = evt.currentTarget;
    tx.textColor = 0x00ff00; 
}

npm install wsscats

package信息

在npm上传了一个模块,用angular+weui+nodejs写的一个新闻DEMO

{
  "name": "wsscats",
  "version": "1.0.0",
  "description": "angular and weui and node server",
  "main": "http.js",
  "scripts": {
    "test": "node http.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/Wscats"
  },
  "keywords": [
    "node",
    "weui",
    "angular"
  ],
  "author": {
    "name": "wscats"
  },
  "license": "ISC",
  "readme": "ERROR: No README data found!",
  "_id": "[email protected]",
  "_shasum": "f65cbb62ed57b04e06df22302ebfce7b1c9c1b54",
  "_from": "wsscats@latest"
}

wsscats模块

安装wsscats模块
npm install wsscats
定位到模块所在目录
cd node_modules/wsscats
运行代码
npm run test

添加查找我是谁

image

npm adduser
npm whoami
npm publish

上传模块

如果之前设置过cnpm,那就要改回来

npm config set registry http://registry.npmjs.org

发布订阅模式

emit和on

这一点有点像vuexredux里面的某部分概念,也跟$emit$on和node自带的event模块作用很相像,其实可以这样理解,如果单独用对象把数据存起来,数据改变的时候没有人会追踪到,所以这里在每次改变数据前都放入一个或多个回调函数形成队列去监听on方法,这些回调函数在队列中等待,直到触发了某些机制,这些函数才按顺序逐一回来触发emit方法,从而可以在这个时刻监听到新的数据变化并完成逻辑

let weux = {};
// 这次换成一个对象类型的缓存列表
weux.list = {};
weux.on = function (key, fn) {
    // 如果对象中没有对应的key值
    // 也就是说明没有订阅过
    // 那就给key创建个缓存列表
    if (!this.list[key]) {
        this.list[key] = [];
    }
    // 把函数添加到对应key的缓存列表里
    this.list[key].push(fn);
};
weux.emit = function (key, param) {
    // 或者let key = [].shift.call(arguments);
    // 或者let fns = this.list[key];
    // 根据获取改函数数组队列
    let fns = this.list[key];
    // 如果缓存列表里没有函数就返回false
    if (!fns || fns.length === 0) {
        return false;
    }
    // 遍历key值对应的缓存列表
    // 依次执行函数的方法
    fns.forEach(fn => {
        // 传入参数
        fn(param);
        // 或者 fn.apply(this, arguments);
    });
};
// 测试用例
weux.on('test', (param) => {
    console.log('位置:' + param.position);
    console.log('技能:' + param.skill);
});
weux.emit('test', {
    position: '前端',
    skill: ['ps', 'css', 'js']
});

简单的队列

可以没有仓库存放中介值,也可以$emit$on的健值对,只有一个简单的队列,按放入的顺序,然后按顺序重新执行出来

class Subject {
    constructor() {
        this.observers = []
    }
    addObserver(observer) {
        this.observers.push(observer)
    }
    removeObserver(observer) {
        var index = this.observers.indexOf(observer)
        if (index > -1) {
            this.observers.splice(index, 1)
        }
    }
    notify() {
        this.observers.forEach(observer => {
            observer.update()
        })
    }
}
let subject = new Subject()
class Observer {
    constructor(vm, key, callback) {
        this.vm = vm // mvvm 对象
        this.key = key // data 的属性
        this.callback = callback //callback
    }
    // 更新值
    update() {
        console.log("update")
        this.callback()
    }
}
subject.addObserver(new Observer(document.body, {
    name: "wscats"
}, () => {
    console.log("回调函数")
}))
console.log(subject)
subject.notify();

在上面的基础上添加健值对,可以改写成下面这样,这样写可以应付大部分发布订阅模式的情况

class Subject {
    constructor() {
        this.observers = []
    }
    addObserver(key, observer) {
        var index = this.observers.indexOf(observer)
        if (!this.observers[key]) {
            // 没有创建新的队列
            this.observers[key] = []
        }
        this.observers[key].push(observer)
    }
    removeObserver(observer) {
        var index = this.observers.indexOf(observer)
        if (index > -1) {
            this.observers.splice(index, 1)
        }
    }
    // 这里其实不但可以放key,还可以再增加一个参数传进观察者里面,在触发的时候,使用该函数
    notify(key) {
        this.observers[key].forEach(observer => {
            observer.update()
        })
    }
}
let subject = new Subject()
// 可以添加观察者,并在里面存着数据和方法,并保留着一个update方法给notify时候触发
class Observer {
    constructor(vm, key, callback) {
        this.vm = vm // mvvm 对象
        this.key = key // data 的属性
        this.callback = callback //callback
    }
    // 更新值
    update() {
        console.log("update")
        this.callback()
    }
}
subject.addObserver("test1", new Observer(document.body, {
    name: "wscats"
}, () => {
    console.log("回调函数1")
}))
subject.addObserver("test2", new Observer(document.body, {
    name: "wscats"
}, () => {
    console.log("回调函数2")
}))
subject.notify("test1");
subject.notify("test2");
console.log(subject)

类似vuex和redux

继续往下改造,可以改成类似vuex的模式,在仓库中留着一份共享的数据,然后利用on缓存回调函数事件放入队列里面,然后利用emit在合适的时候释放该队列

let wuex = {
    store: {
        position: '温哥华',
        skill: ['jsx', 'ts']
    },
    list: {},
    on: function (key, fn) {
        // 如果有则继续加队列
        if (!this.list[key]) {
            // 没有创建新的队列
            this.list[key] = [];
        }
        this.list[key].push(fn);
    },
    emit: function (key, param) {
        let fns = this.list[key];
        // 遍历更改仓库的值
        for (p in param) {
            this.store[p] = param[p]
        }
        // 触发数组中的回调函数
        fns.forEach(fn => {
            fn(param);
        });
    }
};
wuex.on('test', (param) => {
    console.log('位置:' + param.position);
    console.log('技能:' + param.skill);
});
wuex.on('test', (param) => {
    console.log('位置:' + param.position);
    console.log('技能:' + param.skill);
});
wuex.emit('test', {
    position: "香港",
    skill: ['ps', 'css', 'js']
});

组件通信

发布订阅模式可以用在,两个组件之间的通信

实现双向数据绑定

配合Object.defineProperty实现类似Vue的双向数据绑定

let store = {
    list: {},
    on: function (key, fn) {
        // 如果有则继续加队列
        if (!this.list[key]) {
            // 没有创建新的队列
            this.list[key] = [];
        }
        this.list[key].push(fn);
    },
    emit: function (key, param) {
        let fns = this.list[key];
        fns.forEach(fn => {
            fn(param);
        });
    }
};


class Vue {
    constructor(options = {}) {
        this.$el = document.querySelector(options.el);
        let data = this.data = options.data;
        Object.keys(data).forEach((key) => {
            this.proxyData(key);

        });
        this.watcherTask = {}; // 需要监听的任务列表
        this.observer(data); // 初始化劫持监听所有数据
        this.compile(this.$el); // 解析dom
    }
    // 把data挂到Vue实例化后的vm对象里面,vm.data.name获取或者改变,则触发get或者set
    proxyData(key) {
        let that = this;
        Object.defineProperty(that, key, {
            configurable: false,
            enumerable: true,
            get() {
                return that.data[key];
            },
            set(newVal) {
                console.log(newVal)
                that.data[key] = newVal;
            }
        });
    }
    observer(data) {
        let that = this
        Object.keys(data).forEach(key => {
            let value = data[key]
            this.watcherTask[key] = []
            Object.defineProperty(data, key, {
                configurable: false,
                enumerable: true,
                get() {
                    return value
                },
                set(newValue) {
                    console.log(key)
                    if (newValue !== value) {
                        value = newValue
                        // 发布
                        // 更改vm的数据的时候触发set,然后会触发compileText的订阅store.on监听事件
                        store.emit(key, {})
                    }
                }
            })
        })
    }
    compile(el) {
        var nodes = el.childNodes;
        for (let i = 0; i < nodes.length; i++) {
            const node = nodes[i];
            if (node.nodeType === 3) {
                var text = node.textContent.trim(); //移除字串前後的空白字元以及行結束字元。
                if (!text) continue;
                this.compileText(node, 'textContent')
            }
        }
    }
    compileText(node, type) {
        var self = this;
        // 匹配出该节点下的{{}}这种写法
        let reg = /\{\{(.*?)\}\}/g,
            txt = node.textContent;
        if (reg.test(txt)) {
            node.textContent = txt.replace(reg, (matched, value) => {
                // matched    {{name}}
                // value      name
                // 订阅
                store.on(value, () => {
                    //node, this, value, type
                    node[type] = self.data[value]
                });
                return this[value]
            })
        }
    }
}

var vm = new Vue({
    el: "#demo",
    data: {
        skill: "ps"
    }
})

ES6的写法

class Subject {
    constructor() {
        this.observers = []
    }
    addObserver(key, observer) {
        var index = this.observers.indexOf(observer)
        if (!this.observers[key]) {
            // 没有创建新的队列
            this.observers[key] = []
        }
        this.observers[key].push(observer)
    }
    removeObserver(observer) {
        var index = this.observers.indexOf(observer)
        if (index > -1) {
            this.observers.splice(index, 1)
        }
    }
    // 这里其实不但可以放key,还可以再增加一个参数传进观察者里面,在触发的时候,使用该函数
    notify(key) {
        this.observers[key].forEach(observer => {
            observer.update()
        })
    }
}
// 可以添加观察者,并在里面存着数据和方法,并保留着一个update方法给notify时候触发
class Observer {
    constructor(node, vm, value, type, callback) {
        this.node = node // node节点
        this.vm = vm // mvvm 对象
        this.value = value // data 的属性
        this.type = type // node类型
        this.callback = callback //callback
    }
    // 更新值
    update() {
        console.log("update")
        this.callback()
    }
}


class Vue {
    constructor(options = {}) {
        this.$el = document.querySelector(options.el);
        let data = this.data = options.data;
        Object.keys(data).forEach((key) => {
            this.proxyData(key);

        });
        this.subject = new Subject(); // 需要监听的任务列表
        this.observer(data); // 初始化劫持监听所有数据
        this.compile(this.$el); // 解析dom
    }
    // 把data挂到Vue实例化后的vm对象里面,vm.data.name获取或者改变,则触发get或者set
    proxyData(key) {
        let that = this;
        Object.defineProperty(that, key, {
            configurable: false,
            enumerable: true,
            get() {
                return that.data[key];
            },
            set(newVal) {
                console.log(newVal)
                that.data[key] = newVal;
            }
        });
    }
    observer(data) {
        let that = this
        Object.keys(data).forEach(key => {
            let value = data[key]
            this.subject.observers[key] = []
            Object.defineProperty(data, key, {
                configurable: false,
                enumerable: true,
                get() {
                    return value
                },
                set(newValue) {
                    console.log(key)
                    if (newValue !== value) {
                        value = newValue
                        // 发布
                        // 更改vm的数据的时候触发set,然后会触发compileText的订阅store.on监听事件
                        that.subject.notify(key)
                    }
                }
            })
        })
    }
    compile(el) {
        var nodes = el.childNodes;
        for (let i = 0; i < nodes.length; i++) {
            const node = nodes[i];
            if (node.nodeType === 3) {
                var text = node.textContent.trim(); //移除字串前後的空白字元以及行結束字元。
                if (!text) continue;
                this.compileText(node, 'textContent')
            }
        }
    }
    compileText(node, type) {
        var self = this;
        // 匹配出该节点下的{{}}这种写法
        let reg = /\{\{(.*?)\}\}/g,
            txt = node.textContent;
        if (reg.test(txt)) {
            node.textContent = txt.replace(reg, (matched, value) => {
                // matched    {{name}}
                // value      name
                // 订阅
                this.subject.addObserver(value, new Observer(
                    node, this, value, type, () => {
                        node[type] = self.data[value]
                    }))
                return this[value]
            })
        }
    }
}

var vm = new Vue({
    el: "#demo",
    data: {
        skill: "ps"
    },
})

redis配置

安装

在这个地址里下载redis的压缩文件github下载地址解压到C:\redis目录下

image

运行

C:\redis该目录下打开cmd命令执行一下命令启动redis服务

redis-server.exe redis.windows.conf

image

测试

这时候另启一个cmd窗口,原来的不要关闭,不然就无法访问服务端了,切换到redis目录下运行一下命令

redis-cli.exe -h 127.0.0.1 -p 6379

设置键值对

set myKey abc

取出键值对

get myKey

image

配置node_redis

安装redis模块

npm install redis

配置和连接

var redis = require("redis");
var client = redis.createClient();//默认localhost和6379端口
//相当于var client  = redis.createClient('6379', '127.0.0.1');
//监听错误
client.on("error", function(err) {
	console.log("Error " + err);
});

设置

方法 作用
client.hmset("key",obj,function(err) {} 为一个Key一次设置多个哈希键/值,多用于JSON对象的写入
client.hgetall("key",function(err, object) {}) 读取一个Key的所有哈希键/值,多用于JSON对象读取

读写JSON的例子

//写入JavaScript(JSON)对象
client.hmset('sessionid', {
	username: 'wscats',
	password: 'password'
}, function(err) {
	console.log("错误", err)
})

//读取JavaScript(JSON)对象
client.hgetall('sessionid', function(err, object) {
	console.log(object)
})

时效性

30s后sessionid将自动删除

client.expire('sessionid', 30);

这里如果要写定时清理数据,有两个比较有用的时间计算js 取得当天0点 / 23:59:59 时间

可视化

可以安装redis可视化工具方便管理redis数据库
image

参考文章

node的phantomjs

安装

去官网下载phantomjs的压缩文件并安装

PhantomJS官方地址
PhantomJS官方API
PhantomJS官方示例
PhantomJS GitHub

环境变量

打开我的电脑->右键属性->高级系统设置->高级标签->环境变量,在系统变量里找到Path将你的phantomjs添加到环境变量里。比方说我的路径添加的为D:\workspace\phantomjs\bin,切记不要少了前面那个分号

运行

可以开始我们的第一个PhantomJS程序了。打开你的工作目录,新建文件hello.js,敲入以下代码,phantomjs hello.js

// a phantomjs example
var page = require('webpage').create();
phantom.outputEncoding = "gbk";
page.open("http://www.cnblogs.com/front-Thinking", function(status) {
	if(status === "success") {
		console.log(page.title);
	} else {
		console.log("Page failed to load.");
	}
	phantom.exit(0);
});

列编辑、正则查找、替换

声明方式

1、构造函数方式

var reg = new RegExp('\d', 'gi');

2、字面量方式

var reg = /\d/gi;

修饰符

修饰符有三种:i, g, m 可以同时出现,没有顺序(即 giig 一样),请参考下方说明

修饰符 说明
i 忽略大小写匹配
g 全局匹配,即是匹配一个后继续匹配,直到结束
m 多行匹配,即是遇到换行后不停止匹配,直到结束

特殊字符

符号 作用
\ 做为转意,即通常在""后面的字符不按原来意义解释,如/b/匹配字符"b",当b前面加了反斜杆后/\b/,转意为匹配一个单词的边界。 或对正则表达式功能字符的还原,如""匹配它前面元字符0次或多次,/a/将匹配a,aa,aaa,加了""后,/a*/将只匹配"a*"
^ 匹配一个输入或一行的开头,/^a/匹配"an A",而不匹配"An a"
$ 匹配一个输入或一行的结尾,/a$/匹配"An a",而不匹配"an A"
. 匹配任意单个字符,除换行和结束符
* 匹配前面元字符0次或多次,/ba*/将匹配b,ba,baa,baaa
+ 匹配前面元字符1次或多次,/ba+/将匹配ba,baa,baaa
? 匹配前面元字符0次或1次,/ba?/将匹配b,ba
(x) 匹配x保存x在名为$1...$9的变量中
x y
{n} 精确匹配n次
{n,} 匹配n次以上
{n,m} 匹配n-m次
[xyz] 字符集(character set),匹配这个集合中的任一一个字符(或元字符)
[^xyz] 不匹配这个集合中的任何一个字符
[a-z] 任意字母 []中的表示任意一个都可以
[^a-z] 非字母 []中^代表除了
[\b] 匹配一个退格符
\b 匹配一个单词的边界
\B 匹配一个单词的非边界
\cX 这儿,X是一个控制符,/\cM/匹配Ctrl-M
\d 匹配一个字数字符,/\d/ = /[0-9]/
\D 匹配一个非字数字符,/\D/ = /[^0-9]/
\n 匹配一个换行符
\r 匹配一个回车符
\f 匹配换页符
\s 匹配一个空白字符,包括\n,\r,\f,\t,\v等
\S 匹配一个非空白字符,等于/[^\n\f\r\t\v]/
\t 匹配一个制表符
\v 匹配一个重直制表符
\w 匹配一个可以组成单词的字符(alphanumeric,这是我的意译,含数字),包括下划线,如[\w]匹配"$5.98"中的5,等于[a-zA-Z0-9]
\W 匹配一个不可以组成单词的字符,如[\W]匹配"$5.98"中的$,等于[^a-zA-Z0-9]
\0 匹配NUL字符

零宽断言

零宽断言用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言

先行断言 也叫零宽度正预测先行断言(?=表达式)表示匹配表达式前面的位置 例如[a-z]*(?=ing)可以匹配cooking singing中的cook与sing

注意:先行断言的执行步骤是这样的先从要匹配的字符串中的最右端找到第一个ing(也就是先行断言中的表达式)然后 再匹配其前面的表达式,若无法匹配则继续查找第二个ing再匹配第二个ing前面的字符串,若能匹配 则匹配

后发断言 也叫零宽度正回顾后发断言(?<=表达式)表示匹配表达式后面的位置 例如(?<=abc).*可以匹配abcdefg中的defg

注意:后发断言跟先行断言恰恰相反 它的执行步骤是这样的:先从要匹配的字符串中的最左端找到第一个abc(也就是先行断言中的表达式)然后 再匹配其后面的表达式,若无法匹配则继续查找第二个abc再匹配第二个abc后面的字符串,若能匹配则匹配,例如(?<=abc).*可以匹配abcdefgabc中的defgabc 而不是abcdefg

负向零宽断言 (?!表达式)也是匹配一个零宽度的位置,不过这个位置的“断言”取表达式的反值,例如 (?!表达式)表示表达式前面的位置,如果表达式 不成立,匹配这个位置;如果 表达式 成立,则不匹配:同样,负向零宽断言也有“先行”和“后发”两种,负向零宽后发断言为(?<!表达式)
  • 负向零宽后发断言(?<!表达式)
  • 负向零宽先行断言(?!表达式)
  • 负向零宽断言要注意的跟正向的一样

编辑工具中的搜索正则

使用小括号将匹配的字符串包上,然后替换值中可以通过$1表达式来获取到当前正在匹配的值,这里可以更复杂些,比如存在多个小括号,相应的可以使用$1、$2、$3来获取对应值,全部替换后,就是我们最终想要的数据了

比如以下是在搜索框中的正则表达式,就可以把HTML结构中的所有class属性给匹配出来

className="([^0-9]+)"

那我们可以在替换中

className={{$1}}

那就会把

className="xxx" //转化为
className={{xxx}}

相关方法

1、RegExp对象相关方法

方法名 使用场景 返回值 示例
test 判断是否匹配 true或false /\d/.test('eno yao 2019')
test 返回匹配的结果,与match类似 数组或null /\d/.exec('eno yao 2019')

2、String对象相关方法

方法名 使用场景 返回值 示例
match 返回匹配的结果,非全局条件下与exec返回结果一致,并拥有指向匹配字符串的信息,全局条件下一次性返回所有匹配的结果 数组或null 'eno yao 2019'.match(/\d/)
replace 将字符串替换成另外的字符串或者将正则的匹配字符串替换成其他子串 数组或null 'eno yao 2019'.replace(/\d/, '2019')
search 查找第一次匹配子串的位置,返回index值,否则返回-1 index 'eno yao 2019'.search(/\d/, '2019')
split 按约定字符串或字符串拆分数组,接受一个字符串或正则 index 'eno yao 2019'.search(/\d/, '2019')

Electron

安装

在命令行执行以下命令快速安装

# Clone the repository
$ git clone https://github.com/electron/electron-quick-start
# Go into the repository
$ cd electron-quick-start
# Install dependencies
$ npm install
# Run the app
$ npm start

执行完后打开应用的效果图
image

文件目录

  • .gitignore(git上传提交时候过滤文件)
  • index.html(electron应用页面主入口,前端)
  • main.js(控制electron应用窗口的生命周期和参数设置,窗口通信设置)
  • package.json(记录electron应用的依赖,和启动命令)
  • renderer.js(配合index.html使用的了逻辑文件,控制前端的逻辑,可以调用NodeJS所有的内置API和第三方模块)

调试

自动打开dev-tool工具,方便调试

mainWindow.webContents.openDevTools()

renderer

renderer.js是可以在这里调用node内置模块和npm的第三方模块,也可以直接操作DOM写页面逻辑

require('./renderer.js')

我们可以在这里编写前端逻辑,然后用node模块去调计算机系统的功能,比如创建服务器,执行系统命令

const express = require('express');
const fs = require('fs');
// 读取系统文件
fs.readFile('/Users/eno/Documents/Wscats/day1.md', (err,data) => {
        console.log(data.toString())
}
const app = express();
app.get('/', (req, res) => res.send('Hello World!'));
const exec = require('child_process').exec;
// Vue
methods: {
    test() {
        app.listen(3000, () => console.log('Example app listening on port 3000!')); // 启动服务器
        let cmdStr = 'ls'; // ls命令,读取当前electron开发目录的文件列表
        exec(cmdStr, function (err, stdout, stderr) { // 执行系统命令
            if (err) {
                console.log('error:' + stderr);
                return;
            } else {
                console.log(stdout);
            }
        });
},

新版新增了个preload.js,使renderer.js使用不了Node原生API,需要在主进程里面加入这句配置

function createWindow () {
  // Create the browser window.
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true, // <===
      preload: path.join(__dirname, 'preload.js')
    }
  })
}

当然如果没有这一句也可以这样,在preload.js暴露原生Node的模块接口,然后在renderer.js中就可以使用原生模块了

// preload.js
const fs = require('fs')
window.fs = fs

引入jQuery

安装jQuery模块,并在执行脚本中引入

const jquery = require('jquery');
loginedWindow.loadURL("https://github.com/Wscats");
loginedWindow.webContents.executeJavaScript(`
	window.$ = require("jquery");
	console.log($);
`);

页面通信

通过electron的ipcRenderer的send方法,可以实现在页面的脚本中触发nodejs中定义好的逻辑

require("electron").ipcRenderer.send("logined");
ipcMain.on("logined", function(event) {
	        //electron.app.quit();
		setTimeout(function() {
		mainWindow.hide()
	}, 5000)
});

比如在页面中你运行了一个脚本如下

//窗口的执行的脚本
mainWindow.webContents.executeJavaScript(`
	require("electron").ipcRenderer.send("answer","Wscats");
`);
//后端执行的逻辑
ipcMain.on("answer", function (event, data) {
        //执行的逻辑
});

脚本中的数据传递

通过字符串模板拼接,把脚本的数据添加到模板中

var account = {
	username: "wscats",
	email: "[email protected]",
	password: "wscats"
}
mainWindow.webContents.executeJavaScript(`
	$("[name='Account']").val("wscats");
	$("[name='PassWord']").val("wscats");
	//require("electron").ipcRenderer.send("logined");
	$("[type='submit']").trigger("click",function(){})
`)

标签

可以使用webview标签嵌套网页

<webview id="foo src="https://www.github.com/"></webview>

必须要在dom-ready才能调用webview的API方法,可以通过executeJavaScriptinsertCSS往该webview上注入JS脚本和CSS样式

window.onload = function () {
    var webview = document.getElementById("foo");
    webview.addEventListener("dom-ready", function () {
        webview.openDevTools();
        console.log(webview.getURL())
        webview.executeJavaScript(`
            console.log(document.getElementById("js-pjax-loader-bar"))
        `)
        webview.insertCSS(`
            #js-flash-container{background-color: green}
        `)
    });
}

当用户或page尝试开始导航时触发.它能在window.location变化或者用户点击连接的时候触发,这个事件在以 APIS 编程方式开始导航时不会触发,例如<webview>.loadURL<webview>.back,在页面内部导航跳转也将不回触发这个事件,例如点击锚链接或更新window.location.hash使用did-navigate-in-page来实现页内跳转事件,并且使用event.preventDefault()并不会起什么用

webview.addEventListener('will-navigate', function (e) {
    console.log("重定向")
});

参考Electron DOM 标签

打包

我们可以使用electron-packager来打包,下面是package.json文件

"scripts": {
  "start": "electron .",
  "dev": "electron . --debug",
  "test": "mocha && standard",
  "package": "npm-run-all package:*",
  "package:mac": "electron-packager . --overwrite --platform=darwin --arch=x64 --out=out --icon=assets/app-icon/mac/app.icns --osx-sign.identity='Developer ID Application: GitHub' --extend-info=assets/mac/info.plist",
  "package:win": "electron-packager . --overwrite --platform=win32 --arch=ia32 --out=out --icon=assets/app-icon/win/app.ico",
  "package:linux": "electron-packager . --overwrite --platform=linux --arch=x64 --out=out"
}

建议用cnpm代替npm,速度会比较快,还有打包的素材iconsWin系统的图标必须是128*128的素材,如果在Mac下打包Win的安装包,必须要安装wine环境

cnpm run package //打包所有版本
cnpm run package:win // 打包win版本

2019-01-17 12 52 39

图标制作

可以参考想制作和修改mac下icns图标的朋友进

Xcode 已经不再集成 Icon Composer,不用 Icon Composer 一样制作 Icon ,要想再用,只能去

Xcode->Open Develop Tool->More Develop Tools->下载 Additional_Tools_for_Xcode_10.1.dmg

里面会包含Icon Composer

electron-vue

electron-vue 是一个结合 vue-cli 与 electron 的项目,主要避免了使用 vue 手动建立起 electron 应用程序,很方便。

我们需要做的仅仅是像平常初始化一个vue-cli项目一样

npm install -g vue-cli 
vue init simulatedgreg/electron-vue my-project

就可以拥有一个 vue-loader 的 webpack、electron-packager 或是 electron-builder,以及一些最常用的插件,如vue-router、vuex 等等的脚手架

参考文档

electron打包客户端与网页程序交互
electron实战开发详细流程
官方快速入门文档

Javascript模板引擎

译文:只有20行Javascript代码!手把手教你写一个页面模板引擎
原文:JavaScript template engine in just 20 lines

var TemplateEngine = function(html, options) {
    var re = /<%([^%>]+)?%>/g, reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = 'var r=[];\n', cursor = 0;
    var add = function(line, js) {
        js? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
            (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
        return add;
    }
    while(match = re.exec(html)) {
        add(html.slice(cursor, match.index))(match[1], true);
        cursor = match.index + match[0].length;
    }
    add(html.substr(cursor, html.length - cursor));
    code += 'return r.join("");';
    return new Function(code.replace(/[\r\t\n]/g, '')).apply(options);
}

socket.io

介绍

在HTML5规范中,WebSocket现在越来越流行。WebSocket提供了一个受欢迎的技术,以替代我们过去几年一直在用的Ajax技术,这个新的API提供了一个方法,从客户端使用简单的语法有效地推动消息到服务器。让我们看一看HTML5的WebSocket API,它可用于客户端、服务器端。而且有一个优秀的第三方API,名为Socket.IO

WebSocket是下一代客户端-->服务器的异步通信方法。该通信取代了单个的TCP套接字,使用ws或wss协议,可用于任意的客户端和服务器程序。WebSocket目前由W3C进行标准化。WebSocket已经受到Firefox 4、Chrome 4、Opera 10.70以及Safari 5等浏览器的支持。

WebSocket最伟大之处在于服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息。WebSocket并不限于以Ajax(或XHR)方式通信,因为Ajax技术需要客户端发起请求,而WebSocket服务器和客户端可以彼此相互推送信息,XHR受到域的限制,而WebSocket允许跨域通信。

WebSocket技术很聪明的一点是没有设计要使用的方式。WebSocket为指定目标创建,用于双向推送消息。

原生客户端WebSocket

下面的代码片段是打开一个连接,为连接创建事件监听器,断开连接,消息时间,发送消息返回到服务器,关闭连接。

// 创建一个Socket实例
var socket = new WebSocket('ws://localhost:8080'); 

// 打开Socket 
socket.onopen = function(event) { 

  // 发送一个初始化消息
  socket.send('I am the client and I\'m listening!'); 

  // 监听消息
  socket.onmessage = function(event) { 
    console.log('Client received a message',event); 
  }; 

  // 监听Socket的关闭
  socket.onclose = function(event) { 
    console.log('Client notified socket has closed',event); 
  }; 

  // 关闭Socket.... 
  //socket.close() 
};

让我们来看看上面的初始化片段。参数为URL,ws表示WebSocket协议。onopen、onclose和onmessage方法把事件连接到Socket实例上。每个方法都提供了一个事件,以表示Socket的状态。

onmessage事件提供了一个data属性,它可以包含消息的Body部分。消息的Body部分必须是一个字符串,可以进行序列化/反序列化操作,以便传递更多的数据。

WebSocket的语法非常简单,使用WebSockets是难以置信的容易……除非客户端不支持WebSocket。IE浏览器目前不支持WebSocket通信。如果你的客户端不支持WebSocket通信,下面有几个后备方案供你使用:

  • Flash技术 —— Flash可以提供一个简单的替换。 使用Flash最明显的缺点是并非所有客户端都安装了Flash,而且某些客户端,如iPhone/iPad,不支持Flash。
  • AJAX Long-Polling技术 —— 用AJAX的long-polling来模拟WebSocket在业界已经有一段时间了。它是一个可行的技术,但它不能优化发送的信息。也就是说,它是一个解决方案,但不是最佳的技术方案。
  • 由于目前的IE等浏览器不支持WebSocket,要提供WebSocket的事件处理、返回传输、在服务器端使用一个统一的API,那么该怎么办呢?幸运的是,Guillermo Rauch创建了一个Socket.IO技术。

原生服务端websocket

用 Node 实现简单 WebSocket 协议

安装

npm install socket.io

官网参考文档

image

服务端

var app = require('http').createServer(function(req, res) {})
var io = require('socket.io')(app);
app.listen(80);
io.on('connection', function (socket) {
  socket.emit('news', { hello: 'world' });
  socket.on('my other event', function (data) {
    console.log(data);
  });
});

客户端

客户端的JS文件,可以在CDN上得到

<script src="https://cdn.socket.io/socket.io-1.4.4.js"></script>
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
	</body>
	<script src="js/socket.js"></script>
	<script>
		var socket = io('http://localhost');
			socket.on('news', function (data) {
			console.log(data);
			socket.emit('my other event', { my: 'data' });
		});
	</script>
</html>

此时长连接后端有改动,前端就立即能接受新的值

发送公共信息

server

io.on('connection', function (socket) {
        io.emit('pub', { will: 'be received by everyone'});
        //or
        io.sockets.emit("pub",{data:"hello,all"});
});

client

socket.on('pub', function(data) {
	console.log(data);
});

发送私有信息

server

socket.on('private message', function(from, to, msg) {
        //客户端可传多个参数,服务端也可接受多个参数
	console.log(to, ' received a private message by ', from, ' saying ', msg.msg);
});

client

socket.emit('private message', 'oaoafly', 'eno', {
	msg: 'hello'
});

获取socket.id,每个客户端都有一个对应的socket.id
然后通过下面方法实现单对单的私人聊天

io.sockets.sockets[data.toId].emit("getMessage", data)

客户端监听的事件

客户端socket.on()监听的事件:

  • connect:连接成功
  • connecting:正在连接
  • disconnect:断开连接
  • connect_failed:连接失败
  • error:错误发生,并且无法被其他事件类型所处理
  • message:同服务器端message事件
  • anything:同服务器端anything事件
  • reconnect_failed:重连失败
  • reconnect:成功重连
  • reconnecting:正在重连

比如
客户端连接上服务端时候会触发回调函数

socket.on('connect', function() {
	console.log('Client has connected to the server!');
});

终止服务端时候客户端触发的回调函数

socket.on('disconnect', function() {
	console.log('The client has disconnected!');
});

express配合websocket

var ioFn = require("socket.io");
var express = require("express");
var http = require("http")
var server = http.createServer()
var io = ioFn(server);
var app = express();
app.get("/", function(req, res) {
	res.send("hello")
}).listen(9999);
io.on("connection", function() {

})

参考文档

实时通讯之Socket.io

node环境下使用adb驱动安卓手机

打开开发者选项

小米手机点击该位置,多次点击MIUI版本的选项,就会出现开发者调试模式

设置->我的设备->全部参数->MIUI版本

设置开发者选项

设置->更多设置->开发者选项

安装adb驱动

点击下载UniversalAdbDriver或者点击这里

配置adb环境变量,例如将以下路径放在系统的环境变量path

D:\Program Files (x86)\ClockworkMod\Universal Adb Driver

安装后在CMD命令行中输入adb命令出现下图所示则表示安装成功
image

root权限

可以尝试安装KingRoot进行root,或者连接电脑尝试获取root权限

启动adb服务

手机端

因为安卓手机默认是不开启adb服务的,需要在手机端安装Wireless ADB开启此服务

PC端

在命令行中输入以下命令,可以出现下图的信息,说明adb服务已经正常开启

adb devices

image

执行以下命令,成功会出现下图所示

adb shell

image

常用shell命令

  • 模拟输入文本信息:input text HelloWorld
  • 模拟物理按键操作: input keyevent KEYCODE_VOLUME_DOWN
  • 模拟点击操作:input tap 500 500
  • 模拟滑动操作:input swipe 200 500 400 500
input swipe x y x y t

注意可以利用t(时间)来控制屏幕按压的时长

  • 截图直接保存到电脑:screencap -p | sed 's/\r$//' > screen.png

安装pip

进入python安装目录下的Script文件夹,例如

D:\Python27\Scripts

执行以下命令安装pip,并把pip的环境配置添加到path里面

easy_install.exe pip

image

安装函数库

如果安装了pip并配置了环境变量,可以直接在终端执行

pip install numpy
pip install matplotlib

或者直接下载numpy模块等

截图

新建文件夹wscats,截图存进去,然后上传PC,最后清空该文件夹,不断循环执行,该方法比较稳定,这里还建议选择启动的是传输照片的PTP模式,不然可能出现照片上传的失败

adb shell mkdir -p /sdcard/wscats
adb shell screencap -p /sdcard/wscats/screen.png
adb pull /sdcard/wscats/screen.png .
adb shell rm -r /sdcard/wscats/

node上传文件

安装multer模块

单图上传

npm install multer

引用模块

它是依赖于express的一个模块

//引用express并配置
var express = require("express");
var app = express();
app.listen(3000);
var multer = require('multer');
/*var upload = multer({
	//如果用这种方法上传,要手动添加文明名后缀
        //如果用下面配置的代码,则可以省略这一句
	dest: 'uploads/'
})*/

配置

设置保存文件的地方,并根据上传的文件名对应文件添加后缀
可以通过filename属性定制文件保存的格式

属性值 用途
destination 设置资源的保存路径。注意,如果没有这个配置项,默认会保存在/tmp/uploads下。此外,路径需要自己创建
filename 设置资源保存在本地的文件名
var storage = multer.diskStorage({
	//设置上传后文件路径,uploads文件夹会自动创建。
	destination: function(req, file, cb) {
		cb(null, './uploads')
	},
	//给上传文件重命名,获取添加后缀名
	filename: function(req, file, cb) {
		var fileFormat = (file.originalname).split(".");
		//给图片加上时间戳格式防止重名名
		//比如把 abc.jpg图片切割为数组[abc,jpg],然后用数组长度-1来获取后缀名
		cb(null, file.fieldname + '-' + Date.now() + "." + fileFormat[fileFormat.length - 1]);
	}
});
var upload = multer({
	storage: storage
});

接受文件

upload.single('xxx'),xxx与表单中的name属性的值对应
这里虽然用到post请求,但实际上不需要bodyParser模块处理

app.post('/upload-single', upload.single('logo'), function(req, res, next) {
	console.log(req.file)
	console.log('文件类型:%s', req.file.mimetype);
	console.log('原始文件名:%s', req.file.originalname);
	console.log((req.file.originalname).split("."))
	console.log('文件大小:%s', req.file.size);
	console.log('文件保存路径:%s', req.file.path);
	res.send({
		ret_code: '0'
	});
});

多图上传

多图上传只要更改一下地方,前端往file输入框加多一个multiple="multiple"属性值,此时就可以在选图的时候多选了,当然也可以并列多个file输入框(不推荐多个上传图片输入框),这样体验会不好

<input type="file" name="logo" multiple="multiple" />

后端也需要相应的改变

app.post('/upload-single', upload.single('logo'), function(req, res, next) {
//upload.single('logo')变为upload.array('logo', 2),数字代表可以接受多少张图片
app.post('/upload-single', upload.array('logo', 2), function(req, res, next) {

如果不想有图片数量上传限制,我们可以用upload.any()方法

app.post('/upload-single', upload.any(), function(req, res, next) {	
	res.append("Access-Control-Allow-Origin","*");
	res.send({
		wscats_code: '0'
	});
});

前端部分

formData表单提交

<form action="http://localhost:3000/upload-single" method="post" enctype="multipart/form-data">
	<h2>单图上传</h2>
	<input type="file" name="logo">
	<input type="submit" value="提交">
</form>

formData表单+ajax提交

<form id="uploadForm">
	<p>指定文件名: <input type="text" name="filename" value="" /></p>
	<p>上传文件: <input type="file" name="logo" /></ p>
	<input type="button" value="上传" onclick="doUpload()" />
</form>

FormData对象,是可以使用一系列的键值对来模拟一个完整的表单,然后使用XMLHttpRequest发送这个"表单"

注意点

  • processData设置为false。因为data值是FormData对象,不需要对数据做处理。
  • <form>标签添加enctype="multipart/form-data"属性。
  • cache设置为false,上传文件不需要缓存。
  • contentType设置为false。因为是由<form>表单构造的FormData对象,且已经声明了属性enctype="multipart/form-data",所以这里设置为false

上传后,服务器端代码需要使用从查询参数名为logo获取文件输入流对象,因为<input>中声明的是name="logo"

function doUpload() {
	$.ajax({
		url: 'http://localhost:3000/upload-single',
		type: 'POST',
		cache: false, //不必须
		data: new FormData($('#uploadForm')[0]),
		processData: false,//必须
		contentType: false,//必须
		success: function(data) {
			console.log(data)
		}
	})
}

改变样式

可以先隐藏文件上传输入框

<form id="uploadForm">
	<input style="display: none;" type="file" name="logo" onchange="doUpload()" multiple="multiple" />
</form>

为需要出发文件上传的标签添加点击事件

<img onclick="uploadImg()" class="headpic" src="" />
function uploadImg(){
	$("#uploadForm input").trigger("click");//代理文件上传的事件
}
//触发真正的幕后上传事件
function doUpload() {
        //upload code
}

携带其他参数

可以用原生的append对请求体继续添加内容,既可以上传图片也可以带其他参数

//构造form数据 
var data = new FormData();
data.append("avatar", fileNode.files[0]);
data.append("description", "其他参数");

参考文档

Github MyDemo
Github Multer
MDN FormData对象的使用

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.