## 同步本地Markdown至Typecho站点

本项目基于https://github.com/Sundae97/typecho-markdown-file-publisher

实现效果：

- [x] 将markdown发布到typecho
- [x] 发布前将markdown中的公式块和代码块进行格式化，确保能适配md解析器。
- [x] 发布前将markdown的图片资源上传到自己搭建的图床Easyimage中（自行替换成阿里云OSS等）, 并替换markdown中的图片链接。
- [x] 将md所在的文件夹名称作为post的category(mysql发布可以插入category, xmlrpc接口暂时不支持category操作)。
- [x] 以title和category作为文章的唯一标识，如果数据库中已有该数据，将会更新现有文章，否则新增文章。

环境：Typecho1.2.1  php8.1.0

### 项目目录

![image-20250319173057792](https://pic.bitday.top/i/2025/03/21/qimsxc-2.png)

`typecho_markdown_upload/main.py`是上传md文件到站点的核心脚本

`transfer_md/transfer.py`是对md文件进行预处理的脚本。



### **核心思路**

**一、预先准备**

**图床服务器**

本人自己在服务器上搭建了私人图床——easyimage，该服务能够实现图片上传并返回公网 URL，这对于在博客中正常显示 Markdown 文件中的图片至关重要。

当然，也可以选择使用公共图床服务，如阿里云 OSS，但这里不做详细介绍。

需手动修改`transfer_md/upload_img.py`，配置url、token等信息。

参考博客：[【好玩儿的Docker项目】10分钟搭建一个简单图床——Easyimage-我不是咕咕鸽](https://blog.laoda.de/archives/docker-compose-install-easyimage)

github地址：[icret/EasyImages2.0: 简单图床 - 一款功能强大无数据库的图床 2.0版](https://github.com/icret/EasyImages2.0)



**picgo安装：**

使用 Typora + PicGo + Easyimage 组合，可以实现将本地图片直接粘贴到 Markdown 文件中，并自动上传至图床。

下载地址：[Releases · Molunerfinn/PicGo](https://github.com/Molunerfinn/PicGo/releases)

操作步骤如下：

1. 打开 PicGo，点击右下角的小窗口。
2. 进入插件设置，搜索并安装 `web-uploader 1.1.1` 插件（注意：旧版本可能无法搜索到，建议直接安装最新版本）。
3. 配置插件：在设置中填写 API 地址，该地址可在 Easyimage 的“设置-API设置”中获取。

配置完成后，即可实现图片自动上传，提升 Markdown 编辑体验。

<img src="https://pic.bitday.top/i/2025/03/21/qimwlg-2.png" alt="image-20250319180022461" style="zoom:67%;" />



**Typora 设置**

为了确保在博客中图片能正常显示，编辑 Markdown 文档时**必须将图片上传至图床**，而不是保存在本地。请按以下步骤进行配置：

1. 在 Typora 中，打开 **文件 → 偏好设置 → 图像** 选项。
2. 在 “插入图片时” 选项中，选择 **上传图片**。
3. 在 “上传服务设定” 中选择 **PicGo**，并指定 PicGo 的安装路径。

![image-20250319175707761](https://pic.bitday.top/i/2025/03/21/qin8gd-2.png)



**文件结构统一**：

```text
md_files
├── category1
│   ├── file1.md
│   └── file2.md
├── category2
│   ├── file3.md
│   └── file4.md
└── output
    ├── assets_type
    ├── pics
    └── updated_files

```

**注意**：category对应上传到typecho中的文章所属的分类。

如果你现有的图片分散在系统中，可以使用 `transfer_md/transfer.py` 脚本来统一处理。该脚本需要传入三个参数：

- **input_path：** 指定包含 Markdown 文件的根目录（例如上例中的 `md_files`）。
- **output_path：** 输出文件夹（例如上例中的 `output`）。
- **type_value**：
  - `1`：扫描 `input_path` 下所有 Markdown 文件，将其中引用的本地图片复制到 `output_path` 中，同时更新 Markdown 文件中的图片 URL 为 `output_path` 内的路径；
  - `2`：为每个 Markdown 文件建立单独的文件夹（以文件名命名），图片存放在文件夹下的 `assets` 子目录中，整体存入`assets_type`文件夹中，；
  - `3`：扫描 Markdown 文件中的本地图片，将其上传到图床（获取公网 URL），并将 Markdown 文件中对应的图片 URL 替换为公网地址。
  - `4`：预处理Markdown 文件，将公式块和代码块格式化，以便于Markdown解析器解析（本地typora编辑器对于md格式比较宽容，但博客中使用的md解析器插件不一定能正确渲染！）





**二、使用Git进行版本控制**

假设你在服务器上已经搭建了 Gitea (Github、Gitee都行)并创建了一个名为 `md_files` 的仓库，那么你可以在 `md_files` 文件夹下通过 Git Bash 执行以下步骤将本地文件提交到远程仓库：

**初始化本地仓库**：

```text
git init
```

**添加远程仓库**：

将远程仓库地址添加为 `origin`（请将 `http://xxx` 替换为你的实际仓库地址）：

```text
git remote add origin http://xxx
```

**添加文件并提交**：

注意，写一个.gitignore文件将output排除版本控制

```text
git add .
git commit -m "Initial commit"
```

**推送到远程仓库：**

```text
git push -u origin master
```

**后续更新（可写个.bat批量执行）：**

```text
git add .
git commit -m "更新了xxx内容"
git push
```



**三、在服务器上部署该脚本**

**1. 确保脚本能够连接到 Typecho 使用的数据库**

本博客使用 docker-compose 部署 Typecho（参考：[【好玩儿的Docker项目】10分钟搭建一个Typecho博客｜太破口！念念不忘，必有回响！-我不是咕咕鸽](https://blog.laoda.de/archives/docker-compose-install-typecho)）。为了让脚本能访问 Typecho 的数据库，我将 Python 应用pyapp也通过 docker-compose 部署，这样所有服务均在同一网络中，互相之间可以直接通信。

参考docker-compose.yml如下：

```text
services:
  nginx:
    image: nginx
    ports:
      - "4000:80"    # 左边可以改成任意没使用的端口
    restart: always
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - ./typecho:/var/www/html
      - ./nginx:/etc/nginx/conf.d
      - ./logs:/var/log/nginx
    depends_on:
      - php
    networks:
      - web

  php:
    build: php
    restart: always
    expose:
      - "9000"       # 不暴露公网，故没有写9000:9000
    volumes:
      - ./typecho:/var/www/html
    environment:
      - TZ=Asia/Shanghai
    depends_on:
      - mysql
    networks:
      - web
  pyapp:
    build: ./markdown_operation  # Dockerfile所在的目录
    restart: "no"
    volumes:
            - /home/zy123/md_files:/markdown_operation/md_files
    networks:
      - web
    env_file:
      - .env
    depends_on:
      - mysql
  mysql:
    image: mysql:5.7
    restart: always
    environment:
      - TZ=Asia/Shanghai
    expose:
      - "3306"  # 不暴露公网，故没有写3306:3306
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/logs:/var/log/mysql
      - ./mysql/conf:/etc/mysql/conf.d
    env_file:
      - mysql.env
    networks:
      - web

networks:
  web:
```

注意：如果你不是用docker部署的typecho，只要保证脚本能连上typecho所使用的数据库并操纵里面的表就行！



**2. 将 `md_files` 挂载到容器中，保持最新内容同步**

这样有几个优势：

- 不需要每次构建镜像或进入容器手动拉取；
- 本地更新 `md_files` 后，容器内自动同步，无需额外操作；
- 保持了宿主机上的 Git 版本控制和容器内的数据一致性。



**3.仅针对 `pyapp` 服务进行重构和启动，不影响其他服务的运行：**

`pyapp`是本Python应用在容器内的名称。

1.构建镜像：

```text
docker-compose build pyapp 
```

2.启动容器并进入 Bash：

```text
docker-compose run --rm -it pyapp /bin/bash
```

3.在容器内运行脚本：

```text
python typecho_markdown_upload/main.py
```

2、3两步可合并为：

```text
docker-compose run --rm pyapp python typecho_markdown_upload/main.py
```

![image-20250320103325650](https://pic.bitday.top/i/2025/03/20/h37pze-0.png)

此时可以打开博客验证一下是否成功发布文章了！

**如果失败，可以验证mysql数据库:**

1️⃣ 进入 MySQL 容器：

```text
docker-compose exec mysql mysql -uroot -p
# 输入你的 root 密码
```

2️⃣ 切换到 Typecho 数据库并列出表：

```text
USE typecho;
SHOW TABLES;
```

3️⃣ 查看 `typecho_contents` 表结构（文章表）：

```text
DESCRIBE typecho_contents;
```

```text
mysql> DESCRIBE typecho_contents;
+--------------+------------------+------+-----+---------+----------------+
| Field        | Type             | Null | Key | Default | Extra          |
+--------------+------------------+------+-----+---------+----------------+
| cid          | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| title        | varchar(150)     | YES  |     | NULL    |                |
| slug         | varchar(150)     | YES  | UNI | NULL    |                |
| created      | int(10) unsigned | YES  | MUL | 0       |                |
| modified     | int(10) unsigned | YES  |     | 0       |                |
| text         | longtext         | YES  |     | NULL    |                |
| order        | int(10) unsigned | YES  |     | 0       |                |
| authorId     | int(10) unsigned | YES  |     | 0       |                |
| template     | varchar(32)      | YES  |     | NULL    |                |
| type         | varchar(16)      | YES  |     | post    |                |
| status       | varchar(16)      | YES  |     | publish |                |
| password     | varchar(32)      | YES  |     | NULL    |                |
| commentsNum  | int(10) unsigned | YES  |     | 0       |                |
| allowComment | char(1)          | YES  |     | 0       |                |
| allowPing    | char(1)          | YES  |     | 0       |                |
| allowFeed    | char(1)          | YES  |     | 0       |                |
| parent       | int(10) unsigned | YES  |     | 0       |                |
| views        | int(11)          | YES  |     | 0       |                |
| agree        | int(11)          | YES  |     | 0       |                |
+--------------+------------------+------+-----+---------+----------------+
```

4️⃣ 查询当前文章数量（确认执行前后有无变化）：

```text
SELECT COUNT(*) AS cnt FROM typecho_contents;
```



### **自动化**

1.windows下写脚本自动/手动提交每日更新

2.在 Linux 服务器上配置一个定时任务，定时执行 `git pull` 命令和启动脚本更新博客的命令。

- 创建脚本`/home/zy123/typecho/deploy.sh`

  ```text
  #!/bin/bash
  cd /home/zy123/md_files || exit
  git pull
  cd /home/zy123/typecho || exit
  docker compose run --rm pyapp python typecho_markdown_upload/main.py
  ```

  赋予可执行权限`chmod +x /home/zy123/deploy.sh`

- 编辑 Crontab 安排任务（每天0点10分执行）
  打开 crontab 编辑器：$crontab -e$

  ```
  10 0 * * * /home/zy123/typecho/deploy.sh >> /home/zy123/typecho/deploy.log 2>&1
  ```


3.注意md_files文件夹所属者和deploy.sh的所属者需要保持一致。否则会报：
fatal: detected dubious ownership in repository at '/home/zy123/md_files' To add an exception for this directory, call:         git config --global --add safe.directory /home/zy123/md_files



### TODO

- [ ] typecho_contents表中的slug字段代表链接中的日志缩略名，如wordpress风格 `/archives/{slug}.html`，目前是默认int自增，有需要的话可以在插入文章时手动设置该字段。

