简世博客

一个简单的世界——博客空间,写了一些Android相关的技术文章,和一些点滴的想法

0%

Flutter_web加Java Spring 整站前后台开发经验梳理

这段时间开发了一个公司内部使用的网站,本着提前探索熟悉(踩坑)未来的全栈UI框架-Flutter的愿望,使用了Flutter for Web作为前端框架,后台部分则循规蹈矩的用了java Spring。目前已经开发完成,在此简要记录一下开发过程中积累的一些经验。

突然发现,我现在不仅会android,又会java后端,又能用flutter写web+android+ios代码,一下子变成了全栈工程师喽!

阅读全文 »

管道 

command1 | command2

用竖线分割两个命令,把第一个命令的输出,作为第二个命令的输入。 此时需要第二个命令支持这种从管道获取输入的功能,例如cat ls就支持这种功能。

 

xargs

command1 | xargs command2

xargs和管道类似,都是把第一个命令的输出传递到第二个命令。

与管道不同的是,管道给第二个命令传递是直接传到命令输入上,这种方式要求命令本身的支持,支持的命令较少。

而xargs,则是把第一个命令的输出作为参数传递到第二个命令上,这种方式只要第二个命令可以接受参数即可,支持的命令较多。

 

一个例子:

➜  ~ echo test.txt | cat
test.txt
➜  ~ echo test.txt | xargs cat
content of test.txt

echo test.txt | cat 是把 "test.txt" 这个字符串直接让cat输出

echo test.txt | xargs cat 是把 "test.txt" 作为参数传递给cat,表示把 test.txt 这个文件的内容输出

 

xargs还有一些有用的选项:

1. -d 选项
默认情况下xargs将其标准输入中的内容以空白(包括空格、Tab、回车换行等)分割成多个之后当作命令行参数传递给其后面的命令,并运行之,我们可以使用 -d 命令指定分隔符,例如:
echo '11@22@33' | xargs echo
输出:
11@22@33
默认情况下以空白分割,那么11@22@33这个字符串中没有空白,所以实际上等价于 echo 11@22@33 其中字符串 '11@22@33' 被当作echo命令的一个命令行参数

echo '11@22@33' | xargs -d '@' echo
输出:
11 22 33
指定以@符号分割参数,所以等价于 echo 11 22 33 相当于给echo传递了3个参数,分别是11、22、33

2. -p 选项
使用该选项之后xargs并不会马上执行其后面的命令,而是输出即将要执行的完整的命令(包括命令以及传递给命令的命令行参数),询问是否执行,输入 y 才继续执行,否则不执行。这种方式可以清楚的看到执行的命令是什么样子,也就是xargs传递给命令的参数是什么,例如:
echo '11@22@33' | xargs -p -d '@'  echo
输出:
echo 11 22 33
 ?...y      ==>这里询问是否执行命令 echo 11 22 33 输入y并回车,则显示执行结果,否则不执行
 11 22 33   ==>执行结果

3. -n 选项
该选项表示将xargs生成的命令行参数,每次传递几个参数给其后面的命令执行,例如如果xargs从标准输入中读入内容,然后以分隔符分割之后生成的命令行参数有10个,使用 -n 3 之后表示一次传递给xargs后面的命令是3个参数,因为一共有10个参数,所以要执行4次,才能将参数用完。例如:

echo '11@22@33@44@55@66@77@88@99@00' | xargs -d '@' -n 3 echo
输出结果:
11 22 33
44 55 66
77 88 99
00
等价于:
echo 11 22 33
echo 44 55 66
echo 77 88 99
echo 00
实际上运行了4次,每次传递3个参数,最后还剩一个,就直接传递一个参数。

4. -E 选项,有的系统的xargs版本可能是-e  eof-str
该选项指定一个字符串,当xargs解析出多个命令行参数的时候,如果搜索到-e指定的命令行参数,则只会将-e指定的命令行参数之前的参数(不包括-e指定的这个参数)传递给xargs后面的命令
echo '11 22 33' | xargs -E '33' echo
输出:
11 22

