为什么选择 All-in-One 这样的方案?知乎上面有很多 AIO 的劝退贴bushi)
我的主要需求一个是软路由,一个是离线下载和在局域网共享电影,还有就是想试一试Home Assistant这个系统,据说可以统一所有的智能家居设备。这些都是需要长期运行的服务,如果用台式机做这些事情也是可以的,但是用电量就比较大了。工控小主机的低功耗就很适合这种场景,J4125CPU 最大功率是 10W,整机待机的功率也很少超过 10瓦,特别省电。至于安全问题,影音娱乐啥的算不上重要数据,路由的话使用旁路网关的方式就可以避免一出问题全屋上不了网的情况了。
在设备 IP地址的配置中通常有一项是“默认网关”,当计算机要发送数据到不在其直接连接的网络上的目标时,就会把数据包发送到默认网关,由默认网关负责将数据包转发到目标网络。我们的设备访问互联网的时候就是先把数据包发给默认网关(路由器),然后数据包会被发送给互联网上的目标。
“旁路由”的实现方式正是利用了这一点。通常情况下默认网关就是路由器的 IP地址,如果把默认网关配置成其他的设备,让别的设备进行路由和转发,我们就能在不修改原有网络的情况下更好地控制网络流量了。
拓扑图如上面所示,路由器的 LAN 口和 PVE 主机的网口连接。在 PVE里面把连路由器的接口、Openwrt 的接口分配到相同的虚拟网桥(LinuxBridge)上面。其他设备还是正常用 Wi-Fi 或者网线连接路由器。
generic-ext4-combined-efi.img.gz
文件。Openwrt 需要的资源并不多,可以根据自己需要划分内存和CPU。选择的镜像是 efi 的,所以 BIOS 要选择 UEFI 的,不需要勾选添加 EFI磁盘。在磁盘页面也不需要添加磁盘,稍后会从镜像导入。
由于是旁路由,网络设置使用默认的桥接就好了。
登录 PVE,下载镜像文件,并解压:
1 | cd /var/lib/vz/template/iso |
将磁盘镜像文件导入到刚刚创建的虚拟机:
1 | # qm disk import <vmid> <source> <storage> [OPTIONS] |
然后回到 PVE 管理网页,选中新的磁盘编辑,添加。
现在可以开机进入系统了。但是不急,默认磁盘的剩余空间很小,放不了很多东西。为了以后考虑,还要给磁盘扩容。
磁盘操作 > 调整磁盘大小,扩大虚拟磁盘的大小。
接下来是调整分区大小。这一步是可以用命令做的,我偷了一点懒,懒得学分区操作的命令,用了图形化分区工具GParted,操作起来比较简单。
先在 PVE 的 ISO 镜像储存界面上传 GParted 镜像,然后给 OpenWrt的虚拟机的 CD/DVD 添加上。在 选项 > 引导顺序 中调整到光盘最高。
进入系统之后,操作很方便了。根据提示修复错误,然后右键 rootfs分区调整大小,拖动箭头,把剩余空间拉满就可以了。最后点击打勾保存关机。
移除光盘,调整启动顺序,重新启动,就能够进入系统了。
可以看到根分区的大小已经是合适的了。
用 ip addr
查看 IP地址,发现不对,无法连接。去修改配置文件。
1 | vim /etc/config/network |
把 lan
的配置改成自己网络的 IP 地址。我这里用 DHCP自动获得 IP 地址,并且加上了 IPv6 的配置。
1 | config interface 'lan' |
修改完成后重启网络:
1 | service network restart |
现在已经正确获取了 IP 地址,可以用 SSH连接或者浏览器访问管理页面了。
一些基础配置:
1 | # 软件源换成清华镜像源 |
在 OpenWrt 网页管理页面,网络 > 接口 里面,把 DHCP相关的设置关掉,因为旁路由不需要提供 DHCP 服务。
有两种办法,一种是在设备上手动设置网关为软路由的IP地址,另一种是在主路由上面把DHCP 默认网关设置为软路由的IP。为了稳定,我用手动配置的办法。主路由有两个 WiFi 信号,一个 2.4G的一个 5G 的,把 2.4G 的设置成手动 IP 和软路由的网关,5G的那个保持默认,这样切换起来也还算方便。
配置完成之后进行简单的 traceroute 验证:
1 | C:\>tracert -4 blog.beanbang.cn |
可以看到第一跳、第二跳、第三跳分别是软路由、我的硬路由和光猫,然后是联通的网络,最后到达目标地址。
]]>参见:
旁路由的原理与配置一文通- Eason Yang's Blog
超详细,多图PVE 安装 OpenWRT教程(个人记录)_pve安装openwrt_优雅码农的博客-CSDN博客
如果你只是想创建一个系统快照,方便将来搞砸的时候还原回来,可以不用这么麻烦,用Timeshift就可以了。
准备完成后就可以开始了!
Wiki 上面提供了很多方式1,我从中选了最简单的一种,就是打包成tar 压缩包2。
从 Live CD 启动后,查询和挂载你的 Linux 分区:
1 | su root |
要使用的其他分区(如果有的话)需要装载到适当的位置(例如,如果有单独的/home
分区,就把它挂载到 /mnt/home
)。
然后,使用 chroot
把环境切换到分区,准备备份:
1 | chroot /mnt |
执行 tar 命令:
1 | # -p、--acls 和 --xattrs 存储所有权限、ACL 和扩展属性。如果没有这些属性,许多程序会停止工作! |
执行之后,会开始备份系统到 /opt/backup
目录下。--exclude
可以指定想要排除的文件和目录。等到备份结束,把压缩包拷贝出来,放到新电脑的磁盘里面。
在新电脑用 Live CD 启动后,挂载并 cd
到想要还原的分区,解压文件:
1 | tar --acls --xattrs -xpf backupfile |
接下来需要修改 fstab 文件。fstab文件是系统开机时需要挂载的分区信息,我们只需要稍加修改,把 UUID改成新分区的 UUID 就可以了。
1 | cat /etc/fstab |
我的 fstab 是这样:
1 | # /etc/fstab: static file system information. |
这些命令都可以查询磁盘的信息:
1 | fdisk -l |
这一步的 fstab 文件也可以通过挂载好所有目录之后使用genfstab -U <挂载根目录>
来生成。
initramfs包含了能够让系统访问根文件系统的模块。如果迁移的设备有变化,就需要重新生成。
需要准备 chroot 环境:
1 | mount /dev/sda2 /mnt |
挂载临时文件系统目录:
1 | cd /mnt |
如果你用的是 Arch,可以不需要挂载上面的临时文件系统:
1 | arch-chroot /mnt |
重建 initramfs:
1 | mkinitcpio -p linux |
目前新系统还是没有启动引导的,需要手动安装引导。
在之前 chroot 环境,执行命令安装 grub。
1 | grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=GRUB --recheck |
没有报错的话,就是成功了,可以重启系统。
进入系统之后,再执行一次 update-grub
或者grub-mkconfig -o /boot/grub/grub.cfg
。如果启用了os-probe
如果由于一些原因,比如你的 Live CD 里面没有 grub-install
命令,或者不想安装启动引导到磁盘里面,只是临时启动一下系统,那么可以利用GRUB 的命令行手动引导启动系统4。
要进入到 GRUB 命令行,可以在开机进入 Live CD 的 GRUB启动菜单页面的时候按 C。
1 | grub> ls -l |
ls -l
是可以查看分区信息和对应的UUID,linux
命令找 /boot 下 vmlinuz开头的文件,initrd
命令找 initrd开头的文件。命令和文件都可以按 Tab 自动补全,但是那个 UUID参数得手打,有点累。
进入系统后,也可以再把启动引导装回来:
1 | # bootloader-id 会成为 BIOS 启动项的文本 |
]]>参见
- How toreinstall the GRUB boot loader | Support | SUSE
Grub-probe:error: cannot find a GRUB drive for /dev/sda1 - Support - Manjaro LinuxForum 迁移到新硬件- Arch Linux 中文维基
解决办法:
1 |
|
输出的语句:
1 | SELECT |
下面是摸索出结论的过程(心路历程:
所有的 ORM 框架,最后都是要通过 JDBC 去和数据库交互的。类似这样:
1 | try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) { |
为了避免注入问题,基本上所有的语句执行都会使用PreparedStatement
。而 prepared statement的执行过程是这样的:
所以事实上整个查询的过程中都不会有“最终的”SQL 语句生成,没有办法从JDBC 的执行过程得到 SQL 语句。
不过,一些数据库驱动的实现是可以得到语句的。比如 MySQL 的PreparedStatement
的 toString()
方法可以输出拼接参数的完整语句。但是并不是所有数据库厂商都有去实现的,而我们这个项目也是需要支持多种类型的数据库,所以这样也不是很通用。
所以,我们只能手动拼接语句和参数来得到完整的 SQL语句了。接下来就是要知道 Mybatis 是怎么调用 JDBC 的了。
MyBatis 的原理是,在 XML 文件中用模板语言编写 SQL语句,然后在代码里面编写一个和 XML的参数和返回值相匹配的接口(Mapper)。调用接口,并传递相应的参数,MyBatis就会把参数和模板语句结合起来,生成最终需要执行的参数和语句,然后调用PreparedStatement 执行。
1 | <mapper namespace="org.mybatis.example.BlogMapper"> |
1 | try (SqlSession session = sqlSessionFactory.openSession()) { |
Mybatis 的核心是 SqlSessionFactory
类。执行查询,或者获取配置信息都是从这里开始的。程序启动后,Mapper文件到了代码里面就加载成了一个个的 MappedStatement
对象,一个 MappedStatement 实例对应着一个 Mapper文件里的一个查询语句:
1 | Configuration configuration = sqlSessionFactory.getConfiguration(); |
那 MappedStatement 是怎么和我们传入的参数组合,最后生成PreparedStatement的呢?经过一段时间顺藤摸瓜地查找,终于找到了处理逻辑所在的地方:DefaultParameterHandler#setParameters
。
boundSql
:SQL语句和参数信息。储存了用问号占位符标记的生成的 SQL语句,以及每一个参数的类型信息和参数符号表达式(就是用 #{}
包起来的部分);
parameterObject
:传入的参数;
MetaObject
:这个工具可以读取参数对象,然后根据表达式从参数对象取出对应的值。
有了 BoundSql 和 MetaObject 我们就可以手动把问号 SQL 拼成完整 SQL了。
最后一个问题:Mybatis-Plus Wrapper 的原理是什么?它是怎么调用 Mybatis的?
Mybatis-Plus 全面接管了 Mybatis。结合 Spring框架,从头到尾都不需要手动加载配置,创建连接了。并且内置了很多方便的增删改查接口,以及条件构造器,可以很方便地进行条件查询。
通过对生成的 QueryWrapper 断点调试我们可以发现,wrapper自己本身就是传递给 mapper 的参数。Wrapper 负责的是生成 where部分的语句和参数。wrapper 得到的 SQL 语句里面的参数是#{ew.paramNameValuePairs.MPGENVALx}
的形式,很明显引用了自己(ew 即 EntityWrapper,是 MP 旧版本的 Wrapper类名)。
在项目启动的时候,MP 会找到 selectList 方法的实现类SelectList
,然后把所有的 SQL 片段拼接起来,动态生成一个MappedStatement
,然后放入 mybatis 的 sqlSessionFactory里面。
假如我们调用了service.selectList(wrapper)
,其实也相当于执行了一次 Mybatis查询,只不过 Mapper 语句是 MP 帮你生成的(BaseMapper方法注入),传入的参数也是 MP 帮你生成的(在 wrapper 里面)。
这样,完整的链条就串起来了:
]]>参见
- MyBatis原理系列(六)-手把手带你了解 BoundSql 的创建过程 - 掘金(juejin.cn)
- Java 获取Mybatis 动态生成的 sql - yinder - 博客园 (cnblogs.com)
java- MyBatis 3 - get SQL string from mapper - Stack Overflow - MyBatis-Plus简介和原理解析 | 码农家园 (codenong.com)
- MyBatis-Plus 的BaseMapper 实现原理 - 掘金 (juejin.cn)
- 面试官:Mybatis如何将数据库类型转换成 Java 类型?如何创建 SQL 返回的对象?如何起别名?- 知乎 (zhihu.com)
java- How can I get the SQL of a PreparedStatement? - StackOverflow
就体验来看呢,如果日常用的软件在 Linux上都有相同版本或者替代的话,感觉还是不错的。就是驱动和字体渲染比不上Windows。
下面是一些好用的软件推荐。虽然很多链接标的是 Arch Linux软件包的地址,但是对其他的发行版也基本都是支持的。很多软件都提供了
文本:
最好用的 Markdown 编辑器。
SSH 客户端:
终端连接工具,颜值很高。支持所有平台,包括 Windows 和Android。免费的版本提供 SSH客户端和端口映射工具,付费的还提供帐号间连接的同步等等功能。
记事本:Simplenote (
Simplenote是一个功能简单的记事本软件,只能记录纯文本内容。它的优点在于全平台支持,并且实时同步。一个设备上输入的文字马上可以在另一个设备看到(甚至可以当作跨设备的剪切板用)。我把它当作备忘录来用。这个软件和Wordpress 是一个公司,所以稳定性也是有所保证的。
截图工具
推荐两个截图工具,分别是深度截图(
文档处理
这个肯定是
即时通讯
微信:用
QQ:官方 Electron 版本上线了,用 或者用。
剪切板管理:
不错的剪切板管理工具,记录剪切板的历史,文本和图片都支持,可以自定义快捷键激活。
远程桌面:
支持 RDP、VNC、SFTP 等协议,功能强大。
文件查找:
全局的文件搜索。类似 Windows 下的软件 Everything。配合
分区管理:
和 Windows 下的 DiskGenius 类似的分区编辑工具。
输入法:RIME
全平台的开源的可定制化程度很高的输入法引擎。IBus 和 Fcitx都支持。
网络代理:V2rayA
为 Linux 设计的 V2ray 客户端,用网页的形式来管理连接。用
容器:
各种服务类的工具,比如数据库,中间件,Web服务端等等,都可以用 Docker来部署,省时省力。以前安装 MySQL,Oracle数据库需要配置很多东西,现在一行命令就可以启动起来了。甚至图形化应用也可以用Docker 来跑,比如
这是
电子书:https://4lib.org/s/B009WMAZX4
书本实例代码:https://github.com/juven/mvn_in_action_code
构件:在 Maven世界中,任何一个依赖、插件或者项目构建的输出,都可以称为构件。
坐标:每一个构件都有其唯一的坐标,根据这个坐标可以定义其在仓库中的唯一存储路径。
依赖:一个 Maven 项目使用另一个 Maven项目的构件,作为依赖。使用坐标来定位。
仓库:在某个位置储存所有 Maven 项目共享的组件。当需要的时候,Maven会根据坐标找到仓库中的构件,使用它们。
1 | mvn archetype:generate |
1 | <dependency> |
Maven 在编译,测试,和运行的时候各使用一种classpath。依赖范围就是在某个阶段是否需要这个包。
compile:编译依赖范围。默认依赖范围。对编译,测试,运行都会使用到。例如spring-core。
test:测试依赖范围。只对于测试 classpath 有效。例如JUnit。
provided:已提供依赖范围。对于编译和测试有效,在运行时无效。例如servlet-api,编译和测试时需要用到该依赖,但是运行的时候容器已经提供,就不需要重复引入,再把依赖打进jar 包。
runtime:运行时依赖范围。对测试和运行有效,但在编译时不需要。例如JDBC 的驱动实现,在代码里面我们没有直接引用 MySQL 或者 Oracle 的JDBC驱动,而是用 JDBC接口去调用。所以编译的时候不需要,实际运行的时候才要。
system:系统依赖范围。范围和 provided范围一致,但是需要使用 systemPath指定依赖文件的路径,用来导入本级系统中的包,没有可移植性。
import:用于依赖管理,只在 dependencyManagement标签中使用。作用是把另一个 POM 的 dependencyManagement 配置导入到当前POM 的 dependencyManagement 里面。
Scope | 编译 | 测试 | 运行 |
---|---|---|---|
compile | ✔ | ✔ | ✔ |
test | ✔ | ||
provided | ✔ | ✔ | |
runtime | ✔ | ✔ | |
system | ✔ | ✔ |
左边一列是第一直接依赖(A),上面一行表示第二直接依赖(B),单元格表示传递性依赖范围(C)。
A 依赖 B,B 依赖 C,那么 A 对 C 是什么依赖范围。
可选依赖不会被传递。即添加了标签为<optional>true</optional>
的。
compile | test | provided | runtime | |
---|---|---|---|---|
compile | compile | - | - | runtime |
test | test | - | - | test |
provided | provided | - | provided | provided |
runtime | runtime | - | - | runtime |
依赖调解的原则:
1 | graph TD |
<mirrorOf>*</mirrorOf>
:匹配所有远程仓库。
<mirrorOf>external:*</mirrorOf>
:匹配所有远程仓库,使用localhost 的除外,使用 file://协议的除外。也就是说,匹配所有不在本机上的远程仓库。
<mirrorOf>repo1,repo2</mirrorOf>
:匹配仓库repo1 和 repo2,使用逗号分隔多个远程仓库。
<mirrorOf>*,!repo1</mirrorOf>
:匹配所有远程仓库,repo1除外,使用感叹号将仓库从匹配中排除。
需要注意的是,由于镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或者停止服务的时候,Maven仍将无法访问被镜像仓库,因而将无法下载构件。
Maven的生命周期就是为了对所有的构建过程进行抽象和统一。Maven从大量项目和构建工具中学习和反思,然后总结了一套高度完善的、易扩展的生命周期。这个生命周期包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。也就是说,几乎所有项目的构建,都能映射到这样一个生命周期上。
Maven的生命周期是抽象的,实现由插件来完成。有默认插件,也可以绑定其它插件。
Maven 拥有三套独立的生命周期:clean,default,site。
太多了,完整的在
部分重要的
validate
- validate the project is correct and allnecessary information is availablecompile
- compile the source code of the projecttest
- test the compiled source code using a suitableunit testing framework. These tests should not require the code bepackaged or deployedpackage
- take the compiled code and package it in itsdistributable format, such as a JAR.verify
- run any checks on results of integration teststo ensure quality criteria are metinstall
- install the package into the localrepository, for use as a dependency in other projects locallydeploy
- done in the build environment, copies thefinal package to the remote repository for sharing with other developersand projects.Maven 生命周期和插件相互绑定。
可以自定义将某个插件目标绑定到生命周期的某个阶段上。
可以通过命令行参数 -D 对插件进行配置。例如跳过测试:
1 | mvn install -Dmaven.test.skip=true |
如果想要一次构建两个项目,而不是到两个模块的目录下面分别执行 mvn命令,我们可以再新建一个 pom 文件。
1 |
|
模块不一定要是树形嵌套的结构的,也可以平行放置,这样 module标签要改成相对路径。
这样运行 mvn clean install
命令就会同时构建两个工程。
如果多个模块都依赖了一堆相同的东西,那么可以建立一种父子结构,让子模块继承父模块。
作为父模块,打包类型需要为 pom
。
1 | <project xmlns="http://maven.apache.org/POM/4.0.0" |
子模块需要继承它。
1 | <project xmlns="http://maven.apache.org/POM/4.0.0" |
relativePath 似乎可以不需要。
子模块没有配置 groupId 和 version的话,会继承父类的配置。也可以显式声明。
最后要把父模块也添加到聚合模块里面。
1 | groupId:项目组ID,项目坐标的核心元素。 |
使用 dependencyManagement
元素的依赖,不会给子模块引入依赖。子模块需要引入依赖的时候,需要提供groupId 和 artifactId,该依赖的还是要写,不过不需要提供版本号。
1 | <dependencies> |
maven中importscope依赖方式解决单继承问题的理解 - 花花牛 - 博客园
1 | <dependencyManagement> |
使用 import 依赖,可以把另一个 POM 中的 dependencyManagement配置合并到 当前 POM 的 dependencyManagement 元素中。想要重复使用一批dependencyManagement 配置,除了可以复制配置或者继承之外,还可以通过这种import 范围依赖将这一配置导入进来。
如果有多个项目,它们使用的依赖版本都是一致的,则就可以定义一个使用dependencyManagement专门管理依赖的POM,然后在各个项目中导入这些依赖管理配置。
这样,用聚合代替继承,就可以解决 POM文件只能单继承的问题,也可以更好地按功能划分 POM 文件。
pluginManagement
也是类似的原理。
对于聚合模块来说,它知道有哪些被聚合的模块,但那些被聚合的模块不知道这个聚合模块的存在。
对于继承关系的父 POM来说,它不知道有哪些子模块继承于它,但那些子模块都必须知道自己的父 POM是什么。
一个 POM 可以既是聚合 POM,又是父 POM,没有什么问题。
任何一个 Maven 项目都隐式地继承自该 POM。这个超级 POM约定了中央仓库、项目结构、插件版本等等配置。这些配置成了 Maven所提倡的约定,可以在 Maven 的依赖包里面找到。
在一个多模块的Maven项目中,反应堆(Reactor)是指所有模块组成的一个构建结构。对于单模块的项目,反应堆就是该模块本身,但对于多模块项目来说,反应堆就包含了各模块之间继承与依赖的关系,从而能够自动计算出合理的模块构建顺序。
Maven会根据模块之间的依赖关系,决定构建模块的顺序。如果一个模块依赖于另一个模块,就先构建那个模块。这么递归下去,直到找到没有依赖的模块。
模块之间的依赖关系会构成一个有向无环图(DAG),所以说不能出现环,要是出现了循环依赖,Maven就会报错。
用户可以裁剪反应堆,也就是可以选择只构建反应堆中的一些个模块,加速构建。
]]>
如何愉快地写个小parser -知乎
GettingStarted with ANTLR v4 | GitHub
TheDefinitive ANTLR 4 Reference 英文版和示例代码
ANTLR 是:
语法分析器的生成器:可以根据一个程序设计语言的语法描述自动生成语法分析器。
扫描器的生成器:可以根据一个语言的语法单元的正则表达式描述生成词法分析器。
语法制导的翻译引擎:ANTLR会生成语法树并生成相应的访问器或者监听器类来遍历语法树。
使用 LL(*) 文法的解析器。
包含很多现成的语法定义,解析主流的语言都可以开箱即用:
https://www.antlr.org/download/
1 | CLASSPATH =.;D:\javalib\*; |
1 | :: antlr4.bat |
1 | ::grun.bat |
安装插件
1 | // Define a grammar called Hello |
1 | grun Hello r -gui |
退出:Windows 按下 Ctrl + Z,Enter退出。
ANTLR的监听器和访问器能够将语法和程序逻辑代码解耦,可以不需要在语法中内嵌动作。
使用访问器和监听器机制,我们可以完成一切与语法相关的事情。一旦进入Java的领域,就没有什么ANTLR的相关内容值得学习了。我们需要谨记在心的是,语法及其对应的语法分析树,以及访问器或者监听器事件方法之间的关系。除此之外,剩下的仅仅是普通的代码。在对输入文本进行识别时,我们可以产生输出、收集信息(正如本例中我们所做的)、用某种方式验证输入文本,或者执行计算。
类似于 XML 的 SAX 解析。监听器的方法会被 Antlr提供的遍历器对象自动调用。
可以用 ParseTreeProperty 来储存和获取变量。
1 | ParseTreeProperty<Integer> values = new ParseTreeProperty<Integer>(); |
访问器方法中,需要显式地调用 visit()方法来访问子节点,可以自己定义怎么访问子树。
使用 import
可以让语法文件模块化。
1 | stat: expr NEWLINE # printExpr |
加了标签的备选分支,会生成相应的访问器方法。
语法中嵌入动作
使用语义判定改变语法分析过程
孤岛语法。例子:解析xml
重写输入流。例子:java代码添加序列化标识符
将词法送入不同通道。例子:忽略却保留注释和空白字符
起始规则,词法符号,语法规范
比如一个协议语言POP,由关键字、整数和换行组成。
1 | retr : 'RETR' INT '\n' |
任意长度序列可以用 +
字符。(INT)+
或者INT+
。
可以为空,零个或多个用 *
。
零个或一个:?
。
使用符号 |
作为“或者”来表达编程语言中的选择模式。备选分支(alternative)或者可生成的结果(productions)。
1 | type : 'float' | 'int' | 'void' |
依赖符号的语法,比如数组的括号。表达对符号的依赖的方法。
1 | vector : '[' INT+ ']' ; // [1], [1 2], [1 2 3], ... |
自己引用自己。如果一条规则定义中的伪代码引用了它自身,就需要一条递归规则(自引用规则)。
直接递归和间接递归。
1 | expr : ID '[' expr ']' |
经典的从左到右自顶向下的语法分析器无法处理左递归。算符优先级带来的问题。ANTLR解决的方式是,写在前面的语法拥有较高的优先级。如果遇到了从右向左结合的,需要使用assoc 手工指定结合性:
1 | expr : <assoc=right> expr '^' expr |
ANTLR 4 可以能够处理直接左递归,但是不能处理间接左递归。
1 | ID : ('a'..'z'|'A'..'Z')+ ; //匹配1个或多个大小写字母 |
或者:
1 | ID : [a-zA-z]+ ; |
ID 规则可能和其他规则冲突,比如其他关键字 enum 或 for。所以要把 ID规则放在所有关键字规则之后。
1 | INT : '0'..'9'+ ; |
或者
1 | INT : [0-9]+ ; |
浮点数:
1 | FLOAT : DIGIT+ '.' DIGIT* // 1. 3.14 |
将一条规则声明为 fragment可以告诉ANTLR,该规则本身不是一个词法符号,它只会被其他的词法规则使用。
1 | STRING : '"' .*? '"' ; |
.
匹配任意单个字符,.*
匹配零个或多个,?
标记表示使用非贪婪匹配子规则(nongreedysubrule)。
转义字符:
1 | STRING : '"' (ESC|.)*? '"' ; |
C 中的单行和多行注释:
1 | LINE_COMINT : '//' .*? '\r'? '\n' -> skip ; // 消费掉双斜杠后面的一切字符,直到遇到换行符 |
空白字符:
1 | WS : [ \t\r\n]+ -> skip ; |
要想揭开一门语言的神秘面纱,我们需要分析不同来源的信息。语言的规模越大,我们需要的参考文档和各式各样的范例代码就越多。有时候,只有设法对语言现有的实现进行试探,才能发现边界情况。语言的参考文档通常并非一目了然。
使用访问器的方法来返回值
继承 BasicVisitor 的时候指定泛型
使用类的成员在事件方法之间共享数据
比如在监听器的类中声明一个栈,每个子表达式的结果退入栈中,然后在更高层的节点中取出来。
对语法分析树的结点进行标注
可以直接将语句绑定在语法上,这样每一个节点都会有这个值:e returns [int value] ...;
。
使用 ParseTreeProperty
的辅助类,本质上是IdentityHashMap,用节点对象作为键。
(12章第二节)
因为ANTLR自动生成的语法分析器经常在词法符号流中进行非常远的前瞻以作出语法分析决策。这意味着,远在语法分析器能够执行提供上下文信息的行为之前,词法分析器就需要将字符流处理为词法符号。
因为这样,处理上下文相关的词法问题变得比较困难。
关键字作为标识符的问题的解决方法是,令词法分析器将所有关键字当作词法符号送给语法分析器。
]]>20220913 更新:现在 SSH连接的方式已经被大部分软件支持了,别的方式的可以不用看了。
最近在学习用 Docker 来部署项目。我按照这个
从 Docker 的
所以我们如果只需要连接远程服务器的 Docker的话,就没有必要在本地安装完整的 Docker,不需要安装 Dockerdaemon,只需要有能与 Docker daemon 进行交互的客户端,比如 Dockerclient,就可以了。
在默认的设置下,Docker daemon 只监听来自本地的 UNIX socket 连接。
在 /lib/systemd/system/docker.service
下面可以看到dockerd
,即 Docker daemon 的启动参数:
1 | ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock |
我们需要修改这个 systemd
的启动参数来开启外部访问。
使用 sudo systemctl edit docker.service
来编辑docker.service
,这会在它的基础上重写配置。
写上下面的内容,保存文件。
1 | [Service] |
上面这个 ExecStart=
的作用是清空原来项目的值,然后用新的值替代。官方文档这么写的,我之前还以为是笔误……
更新 systemctl 配置和重启 Docker。
1 | sudo systemctl daemon-reload |
检查 dockerd 是否运行起来 1
sudo netstat -lntp | grep dockerd
现在我们已经开放了 TCP 的 2375端口,只要在执行命令的时候加上参数就可以了:
1 | docker -H tcp://docker.beanbang.cn:2375 version |
也可以设置环境变量DOCKER_HOST
,这样就不用每次都带参数:
1 | export DOCKER_HOST=tcp://docker.beanbang.cn:2375 |
注意:使用这种连接方式是不安全的,端口并没有加密,这意味者任何人都可以连接你的Dockerdaemon,往里面运行容器。虚拟机用还好,云服务器上要是被别有用心的人发现了不是纯白给了。所以加密你的Docker 端口还是很有必要的。
需要使用 OpenSSL生成密钥,证书。客户端和守护进程使用这些证书和密钥来进行认证。只要照着官方文档的这篇文章,一步一步做下来就可以了:
生成证书的大致过程是,生成 CA 的密钥和 CA证书,生成客户端和服务端的密钥;生成客户端和服务端的签名请求,然后 CA分别对客户端和服务端的签名请求生成证书。
跟着教程一顿操作之后,可以得到下面的几个文件。
文件 | 描述 |
---|---|
ca-key.pem | CA 密钥 |
ca.pem | CA 证书 |
server-key.pem | 服务端密钥 |
server-cert.pem | 服务端证书 |
key.pem | 客户端密钥 |
cert.pem | 客户端证书 |
然后我们接着修改 docker.service。虽然可以这么写:
1 | sudo systemctl edit docker.service |
1 | [Service] |
不过,这样大长串的参数很不直观。下面我们换成使用 Docker提供的另一种配置 dockerd 的方式,就是 daemon.json
文件。
1 | sudo vim /etc/docker/daemon.json |
1 | { |
原本的 docker.service 只保留一点东西:
1 | sudo vim /etc/docker/daemon.json |
1 | [Service] |
然后重启 Docker 就可以了。
注意:Docker 在启动的时候会同时使用systemd
和 daemon.json
文件。如果两个文件的配置项冲突了,会造成无法启动。所以配置要么写docker.service 里面,要么写 daemon.json里面,不要两个都写,防止出现问题。
注意2:按照惯例,不加密的 TCP 连接使用 2375端口,TLS 加密的 TCP 连接使用 2376 端口。
运行起来后,客户端连接需要 ca.pem,cert.pem,key.pem三个文件。我们可以把它们从主机上拷贝下来,放在自己用户目录的.docker
目录下,这是证书文件的默认查找目录。
纯命令:
1 | docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem \ |
使用环境变量:
1 | export DOCKER_HOST=tcp://docker.beanbang.cn:2376 DOCKER_TLS_VERIFY=1 |
从 Docker 的 18.09 版本开始,Docker client 支持通过 SSH 来连接远程daemon 了。但是到了今天(2020年8月),我能直接下载到的最新 Dokcer clientfor Windows 版本是 17.09。IDEA 和 VS Code 这些 IDE我也试过,同样不能使用 SSH协议来连接。所以这种方法虽然特别方便,但是目前还是比较有局限性的。
要使用 SSH 协议来连接 Docker daemon,你可以使用 Linux 或者 WSL安装新版的 docker-cli。
服务端不需要任何配置,只需要在客户端执行命令:
1 | docker -H ssh://ubuntu@192.168.43.220 version |
注意,这边主机名只能使用 IP 地址,不能使用域名。
这种方式是使用 SSH密钥登录和认证的,如果你没有使用密钥来登录主机,可以现配置一个:
检查一下你的用户目录下的 .ssh
文件夹下面有没有id_rsa
和 id_rsa.pub
文件。如果你在用Git,可能已经创建过密钥了。
没有的话就创建:
1 | ssh-keygen -t rsa -C "youremail@example.com" |
把你的公钥添加到远程主机:
1 | ssh-copy-id ubuntu@192.168.43.220 |
Windows 没有这个命令的话就手动把 id_rsa.pub
里面的内容添加到远程主机用户的 ~/.ssh/authorized_keys
里面。
这就行了。同样可以把这个连接串设置到环境变量中,方便使用:
1 | export DOCKER_HOST=ssh://ubuntu@192.168.43.220 |
直接安装 Docker desktop 是可以的,安装完成之后就可以使用 docker命令了。但是假如我们本地只想用客户端的功能,不想安装它那厚重的 DockerEngine,可以只下载几个二进制可执行文件。下面是下载地址:
Docker CLI Windows:
Docker compose:https://github.com/docker/compose/releases
下载完成后,重命名成 docker.exe
和docker-compose.exe
,丢进设置过 Path
环境变量的文件夹里就可以了。
Linux 客户端只需要安装 docker-ce-cli
。这里推荐一下
IDEA 里面图形化的设置很方便。如果使用了加密的TCP,要选定证书的文件夹,并且 URL 要写成https://docker.beanbang.cn:2376
这样的以 https开头的形式。
首先安装 Docker 插件,然后修改配置文件。
1 | // settings.json |
连接上远程 Docker 之后,我们就可以尝试远程部署了。这是一个例子,在Windows 10 上用 Dokcer CLI 部署一个 Spring Boot项目到远程服务器上。我使用的是 TLS 加密的TCP,就是上面的提到的配置。
环境变量:
1 | $ set | findstr DOCKER |
在 Spring initializr 上面
添加一个控制器:
1 | package cn.beanbang.demo.controller; |
执行 mvnw package
命令打包。会在 target
目录下生成 rest-test-0.0.1-SNAPSHOT.jar
文件。
在项目的目录下创建 Dockerfile
文件:
1 | FROM openjdk:8-jre |
然后可以开始部署了。
创建容器:
1 | $ docker build -t rest-test . |
运行容器:
1 | $ docker run --name rest-demo -p 8081:8080 -d rest-test |
这样,一个简单的 Java Web 服务就搭建起来了。在执行docker build
命令的时候,Docker会把构建所需要的文件上传到服务端的 daemon,然后 daemon负责创建镜像和后续的容器运行等流程。
]]>参见:
- Protect theDocker daemon socket | Docker Documentation
dockerd| Docker Documentation Post-installationsteps for Linux | Docker Documentation DockerTips: Access the Docker Daemon via SSH | by Luc Juggery |Medium
官网:https://certbot.eff.org
certbot 官网有安装向导。打开
1 | # Apache on Ubuntu 18.04 LTS (bionic) |
这一步需要更改 DNS 的解析记录,有个坑要注意一下:更改 DNS记录要等大约 10 分钟才生效,最好用 screen
或者其他命令保证终端不会超时退出。
1 | certbot certonly -d "*.beanbang.cn" -d "beanbang.cn" --manual --preferred-challenges dns-01 --server https://acme-v02.api.letsencrypt.org/directory |
certonly
表示插件,Certbot有很多插件。不同的插件都可以申请证书,用户可以根据需要自行选择。-d
为哪些主机申请证书。如果是通配符,输入 *.xxx.com(根据实际情况替换为你自己的域名)。--preferred-challenges dns-01
使用 DNS方式校验域名所有权。--server
Let's Encrypt ACME v2 版本使用的服务器不同于v1 版本,需要显示指定。执行之后会有提示你记录 IP地址之类的,确定就是了。之后会提示需要你验证你的域名:
1 | Please deploy a DNS TXT record under the name |
括号里的内容说,不要删除和改动先前的记录,你可能被要求向同一个名字(二级域名)添加多条TXT 记录,这个是 DNS 标准所允许的。
去域名服务商的控制台添加解析记录。改完之后,新建一个连接,用dig
命令来测试。
1 | dig -t txt _acme-challenge.beanbang.cn |
改完解析记录没有立刻生效的话,可以手动刷新一下主机的 DNS缓存再查。不同的 DNS 缓存服务刷新的命令不同:
1 | # Systemd Resolved |
如果你发现解析记录已经成功改变了,就可以按下回车。
成功之后会有下面的提示:
1 | Waiting for verification... |
这时候应该能看到 /etc/letsencrypt/live/[域名]
下的证书文件:
1 | ls /etc/letsencrypt/live/beanbang.cn/ |
查了一下文档1,这四个证书文件分别的不同用途,大概是这样:
privkey.pem
证书的私钥,必须时刻保护好。不能公开给任何人。
配置项:Apache 的 SSLCertificateKeyFile
,Nginx的 ssl_certificate_key
。
fullchain.pem
完整的证书密钥链(certificate chain),包括服务端证书(server/leafcertificate)和中间证书(intermediatecertificate)。在文件最开头第一个是服务端证书,接下来的是中间证书。
配置项: Apache >= 2.4.8 的 SSLCertificateFile
,Nginx 的 ssl_certificate
。
cert.pem
和 chain.pem
(较少用)
cert.pem
包含了服务器端证书,chain.pem
包含了中间证书。假如你要使用它们,必须两个文件一起使用,不然浏览器会提示“链接不受信任”。
配置项:Apache < 2.4.8 的 SSLCertificateFile
和 SSLCertificateChainFile
。启用了OSCP 的 Nginx >= 1.3.7,需要将 ssl_trusted_certificate
设置为 chain.pem
。
找到 Apache 的配置文件httpd-ssl.conf
,并且修改相应的内容,把证书添加上去。
1 | cd /opt/lampp |
1 | <VirtualHost 0.0.0.0:443> |
1 | vim /etc/nginx/conf.d/blog.beanbang.cn.conf |
1 | server { |
通配符证书似乎不支持用 certbot renew
来更新,重新执行一下上面申请证书的命令来更新证书。证书时长是三个月,在到期的前10 天 Let's Encrypt 会发邮件提醒你的。
]]>参见:
- Let's Encrypt终于支持通配符证书了 - 简书
- Let's Encrypt免费通配符 SSL 证书申请教程 - 运维之美
- 申请 Let's Encrypt 通配符HTTPS 证书,并配置 Apache27f - 柒风博客 | 7f - 柒风博客
根证书和中间证书的区别- FreeBuf专栏·ssl China
什么是 XML?
下面这张图是网站“Linux中国”的
元素(Element)是指从开始标签到结束标签的部分,包括标签本身和标签包括的内容。
一个元素里面可以有:
子元素还能继续包含其他元素,所以是一个树形结构。
注意:XML 没有预定义的标签,这和 HTML 不同,HTML中的 <p>
、<h1>
标签都是预定义的。XML 所有的标签和属性都是创作者自定义的。
1 | <bookstore> |
所有的元素必须关闭标签
对大小写敏感
标签必须正确的嵌套
文档必须有根元素
特殊意义的字符使用实体引用(Entity References)
实体引用以 &
开头,以 ;
结尾,比如<
字符用 <
替换。
注释:<!-- 注释内容 -->
XML 中空格会被保留,而 HTML 会把多个空格合并成一个
属性(Attribute)提供关于元素的额外(附加)信息。
属性通常提供不属于数据组成部分的信息,但这些信息对于处理这个元素的软件来说很重要。
1 | <file type="gif">computer.gif</file> |
包含双引号的属性值用单引号包围:
1 | <gangster name='George "Shotgun" Ziegler'> |
或者可以使用实体引用:
1 | <gangster name="George "Shotgun" Ziegler"> |
1 | <person sex="female"> |
这两个例子传递的信息都是相同的,只是 sex一个是作为属性存在,一个是作为字段存在。什么时候用属性,什么时候用字段是没有规定的。教程里面建议,如果信息看起来很像数据,就使用子元素。
元数据(有关数据的数据)应当存储为属性,而数据本身应当存储为元素。
命名空间可以避免元素名冲突。
使用前缀可以避命名冲突。前缀是在标签名的前面加上前缀名称,用冒号隔开:
1 | <h:table> |
而命名空间对标签进行限定,声明某个前缀属于一个特定的命名空间,采用的方法是添加一个xmlns 属性:
1 | xmlns:namespace-prefix="namespaceURI" |
1 | <h:table xmlns:h="http://www.w3.org/TR/html4/"> |
命名空间地址的作用是唯一性,不会被解析,通常是对应的开发者的网站。
用于标示命名空间的地址不会被解析器用于查找信息。其惟一的作用是赋予命名空间一个惟一的名称。不过,很多公司常常会作为指针来使用命名空间指向实际存在的网页,这个网页包含关于命名空间的信息。
如果省略了前缀名字,则表示所有子元素都属于这个命名空间。
1 | xmlns="namespaceURI" |
1 | <table xmlns="http://www.w3.org/TR/html4/"> |
XML 解析器不会解析 CDATA 部分内的文本,常用来保存大段原始数据。
CDATA 部分以 <![CDATA[
开始,以 ]]>
结束:
1 | <![CDATA[不会被解析的内容]]> |
CDATA 部分不能包含字符串 ]]>
。也不允许嵌套的 CDATA部分。
标记 CDATA 部分结尾的 ]]>
不能包含空格或折行。
名称 | 描述 |
---|---|
XML HttpRequest | 用于在网页服务器之间传递数据,比如AJAX |
XML Parser | 解析器,用来将 XML 文本解析成 DOM对象 |
XML DOM | 定义了操作 XML 文档的规范,把 XML展示为树形结构 |
XML XPath | XPath 表达式使用类似文件路径的语句来选取XML 中的节点 |
XML CSS | 用 CSS 样式表来格式化 XML 文档 |
XML XSLT | 同样是用来格式化 XML 的,比 CSS复杂,功能更多 |
XML XQuery | XML 的查询语言,如同 SQL 之于数据库 |
XML XLink | XML 里的超链接 |
XML DTD | 规范化,用来定义 XML文档元素和属性的结构 |
XML Schema | 描述 XML 文档结构,自己也是 XML语法,功能和 DTD 一样 |
]]>参考资料:
IntelliJ IDEA提供了自动补全文档格式注释的方法。只要在声明语句的前面打上/**
然后按下回车,IDE就会自动给你补全注释。对于方法的注释,产生的注释片段会包含所需要的标签(每个参数的@param
标签、@return
、@throws
等等)。
文档格式的注释也支持 JavaScript、Python、Ruby、和 PHP。
把光标放在一段类,方法,函数或者成员变量中,然后按下Ctrl+Shift+A。
打上 fix doc comment
然后按下回车。
IDEA 就会自动给注释增加上缺失部分的标签。
如果一个方法的特征改变了,IDEA会高亮出不匹配部分的标签,然后提出快速修改的建议。
对于文档注释的标签,IDEA 也提供了代码自动补全。在 @
标记的后面按下 Ctrl+Space,然后就能够从弹出建议的列表里面选择需要的标签了。如果标签包含多个值(比如@param
),可以在建议的列表里面继续选择。
但是 Ctrl+Space在微软拼音输入法里面是切换中英文的快捷方式,造成冲突而且不能改变……你可以添加一个英语语言,然后按Win+Space切换到英语键盘来编码。
你一定查过
(文档和代码的耦合性这么高,难道这就是 Java一直没有中文文档的原因……)
还有一个好处就是,IDEA也会识别这种注释。将光标移动到类名或方法名上,按下Ctrl+Q,就会弹出气泡提示:
如果要让鼠标移动到类名上自动弹出提示,可以这么做:
File -> Settings.. -> Editor -> General,找到项目 Showquick documentation on mouse move,勾选上就可以了。
]]>参考资料:
Documentingcode - Help | IntelliJ IDEA Codereference information - Help | IntelliJ IDEA - Javadoc -维基百科,自由的百科全书
原题目按此:
给定两个单词 word1 和 word2,计算出将 word1 转换成 word2所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
比如 horse
转换到 ros
所需要的最小操作数是3
,或者说 horse
到 ros
的距离是3
.
能够用动态规划求解的问题通常具有下面的几个特点:
满足这些条件,这个问题就是一个动态规划问题了。求解问题也是按照这些步骤来进行。
我们根据《算法导论》上面提供的步骤来。
“如果一个问题的最优解包含其子问题的最优解,我们就称此问题具有最优子结构性质。”
1
也就是说这一步我们需要正确的划分子问题,让子问题有最优解的时候,原问题也有最优解。想办法找到和原问题形式一样,但是规模比较小的问题。
所以我们可以考虑,两个字符串的距离,可以由另外两个字符串的距离的基础上,插入/删除/替换了一个字符来得到。
设置两个指针 i 和 j,分别放在原字符 word1 和替换字符 word2的最后,指针从右往左移,操作 word1[i],让它和 word2[j]相同。指针经过的地方代表这一部分的字符串已经完成了转换。
1 | horse |
那么,对每一个字符,这边就以 horse 的 e作为例子,我们能有这么几种操作:
1 在 e 的右边插入字符 s。
1 | horses |
假如我们知道 horse -> ro
的距离为n,那么总编辑距离就是 n+1。
2 删除字符 e。接下来需要计算 hors -> ros
的距离。距离+1。
1 | hors |
3 把 e 替换成 s。接下来需要计算 hors -> ro
的距离。注意,这里如果两个字母相同,就不用转换了,这时候相当于代价为0,直接等价于子问题。
1 | horss |
对于这一个字符的修改,相当于是做出了一种选择:我们不知道对这个字符 e怎么处理最终编辑的距离会最短,所以假设每一种可能性都是最优解并且去尝试。通过上面的例子我们发现,子问题中需要求的字符串长度已经缩短了,说明这种方法是可行的。
设 m[i, j] 表示 word1[0..i] 到 word2[0..j]的距离,几个子问题就可以表示成这样:
当 word1[i] == word2[j] 时,m[i, j] = m[i-1, j-1];
当 word1[i] != word2[j] 时,m[i, j] 可以是:
在它们中选距离最短的一个。
因此,我们得到递归公式2:
\[m[i, j]=\left\{\begin{array}{lr}m[i-1, j-1] & w1[i] = w2[j]\\min(m[i, j-1] + 1,\ m[i-1, j] + 1,\ m[i-1, j-1] +1) & w1[i] \neqw2[j]\end{array}\right.\]
因为是递归求解,需要考虑一下递归的终止条件:当 i 或 j 等于 -1时,是空值和字符串之间的转换,显然从空到长度为 n 的字符串的距离就是n。即:
再完善一下公式:
\[m[i, j]=\left\{\begin{array}{lr}j + 1 & i=-1\\i + 1 & j=-1\\m[i-1, j-1] & w1[i] = w2[j]\\min(m[i, j-1] + 1,\ m[i-1, j] + 1,\ m[i-1, j-1] +1) & w1[i] \neqw2[j]\end{array}\right.\]
写出递归式之后,我们已经把一个应用问题转化成了数学问题。接下来就是编程时间,把它转化成代码了😉。
自顶向下法比较贴近我们的思考方式。它比递归更进一步,会保存每一步的计算结果。这样在需要一个子问题的解之前会先检查是不是已经计算过了,如果是的话就直接使用保存过的值。
1 | class Solution { |
这里用二维数组 dist[][]
来保存计算过的距离,用值-1
表示未计算过的距离。
自底向上的方法采用的是这么一种思路:先求解规模小的问题,再求解规模大的问题。在求解一个问题的时候,它所依赖的子问题都已经被计算过了。这样的好处在于可以不用使用递归函数调用,也不需要检查子问题是否计算过。
这张表是 horse -> ros
每个长度编辑距离的表格,和上面自顶向下代码中的 dist
数组存储的数据差不多,多了一行一列的边界值,也就是终止条件的情况。应该能够很直观地看到,表格中的每一个值(m[i,j]),所依赖三个子问题都在它的左边、上面和左上角。只要按照一定顺序计算,就能生成整张表。
1 | class Solution { |
让我们更进一步!
到上面为止,对于解答问题——得到单词间的最小距离,已经完成了。但是假如我们想要知道具体的每一步应该怎么编辑字符,该怎么做呢?我们可以对代码稍加修改,使每一个问题不仅保存最优解的值,还把相应的操作保存下来。
另外定义一个二维数组 act[][]
来储存操作,用值 0~3来表示跳过、插入、删除和替换的操作,在找到最短距离的同时顺便记录下对应的操作,就能得到一张操作表。
假如 act[i][j]
当前的值是 2,代表删除,那么就删除word1[i] 的字符,然后往上移,寻找act[i-1][j]
,以此类推。
代码:
1 | public void printOperations(int[][] act,int i,int j, String w1, String w2) { |
输出的结果:
1 | horse -> ros: |
完整代码:
希望能对你有所帮助。
]]>参见:
《算法导论》 详解一道经典面试题:编辑距离- 知乎 自底向上和自顶向下 - 编辑距离 - 力扣(LeetCode)
是的,我从 WordPress 转移到了 Hexo……
相比于功能庞大的 WordPress,小巧简单的静态网页生成器有它的特点:
如果你像我一样,习惯用 Markdown 来记笔记、写日志,那么使用Hexo、Hugo、Jekyll 这类生成器是最好不过了。它们直接使用 md文件来渲染网页,把一篇写好的笔记转换成可以公开的博客不需要任何的格式转换操作。这种所见即所得的写作方式,相信你一定会喜欢上的~
文档:https://hexo.io/zh-cn/docs/
GitHub: https://github.com/hexojs/hexo
1 | npm install hexo-cli |
常用命令:
1 | hexo new [layout] <title> |
在 _config.yml
文件里面把 post_asset_folder
设置为 true
。
安装 hexo-asset-image 插件。
1 | npm install hexo-asset-image --save |
文章中插入图片:
1 | {% asset_img "span>" '"" "图片"' %} |
这样在 Markdown编辑器里面能看到同名文件夹的图片,生成的静态网页也能显示图片。
1 | --- |
在文章最开始的几行字。用来标识标题和日期。其中一个很有用的就是permalink
。静态网页会根据它的值来设置文章的URL。这样一来,md 文件的名字就可以随便取了。
20220913 更新:新版的 permalink
含义似乎变了,会替换整个URL,包括日期前缀。需要自定义变量来实现,可以看下
在文章的 front-matter中指定了标签和分类,那么在生成网页后,就有了对应的页面。
比如 categories: 日志
,就会生成/categories/日志
。
如果不想在 URL里面显示中文,可以自定义标签和分类的路径,在配置文件里面这么写:
1 | category_map: |
归档页面应该让人一眼看到所有文章,但是 Hexo默认会将它按数量分成页面显示,看起来很不直观。
1 | # _config.yml |
Hexo博客归档不分页显示设置方法| 搜百谷
使用站点地图来方便被收索引擎收录。
GitHub:https://github.com/ludoviclefevre/hexo-generator-seo-friendly-sitemap
1 | npm install hexo-generator-seo-friendly-sitemap --save |
添加 RSS 订阅。
GitHub: https://github.com/hexojs/hexo-generator-feed
1 | npm install hexo-generator-feed |
我的配置文件:
1 | # _config.yml |
hexo 会把所有 md 和 html文件渲染成主题的样式,但是这又是不是我们想要的。
1 | # _config.yml |
**/*.html
可以使它跳过渲染目录下的所有 html 文件。
文档:https://theme-next.org/docs/
GitHub: https://github.com/theme-next/hexo-theme-next
1 | cd hexo |
Pjax 即 HTML5 的 pushState +Ajax。这个技术通过异步加载和更改浏览器地址栏实现了无刷新加载网页。由于只加载部分页面,减少了请求的次数和内容,打开网页将会很快。
安装插件 theme-next-pjax:
1 | cd themes/next |
然后开启 NexT 的 Pjax:
1 | # next/_config.yml |
但是启用 pjax 以后添加自定义的 js 控件将会变得比较困难。
1 | # next/_config.yml |
NexT默认是开启动画的,就是打开网页时的淡入效果。结果就是让读者晚一秒种看到正文内容……有什么用呢?关了关了。
新建一个页面:
1 | hexo new page categories |
然后编辑这个文件的 Front-matter:
1 | --- |
配置文件里面去掉 categories
项的注释:
1 | menu: |
启用标签菜单同理。
主题提供了很多可选的评论系统。我选了
使用之前要去 Leancloud注册和创建应用,然后再设置 -> 应用 Keys 里面,获得应用的AppID
和 AppKey
,写到 NexT的配置文件里面去。
1 | # next/_config.yml |
比较重要的就是 comment_count
参数,设置为false
来让评论数不在首页显示(postmeta),只在文章页面显示。
写公式必备。
1 | sudo apt-get install pandoc |
启用 pandoc 之后图片会有描述,和 fancybox插件显示重复,改一下样式:
1 | /* source/_data/styles.styl */ |
1 | # next/_config.yml |
因为 pandoc渲染器的语法要求比较严格,所以在安装完之后记得要检查一下以前写过的文章,可能会有所变化。
启用 Hexo 的本地搜索功能。
1 | npm install hexo-generator-searchdb --save |
1 | # next/_config.yml |
静态博客怎么搜索呢?安装这个插件后,生成了一个search.xml
,里面包含了博客里面的全部文本。搜索的时候会加载这个文件进行查找。
]]>参见:
hexo中完美插入本地图片| ETRD博客 Hexo使用攻略-添加分类及标签| linlif-blog - YAML语言教程 - 阮一峰的网络日志
Hexo使用攻略:(四)Hexo的分类和标签设置| { GoonX } 为你的Hexo加上评论系统-Valine| BlueLzy's Blog
在一个平常的局域网(开启了 DHCP)下面,获得一台主机的 IP地址常常有这么几种方法:
arp -a
命令查询 arp 缓存;ipconfig
命令;不久之前,我一直用上面的方法来连接我的树莓派。树莓派没接显示器,没办法在主机上操作,每次要么使用手机热点(设备管理里面能显示IP),要么直接改配置分配静态IP。这些方法都有局限性,网络环境一变化,又得配置半天。
现在,一个新的方法出现了!(其实一直就有)只要知道你的设备的主机名,你就可以得到你的设备的IP 地址。例如,我的树莓派是 Raspbian 系统,它的默认主机名是raspberrypi
,就可以通过域名 raspberrypi.local
来连接,像这样:
1 | ssh pi@raspberrypi.local |
这就是 mDNS (Multicast DNS)协议给我们提供的便利。
在研究 IGMP 协议的时候,我用 Wireshark 对无线网卡抓取 IGMP数据包,想看看有没有什么服务是在使用组播(Multicast)地址的。
之后我看到了局域网内的电脑基本上都会加入这几个组播地址:224.0.0.251
,224.0.0.252
,239.255.255.250
。
经过查找,这三个组播地址分别是协议 mDNS,LLMNR 和 SSDP协议的。mDNS实现了类似 DNS的功能,使得主机在局域网内能够加入和通信;LLMNR 的功能和 mDNS 类似;SSDP是 UPnP 协议的一部分,也是用来实现设备和服务的发现的。
在一段 mDNS 报文中,我看到了这样的内容:
1 | Multicast Domain Name System (response) |
来源是 192.168.10.108
,目的是 224.0.0.251
。返回的记录中有我的树莓派的 IPv4 和 IPv6 地址,以及一个相同的字段raspberrypi.local
。
难道说 raspberrypi.local
就是我树莓派的域名?我立马 ping了一下:
1 | PS C:\WINDOWS\system32> ping raspberrypi.local |
确实可以。我流下了激动的泪水:这么好的东西怎么没早发现呢!
通过分析抓包数据,mDNS 协议在局域网内的 IP地址查询过程大致是这样的:
A:发起查询的主机
B:被查询的主机
224.0.0.251
;224.0.0.251
发送 mDNS 组播报文查询 B的主机名,所有在组内的主机都会收到这个查询请求;224.0.0.251
发送回应 mDNS报文,内容包括自己的主机名和 IP 地址;其中,mDNS 报文是包装在 UDP 组播报文中的,使用 5353
端口。
Windows 系统的主机名在环境变量 COMPUTERNAME
查看,可以在系统设置中修改。
1 | echo %COMPUTERNAME% |
Linux 系统主机名存在 /etc/hostname
中。使用hostnamectl
可以查询和修改。/etc/hosts
里也有主机名的记录,也要一并修改。
1 | 查询主机名 |
Windows 10 以后,系统就能够支持 mDNS 协议了。Windows 10以下的系统怎么办?你可以:
给 Windows 安装苹果公司的
使用 NetBIOS 协议,这是微软的网络发现服务。Linux 系统上安装samba
,它的 nmbd
服务使得能被 Windows主机发现。使用方法:
ping raspberrypi
。nmblookup 主机名
来得到 Windows 主机 IP。如果要实现解析NetBIOS 主机名,需要修改配置文件,没折腾了,看这篇文章 -> Linux 一般是安装了 avahi-daemon
服务。使用指令systemctl status avahi-daemon.service
来查看服务运行状态。
安卓系统似乎不支持,我的华为手机没有。
还可以查询 UDP 5353 端口。
Windows:
1 | netstat -ano | findstr 5353 |
Linux:
1 | sudo netstat -nap | grep 5353 |
其实最简单的就是 ping 一下 主机名.local 看看有没有就知道了。
]]>参见:
3种方法更改Linux系统的主机名(hostname)- ZhangYaohui专栏 - CSDN博客 - 技术|使用 mDNS在局域网中轻松发现系统
- LLMNR, MulticastDNS and names on your LAN
- Multicast DNS- Wikipedia
StandardmDNS service on Windows - Stack Overflow
查找附近的无线网络:
1 | sudo iwlist wlan0 scan |
查看当前的 WiFi 网络:
1 | iwgetid |
编辑连接:
1 | sudo vim /etc/wpa_supplicant/wpa_supplicant.conf |
1 | network={ |
更新配置:
1 | wpa_cli -i wlan0 reconfigure |
临时开关:
1 | sudo ifconfig wlan0 down |
永久关闭:
1 | echo "dtoverlay=pi3-disable-wifi" | sudo tee -a /boot/config.txt |
其他开关(蓝牙):
Howto disable onboard WiFi and Bluetooth on Raspberry Pi 3
第一次连接树莓派的时候为了确定 ip 可能会将 ip=xxxx 的信息写在/boot/cmdline.txt
里面,不过后期要用静态 IP的时候不建议这么写,否则树莓派在启动的时候为了连接有线网络会等待很长时间。如果仍要设置静态IP ,这么配置比较好:
1 | sudo vim /etc/dhcpcd.conf |
在最后添加如下内容:
1 | interface eth0 #网络名 |
它的网络配置和其他的有些不同,dhcpcd 用的不是子网掩码,而是 CIDR的斜线记法,两者之间需要稍微换算一下。比如 ip 地址192.168.191.1,子网掩码 255.255.255.0,就可以记为192.168.191.1/24
。
为什么不去编辑/etc/network/interfaces
呢?在我这个版本(2018-04-18-raspbian-stretch)中interfaces
里面说静态 IP 得去 dhcpcd.conf
里面配置:
1 | # interfaces(5) file used by ifup(8) and ifdown(8) |
raspbian- Multiple IP addresses being assigned - Raspberry Pi StackExchange
我的网线走的是联通的个人网,需要设置 Netkeeper才能上网。所以为了给树莓派联网,我只能用手机或电脑开热点。但是在同时连接网线和WiFi的时候,树莓派上不了网,只有拔掉网线之后,树莓派才能打开网页。
不仅仅是树莓派,我装在电脑上的 Ubuntu也是这样,每次上网总得拔掉网线,再连wifi,很麻烦。
上过组网课后今天再次想到这个问题,这应该和路由协议有关。查看路由表:
1 | pi@raspberrypi:~ $ route |
发现默认路由 default
(有时候显示为0.0.0.0
)转发的网关有两个,一个是eth0
(网线)的 172.31.0.1
,一个是wlan0
(WiFi)的 192.168.191.1
。由于 eth0排在 wlan0的前面,发给因特网的请求会转发给网关,然后联通把数据包吞了。看下面的ping 测试:
1 | pi@raspberrypi:~ $ ping -c 4 baidu.com |
既然 172.31.0.1
的路由转发记录无效,不妨把它删除。
1 | pi@raspberrypi:~ $ sudo route del default gw 172.31.0.1 eth0 |
移除完这条记录,地址就能够正常解析了,百度也 ping得通了。所以原因在于存在多个默认网关,并且那个首选的网关是不可到达的。重启系统或网络后路由表就会恢复,可以把它当作一个临时的做法。如果要永久生效的话也有办法的,我就没折腾了,文末的参考连接里面有。
回头一看,上一篇树莓派日记已经是快一年前的事情了……惭愧,或许叫吃灰日记比较好(
]]>参考资料:
鸟哥的Linux 私房菜 -- 架设 Router linuxroute命令的使用详解 - 一个人的旅行的博客 - CSDN博客 双网卡服务器选择默认路由- Avalon - CSDN博客 【树莓派】双网卡添加多路由静态路由持久化的问题处理- 念槐聚 - 博客园
原问题按此:
这是用矩阵来表示的另一种方法:
画一个矩阵,它的行表示第一条线段,列表示第二条线段。用n=6
的情况做个例子。
在下图中,x
表示我们不需要计算的面积,因为:(1)对角线上的值,代表两条线重叠了;(2)矩阵左下角三角形区域和右上角是对称的。
我们从 (1,6)
开始来计算面积,用 o
来表示。现在,如果左边的线段比右边的短,那么在第一行的除(1,6)
外的所有元素的面积都小于(等于)它。所以对于这些情况我们就不需要计算它们的值了(用---
划掉 )。
1 | 1 2 3 4 5 6 |
然后我们移动左边的线段,计算(2,6)
。现在,如果右边的线段短一些,所有低于(2,6)
的情况都可以排除了。
1 | 1 2 3 4 5 6 |
无论这条 o
的路径是怎样的,我们最终只要找到这条路径上最大的数值就可以了,而这需要n-1
步计算。
1 | 1 2 3 4 5 6 |
希望对你有所帮助。用这种方式来看我觉得舒服多了。
下面是我参考题解写的。真的很难想到,这题能用复杂度为 O(n)的方法解决。
1 | // Q010.java |
在安装完 Windows 10 的 1809版本之后,我的电脑两次出现了这样的情况:桌面变得非常卡,大概只有20帧的样子,在显示设置中出现了多个没有用的显示器,而我的笔记本并没有接上任何外接屏幕。
在百度之后确认了,多个显示器是因为我启用了 Hyper-v功能而多出来的,这可能是 win 10 的一个 Bug。而整个桌面的窗口变卡的原因有些不确定,因为在启用 Hyper-v之后还经历了一次 windows 更新。
我尝试过在设备管理器中禁用多余的显示器,使用系统保护回到上一个还原点都没有效果,百度谷歌到的情况相同的问题也都没有解决。真是万不得已,最终只好备份所有文件重置了。也不知道是什么原因导致的这个问题,可能和特定型号的硬件有关吧。难受!
所以,试用新版本的系统的时候一定要谨慎了,特别是在生产力机器上,一些奇怪的问题可能导致难以挽回的后果。
启用系统还原。在 控制面板-> 高级系统设置(系统属性) -> 系统保护中启用系统还原,每当进行重大操作的时候先建立一个还原点,在系统更新的时候也会自动创建还原点。出现问题的时候可以回退回去,需要的时间大约是20分钟。
关闭自动更新驱动。在 系统属性 -> 硬件 -> 设备安装设置中,“是否要自动下载适合你设备的制造商应用和自定义图标”选项,选择“否”来避免系统自动搜索、安装、更新驱动。最新版本的驱动程序不一定是最好的,特别是显卡驱动。
重置。最后的最后,如果一切都无法挽回了,Windows 10 有重置的功能,在设置 -> 更新和安全 -> 恢复中可以重置系统。这个选项会重置整台电脑,恢复出厂设置,相当于自己重装系统,但是保留了驱动程序,省得再安装一次,相比手动重装节省时间。恢复一次可能需要半个小时。
5月5日后记:现在升级到了 1903,打开沙盒模式或启用 Hyper-v的时候仍然会有多个屏幕,不过关闭功能之后重启两次就可以恢复了。
]]>参见:
win10开启Hyper-v后,多出四个非即插即用型监视器- Microsoft Community - win101709故障:开启 Hyper-v 后会出现多余的监视器 -W10之家
KMS (Key Management Service) 是微软针对 Windows系统和软件的一种激活机制。通过在网络中设置 KMS服务器给局域网中的系统批量激活。用这种方式激活的机子有激活周期,一般是6个月。这样既使系统激活的步骤简化,也使激活范围限制在一定的范围之内。
后来微软的官方 KMS 服务器被反向破解了,于是有大神 Hotbird64制作了开源的仿真 KMS 服务器 vlmcsd (原帖
所以,即日起,小站开始提供 Windows 和 Office的正版激活服务啦!只要你的系统满足以下条件就可以使用该方法激活:
一般来说,我们从
使用管理员执行以下命令:
1 | slmgr.vbs /skms kms.beanbang.cn # 设置kms服务器 |
如果之前有过修改密钥的操作,可以将密钥修改回对应版本 GVLK。Win10专业版的密钥是W269N-WFGWX-YVC9B-4J6C9-T83GX
,其他密钥可以去微软的wmic os get caption
查看。
1 | slmgr.vbs /upk # 清除密钥 |
然后再添加上面的KMS服务器激活。
管理员运行:
1 | cd /d D:\Program Files\Microsoft Office\Office16 # office安装位置 |
如果需要 GVLK 同样可以在微软的
1 | cscript ospp.vbs /inpkey:XQNVK-8JYDB-WJ9W3-YJ8YR-WFG99 #2016专业增强版 |
如果在激活过程中出现问题,可以移步下载这个方便的图形化软件
vlmcsd 的服务端可以运行在 VPS上,也可以运行在相同局域网下的电脑上,树莓派上,甚至是手机上,只要 IP地址是可以到达的就可以。但是不能安装在被激活的电脑上,因为激活系统是会自己检测到的。
2021年7月17日更新:来用 Docker 吧,省时省力。
1 | git clone https://github.com/Lazyb0x/vlmcsd-docker.git vlmcsd |
下载地址:源码+可执行文件密码2018 | GithubReleases
在binaries
目录中可以看到已经为各个操作系统和处理器编译好的可执行文件,进入到你的系统的对应CPU 架构目录中,一般都是intel
,Linux系统不确定可以用cat /proc/cpuinfo
命令查询。
1 | wget https://github.com/Wind4/vlmcsd/releases/download/svn1112/binaries.tar.gz |
进入static
目录下文件如下:
1 | ~/vlmcsd/binaries/Linux/intel/static$ ls |
vlmcsd 开头的就是服务端文件,vlmcs 用来检测运行的 KMS服务,vlmcsdmulti 有前面两个的功能。
运行:
1 | ./vlmcsd-x64-musl-static -l log.txt |
运行后会在 1688 端口上开启服务,所以记得需要在防火墙,VPS等设置上允许 1688 端口的 TCP 数据包通过。
结束:
1 | ps -A|grep vlmcsd # 得到进程号,比如11611 |
在Hotbitd64提供的压缩包内还有一个floppy
文件夹内有一个不到2Mb大小的vfd 软盘映像文件,里面是一个微型激活系统,可以在各种虚拟机上,比如Vitrualbox ,Windows 自带的 Hyper-v上运行来激活系统,非常方便(这人也太厉害了8)。只有一个需要注意的问题,就是虚拟机的联网方式。
系统在激活的时候会检测服务器的 IP 地址,如果发现 ip 地址是本机ip,就会报错,激活失败。所以应该选择诸如桥接网卡
等能够分配独立ip 且可以访问得到的联网方式。
启动虚拟机后服务器就开始工作了,只要按照前面的操作,填写虚拟机上显示的的IPv4 地址就可以激活了。
最后,给这个二级域名做了一个简单的网页~
]]>参考资料
EmulatedKMS Servers on non-Windows platforms | My Digital Life Forums 本站上线KMS服务~一句命令激活windows/office| 零散坑(详细的激活教程) 自建KMS激活服务器的两种方法电脑问题 | 花火时光 Windows版本和授权激活方式介绍(KMS MAK)_godspeed_新浪博客 OfficeToolPlus说明文档 · GitHub
MultiMC 有丰富的启动选项,能够方便地安装 Forge mod,Optifine等插件。它的 UI 和官方启动器,HMCl都有点不同。文件结构也和原版游戏不一样(不过大体的结构还是相似的),这样更容易在启动器中管理mod,截图等资源。第一次运行时 MultiMC会下载对应版本的游戏,不想等的话可以去已有的游戏的.minecraft
文件夹下把assets
和libraries
文件夹拷贝到MultiMC 的目录下来。
不过呢,不像其他的第三方启动器,MultiMC必须要登录游戏账号后才能启用离线模式,这么做的目的应该是为了支持正版。其实无正版账号体验这个启动器也是可以的:P。
登录一次后,我的accounts.json
文件多了一些参数。只要稍加改动,确保包含这些参数项就可以了,比如这样:
1 | { |
打开启动器就能用这个空的账号启动离线模式游戏了。
]]>另见:
由于IPv4地址的缺乏,运营商们越来越少给我们提供公网IP,即使能够分配到公网IP,也是动态ip,在网络断开重新链接后IP地址就会改变。而我们使用交换机、路由器,使得我们处于“专用网”中,用ipconfig
(Linux用ifconfig
)查询本机IP会得到下面的几类局域网地址:
1 | A类:10.0.0.0 到 10.255.255.255 (10.0.0.0/8) |
校园网经常分配到上面的A类地址,运营商的宽带可能分配到172开头的地址;而家用路由器分配的是192.168开头的地址。然后我们通过网络地址转换协议NAT 转换IP地址进行上网。
所以,拥有一个公网IP就相当于处在各种网络的最外层,可以直接和其他主机进行通信。而处在NAT路由器内的主机的通信必须由专用网内的主机发起。因为如果是外部主机发起的通信的话,路由器不知道要将数据包转发给专用网中的哪一个主机。于是就有了反向代理的工作方式,把一台处于内网中的主机和一台拥有公网IP的主机的端口绑定,然后由公网主机将来自外部的请求转交给内网主机。这样,就实现了内网主机作为服务器的功能。
接下来推荐几个好用的内网穿透/反向代理软件:
这个其实不算是软件吧,算是路由器应用…
大多数的家庭宽带还是会分配公网IP地址的(除了一些二级运营商比如移动),只不过是临时的动态IP,断开重新拨号后IP地址就会改变。大部分路由器会提供动态域名解析的功能,虽然IP会经常变化,只要有一个域名指向这个IP地址就可以了。
打开路由器的设置界面,通常有一个叫DDNS或类似域名动态解析的选项。在动态域名服务的提供商那里注册账号,选一个二级域名,然后在路由器里面填上用户名和密码。设置完ping
一下你的二级域名比如xxx.wicp.net,如果返回的是你的路由器的IP地址,就说明设置成功了。
现在外部请求已经能够到达路由器了,接下来在路由器里设置端口映射(有的叫虚拟服务器),将路由器的特定端口和局域网内的主机绑定,这样特定的服务就能够和局域网内特定的主机通信了。
通过动态域名和端口映射的方法局限性很大。在家里,我们知道路由器的密码,有更改路由器设置的权限;在学校的校园网,宿舍的个人网呢?如果被分配在多层子网中,你需要一层一层地联络网络管理员给你开放端口,还不能保证他们一定会给你开……遇到这种情况我们还是另辟蹊径吧。
新版的Win10已经自带OpenSSH
了,在设置
->应用
-> 应用和功能
->管理可选功能
中可以启用。
OpenSSH提供了三种端口转发功能:正向代理(local),反向代理(remote),以及动态代理(dynamic)。在这里,我们用到的是它的反向代理功能。
只需要一句命令即可开启端口转发:
1 | ssh -o ServerAliveInterval=60 -R 8888:127.0.0.1:3389 beanbang@121.29.54.117 |
执行命令,输入密码,进入终端就已经建立了从本机的3389端口到服务器的8888端口的链接。现在,你就可以在远程桌面中通过地址121.29.54.117:8888
来连接你的主机了。其中-o ServerAliveInterval=60
项是为了防止长时间无操作被关闭连接,每隔60秒向服务器发送信号。
如果开启失败可能是服务器端没有启用:
1 | sudo vim /etc/ssh/sshd_config #添加一行 GatewayPorts yes |
以及记得在防火墙中允许特定端口的通信。
frp(fast reverse proxy)是一个功能强大的开源反向代理软件。在GitHub上面有详细的
可以很轻松地配置简单的端口转发。只要
解压:
1 | tar -zxvf frp_0.21.0_linux_amd64.tar.gz |
编辑frps.ini
:
1 | # frps.ini |
启动 frps:
1 | ./frps -c ./frps.ini |
编辑frpc.ini
:
1 | # frpc.ini |
启动 frpc:
1 | ./frpc -c ./frpc.ini |
服务端和客户端都启动后,就可以通过121.29.54.117:8888
来连接远程主机了,还可以访问http://121.29.54.117:7500 来打开frp的 Dashboard界面,用网页的形式查看各种相关信息。
开发者细心地编译了多种操作系统架构的程序使得我们可以方便地下载和使用,比如树莓派就可以使用其arm-64 版本来架设小型服务器。
路由侠适合那些没有自己的公网服务器,只需要临时连接,或者不想要自己折腾的人。类似的这种软件还有花生壳、net123、蛤蟆吃(hamachi)等等。这些算是提供服务类型的软件了。只要安装上客户端,进行简单的配置就可以使用它们的服务了。
不过没有免费的午餐,这些软件往往提供有限制的免费套餐,然后提供附加的服务进行收费。比如路由侠就限制1个月1G的流量,1Mb的带宽。提供给你一个二级域名和端口号,用这个域名和端口来访问服务。
想当年和小伙伴玩Minecraft开服用的就是路由侠。在电脑上架上CraftBukkit服务器,然后开放地址开一起来开荒。后来买了vps,服务器就搬到vps上面去了。
其实,端口映射我用的最多的还是远程桌面。去机房上机的时候,打开 mstsc,远程桌面连上自己的电脑。这样避免了机房电脑渣机运行缓慢,而且实验结束不用拿U盘拷走实验结果,文件也可以直接传回电脑去。特别的方便有木有?
]]>参考资料:
Whatis a Reverse Proxy Server | Reverse vs. Forward Proxy | CDN Guide |Incapsula [云路由器]如何映射服务器到外网 - TP-LINK 服务支持 - 使用 ssh-R 穿透局域网访问内部服务器主机,反向代理 无人值守化 - phpdragon -博客园
- 内网穿透工具frpWindows客户端frpc安装及使用教程 | 直呼过瘾
frp-中文文档-GitHub
国庆旅游归来早,忙趁假期瞎折腾。
Fiddler
软件抓取https包的时候就需要先向浏览器添加自己的CA
证书,因为没有证书信任的话就无法获得加密的内容。有许多的免费SSL证书提供商。我用的是腾讯云的DV SSL
证书。在TrustAsia
提供的免费证书,为期1年。(顺便瞟了一眼OV
和EV
的企业付费证书,价格基本5k以上…)
申请需要验证你的主机和域名,会要求在指定位置放置验证文件或者给域名添加一条解析记录。如果你的域名也是从腾讯云买的,它会给你自动添加。验证的速度很快,大约10分钟就能审核通过。
如果你等了1小时还没通过就别等了,赶紧检查一下自己填的信息有没有错吧,不要像我一样把域名打错了然后傻等了一天TT。
审核通过后会颁发的证书可以下载到本地。打开后里面有为各种http服务器使用的证书和密钥文件。我用的是Apache
,就把Apache文件夹下的3个文件上传到服务器上。
编辑 etc/httpd.conf文件,找到#LoadModule ssl_module modules/mod_ssl.so
和#Include conf/extra/httpd-ssl.conf
,去掉前面的#号注释,启用SSL模组。
编辑/lampp/etc/extra/httpd-ssl.conf
文件添加主机和证书:
1 | <VirtualHost 0.0.0.0:443> |
完成后需要重启Apache服务器。
https协议使用的不是80端口而是443端口。如果有防火墙记得允许443端口的TCP
数据包通过。
wordpress目录下的.htaccess
文件:
1 | <IfModule mod_rewrite.c> |
这样使用http访问网页会被301
永久重定向至https页面。
编辑wp-config.php
:
在 “if ( !defined('ABSPATH') )”前面,即"请不要再继续编辑”的提示前面添加。这样登陆博客时会强制使用https以保障安全。
1 | $_SERVER['HTTPS'] = 'ON'; //网页下的链接全部由'http://'转换为'https://' |
在有的教程里面还说需要像更换域名那样把全站的数据库中所有的“http”链接全部转换成“https”。其实不推荐也不需要这么做。上面的$_SERVER['HTTPS'] = 'ON';
设置过以后,wordpress是会自动识别转换的。
现在,打开网站
]]>参考资料
证书安装指引- SSL 证书 - 文档平台 - 腾讯云 - wordpresshttp强制跳转https,我是这么做的,真正的零故障|永云博客
- WordPress启用https访问实战教程| 王商博客
Wordpress使用SSL证书开启HTTPS最简单的办法- 四座 十大免费SSL证书:网站免费添加HTTPS加密- 阳光岛主 - CSDN博客