1.1.4 校验码

码距

两个合法编码对应位之间比较,有多少位编码不同,又称为海明距离。比如10101和00110就是(从高到低1,4,5位不同,码距为3)

计算海明距离的一种方法,就是对两个位串进行异或(xor)运算,并计算出异或运算结果中1的个数。例如110和011这两个位串,对它们进行异或运算,其结果是:

110011=101110 \bigoplus 011=101

异或结果中含有两个1,因此110和011之间的海明距离就等于2。

为了使一个系统能检查和纠正一个差错,码间最小距离必须至少是“3”。最小距离为3时,或能纠正一个错,或能检二个错,但不能同时纠一个错和检二个错。编码信息纠错和检错能力的进一步提高需要进一步增加码字间的最小距离。

码距越大,纠错能力越强,但数据冗余也越大,即编码效率低了。

模二加法

这是一种二进制的运算,等同于“异或”运算。

规则是两个序列按位相加模二,即两个序列中对应位,相加,不进位,相同为0,不同为1。

奇偶校验码(Parity Codes)

是一种通过增加冗余位使得码字中"1"的个数恒为奇数或偶数的编码方法,它是一种检错码。在实际使用时又可分为垂直奇偶校验、水平奇偶校验和水平垂直奇偶校验等几种。因为其利用的是编码中1的个数的奇偶性作为依据,所以不能发现偶数位错误。(错偶数个奇偶性不变)

海明码(Hamming Code)

贝尔实验室Richard Hamming设计,在特定位置插入k个校验码,通过扩大码距来检错纠错。

n位数k校验位,则必须符合:

2k1n+k2^k-1\geq n+k

Pk,Pk1,...p1校验位: P_k, P_{k-1},...p_1

Dn1,Dn2,...D0数据位:D_{n-1},D_{n-2},...D_0

Hn+k,Hn+k1,...H1海明码:H_{n+k},H_{n+k-1},...H_1

Hj=Pi,j=2i1则:H_j=P_i,j=2^{i-1} 数据从低到高占据剩下的位置

海明码中的任何一位都是由若干个校验位来校验的:被校验的海明位的下标==所有参与校验该位的校验位的下标之和,校验位由自身校验。

编码过程

对于8位数,要4个校验位。

  • 1.确定D与P位置
Hamming D&P
H(12) D(7)
H(11) D(6)
H(10) D(5)
H(9) D(4)
H(8) P(4)
H(7) D(3)
H(6) D(2)
H(5) D(1)
H(4) P(3)
H(3) D(0)
H(2) P(2)
H(1) P(1)
  • 2.确定校验关系
海明码 分布 海明码下标 校验位组
H(12) D(7) 12=4+8 P3,P4
H(11) D(6) 11=1+2+8 P1,P2,P4
H(10) D(5) 10=2+8 P2,P4
H(9) D(4) 9=1+8 P1,P4
H(8) P(4) 8 P4
H(7) D(3) 7=1+2+4 P1,P2,P3
H(6) D(2) 6=2+4 P2,P3
H(5) D(1) 5=1+4 P1,P3
H(4) P(3) 4 P3
H(3) D(0) 3=1+2 P1,P2
H(2) P(2) 2 P2
H(1) P(1) 1 P1

根据每个校验位参与校验得

P1=D0D1D3D4D6P_1=D_0 \bigoplus D_1 \bigoplus D_3 \bigoplus D_4 \bigoplus D_6

P2=D0D2D3D5D6P_2=D_0 \bigoplus D_2 \bigoplus D_3 \bigoplus D_5 \bigoplus D_6

P3=D1D2D3D7P_3=D_1 \bigoplus D_2 \bigoplus D_3 \bigoplus D_7

P4=D4D5D6D7P_4=D_4 \bigoplus D_5 \bigoplus D_6 \bigoplus D_7

若使用奇校验,将校验位的偶校验值取反即可

  • 3.检测错误

G1=P1D0D1D3D4D6G_1=P_1 \bigoplus D_0 \bigoplus D_1 \bigoplus D_3 \bigoplus D_4 \bigoplus D_6

G2=P2D0D2D3D5D6G_2=P_2 \bigoplus D_0 \bigoplus D_2 \bigoplus D_3 \bigoplus D_5 \bigoplus D_6

G3=P3D1D2D3D7G_3=P_3 \bigoplus D_1 \bigoplus D_2 \bigoplus D_3 \bigoplus D_7

G4=P4D4D5D6D7G_4=P_4 \bigoplus D_4 \bigoplus D_5 \bigoplus D_6 \bigoplus D_7

如果采用偶校验,G4G3G2G1全为0则无错(奇校验全为1),且其十进制值指出了错误位置,如1010说明H10(D5)出错了,将其取反即可纠正错误。


hexo自动部署+gitlab

前期工作

之前入手了一台miniPC安装好了Ubuntu,并通过frp内网穿透部署好了gitlab。现在决定重拾荒废的Blog,并做成自动部署。

本文想达到的目的是在任意一台主机编写Blog,并能够做到提交后自动部署。

当前hexo版本5.0以上(安装主题的方式有所不同)

