/images/logo.jpg

[Spring] 1. spring web CompletionStage 浅谈

[Spring] 1. spring web CompletionStage 浅谈

介绍

  • spring-web里对异步的支持做的很好,可以通过异步返回的形式,做许多优化
    • 提高吞吐量
    • 精细调控各业务执行线程池

样例说明

 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
/**
 * async interface controller
 *
 * @author Goody
 * @version 1.0, 2024/9/19
 */
@RestController
@RequestMapping("/goody")
@RequiredArgsConstructor
@Slf4j
public class GoodyAsyncController {

    private static final AtomicInteger COUNT = new AtomicInteger(0);
    private static final Executor EXECUTOR = new ThreadPoolExecutor(
        10,
        10,
        10,
        TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(10),
        r -> new Thread(r, String.format("customer-t-%s", COUNT.addAndGet(1)))
    );

    @GetMapping("async/query1")
    public CompletionStage<String> asyncQuery1() {
        log.info("async query start");
        return CompletableFuture.supplyAsync(() -> {
            log.info("async query sleep start");
            ThreadUtils.sleep(1000);
            log.info("async query sleep done");
            log.info("async query done");
            return "done";
        }, EXECUTOR);
    }

    @GetMapping("sync/query1")
    public String syncQuery1() throws InterruptedException {
        log.info("sync query start");
        final CountDownLatch latch = new CountDownLatch(1);
        EXECUTOR.execute(() -> {
            log.info("sync query sleep start");
            ThreadUtils.sleep(1000);
            log.info("sync query sleep done");
            latch.countDown();
        });
        latch.await();
        log.info("sync query done");
        return "done";
    }
}
  • 定义了一个自定义的线程池,用于异步状态下使用
  • 这里一个同步,一个异步,可以看下具体的请求情况

单次请求

请求异步接口

curl –location ‘127.0.0.1:50012/goody/async/query1’

[Java] 1. Lombok

[Java] 1. Lombok

简介

  • Lombok是一款Java库,它可以自动为Java类生成一些重复性的代码,如 getter、setter、equals 和 hashCode 等方法。

原理

  • Lombok 的工作原理是基于注解处理器 (Annotation Processor)和Java Compiler API 。

  • 当 Lombok 被编译器发现时,它会使用注解处理器来修改 Java 代码。在这个过程中,Lombok 会检查类中的特定注解,并根据这些注解生成相应的代码,如 getter、setter 等方法。

  • 具体来说,Lombok 使用的是 Apache BCEL(Bytecode Engineering Library)库来直接操作 Java 字节码,而不是通过反射或运行时操作。这样一来,Lombok 可以在编译期就进行修改,从而提高性能和效率。

注解

  • 自定义一个纯净类Node,编译后如下
1
2
3
4
public class Node {
    private Long item1;
    private String item2;
}
1
2
3
4
5
6
7
public class Node {
    private Long item1;
    private String item2;

    public Node() {
    }
}
  • 解释一下,Java编译器会自定给一个无参构造器,因为任何类不能没有构造器

实体类

@Data & @EqualsAndHashCode & @Getter & @Setter & @ToString

1
2
3
4
5
@Data
public class Node {
    private Long item1;
    private String item2;
}
  • 我们最常用的@Data,其实是一些基本注解的集合,比如@Getter、@Setter、@EqualsAndHashCode、@ToString
 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
public class Node {
  private Long item1;
  private String item2;

  public Node() {
  }

  public Long getItem1() {
    return this.item1;
  }

  public String getItem2() {
    return this.item2;
  }

  public void setItem1(Long item1) {
    this.item1 = item1;
  }

  public void setItem2(String item2) {
    this.item2 = item2;
  }

  public boolean equals(Object o) {
    if (o == this) {
      return true;
    } else if (!(o instanceof Node)) {
      return false;
    } else {
      Node other = (Node)o;
      if (!other.canEqual(this)) {
        return false;
      } else {
        Object this$item1 = this.getItem1();
        Object other$item1 = other.getItem1();
        if (this$item1 == null) {
          if (other$item1 != null) {
            return false;
          }
        } else if (!this$item1.equals(other$item1)) {
          return false;
        }

        Object this$item2 = this.getItem2();
        Object other$item2 = other.getItem2();
        if (this$item2 == null) {
          if (other$item2 != null) {
            return false;
          }
        } else if (!this$item2.equals(other$item2)) {
          return false;
        }

        return true;
      }
    }
  }

