Gowhich

Durban's Blog

当执行单元测试的时候,Laravel 会自动将环境配置成testing。另外 Laravel 会在测试环境导入session 和cache 的配置文件。当在测试环境里这两个驱动会被配置为array (空数组),代表在测试的时候没有 session 或 cache 数据将会被保留。视情况你可以任意的建立你需要的测试环境配置。

testing 环境的变量可以在phpunit.xml 文件中配置。

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
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="bootstrap/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Application Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./app</directory>
<exclude>
<file>./app/Http/routes.php</file>
</exclude>
</whitelist>
</filter>
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
</php>
</phpunit>

创建测试用例,命令如下

1
php artisan make:test RouteTest

测试用例代码示例如下

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
<?php
namespace Tests;

use Exception;

class RouteTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function testExample()
{
$host = env('DAODAO_HOST');

$this->baseUrl = 'http://' . $host;

$response = $this->call('GET', '/api/gift');
$content = $response->getContent();

try {
$content = json_decode($content, true);

$this->assertResponseOk();
$this->assertResponseStatus(200);
$this->assertTrue(isset($content['message']));
} catch (Exception $e) {
\Log::error($e);
$this->assertTrue(false);
}
}
}

代码解释说明

这一步用来配置baseUrl,如果routes中的配置指定了domain,那就需要根据env中的配置来填写,否则的话可以直接使用默认的‘http://localhost’

1
2
3
$host = env('DAODAO_HOST');

$this->baseUrl = 'http://' . $host;
1
2
$response = $this->call('GET', '/api/gift');
$content = $response->getContent();

调用$this->call直接请求接口,如果有参数的话可以参考下面

1
$this->call($method, $uri, $parameters, $cookies, $files, $server, $content);

这个是可以使用的参数,可以根据具体的参数情况来调用

代码记录如下

主要是结合vuejs开发的小活动里面,需要用到截图的功能,开始自己以为实现起来很麻烦,经过同事的帮忙,居然有人实现过,就直接拿过来使用了,目前测试结果,没有发现什么兼容性的问题,不过需要按照自己的需求更改下具体的实现逻辑,代码如下