条件

  • 云服务器,公网IP+域名
  • 服务器自行安装好Nginx+git
  • 本地安装好node

开始

本地安装hexo及初始化

1
2
3
4
5
npm install -g hexo-cli

hexo init myblog
cd myblog
npm install

创建服务端账户

一般我们需要专门为服务创建不同用户及用户组,以便做好权限管理。
当前是以root用户登录服务器的

1
2
3
useradd blog
passwd blog #设置用户密码
usermod -a -G root blog #设置组

为了方便,直接将blog用户加到了root组里面

在服务器创建空的git仓库

届时会通过git的钩子方法自动将文件复制到静态文件夹

1
2
3
4
5
mkdir /home/blog/repo/
chown -R blog:root /home/blog/repo/
chmod -R 755 /home/blog/repo/
cd /home/blog/repo/
git init --bare hexo_static.git

既然创建了专用的用户账号,就把相应的资源放在用户目录下/home/blog/就是用户home目录,我们将文件都放到home目录的repo目录中

在服务器创建hexo静态文件夹

作为站点root使用Nginx做web服务器

1
mkdir /home/blog/repo/hexo

在本地连接服务器(实现免密连接)

可以先检查下是否已经有了

1
cat ~/.ssh/id_rsa.pub

没有就创建公开密钥认证所需的SSH Key

1
2
3
4
5
6
$ ssh-keygen -t rsa -C "your_email@example.com"
Generating public/private rsa key pair.
Enter file in which to save the key
(/Users/your_user_directory/.ssh/id_rsa): 按回车键
Enter passphrase (empty for no passphrase): 输入密码
Enter same passphrase again: 再次

实现免密登录

1
ssh-copy-id -i ~/.ssh/id_rsa.pub  blog@服务器ip或域名
  • 注意: ssh-copy-id 将key写到远程机器的 ~/ .ssh/authorized_key文件中

本地配置hexo的部署方式

1
2
cd myblog
vi _config.yml

找到deploy并修改

1
2
3
deploy:
type: 'git'
repo: 'blog@ip:/home/blog/repo/hexo_static.git'

此时已经可以通过hexo g -d命令构建并部署文件到git仓库了,但是还差一步

添加自动部署钩子

利用git的hooks,在push的时候自动复制文件到hexo目录

1
2
3
cd /home/blog/repo/hexo_static.git
cd ./hooks
vi post-receive
1
2
#!/bin/bash
git --work-tree=/home/blog/repo/hexo --git-dir=/home/blog/repo/hexo_static.git checkout -f

给文件加好执行权限

1
chmod +x post-receive

运行

有变动才能提交,先修改一下

1
hexo g -d

检查文件是否已经到/home/blog/repo/hexo目录了,如过没有,很有可能是访问权限问题,git仓库目录应当和hexo属于同一个用户

配置nginx

自行探索,提供一个简单参考

1
2
3
4
5
server{
listen 80;
server_name blog.sumshare.cn;
root /home/blog/repo/hexo;
}

使用gitlab仓库实现代码管理

以上已经实现自动部署,我们进一步的目的是为了实现多态设备随时写作+发布。这一步其实只要有个代码管理仓库不管是github还是gitlab或者gitee都能实现。

可能的问题

  1. hexo d时ERROR Deployer not found: git只需要npm install --save hexo-deployer-git
  2. nginx 访问403,这个也是权限问题引起的,检查一下nginx是以什么用户启动的,nginx一般会有nginx用户和用户组,酌情考虑将启动用户改成root。否则你需要重新规划以哪个用户进行hexo的部署

参考链接


vue实现input强制输入Number类型

需求

如果想要实现input输入Number类型,又不想用难看的type='number’怎么处理

1
2
<el-input style="width:200px" :maxlength="4" @keyup.native="isNumber(form,'recruitsCount')" v-model.number="form.recruitsCount" placeholder="请输入数字,不能为空"></el-input>

1
2
3
4
5
6
7
8
9
isNumber(obj,key) {
let isnumber = new RegExp('^[0-9]\\d*$')
if (!isnumber.test(obj[key])) {
obj[key] = null
} else {
return true
}
},

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>Document</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
</head>

<body>
<div id="app">
<input style="width:200px" :maxlength="4" @keyup="isNumber(form,'count')" v-model.number="form.count" placeholder="请输入数字,不能为空"></input>
</div>
</body>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
form: {
count: null
}

},
methods: {
isNumber(obj, key) {
let isnumber = new RegExp('^[0-9]\\d*$')
if (!isnumber.test(obj[key])) {
obj[key] = null
} else {
return true
}
}
}
})
</script>

</html>



说明

  • 配合.number修饰符实现纯数字输入
  • .nmber的特点是将字符串转为num型,如果转换结果是NaN,则原样输出。
  • 因此如果第一个字符输入非数字,那么就会变成字符串,根据强制类型转换规则,首字母是数字才能转换成功,如1f这种就会转换为1,因此就出现预想中的输入非数字转换为数字的效果。
  • 因此关键是第一个字符不能为非数字,配合keyUp事件,检测首字母是否是数字即可,检查全部也无所谓,因为keyup事件发生时,input事件未发生,input又经过类型转换,因此能完美运行

