2020 读书记录

2020 年注定是会在历史上留下不平凡的一年,年初的疫情到年底问题依然还在,而且在不确定疫苗的情况,在加上英国的病毒变异情况,不清楚到什么时候才是结束。再回头看今年的大事小事,从年初的李文亮事件,到年末的蛋壳,以及阿里被禁止上A股,有些事情发生地太突然,来不及思考,但只有思考,不仅是在事前的还是事后的思考都有其价值。

李文亮事件

李文亮因言获罪,而密尔早在两百年前就曾经说过,即便是荒谬的言论也不得限制,对该言论的讨论过程能进一步证明相反观点的价值。而李文亮事件就是典型的「被压制的言论包含部分的真理」的情况,而正是因为言论的被压制,终究封城是阻止不了病毒的扩散的。而在看美国宪法,以及关于[[宪法第一修正案]]关于言论自由边界的书,能看到为了维护不进行事先审查言论的权利,经过了多少的争论。

而换一个角度来看,来看看一位金融从业者在[[非对称风险 笔记]]中的观点,「持续暴露在小概率风险下,即使爆仓风险的概率小到万分之一,那么在持续、重复的过程中爆仓的概率会越来越大」。相同的观点套用到现实,能禁止一个人说话,能阻止一群人发表观点,但只要危机没有被解决,终究会造成无法挽回的损失。

金融相关

  • [[黑天鹅]],什么叫黑天鹅,[[塔勒布]] 总结了三点,意外性、极端影响、事后的可预测性。塔勒布让我们留意生活中那些未知的和在我们认知中不可能的事物,而不是一直在讨论琐碎的已知的和重复发生的事物。
  • 非对称风险,一书在讲述一个「风险共担」的理念,举一个最简单的例子,以前降落伞的安全程度很低,所以订购商在验货的时候让制造商穿一件从空中跳下来,于是安全系数就提升了。这是最显而易见的风险共担。塔勒布从风险控制,将其理论拓展到经济,商业,社会生活的方方面面。作者提倡将做出决定和承担后果二者相互平衡的机制。
  • 随机漫步的傻瓜,谨小慎微的投资,学会有[[安全边际]]的[[价值投资]]
  • 牛津通识读本:全球经济史,从资本主义诞生开始,讲述全球这个地区的经济发展道路
  • 牛津通识读本:资本主义,这是一本讲述资本主义起源,解释什么是资本主义,以及资本主义在发展的过程中自身的演变的书。

法律

金斯伯格大法官离世后,先是让我认识了这位饱受尊敬的高龄大法官,然后将我带回了最高法院,在最高法院发展的几百年历史中,让我认识到了自由派的[[浪漫主义]]者 [[霍姆斯大法官]],也让我了解了未曾当过大法官却深刻影响了最高院的 [[勒尼德·汉德]] 法官,以及到更近代的第一人女性大法官[[奥康纳大法官]],当代的自由派[[苏特大法官]],以及许许多多以前可能听过但不曾有印象的名字,这些躲在背后的大法官们在一件件的案件,以及对这些案件的判决中,形象更加丰满起来。

  • 温柔的正义 : 美国最高法院大法官奥康纳和金斯伯格如何改变世界,这本书主要介绍了最高法院历史上的两位女性大法官的故事。
  • 九人,以人物传记的形式记录了最高法院从1990年到2007年17年间的人事更替、提名过程、立场变迁与判决内幕,内容跨越伯格法院、伦奎斯特法院和罗伯茨法院三个时期。
  • 牛津通识读本:美国总统制,讲述了美国总统制,以及行政分支
  • 牛津通识读本:托克维尔,托克维尔的人物传记
  • 大法官说了算,通过具体的历史事件来记录了最高法院权威性的由来,以及最高法院在这些年来对直播,互联网言论,死刑等等问题的观点
  • 牛津通识读本:法哲学,什么是法律,法律是否能维护权利,保障公平等等问题
  • 美国宪政历程,这是一本非常推荐的书,看了上面这么多书,唯有这一本读起来最舒服,作者[[任东来]]先生也说过这是写给普通人看的科普读物。但任先生通过书中的二十多个案例展现了一部形象的宪法历史。
  • 牛津通识读本:美国政党与选举
  • [[联邦党人文集]],今年二次阅读这一本书,初读的时候,因为没有了解当时的社会环境,也没有对其中例如 [[詹姆斯 麦迪逊]]、[[亚历山大·汉密尔顿]] 有所了解,但今年结合一部音乐剧《汉密尔顿》,又重新加深了对汉密尔顿的认识,以及上面提及的诸多哲学,法律的著作中都在引述这本书中的内容,更进一步认识了这一本书的价值所在。读完这本书还还好奇为什么建国之初的联邦党到如今却消失了,还特意查了一番。