  protected boolean canEqual(Object other) {
    return other instanceof Node;
  }

  public int hashCode() {
    int PRIME = true;
    int result = 1;
    Object $item1 = this.getItem1();
    result = result * 59 + ($item1 == null ? 43 : $item1.hashCode());
    Object $item2 = this.getItem2();
    result = result * 59 + ($item2 == null ? 43 : $item2.hashCode());
    return result;
  }

  public String toString() {
    return "Node(item1=" + this.getItem1() + ", item2=" + this.getItem2() + ")";
  }
}
  • 这里介绍一些基本的进阶用法
1
2
3
4
5
6
7
8
@Getter
@Setter
public class Node {
    @Getter(AccessLevel.PRIVATE)
    private Long item1;
    @Setter(AccessLevel.PRIVATE)
    private String item2;
}
  • 可以看到,设置对应的PRIVATE权限的方法变成了private。这样有利于做一些权限的集中
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Node {
    private Long item1;
    private String item2;

    public Node() {
    }

    public String getItem2() {
        return this.item2;
    }

    public void setItem1(Long item1) {
        this.item1 = item1;
    }

    private Long getItem1() {
        return this.item1;
    }

    private void setItem2(String item2) {
        this.item2 = item2;
    }
}

@AllArgsConstructor & @NoArgsConstructor & @RequiredArgsConstructor

1
2
3
4
5
6
@NoArgsConstructor
@AllArgsConstructor
public class Node {
    private Long item1;
    private String item2;
}
  • 很明显会生成无参构造器,全参构造器。必须参构造器,会与无参构造器产生冲突,所以另外展示。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Node {
    private Long item1;
    private String item2;

    public Node() {
    }

    public Node(Long item1, String item2) {
        this.item1 = item1;
        this.item2 = item2;
    }
}
  • 必须参构造器
1
2
3
4
5
6
@RequiredArgsConstructor
@AllArgsConstructor
public class Node {
    private final Long item1;
    private String item2;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Node {
    private final Long item1;
    private String item2;

    public Node(Long item1) {
        this.item1 = item1;
    }

    public Node(Long item1, String item2) {
        this.item1 = item1;
        this.item2 = item2;
    }
}
  • 这里最经典的组合如下
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Service
@Slf4j
@RequiredArgsConstructor
public class Node {
    private final Long item1;
    private final Long item2;
    private final Long item3;
    @Autowired
    @Lazy
    private String lazyBean;
}
  • 输出如下
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Service
public class Node {
    private static final Logger log = LoggerFactory.getLogger(Node.class);
    private final Long item1;
    private final Long item2;
    private final Long item3;
    @Autowired
    @Lazy
    private String lazyBean;

    public Node(Long item1, Long item2, Long item3) {
        this.item1 = item1;
        this.item2 = item2;
        this.item3 = item3;
    }
}
  • 可以看到,这里原本可以初始化的bean,依然可以初始化,有冲突或者需要lazy的,依然会遵循lazy

[MySQL] 1. 浅谈 MySQL 快速查询

[MySQL] 1. 浅谈 MySQL 快速查询

讲在开头

  • 在最开始先举几个我们常用的在平时学习、业务上最常见的优化措施

    1. 单位时间内更多的事情

      • 快排使用二分的思想,单次循环内对多个数组进行排序

        /images/3.%20%E6%B5%85%E8%B0%88MySQL%E5%BF%AB%E9%80%9F%E6%9F%A5%E8%AF%A2/1.%20%E5%BF%AB%E6%8E%92.jpg

    2. 总量查更少的信息

      • KMP 算法对主串进行预处理,做到减少匹配次数。这就是逻辑的力量

        /images/3.%20%E6%B5%85%E8%B0%88MySQL%E5%BF%AB%E9%80%9F%E6%9F%A5%E8%AF%A2/2.%20KMP.jpg

      • 搜索树通过二分的思想,每次过滤剩余数据的一半来提高效率。这就是数据结构的力量