编程日志(08)—vue实现plupload分片上传
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
<!-- plupload封装 -->
<template>
<div class="supload">
<div class="clearfix list-picture" v-if="fileList.length>0&&listType=='picture'">
<div v-for="ff,index in fileList" class="pic-bx small">
<img :src="ff.showUrl">
<div class="cl-bx">
<i class="el-icon-close" @click="deleteFile(ff,index)"></i>
</div>
</div>
</div>
<div class="plupload clearfix" v-show="fileList.length<maxFile">
<template v-if="listType=='text'">
<div class="text_trigger">
<div :id="trigger_id" >
<i class="el-icon-document" ></i>
<span>{{triggerText}}(小于{{maxSize}}Mb)</span>
</div>
</div>
</template>
<template v-if="listType=='picture'">
<div class="fm-up" :id="trigger_id">
<div class="cc">
<i class="el-icon-circle-plus-outline"></i>
<p>点击选择</p>
<span>支持jpg/png格式</span>
<span>560px×400px</span>

</div>
</div>
</template>
</div>
<div class="pgs" v-if="progress>0&&listType=='text'">
<el-progress :text-inside="true" :stroke-width="20" :percentage="progress" status="success"></el-progress>
</div>

<div class="list-text" v-if="listType=='text'">
<template v-for="ff,index in fileList">
<div class="clearfix item-text">
<!-- <div class="progress" :style="{'width':ff.progress+'%'}"></div> -->
<div class="title">{{ff.name}}</div>
<div class="close" @click="deleteFile(ff,index)">删除</div>
</div>

</template>
</div>

</div>
</template>

<script>
/**
* 强制multi_selection为false使其只能逐个上传
*
*/
/**
* tip:
* 为了操作方便fileList以数组形式传入props,直接操作fileList可以实现数据双向绑定
* 并不建议这样去做,父子组件之间尽量保持单向数据流
* 因此可以暂不向外提供文件删除上传的回调入口
*/
/*
xlsx等文档可以在拓展过滤为zip的情况下可选的bug是因为xlsx本质是个压缩包,将xlsx拓展名改成zip再看看
*/
import plupload from 'plupload'
import {guid,getExtName} from '../../lib/util.js'

import config from '../../config/config.js'
export default {
data() {
return {
trigger_id:null, //触发按钮的ID属性
plup:null, // plupload实例对象
progress: 0, // 单文件上传进度
option: null
};
},
props: {
accept: {
type: String,
default: 'image/png,image/jpeg,audio/mp3'
},
// 支持的拓展名以逗号隔开jpg,gif,png,bmp,mp4
extensions:{
type: String,
default: 'mp4'
},
maxFile: {
type: Number,
default: 1
},
maxSize: {
type: Number,
default: 512 //mb
},
// 文件列表
fileList: {
default(){
return []
}
},
// text,picture 以picture形式呈现时,如果超过可上传数量,上传界面将消失
listType:{
default: 'text'
},
params:{
type:Object,
required:true
},
triggerText:{
default:'选择文件'
}
},
computed:{

},
components: {

},

methods: {
transGetUrl(url) {
return config.imgHost + url
},


deleteFile(ff,index){
this.fileList.splice(index,1)
},

},
watch:{
extensions(ne,olg){
this.option.filters.mime_types=[{
title: 'files', extensions: ne
}]

this.plup.destroy()
this.plup = new plupload.Uploader(this.option);
this.plup.init()

}

},
mounted(){
const $this = this;
this.option = {
runtimes: 'html5,flash,silverlight,html4',
url: config.downloadHost+'upload/pluploadUpload',
//url: '/preschool/upload/pluploadUpload',
browse_button: this.trigger_id,
chunk_size: '2048kb',
multi_selection:false,
filters: {
mime_types: [{ title: 'files', extensions: this.extensions }],
max_file_size: this.maxSize+'mb',
prevent_duplicates: true
},
multipart:true,
multipart_params:this.params,
init: {
FilesAdded: function(up, files) {
up.start();
},
UploadProgress: function(up, file) {
$this.progress = file.percent;
},
BeforeUpload: function(up,file) {},
FileUploaded: function(uploader,file,res){
// response,responseHeaders,status
if (res.status==200){
let result = JSON.parse(res.response);
if (result.code==0){
setTimeout(function(){
$this.progress = 0;
$this.fileList.push({
name:file.name,
// url: result.data.replace(/(.*)(pluploadDir)/g,config.imgHost),
url: result.data,
showUrl: $this.transGetUrl(result.data),
type: getExtName(result.data)
});
},500);
}
} else {
}
},
Error:function(up,err){},
Refresh: function(up){},
Destroy(up){},
OptionChanged:function(up,option_name,new_value,old_value){}
// Error: this.error
}
}
this.plup = new plupload.Uploader(this.option);
this.plup.init();
},
beforeMount(){
this.trigger_id = guid();
}
};
/**
* 额外参数传递:
* 1.设置multipart为true
* 为true时将以multipart/form-data的形式来上传文件,为false时则以二进制的格式来上传文件。
* 2. 设置multipart_params:{userId:1024}
* 方式二
* 设置init的BeforeUpload(up,file){up.setOption("multipart_params",{userId:1024})}
* 也可以up.setOption({'multipart_params':{userId:1030}});这样可以方便设置多个参数
* 像 up.setOption({'multipart':true,'multipart_params':{userId:1032}})
* 在某些情况下可能更方便
*/
</script>