clip库的js代码段(这里是我做了部分改动的,因为最终的图片结果我认为还是直接返回比较好)

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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
class Clip{
//因为要保存到vue实例中,所以直接传一个$vm当闭包使用了
constructor(wpId,$vm){
this.regional = document.getElementById(wpId);
this.getImage = document.createElement('canvas');
this.getImage.id = 'image-box';
this.editBox = document.createElement('canvas');
this.editBox.id = 'cover-box';

this.regional.appendChild(this.getImage);
this.regional.appendChild(this.editBox);
this.$vm = $vm;
}

init(file){
this.sx = 0; //裁剪框的初始x
this.sy = 0; //裁剪框的初始y
this.sWidth = 233; //裁剪框的宽
this.sHeight = 233; //裁剪框的高
this.chooseBoxScale = 233/233;

this.handleFiles(file);
}

handleFiles(file){
let t = this;
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function() {
t.imgUrl = this.result;
t.paintImg(this.result);
}
}

paintImg(picUrl){
let t = this;
let cxt = t.getImage.getContext('2d');

//先清空画布
cxt.clearRect(0, 0, this.getImage.width, this.getImage.height);

let img = new Image();
img.src = picUrl;

img.onload = function() {

let imgScale = img.width / img.height;
let boxScale = t.regional.offsetWidth / t.regional.offsetHeight;

//判断盒子与图片的比列
if (imgScale < boxScale) {
//设置图片的像素
t.imgWidth = t.regional.offsetHeight * imgScale;
t.imgHeight = t.regional.offsetHeight;
} else {
//设置图片的像素
t.imgWidth = t.regional.offsetWidth;
t.imgHeight = t.regional.offsetWidth / imgScale;
}

//判断图片与选择框的比例大小,作出裁剪
if (imgScale < t.chooseBoxScale) {
//设置选择框的像素
t.sWidth = t.imgWidth;
t.sHeight = t.imgWidth / t.chooseBoxScale;

//设置初始框的位置
t.sx = 0;
t.sy = (t.imgHeight - t.sHeight) / 2;
} else {
//设置选择框的像素
t.sWidth = t.imgHeight * t.chooseBoxScale;
t.sHeight = t.imgHeight;

t.sx = (t.imgWidth - t.sWidth) / 2;
t.sy = 0;
}

//高分屏下图片模糊,需要2倍处理
t.getImage.height = 2 * t.imgHeight;
t.getImage.width = 2 * t.imgWidth;
t.getImage.style.width = t.imgWidth + 'px';
t.getImage.style.height = t.imgHeight + 'px';

let vertSquashRatio = t.detectVerticalSquash(img);

cxt.drawImage(img, 0, 0,2 * t.imgWidth * vertSquashRatio, 2 * t.imgHeight * vertSquashRatio)

t.cutImage();
t.drag();
}
}

cutImage(){
let t = this;

//绘制遮罩层:
t.editBox.height = 2 * t.imgHeight;
t.editBox.width = 2 * t.imgWidth;

t.editBox.style.display = 'block';
t.editBox.style.width = t.imgWidth + 'px';
t.editBox.style.height = t.imgHeight + 'px';

let cover = t.editBox.getContext("2d");
cover.fillStyle = "rgba(0, 0, 0, 0.7)";

cover.fillRect(0, 0, 2 * t.imgWidth, 2 * t.imgHeight);
cover.clearRect(2 *t.sx, 2 * t.sy, 2 * t.sWidth, 2 * t.sHeight);
}

drag(){
let t = this;
let draging = false;

//记录初始点击的pageX,pageY。用于记录位移
let pageX = 0;
let pageY = 0;

//初始位移
let startX = 0;
let startY = 0;

t.editBox.addEventListener('touchmove', function(ev) {
let e = ev.touches[0];

let offsetX = e.pageX - pageX;
let offsetY = e.pageY - pageY;
if (draging) {
if (t.imgHeight == t.sHeight) {
t.sx = startX + offsetX;
if (t.sx <= 0) {
t.sx = 0;
} else if (t.sx >= t.imgWidth - t.sWidth) {
t.sx = t.imgWidth - t.sWidth;
}
} else {
t.sy = startY + offsetY;
if (t.sy <= 0) {
t.sy = 0;
} else if (t.sy >= t.imgHeight - t.sHeight) {
t.sy = t.imgHeight - t.sHeight;
}
}
t.cutImage();
}
});

t.editBox.addEventListener('touchstart', function(ev) {
let e = ev.touches[0];
draging = true;

pageX = e.pageX;
pageY = e.pageY;

startX = t.sx;
startY = t.sy;

})

t.editBox.addEventListener('touchend', function() {
draging = false;
})
}

save(callback) {
let t = this;
let saveCanvas = document.createElement('canvas');
let ctx = saveCanvas.getContext('2d');

//图片裁剪后的尺寸
saveCanvas.width = 466;
saveCanvas.height = 466;

let images = new Image();
images.src = t.imgUrl;

images.onload = function(){
//计算裁剪尺寸比例,用于裁剪图片
let cropWidthScale = images.width/t.imgWidth;
let cropHeightScale = images.height/t.imgHeight;

t.drawImageIOSFix(
ctx,
images,
cropWidthScale * t.sx ,
cropHeightScale* t.sy,
t.sWidth * cropWidthScale,
t.sHeight * cropHeightScale,
0,
0,
466,
466);

// t.$vm.clipUrl = saveCanvas.toDataURL();
t.regional.removeChild(t.getImage);
t.regional.removeChild(t.editBox);
callback(saveCanvas.toDataURL());
}
}

remove() {
let t = this;
t.regional.removeChild(t.getImage);
t.regional.removeChild(t.editBox);
}

getImageUrl() {
let t = this;
return t.$vm.clipUrl;
}

//用于修复ios下的canvas截图问题
//详情可以看这里http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios
detectVerticalSquash(img) {
if(/png$/i.test(img.src)) {
return 1;
}
let iw = img.naturalWidth, ih = img.naturalHeight;
let canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = ih;
let ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
let data = ctx.getImageData(0, 0, 1, ih).data;

let sy = 0;
let ey = ih;
let py = ih;
while (py > sy) {
const alpha = data[(py - 1) * 4 + 3];
if (alpha === 0) {
ey = py;
} else {
sy = py;
}
py = (ey + sy) >> 1;
}
const ratio = (py / ih);
return (ratio===0)?1:ratio;
}
drawImageIOSFix(ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) {
const vertSquashRatio = this.detectVerticalSquash(img);
ctx.drawImage(
img,
sx * vertSquashRatio,
sy * vertSquashRatio,
sw * vertSquashRatio,
sh * vertSquashRatio,
dx,
dy,
dw,
dh);
}
}