历史

如果说上半年收获最大的作者是 [[塔勒布]] 的话,那么下半年收获最大的作者便是 [[弗朗西斯·福山]],早在去年就看过其[[The Origins of Political Order]],但是碍于当时的认知和环境,实际上并没有完全读懂,今年先读了其之前的著作 [[历史的终结与最后的人]] ,然后再看其更进一步的论述 [[The Origins of Political Order]],才理清了其脉络。

  • [[历史的终结与最后的人]]
  • [[The Origins of Political Order]]
  • 政治秩序与政治衰败

其他

  • 如何阅读一本书,今年年中收获的一本书,以往我都不屑于这一类 How to 的书,但今年重新发现,方法的重要性。How to read a book, How to take smart notes 是今年两本对我影响比较大的书,加上 Obsidian 和 Zettelkasten 的大量使用,不仅让我提升了阅读的乐趣也是的我今年的笔记效率大幅度提升。然后再回顾过去的阅读笔记,发现我大量的时间都只花在了 inspectional reading,也就是所谓的粗读中,连基本的分析阅读都未能做到。
  • 论自由,重读论自由,密尔这位自由主义的奠基人,两百多年前的声音在如今依然振聋发聩,「那些被压制的言论可能包含着为社会所需要的部分或全部的真理。即便一个荒谬的信仰也是有价值的,因为进行争论的过程也是有价值的。因为对其进行争论的过程本身即可以检验和进一步证明相反观点的真实性。」
  • [[韩剧如何讲故事]],很早就在豆瓣上看到的一本书,作者是写个编剧阅读的,而我作为一个门外汉,从侧面看韩国综艺和韩剧这些年的发展,不难看出他们对故事性的追求,他们在发展中进步,在进步中进一步的扩展自己讲故事的能力,韩国在媒体领域里面的创作和创新值得我好好的学习。另外一个让我不由的串起来的便是,[[托克维尔]] 说社会的自由依赖与[[公民自治]],形成大大小小的公民团体谋取利益,而恰恰是在这样一本讲述编剧的故事中,我发现韩国有自己的编剧协会,每一年都会将刚入行的编剧送到这个领域去学习,比如去医院,法院,监狱等等平常人难以进入的场所进行取材,再回想到这些年韩剧中不乏那些描写细腻的医疗剧,法庭辩护,以及监狱生活的剧,一下子就能看到其中的关联。
  • 韩国综艺如何讲故事,Running Man 开启了我对韩国综艺的认识,[[无限挑战]]将我对综艺的认识提升到另外一个境界,我本不看任何形式的综艺,但我发现原来搞笑,甚至有些无厘头的综艺中也能塑造一代人的认识。这又让我想起了去年去首尔,去柜台结帐的时候,老板正在看当时已经停播的无挑选举特辑,一下子就认识到即使做的只是放送局最基础的搞笑综艺,只要能够用心,能够讲好故事,依然能够让最最普通的人感同身受,甚至影响现实世界。如今无挑已经不在,但变成了刘在石一个人的挑战,虽然现在并没有每一期都看,但如果有空闲的时间总会想起。
  • 饱食穷民,一本讲述日本经济的纪实文学,其中描述了盘踞街头的小微贷,无法抑制的[[消费主义]],不断延长的工作时间,人们被改造成了金钱和物质统治下的行尸走肉,深陷其中无法自拔。这个故事像极了今天的中国,小微贷变成了移动互联网各家争夺的地盘,自我启发变成了企业培训,这种网课,而现在广为所知的996,也只不过是日本员工日夜拼命的缩影。
  • [[软技能]]

2020-12-27 reading , book

logback.xml 配置详解

之前两篇文章简单的介绍了 [[Logback]] 是什么,以及基本的使用,这一篇文章着重说一下 Logback 中最重要的 logback.xml 配置文件的编写。

![[Pasted image 20201210145047.png]]

配置文件格式:

<configuration>
    <appender>    //输出到控制台的信息配置
       //....
    </appender>

    <appender>   //输出到info文件的配置
       //...
    </appender>

    <appender>   //输出到error文件的配置
    </appender>

    <logger>     //特殊处理日志定义
        //..
    </logger>

    <root level="debug">  //总日志开关
        //...
    </root>