可以看到正常情况下有3个命令行参数 11、22、33 由于使用了-E '33' 表示在将命令行参数 33 之前的参数传递给执行的命令,33本身不传递。等价于 echo 11 22 这里-E实际上有搜索的作用,表示只取xargs读到的命令行参数前面的某些部分给命令执行。

注意:-E只有在xargs不指定-d的时候有效,如果指定了-d则不起作用,而不管-d指定的是什么字符,空格也不行。

echo '11 22 33' | xargs -d ' ' -E '33' echo  => 输出 11 22 33
echo '11@22@33@44@55@66@77@88@99@00 aa 33 bb' | xargs -E '33' -d '@' -p  echo  => 输出 11 22 33 44 55 66 77 88 99 00 aa 33 bb

## -0 选项表示以 '\0' 为分隔符,一般与find结合使用

find . -name "*.txt"
输出:
./2.txt
./3.txt
./1.txt     => 默认情况下find的输出结果是每条记录后面加上换行,也就是每条记录是一个新行

find . -name "*.txt" -print0
输出:
./2.txt./3.txt./1.txt     => 加上 -print0 参数表示find输出的每条结果后面加上 '\0' 而不是换行

find . -name "*.txt" -print0 | xargs -0 echo
输出:
./2.txt ./3.txt ./1.txt

find . -name "*.txt" -print0 | xargs -d '\0' echo
输出:
./2.txt ./3.txt ./1.txt

xargs的 -0 和 -d '\0' 表示其从标准输入中读取的内容使用 '\0' 来分割,由于 find 的结果是使用 '\0' 分隔的,所以xargs使用 '\0' 将 find的结果分隔之后得到3个参数: ./2.txt ./3.txt ./1.txt  注意中间是有空格的。上面的结果就等价于 echo ./2.txt ./3.txt ./1.txt

实际上使用xargs默认的空白分隔符也是可以的  find . -name "*.txt"  | xargs  echo   因为换行符也是xargs的默认空白符的一种。find命令如果不加-print0其搜索结果的每一条字符串后面实际上是加了换行

 

-exec

command1 -exec command2 {} \;

-exec 和 xargs 的作用相似,都是把前一个命令的输出作为参数传给第二个命令

find . -name "test.txt" -exec cat {} \;

content of test.txt

 

-execxargs不同的是:

-exec是将结果逐条传递给后面的命令,后面的命令逐条执行。

xargs是将结果作为一个列表全部传递给后面的命令,后面的命令一次性执行参数串,可以通过xargs -p ls -l来查看即将要执行的完整的命令。

 

 

打npm包的步骤

  1. 使用parcel编译,parcel build ./index.ts –no-source-maps –target node –bundle-node-modules

加上–no-minify可以设置不混淆

  1. package 中配置name , version ,main ,files 等
    下面是一个例子:

    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
    {
    "name": "mypackage",
    "version": "0.0.1",
    "main": "dist",
    "files": [
    "common",
    "dist"
    ],
    "scripts": {
    "build": "parcel build ./index.ts --no-source-maps --target node --bundle-node-modules --no-minify",
    "dev": "parcel watch ./index.ts --no-source-maps --target node --bundle-node-modules",
    "clean": "rm -rf node_modules && rm -rf dist && rm -rf .cache"
    },
    "dependencies": {
    },
    "devDependencies": {
    "@types/node": "^14.14.22",
    "parcel-bundler": "^1.12.4",
    "typescript": "^4.1.3"
    },
    "publishConfig": {
    "registry": "https://registry.npm.alibaba-inc.com"
    }
    }

  2. https://www.npmjs.com/注册 npm账号

  3. 本地登录 npm账号 npm login

  4. 在仓库目录使用 npm publish 发布包。


本地开发时,可以使用npm link功能来本地依赖库

  1. 在仓库目录中使用npm link命令
  2. 再到调用代码的仓库使用 npm link mypackagename 即可

另外,使用parcel watch ./index.ts --no-source-maps --target node --bundle-node-modules 可以让库中代码修改后自动编译,方便本地依赖开发。