css代码段(这里我只用到了截图图片一小部分的UI,就是弹窗后截取图片的UI部分,这一部分如果自己写的话,就要看源码了。其实截图后的展示部分的功能,完全可以根据自己的需求来实现,而且原作者是用的less)

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
.file{
height: 40px;
display: block;
margin: 40px auto 0;
}

.clip-img{
width: 300px;
height: 225px;
margin: 20px auto 0;
border: 1px solid #999;
overflow: hidden;
}

.clip-img img{
width: 100%;
}

.upload-wp {
text-align: center;
width: 300px;
margin: 20px auto 0;
}

.upload-wp button {
padding: 5px 10px;
}

.upload-wp p {
word-wrap: break-word;
font-size: 12px;
}

.clip-wp {
position: fixed;
width: 100%;
top: 0;
bottom: 0;
z-index: 11;
background-color: #000;
text-align: center;
}

.clip-wp #container{
background-color: #000;
text-align: center;
width: 100%;
left: 0;
right: 0;
top: 20px;
bottom: 80px;
margin: 0 auto;
position: absolute;
}

.clip-wp #save-img{
position: absolute;
bottom: 20px;
width: 40%;
left: 5%;
height: 42px;
line-height: 42px;
color: #fff;
background-color: #32c47c;
border-radius: 20px;
}

.clip-wp #cancel-img{
position: absolute;
bottom: 20px;
width: 40%;
left: 55%;
height: 42px;
line-height: 42px;
color: #fff;
background-color: #32c47c;
border-radius: 20px;
}

.clip-wp #image-box {
position: absolute;
left: 0px;
right: 0px;
bottom: 0px;
top: 0px;
margin: auto;
}

.clip-wp #cover-box {
position: absolute;
z-index: 9999;
display: none;
left: 0px;
right: 0px;
bottom: 0px;
top: 0px;
margin: auto;
}

.preview-wp {
text-align: left;
position: fixed;
top: 0;
bottom: 0;
width: 100%;
background-color: #000;
overflow:auto;
}

.preview-wp .preview-img{
position: absolute;
top: 50%;
width: 100%;
transform: translate(0 , -50% );
-webkit-transform: translate(0 ,-50%);
}

html代码实现部分,这个就要根据具体的情况,我是页面上有两个图片按钮,点击后替换图片 所以我就直接用一个file类型的input,上传完之后将图片替换到指定的按钮上,然后重置input,这样就可以重复不断的更新上传图片

1
2
3
4
5
6
<!-- 上传图片的隐藏元素 -->
<div style="width:9vw;height:9vw;background:red;position:absolute;top:2vw;display:none">
<form id="fileElem1">
<input ref=fileElem type="file" class="file" accept="image/*;capture=camera" name="img" @change="clipImg($event)" style="display: none">
</form>
</div>
1
2
3
4
5
6
7
<span style="display:inline-block;position:relative;">
<span style="display:inline-block;width:9vw;height:9vw;line-height:9vw;text-align:center;border:solid .2vw #fff;background-color:#f7e9d4;box-shadow:0 1vw 1vw 0 #e0cfb3;overflow:hidden;border-radius:50%" @click='choiceMeAvatar()'>
<img v-bind:src="images.left_avatar_kuang" style="width: 100%">
</span>
<span style="display: inline-block;width: 9vw;height: 9vw;line-height:9vw;text-align:center;border: solid 0.2vw #ffffff;background-color: #f7e9d4;box-shadow: 0vw 1vw 1vw 0vw #e0cfb3;overflow: hidden;border-radius: 50%;margin-left: -2vw" @click='choiceTaAvatar()'>
<img v-bind:src="images.right_avatar_kuang" style="width: 100%">
</span>