</configuration>
  • appender 配置打印行为,比如将日志输出到控制台,文件,数据库,网络端口等等
    • 可以配置多个 appender
  • logger 用来处理特定的包和类

根节点

根节点 <configuration> 包含三个属性:

  • scan: 为 true 时,如果配置改表会重新加载,默认是 true
  • scanPeriod: 检测配置文件修改的时间间隔,默认单位毫秒,默认时间间隔 1 分钟
  • debug: 为 true 时,打印 logback 内置日志信息,实时查看 logback 运行状态,默认 false

例如:

<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!--其他配置省略-->
</configuration> 

子节点

contextName 子节点

子节点 <contextName>,设置上下文名称。

<configuration scan="true" scanPeriod="60 seconds" debug="false">
     <contextName>myAppName</contextName>
    <!--其他配置省略-->
</configuration>

property 子节点

用来定义变量值,两个属性 name 和 value,通过 <property> 定义的值会被插入到 Logger 上下文中,可以使用 ${} 来使用。

<configuration scan="true" scanPeriod="60 seconds" debug="false">
   <property name="APP_Name" value="myAppName" />
   <contextName>${APP_Name}</contextName>
   <!--其他配置省略-->
</configuration>

timestamp 子节点

获取时间戳字符串

  • key:标识此 timestamp 名字
  • datePattern:设置当前时间,遵循 java.text.SimpleDateFormat
<configuration scan="true" scanPeriod="60 seconds" debug="false">
  <timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>
  <contextName>${bySecond}</contextName>
  <!-- 其他配置省略-->
</configuration>

appender 子节点

appender 是负责写日志的组件,两个属性:

  • name,指定 appender 名字
  • class,指定 appender 全限定名

logback 实现了一些内置的 appender,可以将日志输出到控制台,文件等等地方。

<appender name="file-appender" class="ch.qos.logback.core.rolling.RollingFileAppender">
  ....
</appender>

内置的 appender:

  • ConsoleAppender
  • FileAppender
  • RollingFileAppender

ConsoleAppender 控制台输出

将日志输出到控制台(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

将日志输出到文件(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>

RollingFileAppender

滚动日志,记录日志到文件,当符合某条件时,将日志记录到其他文件。子节点:

  • <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>

AsyncAppender

异步日志

<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 具体可以参考官方文档:

  • SocketAppender
  • SMTPAppender
  • DBAppender
  • SyslogAppender
  • SiftingAppender

logger 子节点

用来设置某一个包或具体的某一个类的日志打印级别,执行 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>

reference


2020-12-10 logback , spring , logger , log , logging , slf4j , java , java-log

Logback 使用

Logback 在之前的文章中说过,是一个开源日志组件。

[[Logback]] 推荐和 SLF4J 一起使用。

Maven 依赖

<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

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 成功了");
    }
  }

2020-12-10 logback , slf4j , spring , java , logger

Logback 基本使用介绍

[[Logback]] 是 log4j 的创始人开发设计的另一个开源日志组件,期望成为 log4j 项目的继任者,相较于 log4j,Logback 有一些优势。

  • 更快的实现,更小的内存占用
  • 充分的测试,Extensive battery of tests
  • 可以直接使用 [[SLF4J]] 接口
  • 健全的文档
  • 可以通过 XML 或 Groovy 进行配置
  • Logback 可以在配置更改后自动加载
  • 可以优雅的从 IO 失败中恢复
  • 自动移除老的日志存档
  • 自动压缩存档日志文件
  • 在 Prudent mode 模式中,在不同 JVMs 中运行的不同 FileAppender 实例可以安全地写到同一个文件中
  • Lilith,Log 事件的观察者
  • Conditional processing of configuration files
  • Filters,过滤器,要诊断问题,在 Log4j 中,只有降低日志级别才会打印日志,但这样会打出大量的日志,影响性能,但是 Logback 可以继续保持日志级别,而单独添加一个过滤器,比如过滤某用户的登录日志,将其打印在 DEBUG 级别,而其他用户的日志继续打印在 WARN 级别,可以参考 [[MDCFilter]]
  • SiftingAppender,多功能的 Appender,可以根据给定的参数对日志文件进行切分,比如根据用户的 ID 进行切分, 每个用户都会有一个日志文件
  • Stack traces with packaging data
  • Logback-access

模块