调用方式

1
2
3
<plup-vue :file-list="fileListForAttach" list-type="text" :params="extendParamsForUploadComponent" :extensions="format" :maxSize="1024">
</plup-vue>

CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/*上传组件*/
.supload{
line-height: 30px;
.pgs{
line-height:20px;width:600px;
}
.list-text{
line-height:20px;width:600px;
.item-text{
line-height: 30px;
height: 30px;
background: #ededed;
position: relative;
margin: 5px auto;
.progress{
position: absolute;
height: 100%;
width: 1%;
background: green;
top: 0;left: 0;
opacity: 0.2;
transition: width 0.5s;
-moz-transition: width 0.5s;
-webkit-transition: width 0.5s;
-o-transition: width 0.5s;

}
.title{
float: left;height: inherit;line-height: inherit;padding: 0 10px;
width: 500px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.close{
float: right;height: inherit;
line-height: inherit;padding: 0 10px;
cursor: pointer;
color:$colorRed
}
}
}
.list-picture {

.pic-bx{
float: left;
margin: 10px 10px 10px 0;
border:1px dashed #ededed;
img{width: 100%; }
position: relative;
}
.cl-bx{
width: 100%;height: 25px;background: rgba(1,1,1,0.5);
position: absolute;right: 0;top: 0;
text-align: right;
line-height: 25px;
i{
font-size: 15px;
color:#fff;
margin-right: 3px;
cursor: pointer;
}
}
.small{
width: 240px;height: 180px;overflow: hidden;
border-radius: 4px
}
}
.plupload{
.text_trigger{
display: inline-block;
position: relative;
color:$colorBlue;
cursor: pointer;
}

}

}
.fm-up{
width: 240px;height: 180px;
text-align: center;
cursor: pointer;
// border: 1px solid #03A9F4;
.cc{
height: 100px;width: 200px;
line-height: 20px;
margin: auto;
margin-top: 40px;
}
color:#dcdcdc;
border: 1px solid #dcdcdc;
i{font-size: 25px;}
border-radius: 4px;
p{line-height: 20px;}
span{font-size: 12px;line-height: 20px;}
}

编程日志(03)—圣杯布局、双飞翼布局

双飞翼布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
</head>
<style>
* {
margin: 0;
padding: 0
}

body {
min-width: 550px;
font-weight: bold;
font-size: 20px;
}

#header,
#footer {
background: rgba(29, 27, 27, 0.726);
text-align: center;
height: 60px;
line-height: 60px;
}

#container {
overflow: hidden;
}

.column {
text-align: center;
}

#left,
#right,
#center {
float: left;
/*三列高度撑开对齐,关键步*/
padding-bottom: 10000px;
margin-bottom: -10000px;
}

#center {
width: 100%;
background: rgb(206, 201, 201);
}

#left {
width: 200px;
margin-left: -100%;
background: rgba(95, 179, 235, 0.972);
}

#right {
width: 150px;
margin-left: -150px;
background: rgb(231, 105, 2);
}

.content {
margin: 0 150px 0 200px;
height: 400px;
}
</style>

<body>
<div id="header">#header</div>
<div id="container">
<div id="center" class="column">
<div class="content">
#center
</div>
</div>
<div id="left" class="column">#left</div>
<div id="right" class="column">#right</div>
</div>
<div id="footer">#footer</div>
</body>

</html>

image

说明

  • 三列布局,中间宽度自适应,两边定宽
  • 中间栏要在浏览器中优先展示渲染
  • 允许任意列的高度最高

查看源码及更多


pkg-node打包可执行文件工具

前言

最近在研发基于electron的桌面应用,其中涉及到外设交互部分需要使用dll实现,在经历了艰难的环境配置之后,最后由于electron内存管理问题导致应用崩溃使得electron+ffi实现动态链接库调用这条路无法走通。当时还剩下两种方案可以尝试:

  1. 自己创建dll封装原生dll调用,感觉成功几率很低。
  2. 写node addons编译成node插件,需要较强c++功底,难度较大,且最后成功概率不高。

为什么坚持了两个星期还不放弃是因为node+ffi这条路已经走通了,证明第三方的dll是没有问题的。所以试着能不能解决electron中出现的问题。

当electron不能实现的事实逐渐明朗,我开始转移方向。首先想到的是nwjs+ffi的方案,随后迅速想到为什么不使用node自己实现呢?将此部分功能单独分割成为第三方应用,通过某种通信方式实现(写缓存、数据库、http等)。最不济的情况是我为每台终端部署node服务(此应用是B2B,需要实施,不需要自己安装,这一点帮了大忙)

最好的方式是不需要安装node等一堆东西,直接是可执行文件。因此我开始寻找node打包成可执行文件的方法。

为什么要将node应用打包成可执行文件

  1. 部署方便,给客户演示和生产环境部署都很方便
  2. 保护源代码(很有用)
  3. 宿主机器其他应用对node版本可能有不同的要求
  4. 测试应用在不同node版本下兼容性