vue js代码methods实现部分

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
clipImg: function(event){
this.clip = new Clip('container', this);
this.clip.init(event.target.files[0]);
this.isClip = true;
document.body.addEventListener('touchmove', this.noScoll, false);
// main-container 固定
document.getElementById('main-container').style.position = 'fixed';
},

saveImg: function(){
this.isClip = false;
var self = this;
var imageData = this.clip.save(function(imageData){
console.log(self.currentUploadFieldName);
console.log(imageData);

if (self.currentUploadFieldName == 'left_avatar_kuang') {
self.images.left_avatar_kuang = imageData;
} else if (self.currentUploadFieldName == 'right_avatar_kuang') {
self.images.right_avatar_kuang = imageData;
}
});

document.body.removeEventListener('touchmove', this.noScoll, false);
// form中input reset
document.getElementById("fileElem1") && document.getElementById("fileElem1").reset();
// main-container 解除固定
document.getElementById('main-container').style.position = 'relative';
},

cancelImg: function() {
this.isClip = false;
this.clip.remove();
this.clip = null;
document.body.removeEventListener('touchmove', this.noScoll, false);
// form中input reset
document.getElementById("fileElem1") && document.getElementById("fileElem1").reset();
// main-container 解除固定
document.getElementById('main-container').style.position = 'relative';
},

vue变量部分代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
data: {
currentUploadFieldName: '',
images: {
'left_avatar_kuang': '/xxx/avatar_default.png',
'right_avatar_kuang': '/xxx/avatar_default.png',
},
isClip: false,
clipUrl:'',
noScoll: function(evt){
this.isClip && evt.preventDefault();
},
clip:{},
}

我这里只是给出了基本上80%实现部分,还有20%具体细节的部分要具体细化了。

参考文章:链接1

Laravel版本信息 "laravel/framework": "5.2.*"

在项目根目录tests(如果没有tests目录的话,自己创建)目录下创建TestCase.php

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

class TestCase extends BaseTestCase
{

protected $baseUrl = 'http://localhost';
public function createApplication()
{
$app = require __DIR__ . '/../bootstrap/app.php';
$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
return $app;
}
}

修改composer.json,添加下面的配置

1
"autoload-dev": { "classmap": [ "tests/TestCase.php" ] },

再执行composer update

创建测试用例,命令如下

1
php artisan make:test ControllerActionTest

生成的文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ControllerActionTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function testExample()
{
$this->assertTrue(true);
}
}

修改成下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
namespace Tests;

class ControllerActionTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function testExample()
{
$this->assertTrue(true);
}
}

执行测试单元命令

1
2
3
4
5
6
7
8
$ ./vendor/bin/phpunit
PHPUnit 4.8.36 by Sebastian Bergmann and contributors.

.

Time: 1.37 seconds, Memory: 18.00MB

OK (1 test, 1 assertion)

如果遇到上面的输出则表示单元测试正常运行。

最近mac电脑打开远程打开tmux发现窗口变小了

可能的原始是昨晚我使用手机远程使用了tmux导致了窗口变的很小

但是要如何解决这个问题是个关键。

Ctrl-B + Shift-D选一个你需要使用的session

结果搞定了。

参考文章

https://www.v2ex.com/t/208296

https://stackoverflow.com/questions/7814612/is-there-any-way-to-redraw-tmux-window-when-switching-smaller-monitor-to-bigger/20908246#20908246

canvas生成图片的方法,我们通过官方教程都能简单的生成,但是我遇到的情况是,在生成的过程中,图片有点大,其实图片大也没什么问题,但是要知道图片大的话上传是比较消耗时间和流量的,所以对于大尺寸的图片还是要进行下压缩比较好

正常生成图片我们可以配置参数

1
canvas.toDataURL("image/jpeg", quality);

image/jpeg

可以替换为 image/png 或者 image/webp

不过 image/webp 格式的话ios好像默认情况下是不支持的