Logback 划分成三个模块:

  • logback-core 基础模块
  • logback-classic 这是 Log4j 的改良版本,完整实现了 slf4j,可以很方便的切换成其他日志系统比如 log4j 或 JDK Logging
  • logback-access 访问模块和 Servlet 容器集成,提供 HTTP 访问日志

Maven 依赖:

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>${logback.version}</version>
</dependency>

logback 配置

  • Logger 是日志的记录器,关联到应用的 context ,用于存放日志对象,可以定义日志级别、类型
  • Appender 负责指定日志输出的目的地,可以是控制台,文件,远程套接字,MySQL,PostgreSQL,Oracle 等数据库,JMS 和远程 Unix Syslog 守护进程等等
  • Layout 负责把事件转换成字符串,格式化日志信息的输出

默认配置

应用在启动时按如下的顺序寻找配置文件:

  • 在 classpath 中 logback-test.xml 文件
  • logback.groovy
  • logback.xml
  • 上述文件都找不到,JDK 的 SPI 机制查找 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>

Logback 配置文件

  • 以 configuration 开头,后面有 0 个或多个 <appender> 元素,0 个或多个 <logger>,最多有一个 <root> 元素。

[[2020-12-10-logback-xml-config]] [[2020-12-10-logback-usage]]

文档


2020-12-10 logback , spring , java , logger , slf4j

使用 mdBook 生成在线文档