pkg打包的使用方法

将Node.js打包为可执行文件的工具有pkg、nexe、node-packer、enclose等,从打包速度、使用便捷程度、功能完整性来说,pkg是最优秀的。

安装

推荐采用局部安装

1
npm install pkg -D

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ pkg -h

pkg [options] <input>

Options:

-h, --help output usage information
-v, --version output pkg version
-t, --targets comma-separated list of targets (see examples)
-c, --config package.json or any json file with top-level config
--options bake v8 options into executable to run with them on
-o, --output output file name or template for several files
--out-path path to save output one or more executables
-d, --debug show more information during packaging process [off]
-b, --build don't download prebuilt base binaries, build them
--public speed up and disclose the sources of top-level project

Examples:

– Makes executables for Linux, macOS and Windows
$ pkg index.js
– Takes package.json from cwd and follows 'bin' entry
$ pkg .
– Makes executable for particular target machine
$ pkg -t node6-alpine-x64 index.js
– Makes executables for target machines of your choice
$ pkg -t node4-linux,node6-linux,node6-win index.js
– Bakes '--expose-gc' into executable
$ pkg --options expose-gc index.js


项目的入口点是强制CLI参数。它可能是:

  • 输入文件的路径
  • package.json ,pkg会去寻找bin属性作为入口文件
  • 路径是一个目录地址,pkg会在目录下寻找package.json文件,然后如上

targets

pkg能同时为多个平台打包。使用-t 选项,定义由三部分组成,如node6-macos-x64:

  • nodeRange node${n} or latest
  • platform freebsd, linux, alpine, macos, win
  • arch x64, x86, armv6, armv7

native addons

如果说pkg还有哪儿还可以改进的地方,那就是无法自动打包二进制模块*.node文件。如果你的项目中引用了二进制模块,如sqlite3,那么你需要手动地将*.node文件复制到可执行文件同一目录,我通常使用命令cp node_modules/**/*.node .一键完成。但是,如果你要跨平台打包,例如在windows上打包linux版本,相应的二进制模块也要换成linux版本,通常需要你手动的下载或者编译。

后台运行与开机自启动

开机自启动原理就是将exe文件,通过命令注册到开机服务项。

SC Create 创建一个Windows系统服务
描述SC 是用于与服务控制管理器和服务进行通信的命令行程序。

命令行,有一点需要注意,等号和值之间要有空格,否则会报错

1
sc create test binPath="E:\node\app.exe" start=auto

可能遇到问题:

第一次打包的时候,会遇到下包很慢很可能超时的问题。到https://github.com/zeit/pkg-fetch/releases
下载对应的包,然后~/.pkg-cache/2.5/目录下,改名为fetched-v8.11.3-macos-x64(参考运行时下的包名字改)即可。
参考https://github.com/zeit/pkg/issues/419

windows 路径一般是用户/username/.pkg-cache

注册为系统服务

当我使用 sc create xx binPath= xx start= auto创建完系统服务后,发现启动报错,大致找了一下。

exe注册成服务了!但是启动服务的时候会报[错误1053:服务没有及时响应启动或控制请求.] ,我想应该是我的exe被服务启动后没有能回复一个服务的消息,不知道要在exe里面做什么样的操作才能不报这样的错误,不是所有的程序都能添加到服务里,要是专问的程序才行,程序里要相应控件,才会有返回值,返回告诉操作系统服务开启情况,QQ程序开发时就不是用来为作为服而设计的,所以不能当服务用

因此参考issues137找到一个nssm的工具,用法很简单。大致罗列一下

第一步:先将程序地址设置到环境变量,就把nssm.exe放到目录下好了。
第二步:nssm install servicename
第三部:按要求配置,注意设置日志输出文件和时间戳
第四步:进入服务列表进行设置,在恢复里面设置第一次失败重启(因为要做开机启动嘛),第二次也重启服务,第三次无操作,千万别设置重启电脑,之前试了一次把硬盘引导分区都破坏了,导致连BIOS都进不去,只好拆下硬盘,用其他电脑PE重做分区,还好是一个新的测试电脑。

参考


编程日志(02)—同一浏览器多用户登录session冲突问题

账号登录冲突

在后端程序中,往往将session和成功登录的用户id进行绑定来保持登录状态,这样前后端交互时就不用显示传递用户ID来判断是哪个用户,并通过设置session过期时间来保障账户的安全。

session的值会被后端设置在cookie,前端无需做什么特别操作,正常请求接口即可。由于session就相当于用户id,当同一个浏览器存在多个标签页,A页是A用户,B页是B用户,那么session绑定的其实是后登陆的那一个用户的id,因为这种绑定是在登陆接口处理的。这样的话假如请求的数据(比如查看个人信息)是基于session拿用户id的,就会导致A用户拿到B用户的数据。

一个临时思路

登陆成功后将用户id加密后存储到本地,在每个需要验证的页面加上这个参数,当用户刷新页面时与本地存储的值进行比较,不符合就跳转登陆页。(后登陆的用户会覆盖上一个用户的本地值,而url里的参数不会变所以会导致URL中获取的值和本地不一致)