        /images/3.%20%E6%B5%85%E8%B0%88MySQL%E5%BF%AB%E9%80%9F%E6%9F%A5%E8%AF%A2/3.%20%E6%90%9C%E7%B4%A2%E6%A0%91.webp

  • 对于 MySQL 的快速查询,最为关键的核心就是查询数据量少,越少越快。整篇文章均围绕该句进行展开。

没有索引是否一定会慢?

  • 定义插入数据函数
 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
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
    id      bigint(20)                          NOT NULL COMMENT '用户id',
    biz_id  bigint(20)                          NOT NULL COMMENT '业务id',
    message text COMMENT '业务信息',
    created timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
    updated timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
) ENGINE = InnoDB COMMENT '用户'
  CHARSET = utf8mb4;

DELIMITER
$$
CREATE PROCEDURE insertUserData(IN start_id int, IN end_id int, IN bizId int)
BEGIN
    DECLARE i int DEFAULT start_id;

    WHILE i <= end_id
        DO
            INSERT INTO user (id, biz_id, message) VALUES (i, bizId, SUBSTRING(MD5(RAND()), 1, 8));

            SET i = i + 1;
        END WHILE;
END
$$
DELIMITER ;
  • 准备数据若干,bizId 为 1 的只有两条,第一条和最后一条数据
1
2
3
4
5
6
-- 插入第一条业务id1数据
CALL insertUserData(1, 1, 1);
-- 插入一百万万条其他数据
CALL insertUserData(2, 1000000, 2);
-- 插入第二条业务id1数据
CALL insertUserData(1000001, 1000001, 1);
  • 其实看数据分布,我想大家已经知道我想表达的,即便不使用索引。我们这里也是有快速查询的场景。

查询 limit1

[Network] 1. 文件传输优化分享

[Network] 1. 文件传输优化分享

背景(敏感数据已脱敏)

  • 由于种种要求,需要将数据上传到海外的 oss 上进行存储。所以开发了一个代理服务维护数据并进行加密等操作。期间内部发现数据上传下载非常慢,经过一系列排查,最终定位到问题根源,并给出解决方案。现将排查过程进行分享。
  • 当然了,前提之一是内网打通,通过专线网络接入,才能做到理论上的物理极限。使用复杂漫长的公共网络既不适合文件安全,也不适合大文件长时间传输。

服务本身问题

描述
  • 最初怀疑是数据落盘导致的太慢。因为上传必须落盘,防止文件过大。下载直接流式传输,非常合理。唯一的改进是上传进行流式加密和传输,但是当前问题不大。

现象

  • 使用编写的脚本上传 1M 的加密数据,耗时接近 2s
1
2
3
4
5
import requests
requests.post(f"{url}/upload/files", files={
    "data": ('', upload_data, "application/json"),
    "file": transfer_data
})
1
2
3
4
5
6
7
$ python oss.py --file_input=./1M.data --region=us --model=3 --range=5
encrypted_upload
upload ./1M.data, encrypt cost 4.714599609375, upload cost 1788.95849609375
upload ./1M.data, encrypt cost 10.140625, upload cost 1945.90087890625
upload ./1M.data, encrypt cost 9.924560546875, upload cost 1756.984130859375
upload ./1M.data, encrypt cost 8.694580078125, upload cost 1930.31201171875
upload ./1M.data, encrypt cost 8.279296875, upload cost 1739.38623046875

抓包

  • 与运维进行沟通,运维怀疑是网络问题,进行抓包一探究竟。

抓包演示