很早以前就用过 [[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

要生成页内目录可以使用 toc

cargo install mdbook-toc

相关

Python 的文档工具 [[mkdocs]] :

tag: #GitBook #wiki #Book #Markdown #note #writing #个人知识管理


2020-12-03 gitbook , online-document , rust , wiki , markdown , notes , static-site

我的 IntelliJ IDEA Vim 插件配置

IdeaVim 是 IntelliJ IDEA 编辑器下一款模拟 Vim 模式及快捷键的开源插件。鉴于大部分的时间都在 IntelliJ IDEA 下工作,所以总结一下在 IDEA 下使用 Vim 的一些快捷方式。

阅读完本文之后,你可以

  • 在 IntelliJ IDEA 下完全通过键盘操作完成 99% 的操作
  • 可以通过组合按键实现任意自己想达到的操作
  • 保持在多个平台(macOS,Linux,Windows) 下操作一致

为什么要用 IdeaVim

  • 既充分利用了 IntelliJ 提供的代码补全,重构,代码浏览等等功能,又可以充分利用 Vim 的多模式,以及 Vim 在编辑器中的高效操作
  • 利用 ~/.ideavimrc 来复用 Vim 的工作方式,即使将工作环境切换到 Terminal 下也可以沿用,并且充分利用 Idea 提供的 Action
  • 多平台下沿用一套工作流程,不需要解决因为操作系统不同而产生的快捷键冲突

必须安装的插件

利用 .ideavimrc 配置 IdeaVim

重新加载 .ideavimrc

安装了插件之后,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 然后快速输入项目的模糊查询的关键字,然后回车。

Go to Declaration

IDEA 自身就提供了非常多的快捷来在代码之间跳转,比如:

  • 跳转到变量,方法,类的定义
  • 跳转到 Super 类
  • 跳转到方法被调用的地方
  • 跳转到实现的地方
  • 跳转到文件
  • 跳转到测试类

在我的工作流里面,为了方便记忆,统一使用 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>

Toggle Actions

我使用 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>

查看 IDEA 支持的 Action

在安装 IdeaVim 之后,可以在 normal 模式下使用如下命令查看 IDE 支持的 action:

:actionlist [pattern]

如果要搜索对应的 action 可以直接加上模糊词来搜索,比如 :actionlist declaration 来搜索相关的内容。

执行 action

:action {name}

比如执行 :action Debug

重新命名 Action

在 ~/.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 配置。

两个比较有用的快捷键

  • ⌘+F12 ActivateTerminalToolWindow 在 Linux 下设置成 Alt+F12 可以快速调用或隐藏 IDEA 内置的终端
  • ⌘+⇧+F12 HideAllWindows 最大化编辑器,隐藏所有其他窗口

Cmd 组合

  • Cmd+a/c/v/x/z
  • Cmd+w 关闭当前的文件
  • Cmd+e 最近的文件
  • Cmd+t Refactor this 重构
  • Cmd+n Generate 快速生成模板代码
  • Cmd+o File Structure 当前文件的文件结构
  • Cmd+b 跳转到定义
  • Cmd+[/]

surround

启用 surround 插件来模拟 surround

set surround

在了解了简单的 Vim,并知道 Vim 的能力之后就可以做到 Vim Everywhere,比如

  • 在浏览器中安装 Vimium 插件之后可以在浏览器中使用 Vim 的浏览,搜索等
  • 在 Obsidian 中开启 Vim 模式,也可以自定义使用 Vimrc 配置文件

这样在日常、工作的大部分时间中都可以完美的使用 Vim。

reference

[[idea-plugins]]

category: [[IntelliJ IDEA]] [[编程工具]] [[学习笔记]]


2020-12-02 idea , vim , vimrc , config , vim-config , jetbrain , intellij-idea , ide

图片管理工具 Eagle

远在移动互联网还没有那么发达的今天,Google 曾经收购过一家图片管理与分享的网站叫做 Picasa,Picasa 同时提供了一个跨平台的照片管理工具 Picasa Desktop,用这个工具不仅可以非常方便的管理本地的图片文件,也可以非常方便的分享到 Picasa Web 上,然而随着移动互联网的到来,以及 Google 的转型,Picasa 的服务在 Google 变得没有那么重要,随即在 2016 年停止了服务,我的图片管理也被迫迁移到了 Google Photos。然而一切都开始变得不方便,Google Photos 内自动备份的照片,相册开始无法管理,并且 Google 停止了桌面版的开发,同样使得在桌面上管理图片变得困难,这些年来尝试了 Lightroom,TagSpaces 等等工具,都没有找到特别舒心的。

我的需求其实也并不复杂:

  • 管理本地大量的单反照片,添加标签以及其他 meta 信息,可以方便我快速定位、查找,这一点 Lightroom 做的不错,但 Lightroom 有些笨重
  • 集中管理我所有的照片,图片,让我可以快速预览照片
  • 我希望我的图片库可以随时随地可以访问

我带着这一些需求一直在寻求一款合适的工具,直到有一天在 Twitter 上有人有相同的需求时,一则评论吸引了我的注意,打开 Eagle 官网 的同时我就被吸引了,一张软件界面的截图直接了当的说明了这个软件的功能,并且几个关键字,「收集」,「整理」,「搜索』,似乎让我一下子想到了 Picasa。更加了解之后才发现,原来 Eagle 不止图片管理这么简单,音频,字体,GIF 管理等等让我迫不及待地下载进行尝试。 安装后 Eagle 会引导安装浏览器扩展,这个扩展可以用来收藏图片,包括拖拽,Alt+ 右击,右键上下文菜单,甚至可以直接批量一键导入网页图片,或者将整个网页保存为图片,或者剪切部分网页。这部分内容扩展的页面 已经给出了非常详细的说明,这里就略过了。

picasa screenshot

我个人的习惯是每次旅行或外出回来时,会新建一个以日期开头加上地点的文件夹,并导入当前拍摄的照片,然后保证无误后格式化 SD 卡。然后使用 Lightroom 对这个新导入的文件进行一轮的筛选,使用 1~5 快捷键给照片打分,然后对打分的照片进行简单的快速处理。如果有 4 或 5 分的照片,会更进一步微调。然后对有打分的照片进行导出分享。

关于收藏图片

在遇到 Eagle 之前,我接触的大部分图片都是在浏览器中,所以在浏览器中安装了一个叫做 Imagus 的工具,当鼠标悬浮在图片上后会放大缩略图,并且使用 Ctrl+s 就可以直接下载该图片。但这样的问题便是所有的图片都在 Downloads 文件夹下和其他格式的文件混在一起。等到真正需要使用的时候,搜索变得非常不方便。

而有了 Eagle 之后这个工作流程更加简单了,鼠标拖拽图片即可。

字体管理

虽然我不是学设计的,但是大学的时候曾经做过一段时间和字体相关的事情,所以也对字体有一些要求,所以如果阅读器支持更换字体,肯定会换成自己喜欢的方正北魏楷书或方正宋三。曾经下载过很多的字体进行对比,如果当时有 Eagle 就方便很多,直接将字体导入软件就能预览文本,再不用现在文本编辑器中编辑一段对话,然后再挨个更换字体了。

eagle font manage

常用的快捷键

  • ⌘+⇧(Shift)+n 创建文件夹
  • ⌘+t 增加标签
  • ⌘+Alt+x
  • ⌘+j 在不同的文件夹之间切换
  • ⌘+⇧(Shift)+j 将指定图片移动到指定文件夹
  • F 弹出分类对话框,可以快速进行分类

几个独特有有用的功能

  • 按颜色过滤素材

缺点

不管是在 Picasa,还是在 Lightroom 个人的使用习惯是管理工具管理的内容,在磁盘上实际的位置还是原来的位置,不管是编辑了图片或者是移动了图片都是在本地文件中的修改,然而虽然 Eagle 是一个离线可用的图片管理工具,但一旦图片文件夹导入到 Eagle ,再去磁盘中查看时文件的结构就变成了 Eagle 处理过的扁平的样子,并且会冗余两份图片,一份在原来的位置,一份在 Eagle 中,即使之前存在目录结构也被打散了。一旦文件导入到 Eagle 再想要使用文件管理器来管理这些图片变的不可能。当然这个只是个人的习惯问题。

eagle folders

要再想恢复到原来的文件结构可以右击文件夹,选择导出。

另外一点令我顾虑的就是,一旦使用 Eagle 来管理我的大量图片,这就使我锁定在了 Eagle,让我有一点介意的就是当我试用期过了之后,我竟然无法导出我的所有图片,虽然这些图片都在本地,但是因为已经被 Eagle 重新命名过所以几乎无法直接使用,只能让我用虚拟机再导出一遍。这也加重了我的顾虑。

再以缺点就是在我使用的过程中,有两次无法退出,即使使用强制退出,也无法退出,甚至让我在重启电脑的时候都卡在去停止 Eagle 的地方,我从没在 Mac 上遇到软件强制退出都不行的情况。

同类型的其他产品

总结

综上所有的优点,缺点,我仔细思考了一下,Eagle 确实在某些方面做的不错,对于设计师,或者对于刚刚开始整理素材的人来说是一个非常不错的选择,而一旦已经有了自己的素材库的人来说,不可避免的需要将素材导入到 Eagle,而这是一个不算愉快的选择,并且一旦想要放弃 Eagle,那么将数据导出便又是一段不愉快的旅程。所以最后再试用期满后,我迫不得已按了一台虚拟机导出整个库后删除了 Eagle。


2020-12-02 software , photo , photo-management , picasa , picture

提升 mysqldump 导入导出 MySQL 的速度

在前端时间网上泄漏出来一个巨大包括了近 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 会大大的影响导入速度,所以:

  1. 移除所有的索引,包括唯一索引,除非能确保导入的数据是唯一的,否则也不建议留着,Primary Key 也是索引。

但是我移除了所有的索引之后,再执行 mysqldump 速度虽然有提升,但依然非常慢。所以不得不找其他办法。

使用 LAOD DATA INFILE 导入文件到 MySQL

再搜寻了一番之后发现 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 ','"

说明:

  • Terminated 字段分隔符(列分隔符)。一般是空格或者 \t
  • Enclosed 字段括起字符。没有为空字符即可
  • Escaped 转义字符。没有为空字符即可
  • Terminated 记录分隔符(行结束符)

插入语句:

  • Into Table 代表插入,记录已存在(唯一键约束)则失败不再往下执行。
  • Replace Into Table 代表覆盖,记录已存在则覆盖(是整条记录覆盖,没有列出的字段给默认值)。
  • Ignore Into Table 遇到已存在直接跳过。

LOAD DATA INFILE 原理

LOAD DATA INFILE 比单纯的 INSERT 要快。

  • insert 每次运行,都会更新一次索引,而 load 语句全部执行完才会更新索引。

需要注意的是,当时用 LOCAL 或者 LOAD DATA 时,文件的拷贝会保存到服务器的 temp 目录,这个目录不是由 tmpdir or slave_load_tmpdir 配置决定的,而是操作系统的临时目录 (temporary 目录)。

所以如果 CSV 文件比较大,操作系统临时目录无法放下,可以将文件分割成多份,分批次进行操作。

$ split -l (numbersofrowsinfile / ((filesize/tmpsize) + 1)) /path/to/your/<file>.csv

reference


2020-11-25 mysql , linux , mysqldump , sql , methodology

Mac 应用篇:Hammerspoon 自动化工具使用

这是一篇耽搁了很久,一直躺在我的 Obsidian 笔记中的一篇文章,一直就想好好介绍一下 Hammerspoon,但是因为过去虽然也在用 macOS,但是使用最多的还是 Ubuntu,Hammerspoon 只能在 macOS 上使用,就没有那么大的兴致再花时间学习它的使用。但最近更新了一下系统,发现 Hammerspoon 出了一点问题,没有了 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 能做什么

Hammerspoon 实际上是将 macOS 的系统接口实现了一层转发,让用户可以通过简单的 Lua 脚本进行配置,从而实现一定的 UI 自动化,一旦能够直接从 API 层面对接操作系统,那么 Hammerspoon 能够做的事情就非常多了:

  • 创建并管理全局快捷键,这样就可以创建一组属于自己的快捷键工作流
  • 管理窗口 move windows,结合自定义快捷键可以非常轻松地对窗口进行管理
  • 发送通知 display notifications
  • 和其他应用交互 talk to other applications
  • 添加自定义内容到状态栏 add things to your menu bar,有了这个功能就可以在状态栏上添加任何自定义的内容,比如有人利用公开的天气 API 在状态栏显示最近和未来的天气,可以显示系统的网速等等
  • 监听文件内容变化,可以利用这个自动加载配置,或者自动提交任务等等
  • 监听 WiFi 变化,可以利用不同的网络环境配置不同的网络配置,自动切换不同的工作环境等等
  • 监听应用的启动和停止
  • 在屏幕上绘制 draw on the screen
  • 监听电池变化 watch for changes to your battery
  • 当电脑接口对接硬件时触发动作 carry out actions when you plug things into your computer

Installation

通过 Homebrew 安装:

brew install --cask hammerspoon

上手配置

Hammerspoon 的默认配置在 ~/.hammerspoon/init.lua,我个人通过将配置文件放在 dotfiles 中软链接到目的位置来同步配置。

做一个最简单的例子,在 init.lua 文件中写入:

hs.alert.show("Config reload!")

然后重新加载 Hammerspoon 配置,就会看到在屏幕中央出现 “Config reload!” 的弹出提示。

使用 Hammerspoon 作为管理窗口工具

虽然 [[Mac 上的窗口管理工具]] 有很多,免费的,收费的,Moom, Rectangle 等等,但是自由度都没有 Hammerspoon 多。

下面是我使用的一些窗口管理快捷键。

按下 Option+r 进入窗口的管理模式,在该模式下按下快捷键可以实现非常多的操作:

  • HL/JK 可以用来将窗口按照左、右、下、上分屏
  • ASDW 可以用来移动窗口的位置,对应上下左右
  • Y/O/U/I 可以将窗口变成四分之一屏幕大小,分别是左上,右上,左下,右下
  • Shift+HL/JK 可以收缩窗口大小,比如 Shift+H 就是让窗口右侧往左侧缩小
  • =/- 用来扩展和收缩窗口
  • Left/Right/Up/down 将窗口移动到左边、右边、上边、下边
  • F 全屏
  • C 中央
  • ESC/Q 退出该模式
  • Tab 显示 Cheatsheet

这一套窗口管理方法来自 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"))

使用 Hammerspoon 在切换 WiFi 时自动切换对应设置

当连接的 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()

使用 Hammerspoon 一键布局桌面窗口

我抢了同事一个显示器使用,所以外接了三个显示器,在每一个显示器中都有默认的布局。我一般左边竖置的显示器常驻一个 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 来实现也是可以的。

Mute on sleep

在笔记本合上时静音

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()

Locking the screen

定义锁屏的快捷键。

-- lock screen shortcut
hs.hotkey.bind({'ctrl', 'alt', 'cmd'}, 'L', function() hs.caffeinate.startScreensaver() end)

监听 USB 事件并做相应的设置

对我而言最常见的就是当我接入外接键盘的时候,自动切换 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 是什么?

Spoon 是预置在 Hammerspoon 内的插件系统,Spoon 是使用纯 Lua 实现的插件,可以方便用户集成集成到 Hammerspoon 的配置中。

可以从官方的页面获取 Spoon ,源码可以参考对应的 GitHub 页面,下载后解压得到 .spoon 文件,双击导入即可。文件会自动将自己拷贝到 ~/.hammerspoon/Spoons/NAME.spoon,然后在 init.lua 中 hs.loadSpoon("NAME") 即可。

更具体的 Spoon 的使用可以参考官网。

编写 Spoon

Spoon 文件有一定的格式,方便集成调用。

Spoon 文件中的常用方法:

  • NAME: init(),这个方法会被 hs.loadSpoon() 调用,会进行一些初始设置,这里面不应该执行任何动作
  • NAME: start(),如果有需要在后台进行的任务,可以由这个方法启动
  • NAME: stop(),关闭后台任务
  • NAME: bindHotkeys(mapping),定义功能快捷键,通常是 table 的形式:

reference


2020-11-24 mac , hammerspoon , automation , tool , window-manage , mac-app , mac-application

我的 Obsidian 笔记跨设备同步方案

自从半年前发现了 Obsidian 这款笔记软件,我就开始大量的使用该应用做笔记,有人说过:「工具是开发者方法论的固化」。这么多年了我一直有一种工具控的倾向,往往同一个需求会对比可能的所有方案,最后再决定一个,但是近些年来我越来越倾向于「简单就是好」,并且数据要由自己掌控的「工具选择逻辑」。

Do one thing and do it well

基于上面的选择逻辑,我的 Obsidian 跨平台同步工具,我选择了:

  • Syncthing,作为文件同步工具
  • Markor,作为 Android 上的 Markdown 编辑器,我提交了一段模板可以来创建 Zettelkasten 笔记 不再使用 Marker 作为 Android 客户端
  • Git,版本同步(配合git subtree)
  • Bash/Cron/Hammerspoon,定时脚本提交备份到 Git,然后自动推送到远端备份。

每一个工具都只专注做一件事情,搭配起来工作非常完美。

2021 年 9 月更新

这一年来 Obsidian 的生态发生了很多变化,Obsidian 已经发布了 Android/iOS 版本,虽然现在我依然使用 Markor 来查看笔记,但最近已经慢慢的转向 Obsidian 官方的应用,Obsidian 官方的应用在搜索方面做的要比 Markor 好。

而之前使用 Syncthing,现在也一如既往的使用 Syncthing,但是最近购入了 Obsidian Sync 一方面支持一下开发者,另一方面也想慢慢地将移动端使用官方的同步工具来同步笔记。 还是回归了 Syncthing。

我的需求

我最基本的需求,就是当我有什么想法的时候,可以随时随地地记录到一个地方,这个地方以前是 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 来作为编辑器。

总之这就是我实现跨设备的方案,具体我是如何做笔记,以及笔记的内容,如何对笔记内容进行连接,我会在另外一篇文章中再进行阐述。


2020-11-23 obsidian , markdown , git , syncthing , note , note-taking

电子书

本站提供服务

最近文章

  • 2024 年台北之行 去年的时候就得知了海外的大陆人可以通过官方网站申请入台证,从而可以在海外直接入境台湾,所以 4 月份女朋友过来日本之后就通过线上系统申请了入台证,入台证申请通过并付费之后是只有 3 个月有效期的,因为我们申请的比较晚,所以有效期的三个月正好落在了最热的 7,8,9 月份,但考虑到暑假有假期,我们还是决定硬着头皮买了机票。
  • macOS 上的多栏文件管理器 QSpace QSpace 是一个 macOS 上的多窗口平铺的文件管理器,可以作为 Finder 的代替,在 Windows 上曾经用过很长时间的 [[Total Commander]],后来更换到 Linux Mint 之后默认的文件管理器自带多面板,反而是用了很多年 macOS ,才意识到原来我缺一个多窗口,多面板的文件管理器。
  • Dinox 又一款 AI 语音转录笔记 前两天介绍过 [[Voicenotes]],也是一款 AI 转录文字的笔记软件,之前在调查 Voicenotes 的时候就留意到了 Dinox,因为是在小红书留意到的,所以猜测应该是国内的某位独立开发者的作品,整个应用使用起来也比较舒服,但相较于 Voicenotes,Dinox 更偏向于一个手机端的笔记软件,因为他整体的设计中没有将语音作为首选,用户也可以添加文字的笔记,反而在 Voicenotes 中,语音作为了所有笔记的首选,当然 Voicenotes 也可以自己编辑笔记,但是语音是它的核心。
  • Emote 又一款 AI 语音笔记应用 继发现了 Voicenotes 以及 Dinox 之后,又发现一款语音笔记 Emote,相较于前两款应用,Emote 吸引我的就是其实时转录的功能,在用 Voicenotes 的时候时长担心如果应用出现故障,没有把我要录下来的话录制进去,后期怎么办,而 Emote 就解决了这个问题,实时转录的功能恰好作为了一个声音录制的监听。
  • 音流:一款支持 Navidrome 兼容 Subsonic 的跨平台音乐播放器 之前一篇文章介绍了Navidrome,搭建了一个自己在线音乐流媒体库,把我本地通过 [[Syncthing]] 同步的 80 G 音乐导入了。自己也尝试了 Navidrome 官网列出的 Subsonic 兼容客户端 [[substreamer]],以及 macOS 上面的 [[Sonixd]],体验都还不错。但是在了解的过程中又发现了一款中文名叫做「音流」(英文 Stream Music)的应用,初步体验了一下感觉还不错,所以分享出来。