quality 是一个0-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
30
31
32
33
34
35
36
/**
* @param base64 为64位编码的图片数据
* @param w 要压缩的图片的宽度
* @param calback 回掉函数
*/
function dealImage (base64, w, callback) {
var newImage = new Image();
var quality = 1; //压缩系数0-1之间
newImage.src = base64;
newImage.setAttribute("crossOrigin", 'Anonymous'); // url为外域时需要
var imgWidth, imgHeight;
newImage.onload = function () {
imgWidth = this.width;
imgHeight = this.height;
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
if (Math.max(imgWidth, imgHeight) > w) {
if (imgWidth > imgHeight) {
canvas.width = w;
canvas.height = w * imgHeight / imgWidth;
} else {
canvas.height = w;
canvas.width = w * imgWidth / imgHeight;
}
} else {
canvas.width = imgWidth;
canvas.height = imgHeight;
quality = 1;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
var base64 = canvas.toDataURL("image/jpeg", quality); //压缩语句

callback(base64);
}
}

事情是这样子的,之前一直使用linode的服务器,但是这家伙隔几天就给我发个信息说是物理磁盘在干什么事情,然后我的服务器就挂了,好赖也通知我了,但是经不起你这么折腾呀,一个月搞了我好几次,我一个小小站长,做个博客本来就不容易,还没事来搞我,于是一气之下放弃了linode。

转而看了许多,最后选择vultr.com,让我意外的发现,vultr支持的支付方式还是满多的,大喜

随后选了好几个区的服务器,试了好几个,都不太理想,毕竟国外的快的也都被商家玩坏了,好的吧也就慢一些,真的就是慢一些吗?我不信

我还是选择了日本的,毕竟是在亚洲,如果中国访问不了,亚洲其他的区还是可以访问的,比如香港、台湾等

而且如果哪天路线畅通了,还是比较快的,另外我为什么不选择香港,我只能说点不公道的话了,为什么人家国外的便宜,就买香港的大陆的都会贵个10几块钱呢。

本来就不容易为啥还要搞我们呢,其实不针对你,放弃这种想法吧,要从国家策略着想。

接下来配置服务器,不小心选择了Centos 8,在模糊的不知道是几的情况下,反正基本部署完了,部署完才发现是Centos 8.

接下来使用mosh登录后,提示如下信息

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
The locale requested by LANG=zh_CN.UTF-8 isn't available here.
Running `locale-gen zh_CN.UTF-8' may be necessary.

mosh-server needs a UTF-8 native locale to run.

Unfortunately, the local environment ([no charset variables]) specifies
the character set "US-ASCII",

The client-supplied environment (LANG=zh_CN.UTF-8) specifies
the character set "US-ASCII".

locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
LANG=zh_CN.UTF-8
LC_CTYPE="zh_CN.UTF-8"
LC_NUMERIC="zh_CN.UTF-8"
LC_TIME="zh_CN.UTF-8"
LC_COLLATE="zh_CN.UTF-8"
LC_MONETARY="zh_CN.UTF-8"
LC_MESSAGES="zh_CN.UTF-8"
LC_PAPER="zh_CN.UTF-8"
LC_NAME="zh_CN.UTF-8"
LC_ADDRESS="zh_CN.UTF-8"
LC_TELEPHONE="zh_CN.UTF-8"
LC_MEASUREMENT="zh_CN.UTF-8"
LC_IDENTIFICATION="zh_CN.UTF-8"
LC_ALL=

执行命令

1
locale-gen zh_CN.UTF-8

提示你

1
command not found: locale-gen

网上很多copy来copy去的帖子,都是一路的,看一遍差不多也就够了,基本上没啥解决的方案,因为都是CentOS 8之前的系统的帖子

截止今天,好像CentOS 8系统的普及还不是很多,反正在vultr.com上是没有具体的使用指南的。

我说下我是怎么解决的吧,可能能帮到你

1
sudo yum search Chinese

找到这个langpacks-zh_CN.noarch名的包,然后执行

1
sudo yum install langpacks-zh_CN

再然后执行

1
localedef -c -f UTF-8 -i zh_CN zh_CN.UTF-8

修改下~/.bashrc,添加如下代码

1
2
export LANG=zh_CN.UTF-8
export LC_ALL=zh_CN.UTF-8

不过我觉得好像不需要,其实就是少安装了中文支持的包,也许你遇到的并不是这最基础的没有安装支持包的问题。

最近启动nginx,总是提示我8080端口内占用了,但是我就想知道具体是哪个程序占用了这个端口,这个技术点困扰了我很久,一想我又不搞服务器部署,出问题肯定会有人处理的,怎奈我自己的服务器也遇到了这个问题,没办法,必须解决掉。本以为都是命令直接搜索一个拿来用用不就可以了嘛,但是!但是!但是!在不同系统的环境下,命令的使用方式居然不一样。

先看个常用的命令

1
2
3
4
5
6
7
8
9
10
$ netstat -anvp |grep 8080
netstat: option requires an argument -- p
Usage: netstat [-AaLlnW] [-f address_family | -p protocol]
netstat [-gilns] [-f address_family]
netstat -i | -I interface [-w wait] [-abdgRtS]
netstat -s [-s] [-f address_family | -p protocol] [-w wait]
netstat -i | -I interface -s [-f address_family | -p protocol]
netstat -m [-m]
netstat -r [-Aaln] [-f address_family]
netstat -rs [-s]

这个命令提示我netstat: option requires an argument -- p

其实是缺少协议

在Mac上正确使用的方法是:即-f需要加上地址族,-p需要加上协议TCP或者UDP等

  • 如果要查询inet - 命令使用 netstat -anvf inet
  • 如果要查询tcp - 命令使用 netstat -anvp tcp
  • 如果要查询udp - 命令使用 netstat -anvp udp

于是将上面的命令调整为下面

1
2
3
4
$ netstat -anvp tcp |grep 8080
tcp4 0 0 172.18.0.71.56891 180.163.32.172.8080 ESTABLISHED 262144 131920 51388 0
tcp4 0 0 172.18.0.71.56816 114.221.144.160.8080 ESTABLISHED 262144 131072 98268 0
tcp46 0 0 *.8080 *.* LISTEN 131072 131072 45216 0

当然netstat只是查找占用端口命令中的一个命令而已

下面看看另外一个命令lsof

lsof 是一个列出当前系统打开文件的工具

使用 lsof 如果不传任何参数会列出所有端口占用的列表,不过不建议直接使用lsof,太多看不过来 可以使用lsof | less,然后一下一下点击空格键,会一屏一屏的列出

如果查询占用的端口号,可以使用如下命令

1
lsod -i:8080

比如我当前占用8080端口的程序

1
2
3
4
5
6
$ lsof -i:8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
QQBrowser 51388 durban 106u IPv4 0xeed752c692289c43 0t0 TCP 172.18.0.71:56891->180.163.32.172:http-alt (ESTABLISHED)
QQBrowser 51388 durban 115u IPv4 0xeed752c692289c43 0t0 TCP 172.18.0.71:56891->180.163.32.172:http-alt (ESTABLISHED)
QQ 98268 durban 17u IPv4 0xeed752c6923d2213 0t0 TCP 172.18.0.71:56816->114.221.144.160:http-alt (ESTABLISHED)
QQ

这里只要拿到PID就可以进行kill或者是其他的操作了。

translate和transition一直让我觉得,很牛皮很强大,怎么也学不会,其实是自己比较抗拒去了解她,接口看了不到半个小时的文档,大概了解了下,下面是示例,可以下载下来自己运行下试试

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
<!DOCTYPE html>
<html>
<head>
<title>translate和transition</title>
</head>
<body>
<style type="text/css">
div {
width: 100px;
height: 75px;
background-color: red;
border: 1px solid black;
}

div#translate {
transition: all 2s;
-ms-transition: all 2s;
-webkit-transition: all 2s;
}

div#translate:hover{
transform: translate(50px, 100px);
-ms-transform: translate(50px, 100px);
-webkit-transform: translate(50px, 100px);
}
</style>
<div>Hello, This is a Div element</div>
<div id='translate'>Hello, This is another Div element</div>
</body>
</html>

演示demo请点 这里

  • translate(a, b):用官方的话说叫做2D转移,其实就是平面上的x轴和y轴移动,搞那么多名词就是因为我们学识太低,不想让我们容易了解

a - 在横向(左右方向)也就是x轴移动a单位距离,比如是10px,那么就移动10px,正值向右移动,负值向左移动 b - 在纵向(上下方向)也就是y轴移动b单位距离,比如是50px,那么就移动10px,正值向下移动,负值向上移动

起点在左上角哈,但是如果元素位置开始就设置了非原点的话就另说了,就是在元素基础上做计算

1
2
3
4
原点(0,0)-------
|
|
|
  • transition 动画过渡
1
transition: property duration timing-function delay

property - css属性

duration - 动画执行时长 如果为0 动画不执行

timing-function 动画执行方式 默认ease

delay - 动画延迟执行时间 默认0

这四个是属性,别以为我是写了其他的属性,具体的看(文档)[https://developer.mozilla.org/zh-CN/docs/Web/CSS/transition]

这里不再介绍,看我示例就知道了,

实例及实现原理如下

1
2
3
4
5
6
7
8
9
10
var uploadKey = 'avatar/20191212/userid-1.png';

// key 进行base64处理
// 第一步使用encodeURIComponent() 函数把字符串作为 URI 组件进行编码
// 第二步使用unescape() 函数对通过 escape() 编码的字符串进行解码
// 第三步使用btoa() 方法用于创建一个 base-64 编码的字符串。该方法使用 "A-Z", "a-z", "0-9", "+", "/" 和 "=" 字符来编码字符串。base64 解码使用方法是 atob() 。

var key = btoa(unescape(encodeURIComponent(uploadKey)));

var url = "http://upload.qiniup.com/putb64/12" + '/key/' + key;

运行上面代码结果如下

1
2
3
4
5
6
7
8
9
> var uploadKey = 'avatar/20191212/userid-1.png';
< undefined
> var key = btoa(unescape(encodeURIComponent(uploadKey)));
< undefined
> var url = "http://upload.qiniup.com/putb64/12"+'/key/'+key;
< undefined
> console.log(url)
VM7861:1 http://upload.qiniup.com/putb64/12/key/YXZhdGFyLzIwMTkxMjEyL3VzZXJpZC0xLnBuZw==
> undefined

参考如下 https://stackoverflow.com/questions/246801/how-can-you-encode-a-string-to-base64-in-javascript

我们分解执行下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> var a = encodeURIComponent(uploadKey)
< undefined
> console.log(a)
< avatar%2F20191212%2Fuserid-1.png
> undefined
< var b = unescape(a);
> undefined
< console.log(b)
avatar/20191212/userid-1.png
> undefined
< var c = btoa(b)
> undefined
< console.log(c)
YXZhdGFyLzIwMTkxMjEyL3VzZXJpZC0xLnBuZw==
> undefined

实现原理:根据base64,做区分,前面部分是类型,后面部分是真实的的图片字符串

比如下面的base64图片字符串

// 这里只是做了一个例子

var base64DataStr = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABNoAAAigCAY ... pw52VLv8/zk3Uc3CN6sgAAAAASUVORK5CYII=';

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function getBase64ImgSize(base64DataStr) {
var tag = "base64,";
var base64Data = '';

// 截取字符串 获取"base64,"后面的字符串
base64DataStr = base64Data = base64DataStr.substring(base64DataStr.indexOf(tag) + tag.length);

// 根据末尾等号('=')来再次确认真实base64图片字符串
var eqTagIndex = base64DataStr.indexOf("=");
base64DataStr = eqTagIndex != -1 ? base64DataStr.substring(0, eqTagIndex) : base64DataStr;

// 计算大小
var strLen = base64DataStr.length;
var fileSize = strLen - (strLen / 8) * 2; // 这里的原理可以想一下为什么要这样做
return {
fileSize: fileSize,
base64Data: base64Data,
};
}

使用方式如下

1
getBase64ImgSize(base64DataStr)

运行后得到的结果

1
{fileSize: 57, base64Data: "iVBORw0KGgoAAAANSUhEUgAABNoAAAigCAY ... pw52VLv8/zk3Uc3CN6sgAAAAASUVORK5CYII="}
0%