ping 包

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ sudo tcpdump -i bond0 | grep x.x.x.x1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on bond0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:21:19.255718 IP public2.alidns.com.domain > domain1.36590: 43190 1/0/1 A x.x.x.x1 (88)
16:21:19.256404 IP domain1 > x.x.x.x1: ICMP echo request, id 32590, seq 1, length 64
16:21:19.456754 IP x.x.x.x1 > domain1: ICMP echo reply, id 32590, seq 1, length 64
16:21:20.257688 IP domain1 > x.x.x.x1: ICMP echo request, id 32590, seq 2, length 64
16:21:20.458076 IP x.x.x.x1 > domain1: ICMP echo reply, id 32590, seq 2, length 64
16:21:21.259088 IP domain1 > x.x.x.x1: ICMP echo request, id 32590, seq 3, length 64
16:21:21.459506 IP x.x.x.x1 > domain1: ICMP echo reply, id 32590, seq 3, length 64
16:21:22.260538 IP domain1 > x.x.x.x1: ICMP echo request, id 32590, seq 4, length 64
16:21:22.460976 IP x.x.x.x1 > domain1: ICMP echo reply, id 32590, seq 4, length 64
1
2
3
4
5
6
7
8
9
$ ping domain1
PING domain1 (x.x.x.x1) 56(84) bytes of data.
64 bytes from x.x.x.x1 (x.x.x.x1): icmp_seq=1 ttl=58 time=200 ms
64 bytes from x.x.x.x1 (x.x.x.x1): icmp_seq=2 ttl=58 time=200 ms
64 bytes from x.x.x.x1 (x.x.x.x1): icmp_seq=3 ttl=58 time=200 ms
^C
--- domain1 ping statistics ---
4 packets transmitted, 3 received, 25% packet loss, time 3004ms
rtt min/avg/max/mdev = 200.395/200.419/200.456/0.517 ms

三次握手

1
2
3
16:54:06.286416 IP domain1.33666 > x.x.x.x1.http: Flags [S], seq 2682796272, win 64240, options [mss 1460,sackOK,TS val 2595135963 ecr 0,nop,wscale 7], length 0
16:54:06.486797 IP x.x.x.x1.http > domain1.33666: Flags [S.], seq 2198055866, ack 2682796273, win 62643, options [mss 1460,sackOK,TS val 2062390218 ecr 2595135963,nop,wscale 7], length 0
16:54:06.486840 IP domain1.33666 > x.x.x.x1.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 2595136163 ecr 2062390218], length 0

四次挥手

1
2
3
16:54:28.356723 IP domain1.54028 > x.x.x.x1.http: Flags [F.], seq 1746, ack 215, win 501, options [nop,nop,TS val 2595158034 ecr 2062412087], length 0
16:54:28.557169 IP x.x.x.x1.http > domain1.54028: Flags [F.], seq 215, ack 1747, win 477, options [nop,nop,TS val 2062412289 ecr 2595158034], length 0
16:54:28.557222 IP domain1.54028 > x.x.x.x1.http: Flags [.], ack 216, win 501, options [nop,nop,TS val 2595158234 ecr 2062412289], length 0

tcpdump Flags

  • Tcpdump flags 是指示 TCP 连接状态或动作的标志。它们通常在 tcpdump 输出中用方括号表示。Tcpdump 输出中有多种 flags,输出也可能包含多个 TCP flags 的组合 1。一些常见的 flags 有:
    • S (SYN): 这个 flag 用于在两个主机之间建立连接。它在三次握手的第一个包中设置。
    • . (No flag): 这意味着在包中没有设置任何 flag。它通常用于数据传输或确认包。
    • P (PUSH): 这个 flag 用于表示发送方希望尽快发送数据,而不等待缓冲区填满。
    • F (FIN): 这个 flag 用于终止两个主机之间的连接。它在四次挥手的最后一个包中设置。
    • R (RST): 这个 flag 用于重置处于无效状态或遇到错误的连接。它也用于拒绝不想要的连接尝试。
    • W (ECN CWR): 这个 flag 用于表示发送方已经根据网络的显式拥塞通知 (ECN) 减小了其拥塞窗口大小。
    • E (ECN-Echo): 这个 flag 用于表示接收方已经收到了一个带有 ECN 位的包,这意味着网络中存在拥塞。
  • 例如,一个带有 flags [S.] 的包意味着它是一个 SYN 包,是建立 TCP 连接的第一步。一个带有 flags [P.] 的包意味着它是一个 PUSH 包,包含了发送方想要快速传送的数据。一个带有 flags [F.] 的包意味着它是一个 FIN 包,是关闭 TCP 连接的最后一步 2。