最近有个这样的需求,要在我们的后台界面上,让平台使用者可以输入一段脚本,然后在nodejs层,读取这个脚本并执行。并且还要支持自定义输入参数的能力。
研究了一下Function的使用,记录在这里。

1
2
3
4
5
6
7
8
9
interface FunctionConstructor {
/**
* Creates a new function.
* @param args A list of arguments the function accepts.
*/
new(...args: string[]): Function;
(...args: string[]): Function;
readonly prototype: Function;
}

使用new Function(functionScript)可以根据一个脚本字符串生成一个动态的函数。
然后执行这个函数即可得到函数的结果。

例如:

1
2
3
let functionScript = "return 1+2"
let f = new Function(functionScript)();
let result = f();

result执行结果为3。 非常的简单。

复杂一点的也行,比如:

1
2
3
4
5
6
7
8
let functionScript =
"if (1 > 2) {\n" +
" return 1;\n" +
"} else {\n" +
" return 2;\n" +
"}"
let f = new Function(functionScript);
let result = f();

运行结果是2。

上面的都是不带参数的,带参数的也可以,比如下面这个函数:

1
2
3
4
5
6
7
function findMax(data1, data2) {
if (data1 > data2) {
return data1;
} else {
return data2;
}
}

它带有两个参数,data1和data2,这就需要传到Function里面。

1
2
3
4
5
6
7
8
let functionScript =
"if (data1 > data2) {\n" +
" return data1;\n" +
"} else {\n" +
" return data2;\n" +
"}"
let f = new Function("data1","data2",functionScript);
let result = f(1,2);

以上,就完成了脚本动态执行的功能。


另外,扩展运算符( spread )...args 在生成Function中经常使用。
扩展运算符能将一个数组转为用逗号分隔的参数序列。

1
2
console.log(...[1, 2, 3])
console.log(1, 2, 3)

上面这两行代码就是完全等价的。

Function 的构造函数 new(...args: string[]): Function; 的args参数,是需要展开的,不是一个数组。但是实际开发中,参数肯定是要用数组来保存的。这里就需要用扩展运算符来展开。

上面的代码,就可以转行成下面的形式。

1
2
3
4
5
6
7
8
9
10
11
let argsKey = ["data1", "data2"];
let argsValue = [1, 2];

let functionScript =
"if (data1 > data2) {\n" +
" return data1;\n" +
"} else {\n" +
" return data2;\n" +
"}"
let f = new Function(...argsKey, functionScript);
let result = f(...argsValue);

Flutter 中 可以给Container设置decoration来设置背景,边框等等效果,非常方便。

1
2
3
Container(
decoration: TestDecoration(),
);

但是Flutter自带的decoration比较少,只能支持常规样式,当我们需要设置特殊背景时,就满足不了我们的需要了。

这时候,就有必要自定义一个我们定制化的decoration。

搜了一下flutter sdk代码,发现flutter里有一个定制化的FlutterLogoDecoration类,用这个FlutterLogoDecoration可以生成一个flutter logo样式的decoration。
我们可以参考它的代码来写一个我们自定义的decoration。

经过我的分析,自定义decoration的步骤还是很简单的。
一个简单的代码框架是这样的。

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

class MyDecoration extends Decoration {
@override
BoxPainter createBoxPainter([VoidCallback onChanged]) {
return _MyBoxPainter(this);
}
}

class _MyBoxPainter extends BoxPainter {
final TestDecoration myDecoration;
final Paint painter;

_MyBoxPainter(this.myDecoration)
: painter = Paint()

@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
//我们需要实现的代码
//具体绘制decoration的内容
}
}

这段代码中,我们主要需要实现的,就是_MyBoxPainterpaint方法。

上面的的代码,是先写了一个MyDecoration类继承Decoration,然后重写了其中的createBoxPainter方法,创建一个_MyBoxPainter类。
_MyBoxPainter类是继承BoxPainter的,里面的paint方法需要我们实现,在该方法中具体编写decoration实际绘制的内容。
绘制时,主要用到了canvas.draw...相关的api。

下面是一份带有红色边框,和内部有“测试”字样的decoration的demo代码。

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

