以前使用podman的时候,总是会遇到一个问题,就是docker-compose没法用,而podman-compose的功能实在是太少。好在现在podman 3.0发布以后,已经可以无缝衔接docker-compose了。
直接卸载docker-ce就行
1 | sudo apt remove docker-ce |
docker的镜像存储和podman,containerd都是不兼容的,所以本地的镜像也可以删了。
1 | sudo rm -rf /var/lib/docker /etc/docker |
1 | sudo apt install podman |
要让docker-compose能够访问到podman,需要启动podman服务。
1 | sudo systemctl enable podman.socket |
然后就能在docker-compose里指定socket地址来使用了
在shell配置里加入如下内容
1 | export DOCKER_HOST=unix:///var/run/podman/podman.sock |
然后就能无缝使用docker-compose了。这里加alias的作用是,虽然docker-compose是直接连接docker的api的,但是有时候会直接执行docker命令,加上这个才能保证一定不会出错。
上面的方法是以root权限启动podman的,这样导致podman最大的优点非root运行没有了,3.2.0版本后,我们还可以用非root模式来启动podman服务
1 | systemctl --user enable podman.socket |
shell配置文件
1 | export DOCKER_HOST=///run/user/$UID/podman/podman.sock |
Use Docker Compose with Podman to Orchestrate Containers on Fedora Linux
]]>先用一个数组试一下:
1 | func main() { |
看来数组是拷贝的
1 | func main() { |
数组被改变了,这说明切片是引用传递的。
那么问题来了,既然切片都能按引用传递,那怎么数组还是按照值传递的呢?
再来试一下这个:
1 | func main() { |
数组的地址和指针的地址不一样啊。怎么回事?
这是因为切片和数组的构造其实完全不一样,切片其实是一个包含数组信息的结构体:
1 | // SliceHeader is the runtime representation of a slice. |
切片里面包含数组位置,切片长度和切片容量(数组大小),所以我们可以解释刚才的参数传递行为:
golang其实就是完全值传递的,只不过由于切片的特殊结构,里面保存了原数组的指针,所以我们才能在函数内修改原数组。
同时这也解释了另外一个问题:为什么对切片append()
需要用返回值覆盖原来切片?因为append之后切片内部的容量和长度信息变了,而golang为我们隐藏了切片的内部实现,所以我们不能像Java的List那样使用一个对象和属性来变更信息,只能覆盖原切片。
下面再看一个例子:
1 | func main() { |
对切片进行了扩容,再改变切片的话,原来的切片还是没变。这是因为我们原来的切片来自一个长度只有5的数组,所以切片的容量就是数组的长度,想要扩容就只能复制原来的数组了
扩容两次再试试
1 | func changeSlice(slice []int) { |
可以看到,第二次扩容后,slice2的改变能影响到slice1。因为切片第一次扩容后并不是简单的增加了一位。如果,每次append都只+1的话,那岂不是效率很慢,而且会占用很多内存空间。所以golang对切片扩容有其他的规律。具体请看参考资料。
1 | func changeSlice(slice []int) { |
如上,每次扩容切片容量翻倍。
所以切片到底是”传值”还是”传引用”,完全要看你对切片的操作有没有导致扩容。
如果真的要再一个函数里面操作切片的话,不希望改变原切片,可以使用copy(a,b)拷贝一下。
如果希望改变原切片,要么保证你的操作不会引起扩容,要么可以使用闭包。如果以上两个都不行,也可以选择传递指针。
1 | // 用指针修复扩容导致的切片引用失效 |
https://draveness.me/golang/docs/part2-foundation/ch03-datastructure/golang-array-and-slice/
]]>在spring里面配置了两个以上数据源的时候,可能会出现报错,例如我配置了ES和Mongo两个数据源,就会这样:
1 | The bean 'xxxxEsRepository', defined in xx.repositories.xxEsRepository defined in @EnableMongoRepositories declared on xxApplication, could not be registered. A bean with that name has already been defined in me.xxx.repositories.xxxEsRepository defined in @EnableElasticsearchRepositories declared on xxApplication and overriding is disabled. |
一段报错里出现了@EnableMongoRepositories
和@EnableElasticsearchRepositories
两个注解,意思就是Repository的bean冲突了。
由于我们的@EnableElasticsearchRepositories
写在前面,所以所有标注了@@Repository
的都被作为ES的repo注入了,接下来@EnableMongoRepositories
还会再做同样的事情,每个bean都被注册了两次,就冲突了。
这个时候,就要我们手动来指定哪个配置对应哪个repo了,手动指定两个配置对应的repo就行了。
原来的@Enable注解修改一下:
1 | // before |
只要加一个includeFilters,指定你要用哪个repo的类就行了,当然,你也可以用excludeFilters
排除不相干的类。这里用basePackageClasses
没有用,暂时不知道为啥。
还有另外一种常见操作是,在同一个数据源里面我们要请求不同的数据库,或者不同服务器。这个时候还是类似上面的操作。
因为有两个Configuration,所以要注解两次,这时候优先使用Java配置:
1 |
|
还是用注解指定我们要scan的包或者类,注入对应repo,但是光这样还不够。
我们还要手动绑定TemplateRef
,然后需要自己实现mongoTemplate
。这是因为虽然我们只指定scan后,两个config注入对应的repo了,但是databaseFactory
这个bean其实只有一个,生成的mongoTemplate
也就只有一个。
所以我们要忽略掉databaseFactory
,自己实现mongoTemplate()
,手动new一个mongoTemplate
。当然,你也可以实现mongoDbFactory()
都是一样的效果。
关于排序算法的原理与实现直接看这里:https://github.com/hustcc/JS-Sorting-Algorithm
每次将当前数字向前移动,直到插入到前面合适的位置。
最好情况是数组已经排序好,不需要插入,只需要比较n次,时间复杂度O(n)
最坏情况是逆序,每次都把当前数字插入到最前面,需要n+(n-1)+… 1 次交换操作,时间复杂度O(n^2)
冒泡排序是两两比较相邻元素,这样每次都把最小元素放到最前面,最大元素放后面。
当数组排序好时候,只进行比较而不交换,时间复杂度O(n^2),如果进行优化,增加一个标志位,第一次循环没有交换就直接退出,则时间复杂度可以减小到O(n)
最坏情况也是逆序,执行n+(n-1)+…1 次交换操作,时间复杂度O(n^2)
最简单的一种,每次都把最小的数字取出来,存到排序的起始位置。不论是什么情况,都需要进行n+(n-1)+…2次比较,时间复杂度O(n^2)
上面这些排序方法,都有一个共同点,就是每次从最小的序列开始排序,逐渐增加这个序列直到排序完成。这些算法每次都最多只能消除一个逆序。更进一步观察发现,这些算法实质都是每次交换相邻的两个元素来排序:
插入排序每次交换相邻元素,直到当前数字小于插入数字
冒泡排序不用多说
选择排序对临时数字的比较也相当于交换相邻的元素,类似于冒泡排序
要计算平均复杂度,只需要知道数组平均有多少逆序就行。对于N个互异数字的数组L,两两组合一共有N(N-1)种组合,这些组合有逆序也有正序。我们创造L的反序Lr,那么Lr数组中的逆序=L数组的正序数量。于是这两个数组一共有N(N-1)*2个序列,其中一半是逆序,也就是N(N-1)/2。因此可证明平均每个数组的逆序数量是N(N-1)/4。
上述算法每次只能最多消除一个逆序,因而平均时间复杂度为O(n^2)。
上面的推论高速我们,如果一个算法每次只能两两交换删除一个逆序,那么平均时间复杂度最快只能是O(n^2)。为了打破这一规律,需要对相距较远的元素进行比较。
将数组分为许多序列,每次对序列首尾进行交换,然后逐步缩小序列长度。
使用归并操作每次归并小数组。
每次两两合并,一共合并logn次就能合并完,每次合并比较n次,所以任何情况都是O(nlogn)
使用分治法,将数组按照某个基准分为两个小数组排序。
由于使用了分治法,每次操作区间/2,最短要分logn次才能分到最短(每次都从中间分开),平均每次都是操作N个数字,所以最好情况的复杂度是O(nlogn)
利用堆的性质,把数组建成一个堆,然后每次取出堆顶部元素到另一个数组,最后拷贝回来。
堆的高度是log(n),所以每次取出需要
这几个算法的时间复杂度都很难计算。但是已知他们的平均时间复杂度都是O(nlogn)。对于普通的比较排序方法,这是最快的平均时间复杂度。
如果有一个长为N的序列L,那么总共有N!种排列。这些排列中只有一种是有序的。将所有排列组成一个决策树,由于比较只有两种可能(>= <,> <= >都是两种)所以这是一个二叉树。所有的排列都在叶子节点上,一共有N!个叶子节点。那么我们的决策树平均深度至少就是log(N!)。我们的算法就需要至少log(N!)次判断才能得出结果。
计算log(N!),最后结果是 N/2*logN - N/2。也就是O(NlogN)
还是之前的注解
1 | //测试注解 |
反射类
1 | fun reflect(myAnno: Any){ |
两个都是这个逻辑,检查所有属性/字段和方法。
我们先只修改一个字段,修改一亿次试试耗时:
1 |
|
先跑下Java:
1 | public static void main(String[] args) { |
用时41秒
再看Kotlin:
1 | fun main() = runBlocking { |
比Java快了将近一倍!!!
这次改成执行一个set方法试试
1 | class AnnoTest(){ |
1 | //kotlin |
毫无悬念
之前都是对一个对象反复操作,这次试试每次换个对象,还是注解一个set方法,这次跑1次
1 | fun main() = runBlocking { |
1 | public static void main(String[] args) { |
好吧,这次看来是Java赢了。这么看来,初次加载时候,Java比Kotlin快了很对,但是当反射次数多了以后,还是kotloin的后续运行速度会更快。
这主要是因为kotlin的反射依赖于@Metadata
这个注解,kotlin每次反射时都要解析一下这个注解,所以才会造成初次加载慢了很多。
所以我们在用的时候,如果很注意初次加载性能,也不需要kotlin额外的反射特性,就可以用Java的反射包,其他时候还是乖乖用人家官方提供的反射包吧。
kotlin立面定义注解的语法和Java略有不同,不过基本上还是差不多的。
先看上篇文章的那个Java版本注解:
1 | // 定义注解 |
用idea把他自动转换成kotlin代码:
1 | // 定义注解 |
kotlin同样是四个元注解:
- @Target 指定可以用该注解标注的元素的可能的类型(类、函数、属性、表达式等);
- @Retention 指定该注解是否存储在编译后的 class 文件中,以及它在运行时能否通过反射可见 (默认都是 true);
- @Repeatable 允许在单个元素上多次使用相同的该注解;
- @MustBeDocumented 指定该注解是公有 API 的一部分,并且应该包含在生成的 API 文档中显示的类或方法的签名中。
@Target
增加了PROPERTY_GETTER
和PROPERTY_SETTER
,kotlin里面这些分的更细了,而且多个参数不需要大括号了,更方便。
注解的定义和属性换成了kotlin的格式,这个也很容易看明白。Java里是@interface定义注解,kotlin里注解其实和class格式一样了,只不过用annotation
关键字声明他是注解。
还有一个好处是,kotlin注解是一个类,所以使用时候可以按照构造函数的使用方法来使用,按顺序填入参数就行了,不用加参数名。
接下来看反射,与Java不同的是,Java是一种纯面向对象的语言,所有东西都在类里面,所以我们要反射都是从一个类开始的。但是Kotlin还支持函数式编程,类之外还有函数和属性这些东西,所以kotlin中反射不止有类似Java的Class,还有其他的类型,kotlin文档对于反射的介绍是这样的:
反射是这样的一组语言和库功能,它允许在运行时自省你的程序的结构。 Kotlin 让语言中的函数和属性做为一等公民、并对其自省(即在运行时获悉一个名称或者一个属性或函数的类型)与简单地使用函数式或响应式风格紧密相关。
要在kotlin里使用Java的反射,只要这样获取Java类引用就行:
1 | val cls = MyClass::class.java |
使用反射首先添加反射的依赖:
1 | dependencies { |
要获取类引用这样
1 | val c = MyClass::class |
关于函数和属性引用可以看文档:https://www.kotlincn.net/docs/reference/reflection.html
还是和上篇文章一样,我们来写一个反射函数处理刚才的注解:
1 | //测试类 |
1 | fun reflect(myAnno: Any){ |
还是和上次一样,我们的反射函数针对方法和属性进行注入,运行一下:
1 | fun main() { |
成功注入!
既然两个反射都能用,两个都是编译成jvm字节码了,那么试试在Java里读取下注解:
1 | fun main() { |
运行之后没有输出,我们的注解没有被读到。怎么回事呢。
试试读出所有的注解:
1 | fun main() { |
还是没有输出,看来我们的 字段上根本没有注解,试试读取方法的注解:
1 | fun main(){ |
这次能看到了,看来属性的注入实际上是在set方法里了。这样的话,确实可以用Java的反射类来操作,就是用起来有点概念混乱,这两个语言里的元素不是一一对应,这么用容易出bug。
]]>Java注解又称Java标注,是Java语言5.0版本开始支持加入源代码的特殊语法元数据。 Java语言中的类、方法、变量、参数和包等都可以被标注。和Javadoc不同,Java标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java虚拟机可以保留标注内容,在运行时可以获取到标注内容。– Wikipedia
意思就是注解只是一个标注,不会产生任何效果,要让注解起作用,还需要编写反射的代码来实现。
还是先来看怎么定义一个注解,注解就是一个@xxxx
开头的标注,一般这样定义:
1 | // 定义注解 |
上面的代码定义了一个叫做Myanno的注解。注解只有一个String
类型的value
属性,默认值为”hello”。
注解上面的注解叫做元注解,元注解是注解的注解。Java标准库里已经定义了几个元注解:Java 5 定义了 4 个注解,分别是 @Documented、@Target、@Retention 和 @Inherited。Java 8 又增加了 @Repeatable 和 @Native。关于这些注解的信息和使用方法可以自行查看文档。
这里用到的三个注解分别是:
注解的内部可以存放字段,默认的字段是value。可以用@MyAnno("abcd")
直接使用,如果定义的是其他字段,就要用@MyAnno(field = "abcd")
这种方式。
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。 这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。 反射被视为动态语言的关键。 –百毒百科
意思就是反射可以在运行时获取类信息,构造对象,修改属性等东西,给Java这种静态语言增加了动态性。
Java的反射是通过反射包(java.lang.reflect
)提供的。
Java是一个面向对象的语言,所以要反射先要获取类。所以一个反射的操作是这样的:
先来定义一个测试类
1 | class AnnoTest { |
1 | Class<AnnoTest> cls = AnnoTest.class; |
com.company.AnnoTest
name
字段不只是字段,反射还能读取方法和注解,对于刚才的注解:
1 |
|
执行上面的代码,可以看到,我们用反射成功获取到AnnoTest
这个类的注解值。
还是刚才那个注解:
1 | // 定义注解 |
我们的期望是,将这个注解应用于类型为String
的字段或者String
参数的set
方法,可以直接注入value
的值或者将value
作为参数传入set方法:
新建两个测试类:
1 | //方法测试 |
然后编写一个反射函数:
1 | //为了简便删去了try catch |
这个函数干了两件事:第一个for循环读取所有字段,遇到有注解的就注入value值;第二个for循环读取所有方法,碰到有注解的把value作为参数,执行这个(set)方法。
执行一下:
1 | public static void main(String[] args) { |
第一个类的属性注解成功被注入,第二个类的set方法也成功被注入。
]]>那么怎么在c++里实现这种错误处理呢。
对于golang的错误处理,只要使用一个tuple就可以,没什么好说的。
这篇文章主要研究rust式的错误处理。
先看rust的Result定义:
1 | pub enum Result<T, E> { |
去掉宏,其实就是一个枚举,立面有Ok和Err两个泛型成员。然后用match语句就可以方便的从枚举里取出结果。
除了match语句,result还提供了is_ok() ,ok(),err(),unwrap()等方法进行操作
1 | pub trait Error: Debug + Display { |
Error是一个trait,主要是有Displaytrait,也就是打印错误。
c++里面不存在泛型的enum,所以我们可以用一个模板类来代替。
1 | template<class T, class E> |
由于没有枚举,为了区分result是error还是ok,新增一个_isOk
属性来标记。
1 | public: |
增加一些基本的方法,两个构造函数分别初始化成功和失败两种类型,用Ok和Error两个友元新建Result类。isOk判断结果是否成功,
1 | public: |
再增加三个方法用来取出错误和执行成功的结构。
实现这些方法:
1 | template<class T, class E> |
上面的result是能接收任何类型的,为了能够统一错误输出,我们定义一个Err类:
1 | class Err { |
上面是一个最简单的Err类。可以处理exception,输出错误信息,并且随着调用增加描述信息。
但是这样的描述信息只有手动输入的文本,我们还想增加更多的属性,比如文件,行数等。我们可以自定义一个类来存储这些信息:
1 | struct errMsg { |
好了,这个类可以接收调用函数,文件,行数等信息。用它来替代Err
类里的msg
:
1 | class Err { |
这样每次在处理Err类的时候,都会把调用信息也存进去:
1 |
|
但是问题;来了,难道要每次都手动输入文件名,行数吗?当然不可能这么麻烦。编译器立面已经由相关的宏了。
我们要做的就是想办法让这些宏自动填进去。
这里我们需要三个宏:__FILE__
__FUNCTION__
__LINE__
。
自定义几个宏来自动填入这几个宏:
1 |
这样我们的error和result就能使用了
使用下面的代码测试一下
1 |
|
输出结果:
1 | L:\source_code\cpp\cpptest\cmake-build-debug\cpperror.exe |
第一个error说明,我们的错误确实能够逐级输出调用栈了。
第二个error说明,我们的错误也能把exception转化为Err。
更厉害的是,我们的错误输出可以被ide识别,只要点击就能跳转到对应的代码了,是不是非常方便。
上面的只是我学习cpp过程中随便写的,还存在不少缺陷。写完这篇文章时候去Google了一下,github有人写了差不多的一个项目:https://github.com/oktal/result,有兴趣的可以去看看。
]]>update: 修改了代码,现在可以实时检测电脑是否在线
Wake-on-LAN
简称WOL
或WoL
,中文多译为“网络唤醒”、“远程唤醒”技术。WOL是一种技术,同时也是该技术的规范标准,它的功效在于让休眠状态或关机状态的电脑,透过局域网的另一台电脑对其发令,使其唤醒、恢复成运作状态,或从关机状态转成引导状态。
对于支持网络唤醒的主机,我们可以使用路由器的网络唤醒功能来开机。但是网络唤醒需要的是网卡和主板的支持,还需要你有公网ip或者路由器支持远程唤醒,任何一项不支持都不能正常启动。而且即使支持了网络唤醒,万一遇到了意外断电,有的主板也不能正常开机。那么有什么办法能保证网络唤醒一定成功呢?
最简单的方法是智能插座,在BIOS里设置好主板通电就开机,然后远程控制智能插座就可以了。这种方法最简单,但是笔记本是有电池的,所以BIOS里一般没有通电开机的选项,这个办法就行不通了。
那么怎么办呢,首先我们来了解一下电脑的启动流程:
开机按键一端和电源14脚或16脚相接,另一端接地,当我们按下电源按钮时,电源的14或16脚接地,然后就会触发电源开始工作,向设备供电。(这种说法只是为了方便理解,实际流程很复杂)
既然知道了这个原理,那我们只要让电源引脚接地,模拟开机键按下的过程,不就能开机了吗。
为了实现这个功能,需要以下材料:
首先需要的,当然是拆开电脑,找到对应的启动引脚了,台式机的话很简单,看电源按钮排线就行,笔记本的话,可以直接看电源按钮的线路,也可以找根线,一端接地,一端在电源按钮排线上一个一个短接试过去。
找到引脚后,就要想办法用电烙铁引过来地线和电源线,这里推荐用耳机线,很软很细不容易断,不会让设备外壳合不上。
然后把这两根线接到继电器的公共口和常闭口就行。
接着就要写程序远程控制继电器开合。推荐使用点灯科技的blinker
SDK,配合手机app可以很方便的控制。blinker使用方法请自行搜索,
新建一个blinker设备,获得Secret Key,然后开始编程:
需要用到的库:ESP8266Ping,注意不是arduino仓库里的那个ESP8266-ping,这个要自己下载放到libraries文件夹里。
1 |
|
Blinker还可以支持小爱同学远程控制,不过我懒得写了,以后再补上
程序的刷写方法可以参考这里:https://www.diyhobi.com/flash-program-esp-01-using-usb-serial-adapter/
接法是这样的:
写好之后,在blinker app立面编辑界面,新增一个按钮,数据键名为btn-abc
,类型为普通按键(现在可以选择开关按键,样式选择第二个滑块按钮可以看到机器开关状态)。然后就可以用了
Windows terminal
很好用,不过不能像powershell那样右键直接在文件夹打开。我们可以通过修改注册表的方式手动添加右键菜单。首先,右键菜单的地址在:计算机\HKEY_CLASSES_ROOT\Directory\Background\shell\
这个文件夹下面,我们在这里添加键值就能在右键菜单看到了。
再来看Windows terminal
的参数:
-d
参数可以指定指定打开的目录,所以这里我们使用C:\Users\你的用户名\AppData\Local\Microsoft\WindowsApps\wt.exe -d .
这个命令就能在当前文件夹打开Windows terminal
。
-p
参数可以指定要使用哪个shell,shell的名字就是就是Windows terminal
里面配置的name
参数,所以通过C:\Users\你的用户名\AppData\Local\Microsoft\WindowsApps\wt.exe -p "Windows PowerShell" -d .
这个命令就能在当前文件夹使用Windows PowerShell
来打开Windows terminal
。
有了上述的前置知识,我们就知道应该怎么配置右键菜单了。比如用默认shell打开:
1 | Windows Registry Editor Version 5.00 |
用wsl2
打开:
1 | Windows Registry Editor Version 5.00 |
要让右键菜单默认隐藏,按shift才能出现的话,可以添加一行"Extended"=""
:
1 | Windows Registry Editor Version 5.00 |
把上述代码保存为.reg
文件运行即可。
我使用的系统是Debian buster
,首先安装docker
1 | curl -fsSL https://get.docker.com -o get-docker.sh |
接下来安装docker-compose,
1 | sudo apt install python python-pip python-dev python-setuptools libffi-dev |
然后等待一万年。。。。。让pip自动帮你编译安装就可以用了。
]]>主要参考的文章是:Postfix使用外部SMTP服务器发送邮件
接下来我补充一些注意事项
1 | sudo apt install mailutils |
1 | $ sudo vim /etc/postfix/sasl_passwd |
qq邮箱的密码需要在设置-帐号
里生成授权码
然后加密
1 | sudo postmap /etc/postfix/sasl_passwd |
默认是用你的用户名@主机名的方式发送邮件的,这样的邮件会被smtp服务器拒绝,所以要把发件人映射为你的qq邮箱地址
1 | $ sudo vim /etc/postfix/generic |
myhostname是/etc/postfix/main.cf
里面的myhostname
。
加密
1 | sudo postmap /etc/postfix/generic |
1 | $ sudo vim /etc/postfix/main.cf |
接下来重启postfix就可以发信了
1 | sudo service postfix restar |
项目地址:https://github.com/coolrc136/Pcap_DNSProxy_docker/tree/overture
记得切换到overture分支
服务器要用的软件已经用docker-compose
打包好了,pull下来直接跑就行。
我来说一下具体的结构吧,懒得画图了,直接用文字,很简单:
用户通过tls或者https向服务器请求dns
如果是tls的话,仅仅是对tcp格式的dns请求进行了ssl加密而已,直接用nginx的stream模块进行tcp反代。然后dns请求传到overture,overture进行分流查询
如果是https的话,dns请求是用json格式传输的,我们需要dns-over-https这个软件来进行协议转换。
首先nginx反代http请求到dns-over-https,然后dns-over-https把请求转换为普通的udp查询传给overture,再由overture发出请求。
安卓手机在加密dns选项填上服务器域名或ip就行。
windows用Auroradns,主dns填入:https://域名/dns-query就可以
更新:实际使用了两天,windows端还是`simple dnscrypt-proxy`好用,但是配置麻烦,这里不写了,具体使用方法看项目README:https://github.com/coolrc136/Pcap_DNSProxy_docker/blob/overture/README.md。
]]>在TLS1.3中已经可以加密SNI了,可以通过在firefox中开启ESNI实现,但是需要服务端和客户端都支持,预计还要几年才能普及。
在PC上,加密dns很简单,只需要下载一个SimpleDnsCrypt,他就能自动帮你配好dnscrypt-proxy
了。Linux的话,除了dnscrypt-proxy
,还有dns-overtls
和dns-over-https
的各种软件可供选择。但是在安卓手机上,想要实现加密DNS就比较难了。
从安卓9开始,就自带了加密dns功能,只要你在设置里填上合适的服务器地址就行,但是呢,这个自带的加密dns在使用代理时不会生效,如果你使用了SS,那么你还是会用SS里的DNS服务器明文查询,而且只能填一个域名,容易出故障。
为了让全局DNS都能加密,这里就要使用一个magisk
模块了,那就是dnscrypt-proxy
。这个是dnscrypt-proxy
的ARM版本,用magisk
刷入,然后修改位于/sdcard/dnscrypt-proxy
目录下的文件就能启用了。
为了方便配置,你可以在PC端用`SimpleDnsCrypt`配置好,然后把配置文件`dnscrypt-proxy.toml`复制过去。PC端默认监听53端口,建议修改为5353端口
这是我的配置文件:1 | server_names = ["rubyfish-ea", "geekdns-doh-west", "geekdns-doh-north", "geekdns-doh-east"] |
然后,你有两个选择,一是安装一个修改dns的app,将dns指向dnscrypt-proxy监听的地址,比如我这里是127.0.0.1:5353
,填进去,然后app一般会启动一个vpn,用这个vpn上网就行。
如果不想用vpn,那么可以安装这个magisk模块,这个模块会使用iptables
把所有53端口的出口流量转到127.0.0.1:5353
,也就是全局启用了dnscrypt-proxy
。
配置完dns后,我们来测试一下有没有成功,访问https://www.dnsleaktest.com/或者http://nstool.netease.com/。如果看到的DNS和你在dnscrypt-proxy
中配置的一样,恭喜你,你的DNS已经被加密了。
首先想到的肯定是把国内版的主题商店移植回来,这样就能一直用了,但是调查后才发现,用国内版主题商店需要修改build.prop
,这样的话,就会让一些广告重新出现,所以这个方案对我来说并不行。
其实我只是需要安装一个主题而已,为何不直接导入MTZ呢?这时候就要用到一个油猴插件,那就是MIUI主题下载器。安装这个插件,前往http://zhuti.xiaomi.com就能看到下载按钮了。
然后下载,用主题商店导入就是了,波兰版的主题商店没有收费功能,所以即使是收费的主题也能直接导入后应用。唯一有点麻烦的就是,后面要升级的话就得再手动下载一次。
要进行网络分析,我们需要的是矢量的道路网地图。一般自己想办法绘制或者购买数据才能得到精确的底图。路网的要求不是很细致的话,那还有一个办法是从openstreetmap上面下载公开的地图。OpenStreetMap是一个开源地图项目,任何人都可以在上面编辑地图。但是国内用户比较少,上面的路网比较残缺。不过缺少的基本都是小区内的道路,城市道路还是比较完整的。
从OpenStreetMap上面下载下来的地图是OSM
格式的文件,需要转换成Shapfile才能用。比较简单的办法是下载一个QGIS,然后用QGIS导出为shapfile再用ARCGIS打开。
准备好Shapfile以后,进入ARCGIS进行编辑,确保至少有阻抗信息字段,如距离或者行驶时间。如果是单向道路或者有转弯要素,可以参考这里配置相应字段:https://desktop.arcgis.com/zh-cn/arcmap/latest/extensions/network-analyst/types-of-evaluators-used-by-a-network.htm
对于连通性的问题,可以启用菜单栏的拓扑工具来修改连通性。
准备好数据后,就可以创建网络数据集了。首先进入arccatalog启用网络分析拓展。
然后在Shapfile上右键创建网络数据集
然后根据向导配置连通性,阻抗,方向等等各种属性就行了。最后点击完成就能创建一个网络数据集了。
创建好网络数据集后,打开ARCMAP,打开arccatalog窗口,将网络数据集拖进来就能看到了。然后同上启用ARCMAP的网络分析拓展就可以开始分析了。
我们可以加载网络分析工具条,新建一个路径分析试试。打开工具条上的网络分析窗口按钮,选择停靠点,新建几个停靠点,在图层上右键选择求解:
可以看到,我们的网络分析成功了。查看路径属性表,可以看到,这条路径的长度是18566米
git commit
的时候不知道写什么?没关系,这里有一款工具解决你的烦恼。commitizen
是一个自动生成commit messsage
的工具,只需要运行git cz
就能够自动根据你的选择帮你生成整洁美观的commit messsage
。通常都是配合conventional-changelog
使用angular的commit格式。commit messsage
。这里安装所需的工具:commitizen conventional-changelog conventional-changelog-cli cz-customizable,然后使用nielsgl/conventional-changelog-emoji这个项目里面的配置文件自定义commit messsage
格式。
其实用`cz-emoji`可以直接生成emoji格式的`commit message`的,但是这玩意生成的`commit message`用emoji取代了`
1 | npm install -g commitizen conventional-changelog conventional-changelog-cli cz-customizable |
使用git cz
就能按照选项填写 commit message。要生成changelog
的话,参照如下方法:
1 | # 不会覆盖以前的 Change log,只会在 CHANGELOG.md 的头部加上自从上次发布以来的变动 |
生成changelog
后,要发布release什么的只要复制CHANGELOG.md
的内容填进去就行了。
规范你的 commit message 并且根据 commit 自动生成 CHANGELOG.md
commitizen/cz-cli
nielsgl/conventional-changelog-emoji
1 | sudo apt install ruby ruby-dev |
1 | travis login --pro |
加入下面两行配置
1 | skip_cleanup: true |
完整的depoly
如下
1 | ... |
bufio
包如何逐行进行读取和写入。因为bufio
包提供了缓冲,性能比较优秀。1 | func readLines(path string) ([]string, int, error) { |
使用bufio
包的scanner
可以对数据进行扫描输入,除了逐行分割以外,还有其他的分割方式:
ScanLines(默认)
ScanWords(分割单词)
ScanRunes(在遍历 UTF-8 字符串而不是字节时将会非常有用)
ScanBytes
1 | file, err := os.Open("filetoread.txt") |
写入使用的是bufio
中writer
对象的一些方法。
1 | 以下三个方法可以直接写入到文件中 |
1 | func writeLines(path string, lines []string) error{ |
部署起来其实很简单,gitlab已经提供了各种博客系统的示例,当然也包括hexo,去这里https://gitlab.com/pages/hexo/tree/master把.gitignore
和.gitlab-ci.yml
这两个文件扒下来放博客目录里。在.gitignore
最后一行加上package-lock.json
。
然后修改.gitlab-ci.yml
文件,pages
前面加上一个代码块
1 | before_script: |
然后去github申请一个有repo权限的token,>> 传送门
修改hexo的配置文件的repo为
1 | deploy: |
然后把git仓库push上去就行,gitlab就会自动帮你部署啦
hexo的配置文件中一般都存了很多token,安全起见,建议保存到私有仓库。
]]>