为什么 tcpdump 四次挥手只有三个包

  • Tcpdump 四次挥手只有三个包的原因可能有以下几种:
    • 一种可能是被动关闭方(收到 FIN 的一方)在回复 ACK 的同时,也发送了自己的 FIN,将第二次和第三次挥手合并为一个报文,节省了一个包 1。这种情况下,被动关闭方已经没有数据要发送了,所以可以直接进入 LAST_ACK 状态,等待主动关闭方的最后一个 ACK。
    • 另一种可能是主动关闭方(发送 FIN 的一方)在收到被动关闭方的 FIN 后,没有及时回复 ACK,而是在一段时间后才发送 ACK,并且在 ACK 中设置了 RST 标志,表示强制重置连接 2。这种情况下,主动关闭方可能遇到了异常或超时,所以不再等待 2MSL 的时间,而是直接进入 CLOSE 状态。
    • 还有一种可能是 tcpdump 没有抓到所有的包,因为网络延迟或丢包的原因,导致某个挥手的报文没有被捕获到 3。这种情况下,可以尝试重新抓包或增加抓包的时间范围,看是否能够看到完整的四次挥手过程。

实际数据

1
2
3
$ python oss.py --file_input=./1K.data --file_output=./download-1M.data --region=us --model=3 --range=5
encrypted_upload
http://domain1 upload ./1K.data, encrypt cost 1.530029296875, upload cost 408.5546875
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ sudo tcpdump -i bond0 | grep x.x.x.x1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on bond0, link-type EN10MB (Ethernet), capture size 262144 bytes

16:54:06.286416 IP domain1.33666 > x.x.x.x1.http: Flags [S], seq 2682796272, win 64240, options [mss 1460,sackOK,TS val 2595135963 ecr 0,nop,wscale 7], length 0
16:54:06.486797 IP x.x.x.x1.http > domain1.33666: Flags [S.], seq 2198055866, ack 2682796273, win 62643, options [mss 1460,sackOK,TS val 2062390218 ecr 2595135963,nop,wscale 7], length 0
16:54:06.486840 IP domain1.33666 > x.x.x.x1.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 2595136163 ecr 2062390218], length 0
16:54:06.486930 IP domain1.33666 > x.x.x.x1.http: Flags [P.], seq 1:292, ack 1, win 502, options [nop,nop,TS val 2595136164 ecr 2062390218], length 291: HTTP: POST /upload/files HTTP/1.1
16:54:06.486960 IP domain1.33666 > x.x.x.x1.http: Flags [P.], seq 292:1746, ack 1, win 502, options [nop,nop,TS val 2595136164 ecr 2062390218], length 1454: HTTP
16:54:06.687234 IP x.x.x.x1.http > domain1.33666: Flags [.], ack 292, win 488, options [nop,nop,TS val 2062390419 ecr 2595136164], length 0
16:54:06.687279 IP x.x.x.x1.http > domain1.33666: Flags [.], ack 1746, win 477, options [nop,nop,TS val 2062390419 ecr 2595136164], length 0
16:54:06.690277 IP x.x.x.x1.http > domain1.33666: Flags [P.], seq 1:215, ack 1746, win 477, options [nop,nop,TS val 2062390422 ecr 2595136164], length 214: HTTP: HTTP/1.1 200 OK
16:54:06.690314 IP domain1.33666 > x.x.x.x1.http: Flags [.], ack 215, win 501, options [nop,nop,TS val 2595136367 ecr 2062390422], length 0
16:54:06.692023 IP domain1.33666 > x.x.x.x1.http: Flags [F.], seq 1746, ack 215, win 501, options [nop,nop,TS val 2595136369 ecr 2062390422], length 0
16:54:06.892401 IP x.x.x.x1.http > domain1.33666: Flags [F.], seq 215, ack 1747, win 477, options [nop,nop,TS val 2062390624 ecr 2595136369], length 0
16:54:06.892448 IP domain1.33666 > x.x.x.x1.http: Flags [.], ack 216, win 501, options [nop,nop,TS val 2595136569 ecr 2062390624], length 0
  • 实际是上传 1M 数据,进行分析,此处简化。
  • 因为时间跳跃增长全部发生在服务端返回的数据包。此时问题已经很明了了,由于深圳和美东现实的物理距离,200ms 的来回已经做到了极限。所以其实是合理的。