class MyDecoration extends Decoration {
@override
BoxPainter createBoxPainter([VoidCallback onChanged]) {
return _MyBoxPainter(this);
}
}

class _MyBoxPainter extends BoxPainter {
final MyDecoration testDecoration;
final Paint painter;

_MyBoxPainter(this.testDecoration)
: painter = Paint()
..color = Colors.red
..strokeWidth = 2;

@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
var size = configuration.size;
Offset leftTop = offset;
Offset rightTop = leftTop.translate(size.width, 0);
Offset leftBottom = leftTop.translate(0, size.height);
Offset rightBottom = leftTop.translate(size.width, size.height);

canvas.drawLine(leftTop, rightTop, painter);
canvas.drawLine(leftTop, leftBottom, painter);
canvas.drawLine(rightTop, rightBottom, painter);
canvas.drawLine(leftBottom, rightBottom, painter);

var textPainter = TextPainter(
text: TextSpan(
text: "测试",
style: TextStyle(
backgroundColor: Colors.green,
color: Colors.red,
fontSize: 12,
),
),
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(canvas, (leftTop + rightBottom) / 2);
}
}

在这里插入图片描述
这里设置了painter的颜色是红色,宽度是2,用来绘制边框。
paint方法的Offset offset参数表示控件左上角的位置。
configuration.size 可以得到控件的宽高。
所以,

1
2
3
4
Offset leftTop = offset;
Offset rightTop = leftTop.translate(size.width, 0);
Offset leftBottom = leftTop.translate(0, size.height);
Offset rightBottom = leftTop.translate(size.width, size.height);

通过上面这四行代码,就可以得到控件四个角的位置。

1
2
3
4
canvas.drawLine(leftTop, rightTop, painter);
canvas.drawLine(leftTop, leftBottom, painter);
canvas.drawLine(rightTop, rightBottom, painter);
canvas.drawLine(leftBottom, rightBottom, painter);

然后通过drawLine可以绘制四个边框。
这里是演示demo,实际要绘制边框可以使用canvas.drawRect(rect, paint)方法更为方便。

1
2
3
4
5
6
7
8
9
10
11
var textPainter = TextPainter(
text: TextSpan(
text: "测试",
style: TextStyle(
backgroundColor: Colors.green,
color: Colors.red,
fontSize: 12,
),
),
textDirection: TextDirection.ltr,
);

这里定义了一个“测试”文字绘制的painter,文字颜色是红色,底色是绿色,字号是12,文字方向是从左到右。
需要注意的是,需要先调用textPainter.layout(); 才能真正绘制,否则会报错。
调用textPainter.paint(canvas, (leftTop + rightBottom) / 2); 即可完成绘制。(leftTop + rightBottom) / 2)表示绘制在中心位置。


总体来说代码很简单,只要按照这个框架实现对应的方法即可。
主要是用了canvas.draw...相关的api 和 TextPainter.paint方法。
在这里插入图片描述

最近发现了一个线上问题,用户的信息获取错误。多方调试后发现,我们的userId太长了,超过了js支持的精度范围,发生了精度丢失的问题。 js里面16位就开始丢失精度了,我们的到了17位。

比如一个userId:12345678901234567,在js里,如果使用number类型的话,实际会变成 12345678901234570 。

看到了吗,js自带的坑,而且坑的还不是位数变化,位数没变,但是最后一位四舍五入了!!!

经过各种查资料、咨询、调试、测试……
最后确定,js number 对这个情况无解。
换BigInt long bignumber 之类的,也都没啥乱用。这些类型能解决数学运算的问题,但是我这里并不需要数学运算,只是需要返回给后端,而这些框架对json解析仍然是无解。

最后才是采用了最简单直接、也最有效的办法,把数据类型换成string。

ps:我并不是直接用的js,而是用的ts,然而这门“现代化的语言”,仍然没有解决这个js原始的坑。

所以结论:遇到这个问题后,不要浪费生命去研究了。如果是需要数学运算的,换BigInt。如果只是数据的保存、传递,直接换string!

