2020 年注定是会在历史上留下不平凡的一年,年初的疫情到年底问题依然还在,而且在不确定疫苗的情况,在加上英国的病毒变异情况,不清楚到什么时候才是结束。再回头看今年的大事小事,从年初的李文亮事件,到年末的蛋壳,以及阿里被禁止上A股,有些事情发生地太突然,来不及思考,但只有思考,不仅是在事前的还是事后的思考都有其价值。
李文亮因言获罪,而密尔早在两百年前就曾经说过,即便是荒谬的言论也不得限制,对该言论的讨论过程能进一步证明相反观点的价值。而李文亮事件就是典型的「被压制的言论包含部分的真理」的情况,而正是因为言论的被压制,终究封城是阻止不了病毒的扩散的。而在看美国宪法,以及关于[[宪法第一修正案]]关于言论自由边界的书,能看到为了维护不进行事先审查言论的权利,经过了多少的争论。
而换一个角度来看,来看看一位金融从业者在[[非对称风险 笔记]]中的观点,「持续暴露在小概率风险下,即使爆仓风险的概率小到万分之一,那么在持续、重复的过程中爆仓的概率会越来越大」。相同的观点套用到现实,能禁止一个人说话,能阻止一群人发表观点,但只要危机没有被解决,终究会造成无法挽回的损失。
金斯伯格大法官离世后,先是让我认识了这位饱受尊敬的高龄大法官,然后将我带回了最高法院,在最高法院发展的几百年历史中,让我认识到了自由派的[[浪漫主义]]者 [[霍姆斯大法官]],也让我了解了未曾当过大法官却深刻影响了最高院的 [[勒尼德·汉德]] 法官,以及到更近代的第一人女性大法官[[奥康纳大法官]],当代的自由派[[苏特大法官]],以及许许多多以前可能听过但不曾有印象的名字,这些躲在背后的大法官们在一件件的案件,以及对这些案件的判决中,形象更加丰满起来。
如果说上半年收获最大的作者是 [[塔勒布]] 的话,那么下半年收获最大的作者便是 [[弗朗西斯·福山]],早在去年就看过其[[The Origins of Political Order]],但是碍于当时的认知和环境,实际上并没有完全读懂,今年先读了其之前的著作 [[历史的终结与最后的人]] ,然后再看其更进一步的论述 [[The Origins of Political Order]],才理清了其脉络。
之前两篇文章简单的介绍了 [[Logback]] 是什么,以及基本的使用,这一篇文章着重说一下 Logback 中最重要的 logback.xml
配置文件的编写。
![[Pasted image 20201210145047.png]]
配置文件格式:
<configuration>
<appender> //输出到控制台的信息配置
//....
</appender>
<appender> //输出到info文件的配置
//...
</appender>
<appender> //输出到error文件的配置
</appender>
<logger> //特殊处理日志定义
//..
</logger>
<root level="debug"> //总日志开关
//...
</root>
</configuration>
根节点 <configuration>
包含三个属性:
scan
: 为 true 时,如果配置改表会重新加载,默认是 truescanPeriod
: 检测配置文件修改的时间间隔,默认单位毫秒,默认时间间隔 1 分钟debug
: 为 true 时,打印 logback 内置日志信息,实时查看 logback 运行状态,默认 false例如:
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!--其他配置省略-->
</configuration>
子节点 <contextName>
,设置上下文名称。
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<contextName>myAppName</contextName>
<!--其他配置省略-->
</configuration>
用来定义变量值,两个属性 name
和 value
,通过 <property>
定义的值会被插入到 Logger 上下文中,可以使用 ${}
来使用。
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<property name="APP_Name" value="myAppName" />
<contextName>${APP_Name}</contextName>
<!--其他配置省略-->
</configuration>
获取时间戳字符串
java.text.SimpleDateFormat
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>
<contextName>${bySecond}</contextName>
<!-- 其他配置省略-->
</configuration>
appender 是负责写日志的组件,两个属性:
name
,指定 appender 名字class
,指定 appender 全限定名logback 实现了一些内置的 appender,可以将日志输出到控制台,文件等等地方。
<appender name="file-appender" class="ch.qos.logback.core.rolling.RollingFileAppender">
....
</appender>
内置的 appender:
将日志输出到控制台(Console),两个子节点:
<encoder>
: 对日志输出格式化<target>
: 字符串 System.out
或 System.err
例如:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
将日志输出到文件(FileAppender),子节点:
<file>
: 被写入的文件名<append>
: 如果是 true,日志被追加到文件结尾,false 则清空现存文件,默认为 true<encoder>
: 对记录事件进行格式化<prudent>
: 如果是 true,日志会被安全地写入文件(其他 FileAppender 也在向此文件做写入操作)效率低,默认是 false<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>testFile.log</file>
<append>true</append>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="FILE" />
</root>
</configuration>
滚动日志,记录日志到文件,当符合某条件时,将日志记录到其他文件。子节点:
<file>
: 文件名<append>
: true 时,追加到文件结尾,false 时 清空现存文件,默认为 true<rollingPolicy>
: 滚动策略,定义 Rolling 的行为策略定义 class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"
根据时间来记录日志。
<fileNamePattern>
: 文件名的命名方案,%d
可以包含一个符合 java.text.SimpleDateFormat
格式的时间,比如 %d{yyyy-MM-dd}
。如果直接使用 %d
默认是 yyyy-MM-dd
<file>
: 当前日志总是记录到 file 指定的文件,非必需<maxHistory>
: 可选,保留归档文件的最大数量比如配置每天生成一个日志文件,保存 30 天日志。
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="FILE" />
</root>
</configuration>
定义 class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"
,超过大小滚动。
<maxFileSize>
: 默认大小是 10MB<prudent>
: 当为 true 时,不支持 FixedWindowRollingPolicy<minIndex>
: 窗口索引最小值<maxIndex>
: 窗口索引最大值<fileNamePattern>
: 必须包含 %i
,假设最小和最大为 1 和 2,命名模式是 mylog-%i.log
,会产生归档文件 mylog-1.log
和 mylog-2.log
。可以指定压缩选项。按照固定窗口模式生成日志文件,文件大于 5 MB 时,生成新的日志文件,窗口大小是 1 到 3,保存了 3 个归档文件后,覆盖最早的日志。
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>test.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>tests.%i.log.zip</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>3</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>5MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="FILE" />
</root>
</configuration>
异步日志
<appender name ="ASYNC_UTIL_LOG" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>512</queueSize>
<neverBlock>true</neverBlock>
<appender-ref ref="UTIL_LOG"/>
</appender>
<logger name="com.test.util" level="DEBUG" additivity="false" >
<appender-ref ref="ASYNC_UTIL_LOG"/>
</logger>
还有其他的 Appender 具体可以参考官方文档:
用来设置某一个包或具体的某一个类的日志打印级别,执行 appender。
Logger 是 Logback 另一个重要的组成部分,开发者可以将日志信息用特定的等级输出。Logback 定义了 5 个等级的日志级别:TRACE, DEBUG, INFO, WARN, ERROR。
每一个等级都对应着一个特定的方法, trace()
, debug()
, info()
, warn()
, error()
。
<!-- show parameters for hibernate sql 专为 Hibernate 定制 -->
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" />
<logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG" />
<logger name="org.hibernate.SQL" level="DEBUG" />
<logger name="org.hibernate.engine.QueryParameters" level="DEBUG" />
<logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG" />
<!--myibatis log configure-->
<logger name="com.apache.ibatis" level="TRACE"/>
<logger name="java.sql.Connection" level="DEBUG"/>
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
根据不同的环境,配置不同的日志输出。
日志的名称不是 logback.xml
,如果要使用 Spring 扩展,要以 logback-spring.xml
命名。
<springProfile name="test,dev">
<logger name="com.dudu.controller" level="info" />
</springProfile>
<!-- 生产环境. -->
<springProfile name="prod">
<logger name="com.dudu.controller" level="ERROR" />
</springProfile>
Logback 在之前的文章中说过,是一个开源日志组件。
[[Logback]] 推荐和 SLF4J 一起使用。
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<logback.version>1.1.7</logback.version>
<slf4j.version>1.7.21</slf4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
</dependencies>
logback 简单配置:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder
by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
具体的注释写在配置代码中。
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 Logback 的配置中使用相对路径-->
<property name="LOG_HOME" value="/home" />
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 配置每天滚动生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
在 Java 代码中
public class App {
private final static Logger logger = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
logger.info("logback 成功了");
logger.error("logback 成功了");
logger.debug("logback 成功了");
}
}
[[Logback]] 是 log4j 的创始人开发设计的另一个开源日志组件,期望成为 log4j
项目的继任者,相较于 log4j,Logback 有一些优势。
Logback 划分成三个模块:
Maven 依赖:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
应用在启动时按如下的顺序寻找配置文件:
classpath
中 logback-test.xml
文件logback.groovy
logback.xml
META-INF/services/ch.qos.logback.classic.spi.Configurator
中的 logback 配置实现类BasicConfigurator
来配置,并将日志输出到 console。配置文件 logback-test.xml
或 logback.xml
都不存在, logback 会默认调用 BasicConfigurator,创建最小化配置。最小化配置由一个关联到根 logger 的 ConsoleAppender 组成。输出用模式为 %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
的 PatternLayoutEncoder 进行格式化。root logger 默认级别是 DEBUG。
配置文件格式:
<configuration>
<appender> //输出到控制台的信息配置
//....
</appender>
<appender> //输出到info文件的配置
//...
</appender>
<appender> //输出到error文件的配置
</appender>
<logger> //特殊处理日志定义
//..
</logger>
<root level="debug"> //总日志开关
//...
</root>
</configuration>
configuration
开头,后面有 0 个或多个 <appender>
元素,0 个或多个 <logger>
,最多有一个 <root>
元素。[[2020-12-10-logback-xml-config]] [[2020-12-10-logback-usage]]
很早以前就用过 [[GitBook]] 来将 Markdown 生成网页1,但是后来 GitBook 命令行工具不再持续的更新,开发团队转向了维护商业版本的 GitBook 之后就用的少了。
但随后就发现了使用 [[Rust]] 编写的 [[mdBook]],体验和 GitBook 一致,基本上可以完美的代替 GitBook。有趣的是官方的介绍也是对标 GitBook 的:
Create book from markdown files. Like Gitbook but implemented in Rust.
官方网站:
因为 mdBook 依赖与 Rust 所以需要安装 Rust 环境。
然后执行如下命令即可:
cargo install mdbook
初始化:
mdbook init
构建:
mdbook build
监控更改:
mdbook watch
启动一个本地服务:
mdbook serve
清理:
mdbook clean
要生成页内目录可以使用 toc
cargo install mdbook-toc
Python 的文档工具 [[mkdocs]] :
tag: #GitBook #wiki #Book #Markdown #note #writing #个人知识管理
IdeaVim 是 IntelliJ IDEA 编辑器下一款模拟 Vim 模式及快捷键的开源插件。鉴于大部分的时间都在 IntelliJ IDEA 下工作,所以总结一下在 IDEA 下使用 Vim 的一些快捷方式。
阅读完本文之后,你可以
~/.ideavimrc
来复用 Vim 的工作方式,即使将工作环境切换到 Terminal 下也可以沿用,并且充分利用 Idea 提供的 Action安装了插件之后,IntelliJ IDEA 在启动时会自动加载 ~/.ideavimrc
这个配置文件,改动该文件后可以使用如下方式手动重新加载:
:source ~/.ideavimrc
或者直接在编辑器中 :e ~/.ideavimrc
然后在右上角的地方会出现重新 Load 的图标,点击即可让 Idea 重新加载。
我映射了 leader
+ o
打开最近项目列表,用来快速的打开项目。
首先创建一个 keymap
(用过 Vim 的都知道,可以自定义一个 modifier key,通过这个修饰键可以形成一套新的快捷键组合):
let mapleader = ","
nnoremap <Leader>o :<C-u>action RecentProjectListGroup<CR>
然后使用配置的 leader 快捷键 , + o 就可以快速弹出最近打开项目,使用模糊搜索就可以快速打开新的项目。
在不知道这个方法以前,我都是在 [[Alfred]] 中配置了一个 Workflow 来打开新的项目的。在发现上面这个方法后,发现在 IDE 内通过这个方式打开别的项目,远比 Alfred 中要快。熟悉一段时间之后,甚至可以不用看搜索结果,直接使用逗号加 o
然后快速输入项目的模糊查询的关键字,然后回车。
IDEA 自身就提供了非常多的快捷来在代码之间跳转,比如:
在我的工作流里面,为了方便记忆,统一使用 g
作为简记符(表示 go)。比如 gd
表示 go to definition
。
在 .ideavimrc
文件中,定义 map xxx :action yyy
表示自定义一个 keymap
调用 IntelliJ 的 action。
nnoremap gd :action GotoDeclaration
这里的 GotoDeclaration
是 IntelliJ 的一个 action,一个 IntelliJ 的 Action 对应着 IntelliJ 的一个功能。上面的定义就表示在 Normal 模式下定义新的 keymap gd
,表示的是在 Normal 模式下,按下 gd
就会执行 IDEA 的 action GotoDeclaration
。
IntelliJ 提供了一系列的 Action 可以使用。
比如我定义了如下的跳转:
" go to somewhere (g in normal mode for goto somewhere)
nnoremap ga :<C-u>action GotoAction<CR>
nnoremap gb :<C-u>action JumpToLastChange<CR>
nnoremap gc :<C-u>action GotoClass<CR>
nnoremap gd :<C-u>action GotoDeclaration<CR>
nnoremap gs :<C-u>action GotoSuperMethod<CR>
nnoremap gi :<C-u>action GotoImplementation<CR>
nnoremap gf :<C-u>action GotoFile<CR>
nnoremap gm :<C-u>action GotoSymbol<CR>
nnoremap gu :<C-u>action ShowUsages<CR>
nnoremap gt :<C-u>action GotoTest<CR>
nnoremap gp :<C-u>action FindInPath<CR>
nnoremap gr :<C-u>action RecentFiles<CR>
nnoremap gh :<C-u>action Back<CR>
nnoremap gl :<C-u>action Forward<CR>
我使用 t
加上一个字母作为 Toggle 动作的开始方便记忆(t 就表示 toggle)。
比如下面的第一条的 ta
,表示的就是 Toggle Annotate
,在 IDEA 主编辑区域经常看这行代码是谁提交的,那么会使用右击序号空白处,然后选择 Annotate,这个操作可以简化成直接在 Vim 模式的阅读模式下按下 ta
。
一些其他的定义可以参考:
nnoremap ta :action Annotate<cr>
nnoremap tb :action ToggleLineBreakpoint<cr>
nnoremap tm :action ToggleBookmark<cr>
nnoremap tp :action ActivateProjectToolWindow<CR>
在安装 IdeaVim 之后,可以在 normal
模式下使用如下命令查看 IDE 支持的 action:
:actionlist [pattern]
如果要搜索对应的 action 可以直接加上模糊词来搜索,比如 :actionlist declaration
来搜索相关的内容。
执行 action
:action {name}
比如执行 :action Debug
在 ~/.ideavimrc
文件中可以给 Action 其名字,比如
command! Reformat action ReformatCode
在 action 后面的 ReformatCode
是一个合法的 ActionName,通过上面的语句就重新起了一个新的名字叫做 Reformat
。这样就可以通过 :Reformat
来调用。
使用空格加 hl 来切换标签页
nnoremap <space>h gT
nnoremap <space>l gt
Vim 的命令 :e
, :sp
, :vsp
是支持的。
也可以直接使用快捷键 <C-W>s
, <C-W>v
, <C-w>c
来实现对编辑器的分屏。
<C-W>w
可以快速在不同的 Panel 之间切换。
我想要达到的效果是和我在终端中使用 Tmux+Vim 类似,使用 Ctrl+h/j/k/l 来进行分屏。
首先要到设置中把可能的快捷键冲突解决,比如 Ctrl+H 原来被我映射成 Find in Path,现在我使用 gp
(go to path) 作为快捷键触发。
然后将 ,
作为 Leader 快捷键。
" screen management
" Vertical split screen
nnoremap <Leader>\ <C-W>v
nnoremap <Leader>- <C-W>s
nnoremap <C-h> <C-W>h
nnoremap <C-l> <C-W>l
nnoremap <C-j> <C-W>j
nnoremap <C-k> <C-W>k
这样就实现了 IDEA 内部的快速分屏。
记得去设置中将 Ctrl + h/j/k/l 的默认快捷键移除,否则可能会有冲突。
更多的配置可以参考我的 dotfile 配置。
启用 surround 插件来模拟 surround
set surround
在了解了简单的 Vim,并知道 Vim 的能力之后就可以做到 Vim Everywhere,比如
这样在日常、工作的大部分时间中都可以完美的使用 Vim。
[[idea-plugins]]
category: [[IntelliJ IDEA]] [[编程工具]] [[学习笔记]]
远在移动互联网还没有那么发达的今天,Google 曾经收购过一家图片管理与分享的网站叫做 Picasa,Picasa 同时提供了一个跨平台的照片管理工具 Picasa Desktop,用这个工具不仅可以非常方便的管理本地的图片文件,也可以非常方便的分享到 Picasa Web 上,然而随着移动互联网的到来,以及 Google 的转型,Picasa 的服务在 Google 变得没有那么重要,随即在 2016 年停止了服务,我的图片管理也被迫迁移到了 Google Photos。然而一切都开始变得不方便,Google Photos 内自动备份的照片,相册开始无法管理,并且 Google 停止了桌面版的开发,同样使得在桌面上管理图片变得困难,这些年来尝试了 Lightroom,TagSpaces 等等工具,都没有找到特别舒心的。
我的需求其实也并不复杂:
我带着这一些需求一直在寻求一款合适的工具,直到有一天在 Twitter 上有人有相同的需求时,一则评论吸引了我的注意,打开 Eagle 官网 的同时我就被吸引了,一张软件界面的截图直接了当的说明了这个软件的功能,并且几个关键字,「收集」,「整理」,「搜索』,似乎让我一下子想到了 Picasa。更加了解之后才发现,原来 Eagle 不止图片管理这么简单,音频,字体,GIF 管理等等让我迫不及待地下载进行尝试。 安装后 Eagle 会引导安装浏览器扩展,这个扩展可以用来收藏图片,包括拖拽,Alt+ 右击,右键上下文菜单,甚至可以直接批量一键导入网页图片,或者将整个网页保存为图片,或者剪切部分网页。这部分内容扩展的页面 已经给出了非常详细的说明,这里就略过了。
我个人的习惯是每次旅行或外出回来时,会新建一个以日期开头加上地点的文件夹,并导入当前拍摄的照片,然后保证无误后格式化 SD 卡。然后使用 Lightroom 对这个新导入的文件进行一轮的筛选,使用 1~5 快捷键给照片打分,然后对打分的照片进行简单的快速处理。如果有 4 或 5 分的照片,会更进一步微调。然后对有打分的照片进行导出分享。
在遇到 Eagle 之前,我接触的大部分图片都是在浏览器中,所以在浏览器中安装了一个叫做 Imagus 的工具,当鼠标悬浮在图片上后会放大缩略图,并且使用 Ctrl+s 就可以直接下载该图片。但这样的问题便是所有的图片都在 Downloads 文件夹下和其他格式的文件混在一起。等到真正需要使用的时候,搜索变得非常不方便。
而有了 Eagle 之后这个工作流程更加简单了,鼠标拖拽图片即可。
虽然我不是学设计的,但是大学的时候曾经做过一段时间和字体相关的事情,所以也对字体有一些要求,所以如果阅读器支持更换字体,肯定会换成自己喜欢的方正北魏楷书或方正宋三。曾经下载过很多的字体进行对比,如果当时有 Eagle 就方便很多,直接将字体导入软件就能预览文本,再不用现在文本编辑器中编辑一段对话,然后再挨个更换字体了。
不管是在 Picasa,还是在 Lightroom 个人的使用习惯是管理工具管理的内容,在磁盘上实际的位置还是原来的位置,不管是编辑了图片或者是移动了图片都是在本地文件中的修改,然而虽然 Eagle 是一个离线可用的图片管理工具,但一旦图片文件夹导入到 Eagle ,再去磁盘中查看时文件的结构就变成了 Eagle 处理过的扁平的样子,并且会冗余两份图片,一份在原来的位置,一份在 Eagle 中,即使之前存在目录结构也被打散了。一旦文件导入到 Eagle 再想要使用文件管理器来管理这些图片变的不可能。当然这个只是个人的习惯问题。
要再想恢复到原来的文件结构可以右击文件夹,选择导出。
另外一点令我顾虑的就是,一旦使用 Eagle 来管理我的大量图片,这就使我锁定在了 Eagle,让我有一点介意的就是当我试用期过了之后,我竟然无法导出我的所有图片,虽然这些图片都在本地,但是因为已经被 Eagle 重新命名过所以几乎无法直接使用,只能让我用虚拟机再导出一遍。这也加重了我的顾虑。
再以缺点就是在我使用的过程中,有两次无法退出,即使使用强制退出,也无法退出,甚至让我在重启电脑的时候都卡在去停止 Eagle 的地方,我从没在 Mac 上遇到软件强制退出都不行的情况。
综上所有的优点,缺点,我仔细思考了一下,Eagle 确实在某些方面做的不错,对于设计师,或者对于刚刚开始整理素材的人来说是一个非常不错的选择,而一旦已经有了自己的素材库的人来说,不可避免的需要将素材导入到 Eagle,而这是一个不算愉快的选择,并且一旦想要放弃 Eagle,那么将数据导出便又是一段不愉快的旅程。所以最后再试用期满后,我迫不得已按了一台虚拟机导出整个库后删除了 Eagle。
在前端时间网上泄漏出来一个巨大包括了近 8 亿 QQ 账号的绑定电话号码数据库,于是想着导入到本地的 MySQL 看看,提升一下查询的速度,因为这个巨大的绑定关系,即使用 grep 查询也需要花费非常多的时间。
于是我新建了表
CREATE TABLE `qq_bind` (
`phone` bigint NOT NULL,
`qq` bigint DEFAULT NULL,
KEY `ix_qq_bind` (`phone`,`qq`),
KEY `ix_qq` (`qq`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
于是我想当然的想利用 mysqldump 命令来导入文件,但是发现导入的速度非常的慢,并且看到磁盘占用的速度飞速上升。以这样的导入速度,我大致计算了一下得一个多星期才能导入完成。
后来我就想办法怎么才可以提升这个导入的速度,发现如果表上有索引,或者 Primary Key 会大大的影响导入速度,所以:
但是我移除了所有的索引之后,再执行 mysqldump 速度虽然有提升,但依然非常慢。所以不得不找其他办法。
再搜寻了一番之后发现 MySQL 可以使用 LOAD DATA INFILE
这样的语句来批量导入数据。
登录 MySQL cli 后可以执行:
LOAD DATA INFILE '/Users/einverne/Downloads/demo.csv'
IGNORE INTO TABLE demo_table
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
或者使用命令行:
mycli -h host -u root -p -D database_name -e "LOAD DATA INFILE '/path/to/file.csv' INTO TABLE demo_table FIELDS TERMINATED BY ','"
说明:
\t
插入语句:
LOAD DATA INFILE
比单纯的 INSERT 要快。
需要注意的是,当时用 LOCAL
或者 LOAD DATA
时,文件的拷贝会保存到服务器的 temp 目录,这个目录不是由 tmpdir or slave_load_tmpdir
配置决定的,而是操作系统的临时目录 (temporary 目录)。
所以如果 CSV 文件比较大,操作系统临时目录无法放下,可以将文件分割成多份,分批次进行操作。
$ split -l (numbersofrowsinfile / ((filesize/tmpsize) + 1)) /path/to/your/<file>.csv
这是一篇耽搁了很久,一直躺在我的 Obsidian 笔记中的一篇文章,一直就想好好介绍一下 Hammerspoon,但是因为过去虽然也在用 macOS,但是使用最多的还是 Ubuntu,Hammerspoon 只能在 macOS 上使用,就没有那么大的兴致再花时间学习它的使用。但最近更新了一下系统,发现 Hammerspoon 出了一点问题,没有了 Hammerspoon 之后我才发现很多不适应的地方,那就在花一点时间再梳理一下我的配置。
Hammerspoon 是一个 macOS 上开源的自动化工具,什么叫做自动化工具呢?通过 Hammerspoon ,可以使用一些脚本来实现原来只能通过界面操作,或快捷键才能达到的效果,并且实现系统自动化。最简单的例子,比如当我连上家里的 WiFi 的时候,就自动将音量调成 3 档;再比如当我切换窗口的时候,自动切换输入法,比如在 IDEA IntelliJ 中自动使用英文输入法,当我打开 Obsidian 则自动切换成中文输入法。
Hammerspoon 使用 Lua 脚本语言与操作系统通信。通过编写 Lua 脚本实现与 macOS API 的交互,Hammerspoon 提供的 API,包括应用的、窗口的、鼠标指针、文件系统、声音设备、电池、屏幕、键盘/鼠标事件、粘贴板、地理位置服务、WiFi 等等。Hammerspoon 是操作系统和 Lua 执行引擎的桥梁,通过 Hammerspoon 可以让 macOS 实现非常强大的自动化。
官网: https://www.hammerspoon.org/
Hammerspoon 实际上是将 macOS 的系统接口实现了一层转发,让用户可以通过简单的 Lua 脚本进行配置,从而实现一定的 UI 自动化,一旦能够直接从 API 层面对接操作系统,那么 Hammerspoon 能够做的事情就非常多了:
通过 Homebrew 安装:
brew install --cask hammerspoon
Hammerspoon 的默认配置在 ~/.hammerspoon/init.lua
,我个人通过将配置文件放在 dotfiles 中软链接到目的位置来同步配置。
做一个最简单的例子,在 init.lua
文件中写入:
hs.alert.show("Config reload!")
然后重新加载 Hammerspoon 配置,就会看到在屏幕中央出现 “Config reload!” 的弹出提示。
虽然 [[Mac 上的窗口管理工具]] 有很多,免费的,收费的,Moom, Rectangle 等等,但是自由度都没有 Hammerspoon 多。
下面是我使用的一些窗口管理快捷键。
按下 Option+r 进入窗口的管理模式,在该模式下按下快捷键可以实现非常多的操作:
这一套窗口管理方法来自 ashfinal/awesome-hammerspoon
不过我个人最常使用的快捷键还是 Hyper + h/l/j/k 可以将当前的窗口以左/右/下/上方式进行分屏。这里需要结合 [[Mac 应用 Karabiner Elements 键盘自定义工具]]
[[Mac 下的自定义快捷键]]
使用 Hammerspoon 实现 Hyper + h/l/j/k 管理窗口的相关配置:
hyper = {"ctrl", "alt", "cmd", "shift"}
function move_window(direction)
return function()
local win = hs.window.focusedWindow()
local app = win:application()
local app_name = app:name()
local f = win:frame()
local screen = win:screen()
local max = screen:frame()
if direction == "left" then
f.x = max.x + 6
f.w = (max.w / 2) - 9
elseif direction == "right" then
f.x = (max.x + (max.w / 2)) + 3
f.w = (max.w / 2) - 9
elseif direction == "up" then
f.x = max.x + 6
f.w = max.w - 12
elseif direction == "down" then
f.x = (max.x + (max.w / 8)) + 6
f.w = (max.w * 3 / 4) - 12
end
f.y = max.y + 6
f.h = max.h - 12
win:setFrame(f, 0.0)
end
end
hs.hotkey.bind(hyper, "Left", move_window("left"))
hs.hotkey.bind(hyper, "Right", move_window("right"))
hs.hotkey.bind(hyper, "Up", move_window("up"))
hs.hotkey.bind(hyper, "Down", move_window("down"))
hs.hotkey.bind(hyper, "H", move_window("left"))
hs.hotkey.bind(hyper, "L", move_window("right"))
hs.hotkey.bind(hyper, "K", move_window("up"))
hs.hotkey.bind(hyper, "J", move_window("down"))
当连接的 WiFi 发生变化的时候触发一个监听事件,更加详细的配置可以看我的 dotfiles 。
function ssidChangedCallback()
newSSID = hs.wifi.currentNetwork()
local devices = hs.usb.attachedDevices()
if newSSID == homeSSID and lastSSID ~= homeSSID then
-- We just joined our home WiFi network
hs.alert.show("Welcome home!")
hs.audiodevice.defaultOutputDevice():setVolume(25)
-- result = hs.network.configuration:setLocation("Home")
-- hs.alert.show(result)
elseif newSSID ~= homeSSID and lastSSID == homeSSID then
-- We just departed our home WiFi network
hs.alert.show("left home!")
hs.audiodevice.defaultOutputDevice():setVolume(0)
-- result = hs.network.configuration:setLocation("Automatic")
-- hs.alert.show(result)
end
if newSSID == workSSID then
hs.alert.show("work karabiner setup")
selectKarabinerProfile("goku")
else
hs.alert.show("built-in karabiner setup")
selectKarabinerProfile("goku")
end
lastSSID = newSSID
end
wifiWatcher = hs.wifi.watcher.new(ssidChangedCallback)
wifiWatcher:start()
我抢了同事一个显示器使用,所以外接了三个显示器,在每一个显示器中都有默认的布局。我一般左边竖置的显示器常驻一个 Terminal,中间横置的一个显示器为主要工作的区域,一般放 IntelliJ IDEA,DataGrip,SmartGit 等等其他工具,右侧竖置的显示器上面为即时通信窗口,下面是浏览器。
使用 Hammerspoon 可以很快速的恢复所有窗口的布局,不过我自己用的并不多。
比如在特定应用中自动切换成 Rime 输入法 或者切换成 ABC 英文。比如在 IntelliJ IDEA 中不会输入中文的,直接切换成 ABC 输入英文即可,而当切换到浏览器的时候切换到 Rime。
再 结合 Rime 输入法的自动设置输入法的自动切换 就非常舒服了。
完美的代替了 kawa 这款切换输入法的工具。
比如我使用 Obsidian 来作笔记,同时使用 git 来做版本管理,写一个脚本,每 30 分钟提交一次。
log = hs.logger.new('autoscript', 'debug')
local cmdArr = {
"cd /Users/einverne/Sync/wiki/ && /bin/bash auto-push.sh",
}
function shell(cmd)
hs.alert.show("execute")
log.i('execute')
result = hs.osascript.applescript(string.format('do shell script "%s"', cmd))
hs.execute(cmd)
end
function runAutoScripts()
for key, cmd in ipairs(cmdArr) do
shell(cmd)
end
end
myTimer = hs.timer.doEvery(10, runAutoScripts)
myTimer:start()
比如定时提交 git commit,定时 git push 等等。当然直接使用 Crontab 来实现也是可以的。
在笔记本合上时静音
function muteOnWake(eventType)
if (eventType == hs.caffeinate.watcher.systemDidWake) then
local output = hs.audiodevice.defaultOutputDevice()
output:setMuted(true)
end
end
caffeinateWatcher = hs.caffeinate.watcher.new(muteOnWake)
caffeinateWatcher:start()
定义锁屏的快捷键。
-- lock screen shortcut
hs.hotkey.bind({'ctrl', 'alt', 'cmd'}, 'L', function() hs.caffeinate.startScreensaver() end)
对我而言最常见的就是当我接入外接键盘的时候,自动切换 karabiner-Elements
的键盘 profile。
这样当我使用 macOS 自带的键盘和外置键盘的时候就可以保持一致的使用习惯。
快速打开终端:
hs.hotkey.bind({'ctrl', 'alt', 'cmd'}, 'K', function () hs.application.launchOrFocus("iTerm") end)
更多的例子可以参考我的 dotfiles
参考 https://github.com/einverne/dotfiles/hammerspoon/ 关键字 Caffeine。
定义了快捷键 Hyper + 1/2/3 将当前窗口快速移动到其他显示器:
function moveWindowToDisplay(d)
return function()
local displays = hs.screen.allScreens()
local win = hs.window.focusedWindow()
win:moveToScreen(displays[d], false, true)
end
end
hs.hotkey.bind({"ctrl", "alt", "cmd"}, "1", moveWindowToDisplay(1))
hs.hotkey.bind({"ctrl", "alt", "cmd"}, "2", moveWindowToDisplay(2))
hs.hotkey.bind({"ctrl", "alt", "cmd"}, "3", moveWindowToDisplay(3))
非官方支持
Hammerspoon 的配置文件是使用 Lua 书写,如果熟悉 Lua,可以更进一步使用 Lua 的 moonscript 来简化配置。
brew install lua@5.3
luarocks-5.3 install moonscript
luarocks-5.3 install lodash
参考 这里
Hammerspoon 官网文档
Spoon 是预置在 Hammerspoon 内的插件系统,Spoon 是使用纯 Lua 实现的插件,可以方便用户集成集成到 Hammerspoon 的配置中。
可以从官方的页面获取 Spoon ,源码可以参考对应的 GitHub 页面,下载后解压得到 .spoon
文件,双击导入即可。文件会自动将自己拷贝到 ~/.hammerspoon/Spoons/NAME.spoon
,然后在 init.lua
中 hs.loadSpoon("NAME")
即可。
更具体的 Spoon 的使用可以参考官网。
Spoon 文件有一定的格式,方便集成调用。
Spoon 文件中的常用方法:
NAME: init()
,这个方法会被 hs.loadSpoon()
调用,会进行一些初始设置,这里面不应该执行任何动作NAME: start()
,如果有需要在后台进行的任务,可以由这个方法启动NAME: stop()
,关闭后台任务NAME: bindHotkeys(mapping)
,定义功能快捷键,通常是 table 的形式:自从半年前发现了 Obsidian 这款笔记软件,我就开始大量的使用该应用做笔记,有人说过:「工具是开发者方法论的固化」。这么多年了我一直有一种工具控的倾向,往往同一个需求会对比可能的所有方案,最后再决定一个,但是近些年来我越来越倾向于「简单就是好」,并且数据要由自己掌控的「工具选择逻辑」。
基于上面的选择逻辑,我的 Obsidian 跨平台同步工具,我选择了:
每一个工具都只专注做一件事情,搭配起来工作非常完美。
2021 年 9 月更新
这一年来 Obsidian 的生态发生了很多变化,Obsidian 已经发布了 Android/iOS 版本,虽然现在我依然使用 Markor 来查看笔记,但最近已经慢慢的转向 Obsidian 官方的应用,Obsidian 官方的应用在搜索方面做的要比 Markor 好。
而之前使用 Syncthing,现在也一如既往的使用 Syncthing,但是最近购入了 Obsidian Sync 一方面支持一下开发者,另一方面也想慢慢地将移动端使用官方的同步工具来同步笔记。 还是回归了 Syncthing。
用了一年多 #obsidian ,虽然平时都用 Syncthing 来同步,但还是买了一年 Sync,支持一下开发者。 pic.twitter.com/YPvNXMDhyI
— Ein Verne (@einverne) September 22, 2021
我最基本的需求,就是当我有什么想法的时候,可以随时随地地记录到一个地方,这个地方以前是 wiznote,但是我迁移到了 Obsidian 之后,缺乏两个机制:
为了解决第一个问题,引入了 Syncthing, 这个是我已经使用很久了的文件同步工具,代替了 Dropbox,NextCloud,正好 Obisidian 本身管理的就是本地的纯文件,直接添加到同步目录即可。
为了解决第二个问题,纯文本自然地就想到了使用 Git 作为版本管理。
但是问题在于 Zettelkasten 的其中一条原则便是「最好在一个地方管理所有的笔记」,而我曾经有很多笔记已经记录到了 Jekyll 的博客里面,也正好是 Markdown 的文本文件。所以我在一个中心的 Git 仓库中存放了 Obisidian 中的所有笔记,然后使用 git subtree
将我的 Blog 作为其中的一个子目录 Blog
添加进来,这样我就可以在一个 Obsidian 的 Vault 中搜寻,连接我所有的笔记。
利用上面的 Syncthing,Git,我可以做到多地(本地,VPS,Android 手机)的备份,然后将仓库同步到 GitHub 再有一份备份。另外通过 Git 的提交历史可以看到所有的笔记修改记录。这时候我就想通过一个自动提交的脚本,每隔半小时提交一次,这样最多也就丢失半小时的笔记,对我个人而言我也能接受。所以当时直接用 Hammerspoon 的特性写了一个脚本自动同步。
#!/bin/bash
now=$(date +"%m.%d.%Y_%T")
cd /Users/einverne/Sync/wiki
git add --all && git commit -m "Auto commit at $now"
git push origin master
在知识管理的概念中,我了解到了 [[Digital Garden]] 概念1,Digital Garden 是一个开放分享的数字花园,这个概念中有两个重点:
在这个概念上实现最好的就是 Andy Matuschak 的在线笔记。
我也想通过从我的 Obsidian 笔记中选择一部分整理好的内容分享出,但我又不想有额外的操作。所以正好通过上面提及的 git subtree
,我日常的笔记会存放在其他的目录,当我准备发布的时候,将笔记移动到 Blog 中的 _posts
目录,在提交,之后将修改推送到 Blog 的远端时就自动完成了发布的过程。现在唯一不太满意的部分就是现在博客上进行页内跳转的时候还依赖于超链接,在编写文章的时候也需要跳出去选择相应的页面链接,而不能依赖与 Obisidian 提供的双向连接。
但总是上面一套流程是我如何实现在多平台上同步笔记的方案,虽然可能对非编程工作者会有一些障碍,但只要熟悉了这个流程之后,用起来真的非常方便。另外就是我身边并没有 iOS 的设备,在 iOS 上可能需要依赖其他的工具,比如可以使用 iCloud 或其他商业的同步工具来同步笔记,然后使用 1Writer 来作为编辑器。
总之这就是我实现跨设备的方案,具体我是如何做笔记,以及笔记的内容,如何对笔记内容进行连接,我会在另外一篇文章中再进行阐述。