由于项目使用的是vue,可以将在路由里设置钩子函数,在跳转路由前检查对应数据,路由是hash模式,获取url参数和本地存储的方法可以自己想办法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

router.beforeEach((to, from, next) => {
// 工具方法,获取url地址中traceid的值
let urltraceid = getPop('traceid');
// 获取localStorege中traceid的值
let localtraceid = getLocalStorage('traceid').traceid
console.log('url=%s,local=%s ', urltraceid, localtraceid)
// 先判断两个参数是否存在
if (urltraceid && localtraceid) {
if (urltraceid == localtraceid) {
next()
} else {
let url = window.location.href.replace(/[\?,\#]\S*/g, '');
window.location.href = url;
next({
path: "/"
});
}
}

})

在登陆的时候设置参数,跳转地址类似于下面

1
index.html?traceid=xx#/main

编程日志(01)--cocosCreator实现类似jQuery的ajax

为什么在cocosCreator中不使用axios等工具

cocosCreator版本:2.0.7

在游戏场景中少不了要使用网络交互,在以往的编程中,用jQuery实现或者axios实现并不是很难,从论坛的反馈来看,cocos对ES6的支持尤其是promise,经过长时间的更新似乎依然没有很好地完善,由于开发时间限制,这里就没有去体验这些说法直接使用回调函数避免兼容性问题原始实现ajax请求。

构造ajax方法

使用XMLHttpRequest实现ajax请求并不困难,这里需要注意的是针对IE10以下版本ActiveXObject的兼容性问题,目前个人没有去验证这一点,且项目本生要求支持基于vue的webview。下面是官方文档给的一个示例

1
2
3
4
5
6
7
8
9
10
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && (xhr.status >= 200 && xhr.status < 400)) {
var response = xhr.responseText;
console.log(response);
}
};
xhr.open("GET", url, true);
xhr.send();

我的目的是实现像JQuery一样使用$.ajax(),只需要定义好传参结构就可以了,且由于目前是自己使用,可以只给出核心功能,参数的检验可以之后完善。下面是完整代码,定义cc.global方便全局使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

cc.global = {
ajax: function(obj) {

let err = '';
var xhr = cc.loader.getXMLHttpRequest();
xhr.open(obj.type, obj.url);
if (obj.type.toUpperCase() == 'GET') {
xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");

} else if (obj.type.toUpperCase() == 'POST') {
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
}
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && (xhr.status >= 200 && xhr.status <= 207)) {
err = false;
} else {
err = true;
}
if (err) {
//错误处理
if (obj.error) {
obj.error()
}
} else {
if (obj.success) {
obj.success(JSON.parse(xhr.responseText))
}
}
};

xhr.send(obj.data ? cc.global.qsPrimitive(obj.data) : null);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
cc.global.ajax({
type: 'post',
url: '',
data: {
userCode:'ac',
userPwd:'pwd'
},
success: function(res) {
console.log(res)
}
})

非常简单,但是还有问题需要说明一下

qs的实现

根据post请求协议规定,post数据需要放在Request body中传输,四种常见的 POST 提交数据方式(application/x-www-form-urlencoded,multipart/form-data,application/json,text/xml),这些可以再研究一下。

在项目中主要用到的还是get请求和post的请求,他们直观的区别就在于GET参数是放在Query Params上,通过在地址后面拼接如下qs序列化的参数来传参,而POST不是说不可以将参数拼在地址里,关于这一点可以参考99%的人都理解错了HTTP中GET与POST的区别,只是有参数的长度限制以及规范使用。目前的项目中需要传递比较大的数据,因此需要将其放在在Request body中

1
?key1=value1&key2=value2

(大多数)浏览器通常都会限制url长度在2K个字节,而(大多数)服务器最多处理64K大小的url

不管是GET还是POST的x-www-form-urlencoded方式,都要求将参数qs序列化。

下面是从node querrystring模块源码扒出的简化的方法,核心只是一个循环语句只需注意一下中文编码的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 简单查询序列化
qsPrimitive: function(obj) {
var sep = sep || '&';
var eq = eq || '=';

// 假设value都是字符串
if (obj !== null && typeof obj === 'object') {
var keys = Object.keys(obj);
var len = keys.length;
var flast = len - 1;
var fields = '';
for (var i = 0; i < len; ++i) {
var k = keys[i];
var v = obj[k];
var ks = encodeURIComponent(k) + eq;
fields += ks + encodeURIComponent(v);
if (i < flast)
fields += sep;

}
return fields;
} else {
return '';

}

},

Zepto源码学习-核心篇

0.前言

Zepto源码1.2.0未压缩带注释约有1835行,之前是当做设计模式来阅读,并没有深入。且在当前前端环境下,JQuery的重要性大大降低了,从事开发工作大多用的是Angular、Vue等,并没有将jQuery用到精通。以训练为目的,尝试将Zepto源码讲的清楚一点

1.构建Zepto

下载源码并install构建

1
2
3
4
git clone https://github.com/madrobby/zepto.git
cd zepto
npm install
npm run dist