通过git format-patch 、 git diff 、git apply 三个命令,可以生成patch和打入patch,用于在多个git仓库间传递代码的情况。

比如不想提交代码,但是要把代码传给其他协作者,就很适合用这个方式。

git format-patch

1
2
3
4
5
6
7
8
9
10
11
#  把当前没push的提交都打成一个patch
git format-patch origin

# 把commitid1 和 commitid2 间的提交打成一个patch
git format-patch commitid1..commitid2

# 把最近3个提交打成一个patch
git format-patch -3

# 把包含commitid在内和之前的共2个提交打成一个patch
git format-patch commitid -2

git diff

1
2
3
#  把git diff 的输出内容写入本地,直接作为一个patch文件
git diff > ./pathToSave

git apply

1
2
#  打入patch
git apply xxx.patch

下面这是一段我们常写出的代码,注意其中的forEach函数,大家看看它的输出是什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void main(List<String> arguments) {
print(Test.inList('1'));
Test.list.add('1');
print(Test.inList('1'));
}

class Test {
static var list = <String>[];

static bool inList(String template) {
list.forEach((String item) {
if (template == item) {
return true;
} else {
return false;
}
});

return false;
}
}

我们会想当然的认为第一次print是false,第二次print是true。
然而实际上的输出

1
2
false
false

这里是forEach这个函数有个坑

1
2
3
4
5
6
7
/**
* Applies the function [f] to each element of this collection in iteration
* order.
*/
void forEach(void f(E element)) {
for (E element in this) f(element);
}

这是forEach的实现,非常的简单。注意这里传入的函数参数 f 的返回值是void。
函数的实现里也没有对f的返回值做任何处理,毕竟人家声明的就是void——无返回值。

虽然我们在使用的时候,f的实现里面写了return true|false ,并且编译器并没有报错,但是实际上,这里的return是毫无意义的。

更不可能会把当前函数inList的运行中断掉并返回。

那么知道了这个问题,该怎么解决呢?

可以简单的使用any函数。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Checks whether any element of this iterable satisfies [test].
*
* Checks every element in iteration order, and returns `true` if
* any of them make [test] return `true`, otherwise returns false.
*/
bool any(bool test(E element)) {
for (E element in this) {
if (test(element)) return true;
}
return false;
}

any函数和forEach的使用类似, 但是处理了传入函数test的返回值。

1
2
3
4
5
6
7
8
9
 static bool inList(String template) {
return list.any((String item) {
if (template == item) {
return true;
} else {
return false;
}
});
}

改成这样既可,注意及时是any函数,也是不能让return直接中断inList的执行并返回的,return的值是传递到any函数的返回上,然后return any函数的返回既可。

上述代码略显啰嗦,可以再优化下,用一行代码实现:

1
static bool inList(String template) => list.any((String item) => template == item);

再次运行,就输出了期望的结果

1
2
false
true

我们在开发Flutter工程的时候,
经常需要看日志时,发现没有Logcat视图,只能通过Debug视图中的Console Tab来看log。

这个Console Tab比起Logcat来说,功能上差太多了,各种级别过滤、关键字过滤的功能都没有,截图、录屏这些工具不能用。
在这里插入图片描述
想要打开Logcat视图,一般在第一次导入工程的时候,会提示检测到Android Framework,这时候按照提示点击导入,并设置Android Sdk即可。

但是有时候并不是第一次导入的工程,莫名其妙Logcat视图就消失了,并且各个地方都还找不回来。

这时候可以打开Project Structure,然后切到Facets标签,在当前工程下添加一个Android架构。然后Logcat视图就出来了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

git 同步超大仓库的时候,会报如下错误

1
2
3
4
5
6
git fetch
error: git upload-pack: git-pack-objects died with error.iB/s
fatal: git upload-pack: aborting due to possible repository corruption on the remote side.
fatal: the remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed

此时使用 git config core.compression -1 对代码进行压缩

或者 git fetch --depth 1 origin remote_branch_name 只同步一个分支,都可以解决该问题。


zsh 在超大工程下可能会性能变差,可以执行 git config --add oh-my-zsh.hide-dirty 1 来优化该问题。