大狗哥传奇

  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

ip

发表于 2020-08-21 更新于 2020-12-02 分类于 code

IP4 报文的报文头由 14 个属性构成,其中 13 个是必须的,第 14 个属性是可选的。

ip_header.png 我们抓取一段 ip 报文来分析上图

这里是使用tcpdump -i any 'ip[40:4] = 0x47455420' -A -nn -f抓取的一段报文

1
2
3
4
5
6
7
8
9
0x0000:  4500 0078 78c3 4000 4006 c9cb 0ad3 3706  E..xx.@.@.....7.
0x0010: ac14 0a04 9354 0fa0 ea3e 8d2c 98a7 3036 .....T...>.,..06
0x0020: 5018 00e5 f85b 0000 4745 5420 2f20 4854 P....[..GET./.HT
0x0030: 5450 2f31 2e31 0d0a 5573 6572 2d41 6765 TP/1.1..User-Age
0x0040: 6e74 3a20 6375 726c 2f37 2e32 392e 300d nt:.curl/7.29.0.
0x0050: 0a48 6f73 743a 2031 3732 2e32 302e 3130 .Host:.172.20.10
0x0060: 2e34 3a34 3030 300d 0a41 6363 6570 743a .4:4000..Accept:
0x0070: 202a 2f2a 0d0a 0d0a 0000 0000 0000 0000 .*/*............
0x0080: 0000 0000 0000 0000 ........

我们根据 ip 报文头的格式截取整理一下,报文以 hex 显示,一位表示 4 个 byte

1
2
3
4
5
4500 0078
78c3 4000
4006 c9cb
0ad3 3706
ac14 0a04
  1. Version 版本号 4,表示 ip4 协议
  2. IHL ip 报文头长度,计算规则为n * 32bits,最小为 5,当大于 5 时,说明报文头有扩展字段,此处为5,表明没有扩展字段
  3. DSCP 差分服务代码点,用以路由器进行转发时来区分优先级
  4. ECN 拥赛指示标记,用以通知报文传输速度的调整
  5. Total Length 报文总长度,包括报文头和报文体,最小为 20 字节,即报文头的最小长度78表明长度为 120 个字节
  6. Identification 用于确认是否归属同一组报文的标记78c3
  7. Flags 分组标记,占据 3 个 bits,每个 bits 的含义为:

    • bit 0: 备用位
    • bit 1: 不要分组 Don't Fragment (DF)
    • bit 2: More Fragments (MF)

    4000 转换为二进制为 0100000000000000,第二个 bits 为 1,表示不需要分组

  8. Fragement Offset指定分组的报文体相对于总报文的偏移字节量,占据 13 个 bits,最大可以表示(213 – 1) × 8 = 65,528 bytes。4000 转换为二进制为 0100000000000000,后 13 个 bits 表示的便宜量为 0
  9. Time To Live (TTL)报文的最大保存时间,现在一般用来表示转发次数,即访问路由器的次数,每次访问一次路由器就减 1,变 0 后路由器便丢弃该报文,并发送 ICMP Time Exceeded 给发送方。40表示最多转发 64 次
  10. Protocol 报文体的协议类型,详细见rfc790,此处06表示
  11. Header Checksum 报文校验值,通过算法计算报文得出一个值,路由器通过比对校验值,确定报文完整性
  12. Source address 源地址,ip4 的地址一般采用点十表示法,例如127.0.0.1在 ip 报文传输过程中,每段 ip 地址使用 8bits 表示,所以 ip 地址每段的取值范围为0~255。0ad3 3706每两位转换为十进制则是10.211.55.6。转换过程大致如下所示 ip_address.png
  13. Destination address 目标地址
  14. Options 扩展字段

tcpdump

发表于 2020-08-19 更新于 2020-12-02 分类于 linux

tcpdump 是一个命令行实用工具,允许你抓取和分析经过系统的流量数据包。它通常被用作于网络故障分析工具以及安全工具。

使用 tcpdump 抓包,需要管理员权限,因此下面的示例中绝大多数命令都是以 sudo 开头。

tcpdump 会持续抓包直到收到中断信号。你可以按 Ctrl+C 来停止抓包

  1. tcpdump -D 命令列出可以抓包的网络接口

    1
    2
    3
    4
    5
    6
    $ sudo tcpdump -D
    1.eth0
    2.virbr0
    3.eth1
    4.any (Pseudo-device that captures on all interfaces)
    5.lo [Loopback]
  2. -i 抓取指定网络接口的数据,其中特殊接口 any 可用于抓取所有活动的网络接口的数据包。
  3. -c 选项可以用于限制 tcpdump 抓包的数量:
  4. -nn 显示端口号
  5. -w 命令将抓取的数据包保存到文件,可
  6. -X 以十六进制打印出数据报文内容
  7. -A 以 ASCII 值打印出数据报文内容
  8. -r tcpdump 若在-W保存文件不使用-X或-A时会将数据包保存在二进制文件中,所以不能简单的用文本编辑器去打开它。使用 -r 选项参数来阅读该文件中的报文内容,你还可以使用我们讨论过的任何过滤规则来过滤文件中的内容,就像使用实时数据一样

报文字段解释

tcpdump 能够抓取并解码多种协议类型的数据报文,如 TCP、UDP、ICMP 等等。虽然这里我们不可能介绍所有的数据报文类型,但可以分析下 TCP 类型的数据报文,来帮助你入门。更多有关 tcpdump 的详细介绍可以参考其 帮助手册。tcpdump 抓取的 TCP 报文看起来如下:

08:41:13.729687 IP 192.168.64.28.22 > 192.168.64.1.41916: Flags [P.], seq 196:568, ack 1, win 309, options [nop,nop,TS val 117964079 ecr 816509256], length 372

具体的字段根据不同的报文类型会有不同,但上面这个例子是一般的格式形式。

  • 08:41:13.729687 是该数据报文被抓取的系统本地时间戳。

  • IP 是网络层协议类型,这里是 IPv4,如果是 IPv6 协议,该字段值是 IP6。

  • 192.168.64.28.22 是源 ip 地址和端口号,
  • 192.168.64.1.41916 紧跟其后的是目的 ip 地址和其端口号
  • Flags [P.]可以看到是 TCP 报文标记段 Flags [P.]。该字段通常取值如下:

    值 标志类型 描述
    S SYN Connection Start
    F FIN Connection Finish
    P PUSH Data push
    R RST Connection reset
    . ACK Acknowledgment

    该字段也可以是这些值的组合,例如 [S.] 代表 SYN-ACK 数据包。

  • seq 196:568 是该数据包中数据的序列号。对于抓取的第一个数据包,该字段值是一个绝对数字,后续包使用相对数值,以便更容易查询跟踪。例如此处 seq 196:568 代表该数据包包含该数据流的第 196 到 568 字节。

  • ack 1。该数据包是数据发送方,ack 值为 1。在数据接收方,该字段代表数据流上的下一个预期字节数据,例如,该数据流中下一个数据包的 ack 值应该是 568。

  • win 309 是接收窗口大小 ,它表示接收缓冲区中可用的字节数,后跟 TCP 选项如 MSS(最大段大小)或者窗口比例值。更详尽的 TCP 协议内容请参考 Transmission Control Protocol(TCP) Parameters。

  • length 372 代表数据包有效载荷字节长度。这个长度和 seq 序列号中字节数值长度是不一样的。

过滤报文

现在让我们学习如何过滤数据报文以便更容易的分析定位问题。

  1. 用 host 参数只抓取和特定主机相关的数据包:

    1
    sudo tcpdump -i any -c5 -nn host 54.204.39.132
  2. 用 src 就是按源 IP/主机名来筛选数据包。

    1
    sudo tcpdump -i any -c5 -nn src 54.204.39.132
  3. 用 dst 就是按目的 IP/主机名来筛选数据包。

    1
    sudo tcpdump -i any -c5 -nn dst 54.204.39.132
  4. 用 port 参数根据端口号来筛选数据包

    1
    sudo tcpdump -i any -c5 -nn port 80
  5. 按协议过滤

    1
    2
    3
    4
    5
    tcpdump -i enp0s3 arp
    tcpdump -i enp0s3 ip
    tcpdump -i enp0s3 tcp
    tcpdump -i enp0s3 udp
    tcpdump -i enp0s3 icmp

当然,可以使用多条件组合来筛选数据包,使用 and 以及 or 逻辑操作符来创建过滤规则。例如,筛选来自源 IP 地址 192.168.122.98 的 HTTP 数据包:

1
sudo tcpdump -i any -c5 -nn src 192.168.122.98 and port 80

你也可以使用括号来创建更为复杂的过滤规则,但在 shell 中请用引号包含你的过滤规则以防止被识别为 shell 表达式:

1
sudo tcpdump -i any -c5 -nn "port 80 and (src 192.168.122.98 or src 54.204.39.132)"

高级筛选

以 ip 报文为例

下面为 ip 报文的报文头信息说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding | <-- optional
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| DATA ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

通过指定位置的字节进行筛选

1
2
3
4
5
6
7
# proto 即协议类型 ip tcp 等
# 一个字节占据8位byte
proto[x:y] : 过滤从x字节开始的y字节数。比如ip[2:2]过滤出3、4字节(第一字节从0开始排)
proto[x:y] & z = 0 : proto[x:y]和z的与操作为0
proto[x:y] & z !=0 : proto[x:y]和z的与操作不为0
proto[x:y] & z = z : proto[x:y]和z的与操作为z
proto[x:y] = z : proto[x:y]等于z

操作符:

1
2
3
4
5
6
>  : greater 大于
< : lower 小于
>= : greater or equal 大于或者等于
<= : lower or equal 小于或者等于
= : equal 等于
!= : different 不等于

当我们访问一个地址比如说curl 172.20.10.4:4000时,通过抓包我们可以得到请求的报文内容

1
2
3
4
5
6
7
8
9
0x0000:  4500 0078 48a2 4000 4006 f9ec 0ad3 3706  E..xH.@.@.....7.
0x0010: ac14 0a04 9340 0fa0 7a28 0f8a a297 21db .....@..z(....!.
0x0020: 5018 00e5 f85b 0000 4745 5420 2f20 4854 P....[..GET./.HT
0x0030: 5450 2f31 2e31 0d0a 5573 6572 2d41 6765 TP/1.1..User-Age
0x0040: 6e74 3a20 6375 726c 2f37 2e32 392e 300d nt:.curl/7.29.0.
0x0050: 0a48 6f73 743a 2031 3732 2e32 302e 3130 .Host:.172.20.10
0x0060: 2e34 3a34 3030 300d 0a41 6363 6570 743a .4:4000..Accept:
0x0070: 202a 2f2a 0d0a 0d0a 0000 0000 0000 0000 .*/*............
0x0080: 0000 0000 0000 0000 ........

报文内容以 16 进制展示,一个字节即为 2 位 16 进制数字,我们可以看到第 40 个字节后的4745 5420即为内容GET 

1
sudo tcpdump -i any 'ip[40:4] = 0x47455420' -A -nn -f

来抓取数据报文中中请求方法为GET 的请求

mermaid

发表于 2020-08-13 更新于 2020-12-02 分类于 hexo

mermaid 是一个流程图插件的语法,可定义多种类型的流程图,详细文档参考官方文档

流程图

基本的语法 mermaid_基本的语法.png 不要使用;结尾,否则 hexo 可能会渲染会失败,有些关键词不能作为 id,比如end

graph LR;
    Start --> Stop

graph 标明当前为流程图,LR 标明方向

方向

- TB - top to bottom - TD - top-down/ same as top to bottom - BT - bottom to top - RL - right to left - LR - left to right

节点形状

节点可以设置显示文本用于区分与唯一 id

  1. id1[text1]
  2. id2(text2)
graph LR
   id1[text1]
   id2(text2)

线条

线条的长度可以用-来增加

  1. A-->B;
  2. A---B;
  3. A---|text|B
  4. A-->|text|B
  5. A-.->B;
  6. A-.->|text|B
  7. A ==> B

文字可以统一在线条语法后使用|text|的方式来

graph LR
    A-->|1|B;
    A---|2|B;
    A---|3|B
    A-->|4|B
    A-.->|5|B;
    A-.->|6|B
    A ==>|7|B

子图

语法

1
2
3
subgraph title
graph definition
end

例如

1
2
3
4
5
6
7
8
9
10
11
graph TB
c1-->a2
subgraph one
a1-->a2
end
subgraph two
b1-->b2
end
subgraph three
c1-->c2
end
graph TB
    c1-->a2
    subgraph one
    a1-->a2
    end
    subgraph two
    b1-->b2
    end
    subgraph three
    c1-->c2
    end

css 样式

1
2
3
4
5
graph LR
id1(Start)-->id2(Stop)
id3:::red-->id2(Stop)
style id1 fill:#f9f,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
graph LR
    id1(Start)-->id2(Stop)
    id3:::red-->id2(Stop)
    style id1 fill:#f9f,stroke:#333,stroke-width:4px
    style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5

可定义 class

1
2
3
graph LR
A:::someclass --> B
classDef someclass fill:#f96;
graph LR
    A:::someclass --> B
    classDef someclass fill:#f96;

avaya

发表于 2020-08-13 更新于 2020-12-02 分类于 ivr

一通会话的进线过程

  1. 用户拨打电话
  2. 电信运营商接入会话,根据被叫号将会话接入卡中心
  3. 卡中心硬件板卡将模拟电路信号转换为数字信号
  4. SystemManager
  5. epm
  6. mpp
  7. asr
  8. tts
  9. ivr
  10. 座席
graph LR
    1[客户]-->2[运营商]
    2-->|isdn/sip/模拟信号|3[avaya硬件板卡]
    3-->|数字信号|4[SystemManager]
    6-->|CCXML|10[座席]
    subgraph avaya
        4-->5[epm]
        5-->6[mpp]
        5-.->|配置|7[asr]
        5-.->|配置|8[tts]
        5-.->|配置|9[ivr]
        6-->|MRCP|7[asr]
        6-->|MRCP|8[tts]
        6-->|VXML|9[ivr]
    end

aaod 开发过程的一些设想

  • 多模块的开发,在 maven 运行前将非公共流程的代码,通过 shell 脚本合并到一起,然后将所有模块打包成一个应用。涉及到跨模块的流程调用,统一使用一个虚拟的公共流程做跳板,对流程的出口做预设处理。
  • 挂机流程中注册回调流程。指定的流程中触发挂机事件需要跳转到回调流程中。

office

发表于 2020-08-03 更新于 2020-12-02

word

word 的层级结构可以通过增加或减少缩进量来控制层级 增加缩进量 tab 减少缩进量 shift tab 也可以右键通过

  1. 项目符号--> 更改列表级别
  2. 编号-->更改列表级别

voicexml

发表于 2020-07-28 更新于 2020-12-02

三省

发表于 2020-07-27 更新于 2020-12-02

克己

  1. 尽一切可能使用英文
  2. 注意左右平衡姿态
  3. 记录自己
  4. 有空时重复听硅谷练习英语
  5. 坚持读书,一本中文一本英文
  6. 复盘

为人处事

  1. 不要故意说反话
  2. 事情没有按预期发展的时候,不要张口就责怪他人
  3. 不跟关系不大的人争论、不试图说服任何人
  4. 想夸人的话落实到小细节上,更能拉近距离。
  5. 回答一个问题前有个思索的过程。不是装 B,而是大致组织一下语言
  6. 主动回避别人的敏感点
  7. 富兰克林效应
  8. 慢速说话

注意事项

  1. 常备份重要资源

读书计划

  1. elon musk(在读)
  2. 李光耀回忆录
  3. 待定(英文)
  4. 红星照耀中国

websites

发表于 2020-07-24 更新于 2020-12-02 分类于 code

免费英文网站

  1. Library Genesis
  2. zlibray

查找资源

  1. wiki
  2. 好用工具

w3c

  1. voicexml
  2. ccxml

eclipse_plugin_develop_tutorial_setup

发表于 2020-07-24 更新于 2020-12-02 分类于 eclipse

安装软件

  1. 下载 eclipse 在官方链接下载合适的版本,我选择的版本为 oxygen 3a
  2. 安装插件 wiki上维护了最新的官方插件地址,选择你的 eclipse 对应版本的插件地址,例如4.7 在 eclipse 中点击Help -> Install New Software eclipse-plugin-develop-tutorial-setup_安装插件.png

概念

eclipse 工具区由多个部件组成,包括 menu bar,tool bar 等 他们的组成结构如下所示 eclipse-plugin-develop-tutorial-setup_layout.png eclipse 中工具区示例 eclipse-plugin-develop-tutorial-setup_工作区.png

扩展点 常用 - org.eclipse.u.views- add a view - org.eclipse.ui.viewActions - add an action under a view - org.eclipse.ui.editors - allows a user to edit an object(e.g. file), it is like a view, but can be opened multiple times. - org.eclipse.ui.editorActions - add action under an editor - org.eclipse.ui.popupMenus - add a popup menu. A popup menu is a memu shown by right-clicking. There are two types, one is popup for an object, the other is for popup in editor. - org.eclipse.ui.actionSets - use for adding menus, menu items, and tool bar items to the workbench menus and toolbar. - org.eclipse.ui.commands - declaration of a behaviour by id, then other plugins can use the command. It allows "define once, use everywhere". - org.eclipse.ui.menus - can associate with a command and place the command in the main menu, view dropdown menus, context menus, main toolbar, view toolbars, and various trim locations. - org.eclipse.ui.handlers - define handler for a command - org.eclipse.ui.bindings - bind shortcut key for a command

入门实例

  1. 新建一个插件工程 eclipse-plugin-develop-tutorial-setup_new.png

    eclipse-plugin-develop-tutorial-setup_new_name.png 使用一个唯一 ID

  2. 新增一个扩展点 MANIFEST.MF为插件工程的配置文件 eclipse-plugin-develop-tutorial-setup_new_extensions.png

    eclipse-plugin-develop-tutorial-setup_new_extensions_2.png 保存后会生成build.xml文件,编辑该文件,在扩展点org.eclipse.ui.perspectives下,增加perspective布局

  3. 编辑扩展点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <?xml version="1.0" encoding="UTF-8"?>
    <?eclipse version="3.4"?>
    <plugin>
    <extension
    point="org.eclipse.ui.perspectives">
    <perspective
    class="com.leaderli.helloworld.PerspectiveFactory"
    id="com.leaderli.HelloWoldPerspective"
    icon="icon/workflow.png"
    name="hello">
    </perspective>
    </extension>

    </plugin>
    • name 为透视图的名称
    • icon 为透视图的图标,其值为项目路径下的文件
  4. 透视图增加窗口 增加扩展点org.eclipse.ui.perspectiveExtensions,并编辑扩展点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <extension
    point="org.eclipse.ui.perspectiveExtensions">
    <perspectiveExtension targetID="com.leaderli.HelloWoldPerspective">
    <view
    id="org.eclipse.jdt.ui.PackageExplorer"
    minimized="false"
    moveable="false"
    ratio="0.5"
    relationship="left"
    relative="org.eclipse.ui.console.ConsoleView"
    visible="true">
    </view>
    <view
    id="org.eclipse.ui.console.ConsoleView"
    minimized="false"
    moveable="false"
    ratio="0.5"
    relationship="right"
    relative="org.eclipse.jdt.ui.PackageExplorer"
    visible="true">
    </view>
    </perspectiveExtension>
    </extension>
  5. 启动 eclipse-plugin-develop-tutorial-setup_start.png eclipse-plugin-develop-tutorial-setup_perspective.png 我们可以看到生成了 hello 透视图,且透视图下有 console 窗口和资源窗口

perspective

透视图是一个包含一系列视图和内容编辑器的可视容器。默认的透视图叫 java。透视图的布局可自定义修改

在插件中定义一个透视图,并定义一些布局

hexo

发表于 2020-07-15 更新于 2020-12-02 分类于 code

快捷方式

站内引用,引用自己的博客

使用F8快速定位markdown语法错误的地方。

插入的图片上面需要有一行文字,否则生成的网页图片显示会有瑕疵

调试

运行命令时带上参数--debug,则会输出console.log()所打印的日志

本地搜索

安装插件

1
npm install hexo-generator-searchdb --save

根目录_config.yml中开启本地搜索,xml对于特殊字符的处理可能存在问题,建议使用json

1
2
3
4
5
6
7
#本地搜索
search:
# path: search.xml
path: search.json
field: post
content: true
format: html

同时theme下的配置需要开启

1
2
3
4
5
6
7
8
9
local_search:
enable: true
# If auto, trigger search by changing input.
# If manual, trigger search by pressing enter key or search button.
trigger: auto
# Show top n results per article, show all results by setting to -1
top_n_per_article: 1
# Unescape html strings to the readable one.
unescape: false

hexo开始搜索使用插件hexo-generator-searchdb,插件在扫描项目文件后生成search.json文件。 该文件按照特定格式存储了博客的所有内容。但该插件记录的中文为ascii码,因此在搜索时使用中文搜索不到 博客正文内容,因此我们需要修改生成search.json文件的过程。将ascii转码。

修改项目根目录下的文件node_modules/hexo-generator-searchdb/lib/json_generator.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
"use strict";

module.exports = function (locals) {
var config = this.config;
var database = require("./database")(locals, config);

// ---------------------------------------------新增修改
function reconvert(str) {
str = str.replace(/(\\u)(\w{1,4})/gi, function ($0) {
return String.fromCharCode(
parseInt(escape($0).replace(/(%5Cu)(\w{1,4})/g, "$2"), 16)
);
});
str = str.replace(/(&#x)(\w{1,4});/gi, function ($0) {
return String.fromCharCode(
parseInt(escape($0).replace(/(%26%23x)(\w{1,4})(%3B)/g, "$2"), 16)
);
});
str = str.replace(/(&#)(\d{1,6});/gi, function ($0) {
return String.fromCharCode(
parseInt(escape($0).replace(/(%26%23)(\d{1,6})(%3B)/g, "$2"))
);
});

return str;
}
database.forEach(function (item) {
item.content = reconvert(item.content);
});
// -----------------------------------------------
return {
path: config.search.path,
data: JSON.stringify(database),
};
};

vscode 中图片无法预览

vscode 中的 markdown 图片仅能预览当前 md 文件下或其子文件中的图片,但是若是图片在这儿,渲染后的 html 文件中将无法查看图片。我们自定义两个插件来解决这个问题。

根据hexo api我们编写两个插件

  1. 首先我们在source/_posts目录下建一个专门用于存放图片的文件夹images,在 markdown 中我们使用![name](./images/picture.png)来插入图片,这样我们在 vscode 中就可以预览图片了。
  2. 编写一个插件hexo-li-assert-image,用于将![name](./images/picture.png)渲染后的<img>中的 src 替换为/images/picture.png
  3. 编写一个插件,用于在渲染完成后,将source/_posts/images/的所有图片拷贝到public/images下

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
"use strict";
var cheerio = require("cheerio");

hexo.extend.filter.register("after_post_render", function (data) {
var path = hexo.config.li.path; //_config.yml中新增配置
if (path.length < 1) {
return;
}

var toprocess = ["excerpt", "more", "content"];
for (var i = 0; i < toprocess.length; i++) {
var key = toprocess[i];
var $ = cheerio.load(data[key], {
ignoreWhitespace: false,
xmlMode: false,
lowerCaseTags: false,
decodeEntities: false,
});
$("img").each(function () {
if ($(this).attr("src")) {
var src = $(this).attr("src").replace("\\", "/");
if (src.startsWith("./images/")) {
src = src.substring(1);
$(this).attr("src", src);
}
}
});
data[key] = $.html();
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
"use strict";
var fs = require("fs");

hexo.on("generateAfter", function (post) {
var path = hexo.config.li.path;
if (path.length < 1) {
return;
}
var source_img_path = path + "source/_posts/images/";
var target_img_path = path + "public/images/";

if (!fs.existsSync(target_img_path)) {
console.log("mkdir " + target_img_path);
fs.mkdirSync(target_img_path, { recursive: true });
}

let paths = fs.readdirSync(source_img_path);
paths.forEach(function (path) {
if (path.startsWith(".")) {
return;
}
var _src = source_img_path + path;
var _tar = target_img_path + path;
if (fs.existsSync(_tar)) {
return;
}
fs.copyFileSync(_src, _tar, fs.constants.COPYFILE_EXCL);
});
});

本地搜索增加正则模式

当我们开始搜索时,若以/开始,当输入的是有效的 js 正则表达式(/reg/)时,才会开始搜索所有文章,开始搜索时会将正则表达式的的最后一个/截掉

使用 next 主题,修改其本地搜索的逻辑。拷贝` themes/next/layout/_third-party/search/localsearch.swig为li_localsearch.swig`。 将其修改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
{% if theme.local_search.enable %}
<script>
// Popup Window;
var isfetched = false;
var isXml = true;
// Search DB path;
var search_path = "{{ config.search.path }}";
if (search_path.length === 0) {
search_path = "search.xml";
} else if (/json$/i.test(search_path)) {
isXml = false;
}
var path = "{{ config.root }}" + search_path;
// monitor main search box;

var onPopupClose = function (e) {
$(".popup").hide();
$("#local-search-input").val("");
$(".search-result-list").remove();
$("#no-result").remove();
$(".local-search-pop-overlay").remove();
$("body").css("overflow", "");
};

function proceedsearch() {
$("body")
.append(
'<div class="search-popup-overlay local-search-pop-overlay"></div>'
)
.css("overflow", "hidden");
$(".search-popup-overlay").click(onPopupClose);
$(".popup").toggle();
var $localSearchInput = $("#local-search-input");
$localSearchInput.attr("autocapitalize", "none");
$localSearchInput.attr("autocorrect", "off");
$localSearchInput.focus();
}

// search function;
var searchFunc = function (path, search_id, content_id) {
"use strict";

// start loading animation
$("body")
.append(
'<div class="search-popup-overlay local-search-pop-overlay">' +
'<div id="search-loading-icon">' +
'<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>' +
"</div>" +
"</div>"
)
.css("overflow", "hidden");
$("#search-loading-icon")
.css("margin", "20% auto 0 auto")
.css("text-align", "center");

$.ajax({
url: path,
dataType: isXml ? "xml" : "json",
async: true,
success: function (res) {
// get the contents from search data
isfetched = true;
$(".popup").detach().appendTo(".header-inner");
var datas = isXml
? $("entry", res)
.map(function () {
return {
title: $("title", this).text(),
content: $("content", this).text(),
url: $("url", this).text(),
};
})
.get()
: res;
var input = document.getElementById(search_id);
var resultContent = document.getElementById(content_id);
var inputEventFunction = function () {
var searchText = input.value.toLowerCase();
if (searchText.length < 1) {
return;
}
var keywords = [];
var first_keyword;
if (searchText.charAt(0) === "/") {
if (searchText.indexOf("/", 1) == 1) {
keywords.push(searchText.substring(1));
} else if (
searchText.length > 2 &&
searchText.charAt(searchText.length - 1) === "/"
) {
keywords.push("$$");
try {
eval("first_keyword = " + searchText + "g");
} catch (err) {
console.error(err);
return;
}
input.value = searchText.substring(0, searchText.length - 1);
} else {
return;
}
} else {
keywords.push(searchText);
}
var resultItems = [];
// perform local searching
datas.forEach(function (data) {
var isMatch = false;
var hitCount = 0;
var searchTextCount = 0;
var title = data.title.trim();
var titleInLowerCase = title.toLowerCase();
var content = data.content.trim();
var contentInLowerCase = content.toLowerCase();
var articleUrl = decodeURIComponent(data.url).replace(
/\/{2,}/g,
"/"
);
var indexOfTitle = [];
var indexOfContent = [];
// only match articles with not empty titles
if (title != "") {
if (first_keyword) {
keywords = contentInLowerCase.match(first_keyword);
}
if (!keywords) {
keywords = [];
}
keywords.forEach(function (keyword) {
if (
keyword === "test" &&
titleInLowerCase.indexOf("bash") > -1
) {
console.log(contentInLowerCase);
}
function getIndexByWord(word, text, caseSensitive) {
var wordLen = word.length;
if (wordLen === 0) {
return [];
}
var startPosition = 0,
position = [],
index = [];
if (!caseSensitive) {
text = text.toLowerCase();
word = word.toLowerCase();
}
while ((position = text.indexOf(word, startPosition)) > -1) {
index.push({ position: position, word: word });
startPosition = position + wordLen;
}
return index;
}

indexOfTitle = indexOfTitle.concat(
getIndexByWord(keyword, titleInLowerCase, false)
);
indexOfContent = indexOfContent.concat(
getIndexByWord(keyword, contentInLowerCase, false)
);
});
if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
isMatch = true;
hitCount = indexOfTitle.length + indexOfContent.length;
}
}

// show search results

if (isMatch) {
// sort index by position of keyword

[indexOfTitle, indexOfContent].forEach(function (index) {
index.sort(function (itemLeft, itemRight) {
if (itemRight.position !== itemLeft.position) {
return itemRight.position - itemLeft.position;
} else {
return itemLeft.word.length - itemRight.word.length;
}
});
});

// merge hits into slices

function mergeIntoSlice(text, start, end, index) {
var item = index[index.length - 1];
var position = item.position;
var word = item.word;
var hits = [];
var searchTextCountInSlice = 0;
while (position + word.length <= end && index.length != 0) {
if (word === searchText) {
searchTextCountInSlice++;
}
hits.push({ position: position, length: word.length });
var wordEnd = position + word.length;

// move to next position of hit

index.pop();
while (index.length != 0) {
item = index[index.length - 1];
position = item.position;
word = item.word;
if (wordEnd > position) {
index.pop();
} else {
break;
}
}
}
searchTextCount += searchTextCountInSlice;
return {
hits: hits,
start: start,
end: end,
searchTextCount: searchTextCountInSlice,
};
}

var slicesOfTitle = [];
if (indexOfTitle.length != 0) {
slicesOfTitle.push(
mergeIntoSlice(title, 0, title.length, indexOfTitle)
);
}

var slicesOfContent = [];
while (indexOfContent.length != 0) {
var item = indexOfContent[indexOfContent.length - 1];
var position = item.position;
var word = item.word;
// cut out 100 characters
var start = position - 20;
var end = position + 80;
if (start < 0) {
start = 0;
}
if (end < position + word.length) {
end = position + word.length;
}
if (end > content.length) {
end = content.length;
}
slicesOfContent.push(
mergeIntoSlice(content, start, end, indexOfContent)
);
}

// sort slices in content by search text's count and hits' count

slicesOfContent.sort(function (sliceLeft, sliceRight) {
if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
return sliceRight.searchTextCount - sliceLeft.searchTextCount;
} else if (sliceLeft.hits.length !== sliceRight.hits.length) {
return sliceRight.hits.length - sliceLeft.hits.length;
} else {
return sliceLeft.start - sliceRight.start;
}
});

// select top N slices in content

var upperBound = parseInt(
"{{ theme.local_search.top_n_per_article }}"
);
if (upperBound >= 0) {
slicesOfContent = slicesOfContent.slice(0, upperBound);
}

// highlight title and content

function highlightKeyword(text, slice) {
var result = "";
var prevEnd = slice.start;
slice.hits.forEach(function (hit) {
result += text.substring(prevEnd, hit.position);
var end = hit.position + hit.length;
result +=
'<b class="search-keyword">' +
text.substring(hit.position, end) +
"</b>";
prevEnd = end;
});
result += text.substring(prevEnd, slice.end);
return result;
}

var resultItem = "";

if (slicesOfTitle.length != 0) {
resultItem +=
"<li><a href='" +
articleUrl +
"' class='search-result-title'>" +
highlightKeyword(title, slicesOfTitle[0]) +
"</a>";
} else {
resultItem +=
"<li><a href='" +
articleUrl +
"' class='search-result-title'>" +
title +
"</a>";
}

slicesOfContent.forEach(function (slice) {
resultItem +=
"<a href='" +
articleUrl +
"'>" +
'<p class="search-result">' +
highlightKeyword(content, slice) +
"...</p>" +
"</a>";
});

resultItem += "</li>";
resultItems.push({
item: resultItem,
searchTextCount: searchTextCount,
hitCount: hitCount,
id: resultItems.length,
});
}
});
if (keywords.length === 1 && keywords[0] === "") {
resultContent.innerHTML =
'<div id="no-result"><i class="fa fa-search fa-5x"></i></div>';
} else if (resultItems.length === 0) {
resultContent.innerHTML =
'<div id="no-result"><i class="fa fa-frown-o fa-5x"></i></div>';
} else {
resultItems.sort(function (resultLeft, resultRight) {
if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
return resultRight.searchTextCount - resultLeft.searchTextCount;
} else if (resultLeft.hitCount !== resultRight.hitCount) {
return resultRight.hitCount - resultLeft.hitCount;
} else {
return resultRight.id - resultLeft.id;
}
});
var searchResultList = '<ul class="search-result-list">';
resultItems.forEach(function (result) {
searchResultList += result.item;
});
searchResultList += "</ul>";
resultContent.innerHTML = searchResultList;
}
};

if ("auto" === "{{ theme.local_search.trigger }}") {
input.addEventListener("input", inputEventFunction);
} else {
$(".search-icon").click(inputEventFunction);
input.addEventListener("keypress", function (event) {
if (event.keyCode === 13) {
inputEventFunction();
}
});
}

// remove loading animation
$(".local-search-pop-overlay").remove();
$("body").css("overflow", "");

proceedsearch();
},
});
};

// handle and trigger popup window;
$(".popup-trigger").click(function (e) {
e.stopPropagation();
if (isfetched === false) {
searchFunc(path, "local-search-input", "local-search-result");
} else {
proceedsearch();
}
});

$(".popup-btn-close").click(onPopupClose);
$(".popup").click(function (e) {
e.stopPropagation();
});
$(document).on("keyup", function (event) {
var shouldDismissSearchPopup =
event.which === 27 && $(".search-popup").is(":visible");
if (shouldDismissSearchPopup) {
onPopupClose();
}
});
</script>
{% endif %}

修改同目录下的index.swig

1
2
{% include 'algolia-search.swig' %} {% if theme.local_search.li%} {% include
'li_localsearch.swig' %} {% else %} {% include 'localsearch.swig' %} {% endif %}

在主题配置文件中,增加

1
2
3
4
5
6
7
8
9
10
local_search:
enable: true
# If auto, trigger search by changing input.
# If manual, trigger search by pressing enter key or search button.
trigger: auto
# Show top n results per article, show all results by setting to -1
top_n_per_article: 1
# Unescape html strings to the readable one.
unescape: false
li: true

后台进程启动 hexo server

在博客根目录下面创建一个 hexo_run.js

1
2
3
4
5
6
7
8
9
const { exec } = require("child_process");
exec("hexo server", (error, stdout, stderr) => {
if (error) {
console.log("exec error: ${error}");
return;
}
console.log("stdout: ${stdout}");
console.log("stderr: ${stderr}");
});
1
2
3
4
#全局安装
$ npm install -g pm2
#在根目录下运行
$ pm2 start hexo_run.js

增加 echarts 树图

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
hexo 中 在 `{% %}`中包含的内容,会被 hexo 视为一个 tag

例如

{% pullquote li-echarts-tree %}

- 前言
- 使用方法
- 使用方法
- 1
- 二
- 一
- 二
- 太长不看
- 参考资料

{% endpullquote %}

hexo 插件node_modules/hexo/lib/plugins/tag/index.js中会注册相关 tag 的处理器 例如

1
tag.register("pullquote", require("./pullquote")(ctx), true);

node_modules/hexo/lib/plugins/tag/pullquote.js的内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"use strict";

/**
* Pullquote tag
*
* Syntax:
* {% pullquote [class] %}
* Quote string
* {% endpullquote %}
*/
module.exports = (ctx) =>
function pullquoteTag(args, content) {
args.unshift("pullquote");

const result = ctx.render.renderSync({ text: content, engine: "markdown" });

return `<blockquote class="${args.join(" ")}">${result}</blockquote>`;
};

pullquote最终生成一个<blockquote>标签,并且其 class为pullquote以及其后定义的值,我们在theme下的footer.swig中,新增关于对li-echarts-tree的元素进行 echarts 渲染。我们定义无序列表的来实现 echarts 中的树图。无序列表会被渲染成ul,li元素。那么我们可以这样实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<script src="/js/echarts.min.js"></script>

<script>
function recursion_tree(dom, tree) {
tree = tree || {}
if (dom.tagName === 'UL') {
tree.children = []
Array.prototype.forEach.call(dom.children, child => {
tree.children.push(recursion_tree(child))
})
return tree;
} else if (dom.tagName === 'LI') {
html = dom.innerHTML
tree.name = html;
var ulIndex = html.indexOf('<')
if (ulIndex > 0) {
tree.name = html.substring(0, ulIndex).trim()
Array.prototype.forEach.call(dom.children, child => {
console.log(child)
console.log(recursion_tree(child, tree))

})
}
return tree
} else {

}
}
var title = document.querySelector('.post-title').innerText.trim()

Array.prototype.forEach.call(document.getElementsByClassName("li-echarts-tree"), function (dom) {
dom = dom.children[0]
data = recursion_tree(dom, { "name": title })
var div = document.createElement('div')
div.setAttribute('style', 'width: 600px;height:400px;')
dom.innerHTML=''
dom.appendChild(div);
var myChart = echarts.init(div);
myChart.setOption(option = {
tooltip: {
trigger: 'item',
triggerOn: 'mousemove'
},
series: [
{
type: 'tree',

data: [data],

top: '1%',
left: '7%',
bottom: '1%',
right: '20%',

symbolSize: 7,

label: {
position: 'left',
verticalAlign: 'middle',
align: 'right',
fontSize: 9
},

leaves: {
label: {
position: 'right',
verticalAlign: 'middle',
align: 'left'
}
},

expandAndCollapse: false,
animationDuration: 350,
animationDurationUpdate: 450
}
]
});
})
</script>

hexo 错误解决

Error: expected end of comment, got end of file

markdown和KaTex_注释.png
markdown和KaTex_注释.png

不支持写双大括号,使用下述方式来替代

1
<span>{</span><span>{</span><span>}</span><span>}</span>
1234…15

lijun

与其感慨路难行,不如马上就出发
145 日志
30 分类
117 标签
© 2019 – 2020 lijun