这部分内容可以通过阅读官方仓库指南获得,通过进一步分析package.json文件,可以看出是用coffee-script的cake方法做的构建文件make,但是源码并没有用coffee-script来写,所以不用担心。关于coffee-script,个人觉得如果闲的厉害可以去学学,否则不如去学点别的,或者多看一些源码。

2.模块分析

首先要明白一点,如果第一次看,逐行分析是没有意义的,必须先了解整体结构。先整体再局部,然后细化。
通过阅读手册或者make文件可以知道最终代码会包含zepto event ajax form ie几个模块。实际上,手册排版也大致是按照这些模块划分的。

中文手册

3.整体结构分析

如果对原型链有一定的了解和开发经验,将很容易理解zepto的源码结构,如果是新手,那么先了解原型链,再阅读源码,将会加深对原型链的理解。下面给出打包出的zepto.js整体结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(function(global, factory) {
if (typeof define === 'function' && define.amd)
define(function() {
return factory(global)
})
else
factory(global)
}(window, function(window) {
// Zepto核心
var Zepto = (function() {})()
window.Zepto = Zepto
// 一种短路运算,如果成立则执行后面的,否则不执行
window.$ === undefined && (window.$ = Zepto)
// 相应的,也可以这么写
// '$' in window || (window.$ = Zepto);
// 以下是其他Zepto模块
;
(function($) {})(Zepto)
return Zepto
}))

这段代码首先关注最外层的IIFE(立即执行函数表达式),针对AMD模块做了处理。这里可以拓展了解JS的模块化规范CommonJS、AMD等。其他要点已经在代码里做了注释

4.核心模块

上面已经介绍了整个代码的组织结构,接下来分析一下最重要的Zepto核心是怎么实现的。这里可以利用chrome调试工具逐行运行,观察代码的调用栈来了解运行过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script type="text/javascript" src="./dist/zepto.js">

</script>
</head>
<body>
<div class="z"></div>
</body>
<script type="text/javascript">
debugger
$('.z').html('x')
</script>
</html>

写一个简单的页面,从第一行设置断点,然后逐步运行。

4.1 核心模块结构

先看一下简单的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var Zepto = (function() {
function Z(dom, selector) {
var i, len = dom ? dom.length : 0
for (i = 0; i < len; i++) this[i] = dom[i]
this.length = len
this.selector = selector || ''
}
// `$.zepto.Z` swaps out the prototype of the given `dom` array
// of nodes with `$.fn` and thus supplying all the Zepto functions
// to the array. This method can be overridden in plugins.
zepto.Z = function(dom, selector) {
return new Z(dom, selector)
}
zepto.init = function(selector, context) {
// ...
return zepto.Z(dom, selector)
}
// `$` will be the base `Zepto` object. When calling this
// function just call `$.zepto.init, which makes the implementation
// details of selecting nodes and creating Zepto collections
// patchable in plugins.
$ = function(selector, context) {
return zepto.init(selector, context)
}
zepto.Z.prototype = Z.prototype = $.fn
$.zepto = zepto
return $
})()

4.2 zepto.Z和Z对象

这里有一个关键的对象zepto.Z,返回Z对象实例

1
2
3
4
5
6
function Z(dom, selector) {
var i, len = dom ? dom.length : 0
for (i = 0; i < len; i++) this[i] = dom[i]
this.length = len
this.selector = selector || ''
}

可以打断点各种试一试,其实是将选择器选中的dom数组转为对象

1
console.log('%o',$(document).length)

之所以能访问length是因为Z对象实例本身有这个属性

4.3 zepto.init

为了减轻阅读负担,先简化zepto.init函数。简而言之,zepto.init对selector参数,也就是$(selector,context)传入的第一个参数可能出现的情况编写了逻辑分支。看下面代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
zepto.init = function(selector,context){
var dom
if(!selector){
return zepto.Z()
} else if(typeof selector == 'string'){
// ...
} else if(isFunction(selector)){
return $(document).ready(selector)
} else if(zepto.isZ(selector)){
return selector
} else {
// ...
}
return zepto.Z(dom, selector)
}