灵魂拷问

  • 此时,一个灵魂拷问出现了,为什么之前走公网反而更快?
  • 与兄弟部门同事沟通,模拟他们的代码,使用 aws 的 sdk 进行测试
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import boto3
from boto3.s3.transfer import TransferConfig

def download():
    s3_client = client(access_key, access_secret, host)
    GB = 1024 ** 3
    config = TransferConfig(multipart_threshold=2 * GB, max_concurrency=10, use_threads=True)
    s3_client.download_file(Bucket="bucket", Key="name-100.jpg", Filename="name-100.jpg", Config=config)

if __name__ == '__main__':
    download()
    # ...
    download()

结果

[Github] 1. Deploy GitHub Blog Site

[Github] 1. Deploy GitHub Blog Site

1. intro

  • GitHub Pages is a static site hosting service that takes HTML, CSS, and JavaScript files straight from a repository on GitHub, optionally runs the files through a build process, and publishes a website.

2. pre-work

2.1 create a repository

  • Create a repository named your_github_username.github.io, where your_github_username is your GitHub username. For example, if your GitHub username is octocat, the repository name should be octocat.github.io.

2.2 hugo install

3. create a blog site

3.1 hugo init site

1
2
3
4
5
6
7
8
# create directory
mkdir your_github_username.github.io
# cd to directory
cd your_github_username.github.io
# init site
hugo new site .
# git init, make sure it's a git repository
git init

3.2 add a theme

1
2
3
4
5
6
7
8
9
# add a theme, here we use LoveIt theme.
git submodule add https://github.com/dillonzq/LoveIt.git themes/LoveIt
# now the git is main branch which is not stable, we need to checkout to the latest stable version.
cd themes/LoveIt
git checkout v0.3.0
cd ../..
# now, there should be a .gitmodules file in your directory. if not, you need to run `git init` first.
# copy the exampleSite config file to the root directory
cp themes/LoveIt/exampleSite/hugo.toml .

3.3 modify the config file

  • modify the config file hugo.toml

3.3.1 bashURL

1
baseURL = "https://gooddayday.github.io"

3.3.2 themes directory

1
2
3
# themes directory
# 主题目录
themesDir = "./themes"

3.3.3 website title

1
2
3
# website title
# 网站标题
title = "GoodyHao's Blog"

3.3.4 website images

1
2
3
# website images for Open Graph and Twitter Cards
# 网站图片, 用于 Open Graph 和 Twitter Cards
images = ["/logo.jpg"]

3.3.5 website icon

  • put icon file in the static directory

3.3.6 gitRepo

  • modify the gitRepo to your public git repo url
1
2
3
# public git repo url only then enableGitInfo is true
# 公共 git 仓库路径,仅在 enableGitInfo 设为 true 时有效
gitRepo = "https://github.com/GOODDAYDAY/GOODDAYDAY.github.io"

4. github deploy

4.1 create a workflow file

  • create a file .github/workflows/deploy.yaml, and add the following content:
 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
name: Deploy Hugo to GitHub Pages
on:
  push:  # 触发条件:推送代码到master分支
    branches:
      - master
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest  # 使用Ubuntu环境
    steps:
      # 1. 检出仓库代码(递归拉取主题submodule)
      - uses: actions/checkout@v4
        with:
          submodules: true
      
      # 2. 安装Hugo(使用extended版本,支持SASS)
      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: 'latest'  # 或指定版本(如'0.147.2')
          extended: true
      
      # 3. 缓存依赖(加快后续构建速度)
      - uses: actions/cache@v3
        with:
          path: |
            resources/_gen
            public
          key: ${{ runner.os }}-hugo-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-hugo-
      
      # 4. 构建Hugo站点(开启压缩)
      - name: Build Hugo site
        run: hugo --minify
      
      # 5. 部署到GitHub Pages(自动推送public目录到gh-pages分支)
      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN  }}  # GitHub自动提供的Token(无需手动创建)
          publish_dir: ./public  # 指向Hugo生成的静态文件目录
          force_orphan: true  # 强制创建新提交(避免分支历史混乱)
  • github repository settings -> pages -> source -> select gh-pages branch and / (root) folder -> save
    • if gh-pages branch not exist, need to push code to github first

/images/1.%20deploy%20github%20blog%20site.md/1.%20github-page-setting.png