Skip to content

Commit 5390347

Browse files
committedJul 24, 2022
docs: update scripts
1 parent 3608fe5 commit 5390347

File tree

4 files changed

+388
-21
lines changed

4 files changed

+388
-21
lines changed
 
+326
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
# 利用 Github Action 实现博客自动发版
2+
3+
![](https://cdn.zhangferry.com/Images/carl-heyerdahl-KE0nC8-58MQ-unsplash.jpg)
4+
5+
## 背景
6+
7+
先说下背景需求,在摸鱼周报的整理流程中,最后一步需要生成公众号的原文链接,原文链接指向的是个人博客地址。博客需要发布才能产生外部链接,发布到不费事,但是操作步骤重复,且因为涉及博客推送相关的配置都在我的个人电脑里,所有步骤必须由我来完成。来回多次之后就考虑将这个流程做成自动化了,目标是让周报协作者都可以实现博客推送,用到的实现方式是 Github Action。
8+
9+
## 实现思路
10+
11+
在开始之前先了解下原先的发布流程,如下图表示:
12+
13+
![](https://cdn.zhangferry.com/Images/20220719235714.png)
14+
15+
整个过程涉及 3 个仓库:
16+
17+
* Moyu Repo。管理周报文章的公共仓库,协作者可以通过它拉取和推送内容。
18+
19+
* Blog Repo。管理博客内容的私有仓库,周报只是其中一部分。
20+
21+
* Blog Website。博客的网站,它部署在一台腾讯云服务器上,它也是私有的。
22+
23+
因为涉及两个私有仓库,普通协作者都没有他们的访问权限,所以发布流程都依赖我来完成。解决方案就是消除发布流程对权限的依赖,理想流程是这样的:
24+
25+
![](https://cdn.zhangferry.com/Images/20220719235757.png)
26+
27+
这样触发入口就都集中在了共有仓库,协作者也可以参与博客发布。要实现这个流程需要将需求分为两步:
28+
29+
1、Moyu Repo 通过 Github Action 推送 Moyu 内容到 Blog Repo。
30+
31+
2、Blog Repo 通过 Github Action 发布内容到网站。
32+
33+
这其中最关键的是访问私有仓库时如何处理权限的问题。
34+
35+
## Github Action
36+
37+
这里先简单了解下 Github Action。它是 Github 提供的为仓库定义自动化流程的方案,类似 Jenkins、GitLab CI/CD。Github Action 有一套自己的流水线配置方式,所有的流程都可以通过一个 yml 文件下发。Gtihub Action 有自己的虚拟机,支持 Windows Server/Linux/Mac,使用者无需关心环境配置问题,可以直接使用。
38+
39+
配置入口如下图所示:
40+
41+
![](https://cdn.zhangferry.com/Images/20220724120626.png)
42+
43+
点击`set up a workflow yourself`,即创建了一个用于编排自动化任务的 workflow,它对应一个 yml 文件,所有的配置流程都在这里进行。
44+
45+
自动化任务配置前我们需要先考虑这几个问题:什么时机触发?在什么设备运行?如何执行自动化任务?我们看一个简单的例子来学习下 Github Action 如何定义这些行为:
46+
47+
```yml
48+
name: GitHub Actions Demo
49+
on: [push]
50+
jobs:
51+
Explore-GitHub-Actions:
52+
runs-on: ubuntu-latest
53+
steps:
54+
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
55+
- name: Check out repository code
56+
uses: actions/checkout@v3
57+
```
58+
59+
name 表示当前流水线的名字。
60+
61+
### 什么时机触发
62+
63+
在什么场景触发,对应的key 是 `on`。上面Demo里的`[push]`,表示仓库发生push行为时触发任务。on 还有其他几种触发途径:
64+
65+
* pull_request:提交 PR 时触发
66+
* schedule:定时触发,可以按 cron 语法配置定频
67+
* workflow_dispatch:手动触发,有用户手动激活触发行为
68+
69+
### 在什么设备运行
70+
71+
对应的关键词是`runs-on`,Demo里指定值为`ubuntu-latest`,表示执行设备是一个 ubuntu 设备。Github Action 还支持 macOS 环境,目前有三个 macOS 版本可以支持:
72+
73+
| 虚拟环境 | YAML标签 |
74+
| -------------------- | -------------------------- |
75+
| macOS Monterey 12 | macos-12 |
76+
| macOS Big Sur 11 | macos-latest` 或 `macos-11 |
77+
| macOS Catalina 10.15 | macos-10.15 |
78+
79+
需要注意:macos-latest 不是最新的 macos 版本,而是 macOS 11。iOS 开发中我们可能还会关心 Xcode 版本,Ruby 版本等。以 macOS 12 虚拟机为例,Xcode 版本:
80+
81+
| Version | Build | Path |
82+
| ---------------- | -------- | ------------------------------ |
83+
| 13.4.1 (default) | 13F100 | /Applications/Xcode_13.4.1.app |
84+
| 13.4 | 13F17a | /Applications/Xcode_13.4.app |
85+
| 13.3.1 | 13E500a | /Applications/Xcode_13.3.1.app |
86+
| 13.2.1 | 13C100 | /Applications/Xcode_13.2.1.app |
87+
| 13.1 | 13A1030d | /Applications/Xcode_13.1.app |
88+
89+
Ruby版本:2.7.6/3.0.4/3.1.2。
90+
91+
其他预制环境可以参考这篇文档:[macos-12-Readme](https://github.com/actions/virtual-environments/blob/main/images/macos/macos-12-Readme.md "macos-12-Readme")。
92+
93+
另外 Github Action 还支持将自己的设备定义为运行机,你可以在这里了解:[About self-hosted runners](https://docs.github.com/cn/enterprise-server@3.1/actions/hosting-your-own-runners/about-self-hosted-runners "About self-hosted runners");支持联机调试,可以通过这个插件了解:[A debugger for actions](https://github.com/marketplace/actions/a-debugger-for-actions "A debugger for actions")。
94+
95+
### 如何执行自动化任务
96+
97+
有两种执行任务的方式,一种是直接在 yml 文件里编辑脚本,关键词是`run`。像是 Demo 里的 echo 命令,我们可以直接输入 shell 命令进行执行。
98+
99+
另一种方式是插件市场,像下面这种形式:
100+
101+
```yml
102+
- name: Check out repository code
103+
uses: actions/checkout@v3
104+
```
105+
106+
就是使用了 `actions/checkout@v3` 这个插件。Github 有一个插件市场,可以搜索所需插件。像是 Code review,SSH 登录等都有封装好的插件可以直接使用。
107+
108+
![](https://cdn.zhangferry.com/Images/20220723192554.png)
109+
110+
## 实现方案
111+
112+
有了这些 Github Action 知识,我们就可以开始实现我们的需求了。最终效果分成两个需求。
113+
114+
### Moyu Repo 向 Blog Repo 推送内容
115+
116+
我们按照前面的三个问题来设计这个功能。
117+
118+
#### 什么时机触发?
119+
120+
发布之前需要经过多次修改,会有多个 PR 和 Push 行为,而 Blog 发布需要等所有内容都准备完成才会执行,一般只有一次。所以考虑使用手动发布的方式,以下是配置内容:
121+
122+
```yml
123+
# Action name
124+
name: Weekly Article Deploy
125+
# Controls when the workflow will run
126+
on:
127+
# Allows you to run this workflow manually from the Actions tab
128+
workflow_dispatch:
129+
inputs:
130+
weekly_index:
131+
description: 'weekly index for deploy'
132+
```
133+
134+
手动发布还补充了一个 `inputs`,用于接收输出参数,`weekly_index`为参数名,用于表示要发布第几期。执行效果如下所示:
135+
136+
![](https://cdn.zhangferry.com/Images/20220723194746.png)
137+
138+
#### 在什么设备运行?
139+
140+
这个需要根据执行任务来定,这里只涉及一些文本转换和仓库操作,所以任意机器都满足需求,ubuntu 资源比较多,调度会快那么一点点,所以都可的情况优先选 ubuntu。
141+
142+
```yml
143+
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
144+
jobs:
145+
# This workflow contains a single job called "build"
146+
build:
147+
# The type of runner that the job will run on
148+
runs-on: ubuntu-latest
149+
```
150+
151+
Github Action 里有几个名词:workflow,job,steps,这里简单捋一下。整个 Yml 文件对应为一个 workflow,它表示一次完整的自动化任务运行过程。当前仓库的整个配置都是一个 workflow。
152+
153+
一个 workflow 可以包含一个或多个 job,这里的 `jobs` 下面一级内容就是各个 job。不同 job 之间可以串行也可以并行。`build`为会其中一个 job,也是本 workflow 唯一的 job。
154+
155+
#### 如何执行自动化任务?
156+
157+
这个流程需要做的事情是把 Moyu Repo 内容转成 Blog Repo 的格式,然后推送到 Blog Repo 里。前一步可以封装成一个脚本,后一步往私有仓库推送需要生成一个具有推送私有仓库权限的 token。
158+
159+
token 的生成需要到这里:个人头像 -> Settings -> Developer settings -> Personal access tokens,点击 Generate new token。这一步需要输入密码,然后我们可以选择所需权限去生成一个token。对于私有仓库的推送,我们选中这一个权限就可以了:
160+
161+
![](https://cdn.zhangferry.com/Images/20220723200801.png)
162+
163+
为了安全考虑,这个token生成之后只会可见一次,因为后面的步骤会使用,所以我们需要做好保存。
164+
165+
注意这个 token 是用户级别的,它可以用于访问修改该账户名下的任意仓库。
166+
167+
为了让 Github Action 可以访问到这个token,需要给它做一个配置。配置路径是:在该仓库下的 Settings(注意这个是仓库下的设置而非个人下的设置) -> Secrets -> Actions 点击 New repository secret。
168+
169+
![](https://cdn.zhangferry.com/Images/20220724012424.png)
170+
171+
Name 的话可以命名为 `ACCESS_TOKEN`,Value 为上一步生成的访问 token。这里配置的任意内容都可以通过Github Action 访问到,且是加密的,创建之后只能看到 Name 看不到 Value。
172+
173+
![](https://cdn.zhangferry.com/Images/20220724012350.png)
174+
175+
下面是具体配置:
176+
177+
```yaml
178+
env:
179+
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
180+
181+
# Steps represent a sequence of tasks that will be executed as part of the job
182+
steps:
183+
- name: print inputs
184+
run: |
185+
echo "Weekly Index: ${{ github.event.inputs.weekly_index }}"
186+
187+
- name: Git Config
188+
run: |
189+
git config --global user.email moyuweekly@github.com
190+
git config --global user.name moyuweekly
191+
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
192+
- uses: actions/checkout@v3
193+
194+
# Runs a single command using the runners shell
195+
- name: Run a one-line script
196+
run: ./Script/ci_run.sh ${{ github.event.inputs.weekly_index }}
197+
shell: bash
198+
```
199+
200+
`secrets.ACCESS_TOKEN` 表示的是该仓库下面 secrets 里 name 为 `ACCESS_TOKEN` 的内容。`${{}}`为 action 语法获取变量的写法。
201+
202+
因为我把对私有仓库的获取和推送都放到了执行脚本里,所以这里通过环境变量的形式把这个值传给脚本。
203+
204+
steps 用于表述运行步骤,这里是顺序执行的。上述流程涉及到4个执行步骤:
205+
206+
1、打印外部传参。`${{github.event.inputs.weekly_index}}` 表示输入的参数。
207+
208+
2、配置 git user 的 email 和 name。因为执行内容涉及提交操作,这里是为了标记自动化流程的提交。
209+
210+
3、uses 语法对应的是插件功能,这里输入插件名即可执行对应插件配置的功能。 `actions/checkout@v3` 是官方插件,用于拉取本仓库代码的。
211+
212+
4、执行脚本。我把私有仓库的拉取,内容的格式化,私有仓库推送都放到了这个脚本里。
213+
214+
私有仓库的管理需要考虑 Git 链接的形式,Git 链接有两种方式,一种是给 SSH 形式,这对于本地机器比较容易,对于不固定的打包机配置起来较为麻烦。第二种是 HTTPS 形式,HTTPS 是公有链接无法处理权限问题,但 Github 支持把 token 传入链接的形式来管理权限。这里选择使用HTTPS形式,配置的 Git 地址如下:
215+
216+
```
217+
https://{github_token}@github.com/username/repo.git
218+
```
219+
220+
对仓库的操作使用这个链接就可以解决权限问题了,执行结果如下:
221+
222+
![](https://cdn.zhangferry.com/Images/20220723203939.png)
223+
224+
左边是 job 描述,右边是 steps 描述,每个 steps 都可以展开查看详情。因为这里的步骤只有代码拉取推送和格式处理,所以执行很快。
225+
226+
### Blog Repo 发布网站
227+
228+
这个阶段对应的是 Blog Repo 推送内容到腾讯云服务器。还是按上面的流程设计实现方式:
229+
230+
#### 如何触发任务?
231+
232+
这个历程是上一步的承接,前一步已经定好了推送频率,这里可以接收到 push 即触发任务。
233+
234+
```yml
235+
push:
236+
branches: [ "master" ]
237+
```
238+
239+
#### 在什么机器触发?
240+
241+
这需要考虑到所使用的博客框架,如果是 Hexo/Jekyll 使用 Ubutu 就可以了。因为我将博客框架迁移到了 [Publish](https://github.com/JohnSundell/Publish "Publish"),Publish 是一个由 Swift 编写的静态博客框架,所以运行机器只能是 macOS。测试时发现 Publish 引用了`libswift_Concurrency.dylib` 这个库,所以还需要指定版本为 macos-12。
242+
243+
```yml
244+
jobs:
245+
build:
246+
# macOS Monterey 12
247+
runs-on: macos-12
248+
```
249+
250+
#### 如何执行自动化任务?
251+
252+
执行流程大概是这样的:编译 publish -> 使用 publish 把 md 源文件转成静态网站格式 -> 发布到腾讯云。
253+
254+
正常能获取到 publish 执行文件是无需编译的,但因为我为了让它兼容 hexo 的格式,做了一些魔改,所以我使用的 publish 是一个动态的版本,它需要随修改随时编译。
255+
256+
发布至腾讯云,也需要考虑权限问题,个人服务器没有 Github 那种 token 授权形式,只能借助于 SSH 了。
257+
258+
**SSH**
259+
260+
在开始之前再简单回顾下 SSH 登录的一点原理。SSH 支持密码和密钥两种登录方式,我们一般为省去繁琐的密码输入,会采用密钥登录的形式。密钥登录使用的是对称加密,一般的做法是登录端生成一对公私钥,把公钥放到服务端,私钥保存在本地。对称加密解决的是信息传输不会被篡改的问题,它无法防止中间人攻击,因为它没有HTTPS 那样的 CA 来验证可信性。SSH 选择的是通过手动验证的方式,关于手动验证不知你是否还还记得这段内容:
261+
262+
```
263+
The authenticity of host 'host (172.168.*.*)' can't be established.
264+
RSA key fingerprint is 98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d.
265+
Are you sure you want to continue connecting (yes/no)?
266+
```
267+
268+
它就是用来手动验证的,我们需要通过 host ip 验证该链接是来自于可信的服务器还是中间人。确定过一次之后,这个信息会被写到本地的 `known_hosts` 文件中,之后对于同一 ip 的服务器登录就不会再弹这个验证了。对于自动化流程来说,我们应该将私有服务器的验证信息直接填入`known_hosts`文件,跳过阻塞式的二次确认。
269+
270+
**流程配置**
271+
272+
有了以上知识,我们对于密钥的配置流程应该是如下图所示:
273+
274+
![](https://cdn.zhangferry.com/Images/20220723233812.png)
275+
276+
蓝色的钥匙为 pub_key,红色钥匙为 private_key,带钥匙的文件是 `know_hosts`。Github Action Runner 的配置流程如果都手动实现比较麻烦,我们可以使用[install-ssh-key](https://github.com/marketplace/actions/install-ssh-key "install-ssh-key")这个插件快速实现这个功能:
277+
278+
```yml
279+
- name: Install SSH Key
280+
uses: shimataro/ssh-key-action@v2.3.1
281+
with:
282+
key: ${{ secrets.SSH_PRIVATE_KEY }}
283+
# it's value doesn't matter
284+
known_hosts: 'knowen_host_value for ssh-rsa'
285+
```
286+
287+
它要求两个参数,key 为 ssh 的 private key。SSH 私钥可以使用上个章节介绍的 Secrets Actions 进行存储,将其命名为 `SSH_PRIVATE_KEY`。
288+
289+
`known_hosts` 是对 Server 端的信任记录,用于免去手动确认的流程。这个内容的获取,有两种方式,你可以查看本地的 `known_hosts` 文件找到对应的目标服务器的记录,也可以利用 `ssh-keyscan` 去手动查找。
290+
291+
```bash
292+
$ ssh-keyscan -H <host-ip>
293+
```
294+
295+
这个结果会按多种加密算法产生多个结果,我们需要选择类型为 ssh-rsa 的内容,[因为Github Action 仅支持这一种加密结果](https://zellwk.com/blog/github-actions-deploy/ "Deploying to a server via SSH and Rsync in a Github Action")。
296+
297+
![](https://cdn.zhangferry.com/Images/20220724001820.png)
298+
299+
我们把这一条内容添加 `known_hosts` 参数即可。当然你也可以选择使用密钥的形式存放。
300+
301+
最重要的步骤已经完成了,下面就可以编译 publish 并发布内容了。
302+
303+
```yml
304+
- name: Create Publish
305+
run: |
306+
git clone https://github.com/zhangferry/Publish.git ./publish
307+
cd publish
308+
make
309+
cd ..
310+
- name: Blog Deploy
311+
run: |
312+
echo "begin deploy"
313+
./publish/.build/release/publish-cli deploy
314+
```
315+
316+
最后执行结果:
317+
318+
![](https://cdn.zhangferry.com/Images/20220723210525.png)
319+
320+
#### 遗留问题
321+
322+
往腾讯云服务器推送内容时遇到一个腾讯云给我发了告警邮件,说是检测到异地登录,来源IP是美国,危险等级高危。这个登录应该指的是 Github Action Runner 设备的登录,这个目前还没有找到有效的解决办法,因为IP是动态的,无法通过手动加白的形式避免。
323+
324+
另外也可关闭异地登录的报警,在[告警设置](https://console.cloud.tencent.com/cwp/setting)里关闭异地登录报警选项即可。但这种方式也存在一定的安全风险,因为它不是解决问题而是无视问题。
325+
326+
我暂时没有找到更好的解决方案,如果有人知道更好的处理方式,欢迎告知。

‎Script/deploy.py

+54-19
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99

1010
class BlogRepo:
1111
"""blog仓库信息"""
12-
def __init__(self, token, git_url, branch):
13-
self.token = token
12+
def __init__(self, git_url, branch):
1413
self.git_url = git_url
1514
self.branch = branch
1615
self.execute_path = os.getcwd()
@@ -27,7 +26,7 @@ def clone_or_update_repo(self):
2726
os.chdir(f"{self.repo_path}")
2827
os.system(f"git reset --hard origin/{self.branch}")
2928

30-
def get_blog_head_contents(self, title):
29+
def get_blog_head_contents(self, title, file_path, tags):
3130
"""拼接文章抬头,固定内容"""
3231
top_line = "---"
3332
the_title = f"title: {title}"
@@ -40,17 +39,33 @@ def get_blog_head_contents(self, title):
4039
blog_url = f"https://zhangferry.com/{date_path_format}/{file_name_format}"
4140
print(f"Blog url: {blog_url}")
4241

43-
cover = "cover: https://cdn.zhangferry.com/Images/moyu_weekly_cover.jpeg"
44-
tags = "tags:\n\t- iOS摸鱼周报"
42+
cover, tags_str = self.ready_blog_head(file_path=file_path, tags=tags)
4543
comments = "comments: true"
4644
empty_line = "\n"
4745

48-
list_str = [top_line, the_title, today, cover, tags, comments, top_line, empty_line]
46+
list_str = [top_line, the_title, today, cover, tags_str, comments, top_line, empty_line]
4947
total_title = "\n".join(list_str)
5048
return total_title
5149

50+
def ready_blog_head(self, file_path, tags):
51+
cover_url = ""
52+
if "iOSWeeklyLearning" in self.file_name:
53+
cover_url = "https://cdn.zhangferry.com/Images/moyu_weekly_cover.jpeg"
54+
tags_value = ["iOS摸鱼周报"]
55+
else:
56+
with open(file_path, "r") as fileHandler:
57+
lines = fileHandler.read()
58+
# find the first img as cover
59+
cover_url = re.findall(r"!\[.*\]\((.*)\)", lines)[0]
60+
tags_value = tags.split(",")
61+
tags_value_str = "tags:"
62+
for tag_value in tags_value:
63+
tags_value_str += f"\n\t- {tag_value}"
64+
65+
return cover_url, tags_value_str
66+
5267
# 修改文件
53-
def modify_file(self, title_str, file_path):
68+
def modify_file(self, title_str, file_path, tags):
5469
"""修改内容"""
5570
self.file_name = os.path.basename(file_path)
5671
with open(file_path, "r+") as fileHandler:
@@ -60,7 +75,7 @@ def modify_file(self, title_str, file_path):
6075
if len(title_str) == 0:
6176
print(f"Add new article: {self.file_name}")
6277
title = lines[0].strip("#").strip()
63-
title_content = self.get_blog_head_contents(title)
78+
title_content = self.get_blog_head_contents(title, file_path, tags)
6479
new_content.append(title_content)
6580
else:
6681
print(f"Update article: {self.file_name}")
@@ -87,13 +102,17 @@ def push(self):
87102
class BlogArticleBuilder:
88103
"""创建博客文章,如果以存在会自动更新"""
89104
def __init__(self):
90-
parser = argparse.ArgumentParser(description='Input the weekly index')
105+
parser = argparse.ArgumentParser(description='Input the article identifier')
91106
parser.add_argument('--index', '-i', type=int, help='Please input the weekly index')
107+
parser.add_argument('--name', '-n', type=str, help='Please input the Articles name')
108+
parser.add_argument('--tags', '-t', type=str, help='Please input article tags, separate with commas')
92109
parse_args = parser.parse_args()
93110
self.weekly_index = parse_args.index
111+
self.article_name = parse_args.name
112+
self.tags = parse_args.tags
94113

95114
def get_weekly_article_path(self):
96-
"""get article source path"""
115+
"""get weekly article source path"""
97116
# scripts path
98117
script_path = sys.path[0]
99118
weekly_folder_path = script_path + "/../" + "WeeklyLearning"
@@ -107,6 +126,17 @@ def get_weekly_article_path(self):
107126

108127
return f"{weekly_folder_path}/iOSWeeklyLearning_{self.weekly_index}.md"
109128

129+
def get_personal_article_path(self):
130+
"""get personal artcile source path"""
131+
if not self.article_name:
132+
raise Exception("You must assign article name")
133+
script_path = sys.path[0]
134+
articles_folder_path = script_path + "/../" + "Articles"
135+
article_path = articles_folder_path + f"/{self.article_name}.md"
136+
if not os.path.exists(article_path):
137+
raise Exception("Article path is not exist")
138+
return article_path
139+
110140
# 复制文件到目标路径
111141
def copy_file_to_repo(self, source_path, target_folder):
112142
"""copy weekly file to blog repo"""
@@ -129,26 +159,31 @@ def copy_file_to_repo(self, source_path, target_folder):
129159
return head_str, target_path
130160

131161
def run_with(self, blog_repo):
132-
article_path = self.get_weekly_article_path()
162+
if self.article_name:
163+
article_path = self.get_personal_article_path()
164+
else:
165+
article_path = self.get_weekly_article_path()
133166
# print(article_path)
134-
blog_repo.clone_or_update_repo()
167+
# blog_repo.clone_or_update_repo()
135168
target_folder = f"{blog_repo.repo_path}/Content/posts"
136169
# print(target_folder)
137170
head_str, target_path = self.copy_file_to_repo(source_path=article_path, target_folder=target_folder)
138-
blog_repo.modify_file(head_str, target_path)
139-
blog_repo.push()
171+
blog_repo.modify_file(head_str, target_path, self.tags)
172+
# blog_repo.push()
140173

141174

142175
if __name__ == '__main__':
143176

144177
builder = BlogArticleBuilder()
145178

146-
# 通过环境变量传入
147-
blog_token = os.environ["ACCESS_TOKEN"]
148-
149-
blog_git_url = f"https://{blog_token}@github.com/zhangferry/GithubPage.git"
179+
if os.environ.get("CI"):
180+
# 通过环境变量传入
181+
blog_token = os.environ["ACCESS_TOKEN"]
182+
blog_git_url = f"https://{blog_token}@github.com/zhangferry/GithubPage.git"
183+
else:
184+
blog_git_url = "git@github.com:zhangferry/GithubPage.git"
150185
blog_branch = "master"
151-
blog_repo = BlogRepo(token=blog_token, git_url=blog_git_url, branch=blog_branch)
186+
blog_repo = BlogRepo(git_url=blog_git_url, branch=blog_branch)
152187
builder.run_with(blog_repo=blog_repo)
153188

154189
print("Deploy weekly article success! It will take effect in about 10 minutes 🚀")

‎WeeklyLearning/iOSWeeklyLearning_61.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ App 实现录音保存音频文件,并实现本地语音识别匹配功能。
137137

138138
1. [JSON Hero](https://github.com/jsonhero-io/jsonhero-web "JSON Hero"),一款让你轻松直观地查看 JSON 文档的工具,为你提供类似 Mac Finder 体验的工具。
139139

140-
![](https://cdn.zhangferry.com/Images/features-treeview (1).gif)
140+
![](https://cdn.zhangferry.com/Images/features-treeview.gif)
141141

142142
如果你的 JSON 文件足够庞大,这款软件必不可少。
143143

‎WeeklyLearning/iOSWeeklyLearning_62_process.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# iOS 摸鱼周报 #59
1+
# iOS 摸鱼周报 #62
22

33
![](https://cdn.zhangferry.com/Images/moyu_weekly_cover.jpeg)
44

@@ -22,7 +22,13 @@
2222

2323
## 内容推荐
2424

25+
1、 [开源|WBBlades重要节点更新-专为提效而设计](https://mp.weixin.qq.com/s/tXxhnDKerobyxoWuEBGjNQ) -- 来自:58技术
2526

27+
[@皮拉夫大王]():给iOS开发人员提供基于Mach-O文件解析的工具集,工具包括无用类检测(支持OC和Swift)、包大小分析(支持单个静态库/动态库的包大小分析)、点对点崩溃解析(基于系统日志,支持有符号状态和无符号状态),主要基于Mach-O文件的分析、轻量符号表剥离,系统日志解析等技术手段。
28+
29+
2、[iOS不必现崩溃的点对点解析以及治理](https://mp.weixin.qq.com/s/tGvE-2flzhm4skkrfbUIBA) -- 来自:58技术
30+
31+
[@皮拉夫大王]():本文章中介绍iOS端发生崩溃后,在无法复现的情况下如何针对各种不同类型的崩溃日志进行解析,包括普通堆栈,wakesup崩溃,json格式日志,bugly堆栈类型等。此外还介绍了系统日志存在异常情况进行自动修正的方法,包括进程名称丢失,基地址丢失,偏移地址错误等。
2632

2733
## 摸一下鱼
2834

0 commit comments

Comments
 (0)
Please sign in to comment.