首先主要逻辑有五条

  1. selector不存在则返回Z空对象
  2. selector是个string则进一步处理
  3. selector是个function则执行ready
  4. selector是Z实例则原样返回,可多层嵌套(((selector))验证
  5. else进一步处理

按照顺序本来应该分别阐述各个逻辑的实现细节,不过可以先放一放。目前我们依然不能够去深追细节,而应该继续从结构层面分析后续的内容。目前,我们只需有一个模糊概念:一切似乎都与Z对象有某种联系。

4.4 运行原理

到目前为止,我们分析了(selector)Z(selector)执行的逻辑从而接触了Z对象。接下去从(selector).html()执行过程进一步了解zepto核心的解构。

首先我们得思考$(selector)拿到的是一个Z对象实例,那么它为什么能够执行html()方法。

首先我们先看一下Z构造方法

1
2
3
4
5
6
function Z(dom, selector) {
var i, len = dom ? dom.length : 0
for (i = 0; i < len; i++) this[i] = dom[i]
this.length = len
this.selector = selector || ''
}

实例化后应当只有length,selector这两个属性,以及类似数组下标的dom节点,马上想到其他方法应该是委托给了原型。

1
zepto.Z.prototype = Z.prototype = $.fn

通过查看$.fn源码,果然发现了html()方法的定义,还有许多attr等也同样在这里定义,这样我们至少弄清楚了运行的流程,剩下的按部就班的阅读各个部分的实现细节就可以了。

1
console.log($(document).__proto__===$.fn)//true

最后看看html的实现

1
2
3
4
5
6
7
8
html: function(html){
return 0 in arguments ?
this.each(function(idx){
var originHtml = this.innerHTML
$(this).empty().append( funcArg(this, html, idx, originHtml) )
}) :
(0 in this ? this[0].innerHTML : null)
},

这里面另外又涉及了empty方法,append方法,说明各个方法之间并不是孤立的。建议用debug的方式追溯,这将覆盖绝大部分的源码,所以我们来看看.fn.fn以外的代码,将.fn放到最后来看。

4.5 $.extend()方法

查阅手册,$.extend()有两个用法

  • $.extend(target, [source, [source2, …]]) ⇒ target

  • $.extend(true, target, [source, …]) ⇒ target v1.0+

  • 通过源对象扩展目标对象的属性,源对象属性将覆盖目标对象属性。

  • 默认情况下为,复制为浅拷贝(浅复制)。如果第一个参数为true表示深度拷贝(深度复制)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 观察递归实现拷贝
function extend(target, source, deep) {
for (key in source) {
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
if (isPlainObject(source[key]) && !isPlainObject(target[key])){
target[key] = {}
}
if (isArray(source[key]) && !isArray(target[key])){
target[key] = []
}
extend(target[key], source[key], deep)
} else if (source[key] !== undefined) {
target[key] = source[key]
}
}
}

// Copy all but undefined properties from one or more
// objects to the `target` object.
$.extend = function(target) {

// target 如果是布尔值则第二个参数是目标对象
var deep, args = slice.call(arguments, 1)
if (typeof target == 'boolean') {
deep = target
target = args.shift()
}
// 目标对象只有一个,源对象可以有多个
args.forEach(function(arg) {
extend(target, arg, deep)
})
// 即使传入时是布尔值,运行后已经被替换目标对象
return target
}

代码应当是很好理解的,顺便可以看看设计的两个工具方法

1
2
3
4
5
6
7
isArray = Array.isArray ||function(object){ return object instanceof Array }
function isPlainObject(obj) {
return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
}

$.isArray = isArray
$.isPlainObject = isPlainObject

我们已经可以参考手册给出的方法顺序,逐个去研究一些工具方法,比如再看一个

  • $.parseJSON
  • $.trim
  • $.noop
1
2
3
4
5
6
7
8
9
10
11
if (window.JSON) $.parseJSON = JSON.parse

$.trim = function(str) {
return str == null ? "" : String.prototype.trim.call(str)
}

$.noop = function() {}




4.6 其他工具方法

到目前为止,我们已经大致了解Zepto是怎么运行的,随之代码覆盖面的扩大,出现了许多工具库方法,比如isObject,isWindow这些。这个时候可以按照源码顺序,尝试逐行理解代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 驼峰化
camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) }
$.camelCase = camelize

$.contains = document.documentElement.contains ?
function(parent, node) {
return parent !== node && parent.contains(node)
} :
function(parent, node) {
while (node && (node = node.parentNode))
if (node === parent) return true
return false
}

1
2
3
4
5
6
7
8
9
10
11
12
13
$.each = function(elements, callback){
var i, key
if (likeArray(elements)) {
for (i = 0; i < elements.length; i++)
if (callback.call(elements[i], i, elements[i]) === false) return elements
} else {
for (key in elements)
if (callback.call(elements[key], key, elements[key]) === false) return elements
}

return elements
}

1
2
3
4
// filter是数组原生filter
$.grep = function(elements, callback){
return filter.call(elements, callback)
}

差不多覆盖文档中所有的方法,只剩下–$.fn

4.6 $.fn

参考阅读


Linux部署(一)NodeJs安装

Linux安装NodeJs

一. 源码安装

官方文档

1. 下载

在官网下载对应的Linux二进制文件然后扔到服务器上!或者

1
wget https://nodejs.org/dist/v10.14.2/node-v10.14.2-linux-x64.tar.xz

2. 解压

目前官网下载的是.xz文件,解压到当前文件目录(取决于想放哪,建议放到/usr/local/下,好找一点)

1
tar -xJvf node-v10.14.2-linux-x64.tar.xz

3. 添加软链接到bin目录

为了能直接使用node和npm命令,为/usr/local/bin目录下添加软链接

1
2
ln -s /opt/nodejs/node-v9.8.0-linux-x64/bin/node /usr/local/bin/node
ln -s /opt/nodejs/node-v9.8.0-linux-x64/bin/npm /usr/local/bin/npm

4. 测试

1
2
3
node -v
npm -v
npm list -g --depth 0 // 查看自己全局安装过的包

5. npm源

1
2
3
4
5
6
// 1.设成淘宝的

npm config set registry http://registry.npm.taobao.org/
// 2.换成原来的

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