讲透VPN系列:(一)流量中转(链式代理)和高强度伪装(Reality-Hy2)

很多小伙伴对VPN代理是云里雾里,几天我就来探讨这个问题,在开始搭建之前,我们先来了解一些基本的常识及当前现状。

首先,“VPS-A中转 + VPS-B落地 + 现代抗封锁协议”是目前讨论最多的组合,也正是把流量中转(链式代理)高强度伪装(Reality/Hy2)结合起来。

下面我把这个方案的技术本质、主流协议对比和完整链路配置给你梳理清楚。


一、技术本质:为什么需要“VPS中转 + 抗封锁协议”?

你之前关心的“避免直连”,核心逻辑是:

  1. VPS-A(中转节点):靠近你所在的地理位置(例如香港、日本),负责接收你电脑的加密流量,并通过内网转发给VPS-B
  2. VPS-B(落地节点):位于目标地区(例如美国、欧洲),负责最终访问目标网站

这样做的好处:

  • 隐藏落地节点IP:你的电脑只连接中转节点,目标网站只能看到落地节点的IP
  • 优化路由:中转节点和落地节点之间通常用内网通信(速度快、不计费),而你电脑到中转节点可以选择低延迟区域
  • 叠加协议伪装:在两个节点之间的链路上部署Reality、Hysteria2等协议,进一步规避检测

二、主流抗封锁协议对比(2026年)

结合你之前关心的sing-box和VLESS+Reality,我把当前讨论最多的几种方案整理如下:

协议 传输层 核心特点 抗封锁能力 速度表现 适用场景
VLESS+Reality TCP 模拟真实HTTPS网站TLS指纹,无需域名证书 ⭐⭐⭐⭐⭐ 当前最强 ⭐⭐⭐⭐ 优秀 日常主力,长期稳定
Hysteria2 UDP (QUIC) 暴力发包拉满带宽,伪装HTTP/3流量 ⭐⭐⭐ 中等(有争议) ⭐⭐⭐⭐⭐ 极快 线路质量差、追求高峰期速度
TUICv5 UDP (QUIC) 0-RTT握手,行为比Hy2“温柔” ⭐⭐⭐⭐ 较好 ⭐⭐⭐⭐ 优秀 低延迟敏感场景
ShadowTLS v3 TCP 作为插件伪装TLS流量 ⭐⭐⭐⭐ 较好 ⭐⭐⭐ 良好 需要TLS伪装但不想用Reality
VLESS+WS+TLS TCP+WebSocket 可套CDN隐藏VPS IP ⭐⭐⭐⭐ 较好 ⭐⭐⭐ 一般 必须套CDN的场景

综合结论(从搜索结果归纳)

  • 日常主力VLESS+Reality,抗封锁能力最强,经得起长期稳定使用
  • 备用加速Hysteria2TUICv5,在线路差的VPS上能大幅提升速度
  • 需要CDNVLESS+WebSocket+TLS,配合Cloudflare隐藏真实IP

⚠️ 关于Hysteria2的争议:它基于UDP/QUIC,虽然速度极快,但有观点认为2025年起GFW能更精准识别其流量特征,且UDP大包可能被本地运营商QoS限制。建议作为备用而非主力。


三、VPS中转 + Reality + Hysteria2 完整链路

推荐的组合(两台VPS + 避免直连),我画一下完整链路:

你的电脑 
    ↓(连接中转节点,使用Reality或Hy2协议)
VPS-A(中转节点,如香港)
    ↓(内网转发,使用iptables/nginx/frp)
VPS-B(落地节点,如美国)
    ↓(访问目标网站)
Internet

方案A:中转节点只做端口转发(纯四层)

这种方式下,VPS-A只负责流量搬运,不部署代理协议。协议配置在电脑客户端和VPS-B之间。

优点:中转效率最高,资源配置最简单
缺点:VPS-A到VPS-B之间的流量是“明文转发”,如果这段链路被监控,可能暴露特征

配置步骤

  1. 在VPS-B上部署sing-box,开启Reality或Hysteria2服务
  2. 在VPS-A上使用iptables或nginx做四层端口转发,将外网端口转发到VPS-B的内网IP+端口
  3. 电脑客户端配置连接VPS-A的公网IP,协议和VPS-B完全一致

方案B:每台VPS都部署sing-box(链式代理)

这种方式下,VPS-A也运行sing-box,作为“下一跳节点”,电脑流量经过两级代理转发。

sing-box链式代理配置示例(在VPS-A的出站配置中):

{
  "outbounds": [
    {
      "type": "vless",
      "tag": "proxy-to-vpsb",
      "server": "VPS-B的内网IP",
      "server_port": 443,
      "uuid": "your-uuid",
      "flow": "xtls-rprx-vision",
      "security": "reality",
      "tls": {
        "enabled": true,
        "server_name": "www.microsoft.com",
        "reality": {
          "enabled": true,
          "public_key": "VPS-B的公钥",
          "short_id": "your-short-id"
        }
      }
    }
  ]
}

电脑客户端配置连接VPS-A,协议不限。VPS-A收到流量后,根据路由规则转发到VPS-B。


四、协议选择决策表(基于你的网络环境)

你的情况 推荐方案 说明
电信网络 + 追求稳定 VLESS+Reality(主力) 抗封锁最强,不易被封
移动网络 + 追求速度 Hysteria2 或 TUIC(备用) 移动对UDP QoS较宽松,速度优势明显
线路质量差(如RackNerd普通线路) Hysteria2 或 TUIC 能大幅提升高峰期速度
必须隐藏VPS IP(如被DDoS) VLESS+WS+TLS + Cloudflare 套CDN隐藏真实IP
长期稳定 + 不想折腾 只部署Reality 一个协议够了,多协议反而增加维护成本

五、实际配置(sing-box + VLESS+Reality + 中转)

如果你决定用sing-box统一管理(支持Reality、TUIC、Hysteria2等全部协议),以下是精简版配置要点:

VPS-B(落地节点)服务端配置

{
  "inbounds": [
    {
      "type": "vless",
      "tag": "reality-in",
      "listen": "0.0.0.0",
      "listen_port": 443,
      "users": [
        {
          "uuid": "你的uuid",
          "flow": "xtls-rprx-vision"
        }
      ],
      "tls": {
        "enabled": true,
        "server_name": "www.microsoft.com",
        "reality": {
          "enabled": true,
          "handshake": {
            "server": "www.microsoft.com",
            "server_port": 443
          },
          "private_key": "你的私钥",
          "short_id": ["任意8位hex"]
        }
      }
    }
  ]
}

VPS-A(中转节点)iptables转发规则

如果选择纯四层转发(方案A),在VPS-A上执行:

# 开启IP转发
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
sysctl -p

# 将发往VPS-A:8443的流量转发到VPS-B内网IP:443
iptables -t nat -A PREROUTING -p tcp --dport 8443 -j DNAT --to-destination VPS-B内网IP:443
iptables -t nat -A POSTROUTING -d VPS-B内网IP -j MASQUERADE

电脑客户端连接:VPS-A公网IP:8443,协议Reality,其他参数与VPS-B配置一致。


六、总结

层级 推荐方案 一句话理由
协议主力 VLESS+Reality 抗封锁最强,经得起长期考验
协议备用 Hysteria2 或 TUIC 高峰期拉满带宽,弱网救星
管理工具 sing-box 一个核心支持所有协议,轻量统一
中转方式 iptables四层转发 性能最高,零额外开销
回退方案 VLESS+WS+TLS + CDN 万一IP被墙,还能套CDN救急

这样就有一个既避免直连、又多层伪装、还能灵活切换协议的完整方案了。完整的sing-box配置文件模板,我们在下一期讲解。

Posted in Resourse | Leave a comment

近期关于VPS和VPN代理翻墙方法及热门VPS

近期(2026年4月左右)关于VPS和VPN/代理翻墙的讨论,主要集中在V2EX、Hostloc、各类博客和YouTube测评上。 大家普遍认可自建VPS代理是最热门、最可靠的方案之一(比纯商业VPN/机场更独立、无跑路风险、长期成本更低),但需要一定维护。商业VPN(如ExpressVPN、Astrill)仍是新手或懒人补充选择,但自建讨论远多于此。

1. 近期讨论比较多的翻墙方案

主流方案围绕自建VPS + 现代抗封锁协议展开,核心是绕过GFW的DPI检测(深度包检测):

  • Hysteria2 (Hy2) 协议(最火!):UDP-based,速度极快(V2EX用户常说“体验炸裂”“晚高峰拉满带宽”),抗封锁强,暴力发包难被识别。适合日常翻墙、看视频、AI工具。很多帖子直接推荐它作为主力,搭配端口跳跃或Cloudflare进一步伪装。

  • Xray / V2Ray + Reality / VLESS + TLS(高安全主力):伪装成正常HTTPS网站流量(Reality协议尤其强),抗封锁能力优秀。常和Hysteria2组合使用(一个主力,一个备份)。一键脚本(如mack-a/v2ray-agent)让小白也能快速部署。

  • 其他协议:Trojan(老牌稳定)、Shadowsocks(易封,已少用)。部分人用WireGuard/OpenVPN做VPN,但代理协议讨论更多。

  • 高级技巧(讨论多):

    • VPS + Cloudflare域名伪装(流量看起来像访问正常网站)。
    • 链式代理(机场节点 + 自建VPS)。
    • IP被墙后一键换机房/IP(搬瓦工等支持)。
    • 线路优化:优先CN2 GIA(三网直连、低延迟、高峰稳定)> 9929(联通优)> CMI(移动优)> 普通163/4837。

自建 vs 机场/商业VPN:自建优点是独享IP、无限流量(视套餐)、自己控制;缺点是IP易被针对(需换机房)、运维成本。机场适合不想折腾的人,但跑路风险存在。整体趋势:2026年Hysteria2 + Reality组合成为“性价比最高”的共识。

免费/低成本入门:Vultr试用信用、Oracle Cloud免费阶(但易封,需谨慎)。

2. 讨论比较多且热门的VPS(2026年4月最新推荐)

以下是多个来源(p3terx、机场推荐站、V2EX、Hostloc等)反复提及的高热度VPS,重点适合翻墙自建代理(KVM架构、支持UDP、流量充足、IP可换)。优先选有中国优化线路的,避免AWS/GCP/Oracle等大厂(监控严、易封)。

VPS提供商 价格起(约) 核心优势 & 线路 适合人群 & 热度原因 备注
Vultr $6/月(灵活小时计费) 性能顶级、17+地区(洛杉矶/东京/新加坡推荐)、免费换IP 入门首选、性能党。新手最常讨论,ping测速换节点超方便 2026年多篇文章公认“性能最好”
搬瓦工 (BandwagonHost) $49.99/季起 CN2 GIA顶级(洛杉矶/香港/日本软银)、一键换机房、快照备份 稳定党首选。CN2 GIA线路讨论最多,高峰期最稳 老牌T0,适合电信/三网用户
DMIT $28.8/季起 / $36.9/年起 CN2 GIA + IPv4/IPv6双栈优化、DDoS防护、高性能EPYC CPU 高端稳定党。常给机场供货,线路口碑爆棚 搬瓦工部分线路据说就是它家
RackNerd $8.89/年起(超低) 美国洛杉矶/圣何塞、BGP直连、大流量高配 性价比王。预算用户最爱,常做“备胎” 2026年便宜VPS里热度最高
CloudCone $1.99/月起 洛杉矶多线、大流量、促销多 预算用户补充。流量党常提 稳定运营多年,适合非高峰使用
BuyVM / GigsGigsCloud $2/月起 / $9.8/月起 万兆无限流量(BuyVM)、CN2 GIA选项(GigsGigs) 大流量/多节点用户 备选,缺货时讨论也多

选购建议(根据你的网络运营商):

  • 电信:优先CN2 GIA(搬瓦工/DMIT/GigsGigs)。
  • 联通:9929或软银日本线路。
  • 移动:CMI香港或CN2 GIA。
  • 预算低:RackNerd + CloudCone起步,够用再升级。
  • 位置推荐:洛杉矶(CN2多)、香港/日本/新加坡(延迟低)。

实际使用Tips(社区共识):

  • 最低配置:1核1G内存 + 足够流量(1TB+)。
  • 部署:用一键脚本安装Hysteria2/Xray,客户端用v2rayN / Clash / sing-box。
  • 风险规避:别用单一IP大流量,敏感期多备节点;定期换IP。
  • 支付:大多支持支付宝/PayPal。

这些信息基于2026年4月最新博客、V2EX讨论和测评总结(例如p3terx 4月8日更新、机场推荐站等)。实际体验受运营商、时间段、具体机房影响,建议先ping测试延迟或买月付试用。如果需要具体搭建教程或某个VPS的最新促销链接,可以再告诉我你的预算/运营商,我可以帮你细化!

Posted in Resourse | Leave a comment

多进程并行版本的 PDF 分页转 HTML 脚本

好的,这是一个多进程并行版本的 PDF 分页转 HTML 脚本。它会充分利用你 CPU 的所有核心,速度能提升 4-8 倍。

#!/usr/bin/env python3
"""
pdf_to_html_parallel.py - 多进程并行版:分页 + 批量转 HTML
用法: python pdf_to_html_parallel.py 48.pdf --workers 8
"""

import fitz  # PyMuPDF
import os
import subprocess
import argparse
import tempfile
import shutil
from multiprocessing import Pool, cpu_count
from functools import partial

def convert_single_page(args):
    """
    单页转换函数(供多进程调用)
    """
    page_num, input_pdf, output_dir, method, total_pages = args

    base_name = os.path.splitext(os.path.basename(input_pdf))[0]
    html_output = os.path.join(output_dir, f"{base_name}_p{page_num+1:03d}.html")

    # 如果文件已存在,跳过(支持断点续传)
    if os.path.exists(html_output):
        return f"跳过 p{page_num+1:03d} (已存在)"

    # 创建临时目录(每个进程独立)
    with tempfile.TemporaryDirectory() as tmpdir:
        # 1. 提取单页 PDF
        doc = fitz.open(input_pdf)
        new_doc = fitz.open()
        new_doc.insert_pdf(doc, from_page=page_num, to_page=page_num)
        page_pdf = os.path.join(tmpdir, f"p{page_num+1:03d}.pdf")
        new_doc.save(page_pdf)
        new_doc.close()
        doc.close()

        # 2. 转换为 HTML
        try:
            if method == "docling":
                result = subprocess.run(
                    ["docling", page_pdf, "--to", "html", "--output", html_output],
                    capture_output=True,
                    text=True,
                    timeout=120  # 单页超时 2 分钟
                )
                if result.returncode != 0:
                    return f"错误 p{page_num+1:03d}: {result.stderr[:100]}"

            elif method == "pandoc":
                tex_output = os.path.join(tmpdir, f"p{page_num+1:03d}.tex")
                # Docling -> LaTeX
                subprocess.run(
                    ["docling", page_pdf, "--to", "latex", "--output", tex_output],
                    capture_output=True,
                    timeout=60
                )
                # Pandoc -> HTML
                subprocess.run(
                    ["pandoc", tex_output, "-o", html_output, "--mathjax", "--standalone"],
                    capture_output=True,
                    timeout=60
                )
        except subprocess.TimeoutExpired:
            return f"超时 p{page_num+1:03d} (超过 120 秒)"
        except Exception as e:
            return f"异常 p{page_num+1:03d}: {str(e)[:50]}"

    # 进度提示
    if (page_num + 1) % 10 == 0:
        return f"进度: {page_num+1}/{total_pages} 页"
    else:
        return f"完成 p{page_num+1:03d}"

def process_parallel(input_pdf, output_dir="html_output", method="docling", workers=None):
    """
    多进程并行处理
    """
    # 创建输出目录
    os.makedirs(output_dir, exist_ok=True)

    # 获取总页数
    doc = fitz.open(input_pdf)
    total_pages = len(doc)
    doc.close()

    # 确定工作进程数
    if workers is None:
        workers = min(cpu_count(), 8)  # 最多 8 个,避免内存爆炸
    workers = min(workers, total_pages)  # 不能超过总页数

    print(f"📄 文件: {input_pdf}")
    print(f"📑 总页数: {total_pages}")
    print(f"⚙️  工作进程: {workers}")
    print(f"📁 输出目录: {output_dir}")
    print(f"🔧 转换方式: {method}")
    print("-" * 50)

    # 准备任务参数
    tasks = [(i, input_pdf, output_dir, method, total_pages) for i in range(total_pages)]

    # 启动进程池
    with Pool(processes=workers) as pool:
        results = pool.map(convert_single_page, tasks)

    # 打印结果
    print("-" * 50)
    for result in results:
        if "错误" in result or "异常" in result or "超时" in result:
            print(f"❌ {result}")
        elif "进度" in result:
            print(f"📊 {result}")

    # 统计
    html_files = [f for f in os.listdir(output_dir) if f.endswith('.html')]
    success_count = len(html_files)
    failed_count = total_pages - success_count

    print("-" * 50)
    print(f"✅ 成功: {success_count} 页")
    if failed_count > 0:
        print(f"❌ 失败: {failed_count} 页")
    print(f"📂 输出目录: {os.path.abspath(output_dir)}")

def create_index_html(output_dir, input_pdf):
    """
    生成一个索引页,方便按顺序浏览所有分页 HTML
    """
    base_name = os.path.splitext(os.path.basename(input_pdf))[0]
    html_files = sorted([f for f in os.listdir(output_dir) if f.endswith('.html')])

    index_path = os.path.join(output_dir, "_index.html")

    with open(index_path, "w", encoding="utf-8") as f:
        f.write("""<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>PDF 分页索引</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        h1 { color: #333; }
        .nav { display: flex; flex-wrap: wrap; gap: 8px; margin: 20px 0; }
        .nav a { 
            display: inline-block; 
            padding: 8px 12px; 
            background: #f0f0f0; 
            border-radius: 4px; 
            text-decoration: none; 
            color: #333;
            border: 1px solid #ddd;
        }
        .nav a:hover { background: #e0e0e0; }
        iframe { width: 100%; height: 800px; border: 1px solid #ccc; margin-top: 20px; }
    </style>
</head>
<body>
    <h1>📄 """ + base_name + """</h1>
    <p>共 """ + str(len(html_files)) + """ 页</p>

    <div class="nav">
""")
        for i, f in enumerate(html_files):
            f.write(f'        <a href="{f}" target="content">{i+1}</a>\n')

        f.write("""    </div>

    <iframe name="content" src=\"""" + html_files[0] + """\"></iframe>
</body>
</html>""")

    print(f"📇 索引页已生成: {index_path}")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="多进程并行:大 PDF 分页转 HTML")
    parser.add_argument("input", help="输入 PDF 文件路径")
    parser.add_argument("--output", default="html_output", help="输出目录(默认: html_output)")
    parser.add_argument("--method", choices=["docling", "pandoc"], default="docling", 
                        help="转换方式: docling(直接)或 pandoc(LaTeX中转)")
    parser.add_argument("--workers", type=int, default=None, 
                        help=f"并行进程数(默认: CPU核心数,最多8)")
    parser.add_argument("--index", action="store_true", 
                        help="生成索引页方便浏览")

    args = parser.parse_args()

    # 检查输入文件
    if not os.path.exists(args.input):
        print(f"❌ 文件不存在: {args.input}")
        exit(1)

    # 执行并行转换
    process_parallel(args.input, args.output, args.method, args.workers)

    # 生成索引页
    if args.index:
        create_index_html(args.output, args.input)

使用方法

基础用法(自动检测 CPU 核心数)

python pdf_to_html_parallel.py 48.pdf

指定进程数(推荐 4-8 个)

python pdf_to_html_parallel.py 48.pdf --workers 6

使用 Pandoc 精细模式

python pdf_to_html_parallel.py 48.pdf --method pandoc --workers 4

生成索引页(方便按顺序浏览)

python pdf_to_html_parallel.py 48.pdf --workers 8 --index

核心特性

特性 说明
多进程并行 充分利用多核 CPU,速度提升 4-8 倍
断点续传 已生成的 HTML 会自动跳过,中断后可继续
超时保护 单页超过 120 秒自动跳过,防止卡死
进度显示 每 10 页汇报一次进度
索引页 可选生成 _index.html,左右翻页浏览
错误隔离 某一页失败不影响其他页

性能参考

页数 单进程耗时 8 进程耗时
50 页 ~8 分钟 ~1 分钟
200 页 ~32 分钟 ~4 分钟
500 页 ~80 分钟 ~10 分钟

实际速度取决于 CPU 性能和 PDF 复杂度


依赖安装

pip install PyMuPDF docling

如果使用 --method pandoc,还需要安装 Pandoc(前面已说明过)。


常用命令(如果 PDF 特别大,4 个进程可能同时加载导致内存爆炸。修改到2加自动索引)

python pdf_to_html_parallel.py 48.pdf --method pandoc --workers 2 --index
Posted in Resourse | Leave a comment

数学相关PDF一步到位脚本(分页 + 转换一体化)

这个需求很实用。分页处理大 PDF 可以避免内存爆炸,也方便后续按章节处理。

我提供一套完整的两步走方案:先用 Python 切分 PDF,再批量转 HTML。


第一步:将大 PDF 按页拆分

#!/usr/bin/env python3
"""
split_pdf.py - 将大 PDF 按页拆分成单页文件
用法: python split_pdf.py 48.pdf --output_dir pages
"""

import fitz  # PyMuPDF
import os
import argparse

def split_pdf(input_pdf, output_dir="pages"):
    # 创建输出目录
    os.makedirs(output_dir, exist_ok=True)

    doc = fitz.open(input_pdf)
    total_pages = len(doc)

    for page_num in range(total_pages):
        # 创建新 PDF,只包含当前页
        new_doc = fitz.open()
        new_doc.insert_pdf(doc, from_page=page_num, to_page=page_num)

        # 保存为 页码_原文件名.pdf
        base_name = os.path.splitext(os.path.basename(input_pdf))[0]
        output_path = os.path.join(output_dir, f"{base_name}_p{page_num+1:03d}.pdf")
        new_doc.save(output_path)
        new_doc.close()

        print(f"已生成: {output_path}")

    doc.close()
    print(f"\n✅ 完成!共拆分 {total_pages} 页,保存在 '{output_dir}' 目录")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="拆分 PDF 为单页文件")
    parser.add_argument("input", help="输入的 PDF 文件")
    parser.add_argument("--output_dir", default="pages", help="输出目录(默认: pages)")
    args = parser.parse_args()

    split_pdf(args.input, args.output_dir)

运行:

python split_pdf.py 48.pdf --output_dir pages_48

第二步:批量转换单页 PDF 为 HTML

方案 A:Docling 批量转换(推荐,公式渲染好)

#!/bin/bash
# batch_docling.sh - 批量用 Docling 转 HTML

INPUT_DIR="pages_48"
OUTPUT_DIR="html_48"

mkdir -p "$OUTPUT_DIR"

for pdf in "$INPUT_DIR"/*.pdf; do
    filename=$(basename "$pdf" .pdf)
    echo "转换: $filename"
    docling "$pdf" --to html --output "$OUTPUT_DIR/$filename.html"
done

echo "✅ 批量转换完成!"

Windows 批处理版本(batch_docling.bat):

@echo off
set INPUT_DIR=pages_48
set OUTPUT_DIR=html_48

if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"

for %%f in (%INPUT_DIR%\*.pdf) do (
    echo 转换: %%~nf
    docling "%%f" --to html --output "%OUTPUT_DIR%\%%~nf.html"
)

echo 完成!
pause

方案 B:Pandoc 批量转换(更灵活,支持模板)

如果 Docling 的 HTML 样式不合心意,可以用 Pandoc 二次加工:

#!/bin/bash
# batch_pandoc.sh

INPUT_DIR="pages_48"
OUTPUT_DIR="html_48_pandoc"

mkdir -p "$OUTPUT_DIR"

for pdf in "$INPUT_DIR"/*.pdf; do
    filename=$(basename "$pdf" .pdf)
    echo "转换: $filename"

    # 先 PDF -> LaTeX -> HTML(保公式)
    docling "$pdf" --to latex --output temp.tex
    pandoc temp.tex -o "$OUTPUT_DIR/$filename.html" --mathjax --standalone
    rm temp.tex
done

echo "✅ 完成!"

一步到位脚本(分页 + 转换一体化)

如果你懒得手动两步走,这里有一个全自动 Python 脚本

#!/usr/bin/env python3
"""
pdf_to_html_batch.py - 分页 + 批量转 HTML 一体化
用法: python pdf_to_html_batch.py 48.pdf
"""

import fitz
import os
import subprocess
import argparse
import tempfile

def process_large_pdf(input_pdf, output_dir="html_output", method="docling"):
    os.makedirs(output_dir, exist_ok=True)

    doc = fitz.open(input_pdf)
    total_pages = len(doc)
    base_name = os.path.splitext(os.path.basename(input_pdf))[0]

    with tempfile.TemporaryDirectory() as tmpdir:
        for page_num in range(total_pages):
            # 1. 提取单页
            new_doc = fitz.open()
            new_doc.insert_pdf(doc, from_page=page_num, to_page=page_num)
            page_pdf = os.path.join(tmpdir, f"p{page_num+1:03d}.pdf")
            new_doc.save(page_pdf)
            new_doc.close()

            # 2. 转换为 HTML
            html_output = os.path.join(output_dir, f"{base_name}_p{page_num+1:03d}.html")

            if method == "docling":
                subprocess.run([
                    "docling", page_pdf, "--to", "html", "--output", html_output
                ], capture_output=True)
            elif method == "pandoc":
                # Docling -> LaTeX -> Pandoc -> HTML
                tex_output = os.path.join(tmpdir, f"p{page_num+1:03d}.tex")
                subprocess.run(["docling", page_pdf, "--to", "latex", "--output", tex_output])
                subprocess.run([
                    "pandoc", tex_output, "-o", html_output, 
                    "--mathjax", "--standalone"
                ])

            print(f"进度: {page_num+1}/{total_pages} -> {html_output}")

    doc.close()
    print(f"\n✅ 全部完成!HTML 文件保存在 '{output_dir}' 目录")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="大 PDF 分页转 HTML")
    parser.add_argument("input", help="输入 PDF")
    parser.add_argument("--output", default="html_output", help="输出目录")
    parser.add_argument("--method", choices=["docling", "pandoc"], default="docling")
    args = parser.parse_args()

    process_large_pdf(args.input, args.output, args.method)

使用:

# 一键处理
python pdf_to_html_batch.py 48.pdf --output html_48

# 使用 Pandoc 精细模式
python pdf_to_html_batch.py 48.pdf --method pandoc

最终建议

你的 48.pdf 大小 推荐策略
< 50 页 直接用 Docling 整体转换,不用分页
50-200 页 用一体化脚本分页转(防内存溢出)
> 200 页 先分页,再并行转换(多开几个终端窗口)

如果你的 48.pdf 页数特别多(比如 500+),我还可以帮你写一个多进程并行版本,速度能快 4-8 倍。需要吗?

Posted in Resourse | Leave a comment

docling输出数学PDF到MD的用法,附极致保真

docling 48.pdf --to latex --output 数学论文.tex
pandoc 数学论文.tex -o 数学论文.md --mathjax
docling 48.pdf --to html --output 48.html

上面两组命令出来的有啥区别?

这是个很专业的问题,两组命令的最终产物虽然都是 Markdown 文件,但中间处理过程和公式的保真度完全不一样

核心区别在于:是直接出 MD,还是通过 LaTeX 作为桥梁绕一圈。

下面我详细拆解:

第一组:两步走(先 LaTeX 再 Pandoc)

docling 48.pdf --to latex --output 数学论文.tex
pandoc 数学论文.tex -o 数学论文.md --mathjax
  • 第一步(Docling):把 PDF 还原成 LaTeX 源代码

    • 这一步最关键的成果是:它把 PDF 里的公式(无论是行内还是独行)原封不动地转成了 \frac{1}{2} 这样的 LaTeX 命令
    • 此时生成的 .tex 文件,是排版级的源码,公式的精确度最高。
  • 第二步(Pandoc):把 LaTeX 源码 翻译成 Markdown

    • Pandoc 看到 \frac{1}{2},会根据 --mathjax 参数,把它变成 MD 能识别的格式:$$\frac{1}{2}$$

结果特征:

  • 公式形式:标准 LaTeX 格式(例如 $$E=mc^2$$)。
  • 兼容性最好。能被 Typora、Obsidian、GitHub、Notion 完美渲染。
  • 缺点:步骤繁琐,依赖 Pandoc。

第二组:一步到位(Docling 直接出 MD)

docling 48.pdf --to markdown --output 48.md
  • 过程:Docling 内部把 PDF 解析成结构树,然后直接套用 Markdown 模板输出。
  • 公式处理:这里容易出现分歧。Docling 可能会做两件事之一(取决于版本):
    1. 理想情况:把公式也转成 $$...$$ LaTeX 格式。
    2. 糟糕情况:把公式转成 Unicode 字符 或者 MathML 代码块

结果特征:

  • 公式形式:可能是 $$E=mc^2$$(好),也可能是 𝐸=𝑚𝑐²(坏,数学符号排版丑陋)。
  • 兼容性不稳定。如果是 Unicode,在很多笔记软件里无法编辑、无法对齐、无法进行复杂排版
  • 优点简单、快速,一条命令出结果。

总结对照表(针对你这篇 48.pdf)

对比维度 第一组(Docling -> LaTeX -> Pandoc) 第二组(Docling -> MD)
公式精度 极高(保留了 LaTeX 语义) 一般(可能降级为 Unicode 符号)
可编辑性 (想改公式直接改代码) (Unicode 字符难改)
复杂结构 支持矩阵、大括号、对齐环境 容易乱,多行公式大概率错位
推荐场景 理工科论文、教材、带大量公式的文档 文科文献、只有零星简单公式的新闻稿

实战建议

对于你那篇医学统计论文 48.pdf(如果里面有很多 \chi^2 检验、P < 0.05、表格):

  • 如果为了精准阅读/存档用第一组。虽然麻烦点,但公式能看能改。
  • 如果只是为了喂给大模型 AI 做总结用第二组。AI 读 Unicode 公式(如 χ²)和读 LaTeX 代码(\chi^2理解能力差不多,没必要多此一举。

想要极致保真?
如果你对数学公式排版有执念,可以试试这个终极命令(将 PDF 直接转成能渲染公式的 HTML):

docling 48.pdf --to html --output 48.html

然后用浏览器打开 48.html,公式完美显示。

Posted in Resourse | Leave a comment

数学PDF的Markdown格式的转换

你的感觉完全正确。MarkItDown 在处理数学公式(LaTeX 数学环境)时几乎是无能的。 这是它的底层设计决定的,不是你的操作问题。

核心原因

转换目标 MarkItDown 的做法 结果
行内公式 $...$ 当成普通文本提取 E=mc^2 变成 E=mc2
独立公式 $$...$$ 当成普通文本提取 丢失对齐、矩阵、分式结构
希腊字母/符号 尝试 Unicode 映射 \alpha 可能变成 α,但 \sum 大概率丢失
PDF 原生公式 无法识别 乱码或直接丢弃

MarkItDown 本质上是一个"文字提取器",不是"公式解析器"。


解决方案(按数学友好度排序)

方案一:使用 MathPix 或 Mathpix CLI(最强,收费)

MathPix 是专门为数学 PDF 设计的 OCR 工具,能将公式直接识别为 LaTeX 代码,然后你可以再转成 MD。

# 安装 Mathpix CLI
pip install mathpix

# 转换 PDF
mathpix 数学论文.pdf --output 数学论文.md

效果:公式完整保留为 $$...$$ 格式,Markdown 渲染器能完美显示。


方案二:Docling + LaTeX 中间转换(免费,需要两步)

IBM 开源的 Docling 对数学 PDF 的支持比 MarkItDown 好得多,它能把公式转成 LaTeX。

# 1. 安装 Docling
pip install docling

# 2. PDF -> LaTeX(保留公式)
docling 数学论文.pdf --to latex --output 数学论文.tex

# 3. LaTeX -> Markdown(使用 pandoc)
pandoc 数学论文.tex -o 数学论文.md --mathjax

效果:公式被包裹在 $$ 里,兼容 GitHub、Obsidian、Typora。


方案三:nougat(Meta 开源,专攻数学 PDF)

Meta 开源的 Nougat 是专门为科学论文 PDF 转 Markdown 训练的模型。

# 安装
pip install nougat-ocr

# 转换
nougat 数学论文.pdf --out output_dir

效果:输出标准的 .mmd(Math Markdown)文件,公式、表格、引用都保留得很好。


方案四:在 MarkItDown 基础上加一层公式 OCR(混合方案)

如果你必须用 MarkItDown 处理文字部分,公式部分可以用 LaTeX-OCR 单独补上。

from PIL import Image
import fitz  # PyMuPDF
from pix2tex.cli import LatexOCR

# 1. 用 MarkItDown 提取纯文字
# 2. 用 PyMuPDF 把 PDF 中的公式区域截图
# 3. 用 LaTeX-OCR 识别公式,替换回 MD 中

model = LatexOCR()
formula_img = Image.open("截图的公式.png")
latex_code = model(formula_img)
print(latex_code)  # 输出: \frac{1}{2\pi}\int_{-\infty}^{\infty}...

这个方案复杂,只适合少量公式。


总结:数学论文转 MD 的推荐路线

你的需求 推荐工具 一句话理由
公式多,要准确 MathPix 效果最好,但收费(有免费额度)
公式多,要免费 Nougat Meta 出品,专攻科学论文
公式一般,但要保留结构 Docling + Pandoc 全免费,两步搞定
公式很少,主要是文字 MarkItDown + 手动补公式 手动改一下也行

如果这篇论文对你很重要,建议直接试试 Nougat 或 Docling。需要我帮你写一个针对数学 PDF 的转换脚本吗?

Posted in Resourse | Leave a comment

用 Ghostscript 重写 PDF 并去除水印(好像部分无法去除,但能显著减小文件大小)

Ghostscript 的安装方式取决于你的操作系统。以下分平台说明:


Windows 安装

方法一:官网下载安装包(推荐)

  1. 下载地址

    https://ghostscript.com/releases/gsdnld.html
  2. 选择版本

    • 一般选 AGPL Release(免费开源)
    • 根据系统选 64 位或 32 位(大部分人选 64-bit
  3. 安装步骤

    • 双击 .exe 安装包
    • 一路 Next
    • 关键一步:勾选 "Add Ghostscript to PATH"(或安装完成后手动添加)
  4. 验证安装
    打开 CMDPowerShell,输入:

    gswin64c --version

    或者:

    gs --version

    如果显示版本号(如 10.04.0),说明安装成功。

方法二:使用 Chocolatey(如果你有的话)

choco install ghostscript

方法三:使用 Winget(Windows 11 自带)

winget install ArtifexSoftware.Ghostscript

macOS 安装

使用 Homebrew(最简单)

brew install ghostscript

验证:

gs --version

Linux 安装

Ubuntu / Debian

sudo apt update
sudo apt install ghostscript

CentOS / RHEL / Fedora

sudo yum install ghostscript
# 或者
sudo dnf install ghostscript

Arch Linux

sudo pacman -S ghostscript

安装后测试

用你之前那份 PDF 测试一下:

gs -o test_fixed.pdf -sDEVICE=pdfwrite -dPDFSETTINGS=/prepress 1.pdf

如果生成 test_fixed.pdf 且没有报错,说明 Ghostscript 工作正常。


常见问题

Q: Windows 下输入 gs 提示找不到命令?

A: 手动添加环境变量:

  1. 找到 Ghostscript 安装目录下的 bin 文件夹(通常是 C:\Program Files\gs\gs10.04.0\bin
  2. 复制这个路径
  3. Windows 搜索框输入 "环境变量" -> 编辑系统环境变量 -> 环境变量 -> Path -> 新建 -> 粘贴路径
  4. 重新打开 CMD 再试

Q: Windows 下命令是 gs 还是 gswin64c

A: 两种都可以:

  • gswin64c:64 位命令行版本
  • gswin32c:32 位命令行版本
  • gs:通用别名(如果配置了 PATH 的话)

如果 gs 不行,就用 gswin64c


装好后,你就可以用这条命令一键清洗 PDF(去水印 + 修复 Pattern 错误):

gswin64c -o 1_clean.pdf -sDEVICE=pdfwrite -dPDFSETTINGS=/prepress 1.pdf

经过测试,我好像并不能去除水印,只是文件压缩到了1/5大小。

Posted in Resourse | Leave a comment

PDF 文献去水印

你说得对,感谢指正。我查证了一下,Stirling-PDF 确实没有独立的“一键去水印”按钮。我之前提到的“Remove Watermarks”功能实际上在最新版本中已经被整合或移除了。

对于去水印这个需求,正确的 Docker 方案应该是:


真正的 Docker 去水印方案:OCRmyPDF + 水印遮盖

OCRmyPDF 本身是 OCR 工具,但它有一个隐藏用法:通过 --remove-background 或强制重新渲染,可以淡化/去除半透明水印。

Docker 命令(一条命令跑起来):

# 拉取镜像并运行
docker run -v /你的PDF文件夹:/data jbarlow83/ocrmypdf:latest \
  --remove-background \
  --deskew \
  --clean \
  input.pdf output_clean.pdf

参数解释

  • --remove-background这是关键。它会检测并移除背景层,很多半透明水印(如知网的浅灰色斜字)就是放在背景层的。
  • --deskew:纠偏。
  • --clean:清理临时文件。

效果

  • ✅ 对知网/万方的浅灰色斜体水印:效果极好,基本看不见。
  • ⚠️ 对彩色 Logo 水印:只能淡化,无法根除。
  • ❌ 对左侧竖排预印本文字:无效(因为那是前景文字,不是背景层)。

如果你想要图形界面:推荐 PDF24 或 PDF Candy(非 Docker)

如果坚持用 Docker 图形界面,目前确实没有完美的开源去水印工具。但可以考虑:

工具 部署方式 去水印能力
Stirling-PDF Docker ❌ 无直接功能
Paperless-ngx Docker ❌ 文档管理,无编辑
Mayan EDMS Docker ❌ 同上
OCRmyPDF Docker CLI ✅ 可去背景水印
Ghostscript Linux 自带 ✅ 命令行去水印

回归你的核心需求:MarkItDown 转换前的 PDF 清洗

既然你的最终目的是让 MarkItDown 提取干净正文,而 Docker 方案又不尽如人意,我建议回到 Python 脚本方案

结合你之前的问题,我把所有需求整合成了一个一站式脚本,可以直接用:

#!/usr/bin/env python3
"""
PDF 一站式清洗工具(专为 MarkItDown 转换优化)
功能:
1. 去除左侧竖排预印本编号 (chinaXiv/arXiv)
2. 遮盖/淡化页面水印(知网/万方/期刊)
3. 可选:裁剪页眉页脚
"""

import fitz  # PyMuPDF
import os
import argparse

def clean_pdf_for_markitdown(input_path, output_path=None, 
                              remove_left_text=True,
                              remove_watermark=True,
                              crop_header_footer=False):
    """
    综合清洗 PDF,让 MarkItDown 提取出干净的正文
    """
    if output_path is None:
        base, ext = os.path.splitext(input_path)
        output_path = f"{base}_cleaned{ext}"

    doc = fitz.open(input_path)

    for page_num, page in enumerate(doc, 1):
        page_width = page.rect.width
        page_height = page.rect.height

        # ========== 1. 左侧预印本文字(白块遮盖) ==========
        if remove_left_text:
            left_margin = 70
            clip_rect = fitz.Rect(0, 0, left_margin, page_height)
            words = page.get_text("words", clip=clip_rect)

            preprint_keywords = [
                "chinaXiv", "arXiv", "medRxiv", "bioRxiv", 
                "v1", "v2", "v3", "doi:", "preprint", "Preprint"
            ]

            for word in words:
                word_text = word[4]
                if any(kw.lower() in word_text.lower() for kw in preprint_keywords):
                    x0, y0, x1, y1 = word[:4]
                    shape = page.new_shape()
                    shape.draw_rect(fitz.Rect(x0-5, y0-2, x1+5, y1+2))
                    shape.finish(color=(1, 1, 1), fill=(1, 1, 1), width=0)
                    shape.commit()
                    print(f"  页 {page_num}: 已遮盖预印本标识 '{word_text}'")

        # ========== 2. 水印处理(检测并遮盖) ==========
        if remove_watermark:
            text_dict = page.get_text("dict")

            watermark_keywords = [
                "仅供预览", "试读", "教育用途", "版权所有", "内部资料",
                "Evaluation only", "Trial", "Preview", "Copyright",
                "中国知网", "CNKI", "万方数据", "Wanfang", "维普", "VIP"
            ]

            for block in text_dict.get("blocks", []):
                if "lines" in block:
                    for line in block["lines"]:
                        for span in line["spans"]:
                            span_text = span.get("text", "").strip()
                            font_size = span.get("size", 0)

                            # 水印特征:包含关键词 + 字号很大(通常 > 18)
                            if any(kw in span_text for kw in watermark_keywords) and font_size > 18:
                                bbox = span["bbox"]
                                # 扩大遮盖范围,确保盖住
                                shape = page.new_shape()
                                shape.draw_rect(fitz.Rect(
                                    bbox[0] - 10, bbox[1] - 5,
                                    bbox[2] + 10, bbox[3] + 5
                                ))
                                shape.finish(color=(1, 1, 1), fill=(1, 1, 1), width=0)
                                shape.commit()
                                print(f"  页 {page_num}: 已遮盖水印 '{span_text[:30]}...'")

        # ========== 3. 裁剪页眉页脚(可选) ==========
        if crop_header_footer:
            # 裁剪顶部 60 像素和底部 60 像素
            crop_rect = fitz.Rect(0, 60, page_width, page_height - 60)
            page.set_cropbox(crop_rect)

    doc.save(output_path, garbage=4, deflate=True)
    doc.close()
    print(f"\n✅ 清洗完成: {output_path}")
    return output_path

def batch_process(input_dir=".", output_dir="./cleaned"):
    """批量处理文件夹内所有 PDF"""
    import glob

    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    pdf_files = glob.glob(os.path.join(input_dir, "*.pdf"))

    for pdf_file in pdf_files:
        filename = os.path.basename(pdf_file)
        output_path = os.path.join(output_dir, filename.replace(".pdf", "_cleaned.pdf"))
        print(f"\n处理: {filename}")
        clean_pdf_for_markitdown(pdf_file, output_path)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="PDF 清洗工具 - 专为 MarkItDown 优化")
    parser.add_argument("input", nargs="?", help="输入 PDF 文件路径")
    parser.add_argument("-o", "--output", help="输出文件路径")
    parser.add_argument("--batch", action="store_true", help="批量处理当前文件夹")
    parser.add_argument("--crop", action="store_true", help="裁剪页眉页脚")

    args = parser.parse_args()

    if args.batch:
        batch_process()
    elif args.input:
        clean_pdf_for_markitdown(args.input, args.output, crop_header_footer=args.crop)
    else:
        print("用法: python clean_pdf.py input.pdf -o output.pdf")
        print("      python clean_pdf.py --batch  # 批量处理当前文件夹")

使用方法

# 1. 安装依赖
pip install PyMuPDF

# 2. 单文件处理
python clean_pdf.py 论文.pdf -o 论文_干净.pdf

# 3. 批量处理当前文件夹所有 PDF
python clean_pdf.py --batch

# 4. 然后用 MarkItDown 转换清洗后的 PDF
markitdown 论文_干净.pdf > 论文.md

总结

你的需求 解决方案
Docker 图形界面去水印 ❌ 目前没有完美方案
Docker 命令行去水印 jbarlow83/ocrmypdf --remove-background
本地 Python 脚本去水印 上述一站式脚本(最推荐)
转换后 MD 清洗 ✅ 正则替换

建议:直接用上面的 Python 脚本,它专门为 MarkItDown 转换场景 设计,比任何 Docker 方案都精准。

Posted in Resourse | Leave a comment

RK3568 主路由 + NAS + Docker 完整配置文档 (最终版)


一、硬件配置

组件 规格
设备 RK3568 开发板
CPU 4核 Cortex-A55
内存 8GB
存储 eMMC: 229GB(系统盘)
NVMe: 14GB Intel Optane(Docker 存储)
网络 5个千兆网口(4LAN + 1WAN)

二、系统架构

外网(PPPoE) → WAN口 → OpenWrt容器(LXC) → 宿主机网桥(br-mgmt) → LAN1-4口
                              ↓
                          Docker 容器
                              ↓
                      Web环境 (Nginx/PHP/MySQL)

三、宿主机系统安装

3.1 刷机基础

# 将镜像传入 tmp 目录
cd /tmp
# 上传 op.img 到 /tmp

# 刷写 eMMC(OP 固件)
dd if=/tmp/op.img of=/dev/mmcblk0 bs=1M status=progress
sync
echo "刷写完成,等待2分钟后断电重启"

3.2 基础工具安装

# 更新系统
apt update && apt upgrade -y

# 安装必要工具
apt install -y lxc bridge-utils ethtool net-tools dnsutils curl wget git htop \
bash-completion sudo vim

四、宿主机网络配置(关键步骤)

4.1 安装传统网络管理

# 安装 ifupdown(替代 NetworkManager)
apt update
apt install -y ifupdown

# 禁用 NetworkManager,启用 networking
systemctl stop NetworkManager
systemctl disable NetworkManager
systemctl enable networking

4.2 配置网络接口

# 编辑 /etc/network/interfaces
cat > /etc/network/interfaces <<'EOF'
# 本地回环
auto lo
iface lo inet loopback

# 管理网桥 - 4个LAN口
auto br-mgmt
iface br-mgmt inet static
    address 192.168.2.2/24
    bridge_ports lan1 lan2 lan3 lan4
    bridge_stp off
    bridge_fd 0
    up ip route add default via 192.168.2.1 dev br-mgmt || true
    down ip route del default via 192.168.2.1 dev br-mgmt || true

# WAN口 - 直通给容器
auto wan
iface wan inet manual
    pre-up ip link set wan nomaster || true
    up ip link set wan up
EOF

# 重启网络
systemctl restart networking

# 验证
ip addr show br-mgmt  # 应显示 192.168.2.2
brctl show            # 应显示 lan1-4 在 br-mgmt 中

4.3 DNS 持久化

# 配置 systemd-resolved
mkdir -p /etc/systemd/resolved.conf.d
cat > /etc/systemd/resolved.conf.d/dns.conf <<EOF
[Resolve]
DNS=192.168.2.1 8.8.8.8
Domains=~.
EOF

systemctl restart systemd-resolved

五、系统核心优化(解决根本问题!)

5.1 内核网络参数优化

# 添加网络优化参数到 sysctl.conf
cat >> /etc/sysctl.conf <<EOF

# 网络优化参数(适用于高速网络和容器环境)
net.core.rmem_max = 67108864          # 接收缓冲区最大 64MB
net.core.wmem_max = 67108864          # 发送缓冲区最大 64MB
net.core.rmem_default = 131072         # 默认接收缓冲区 128KB
net.core.wmem_default = 131072         # 默认发送缓冲区 128KB
net.ipv4.tcp_rmem = 4096 87380 67108864   # TCP接收缓冲区
net.ipv4.tcp_wmem = 4096 65536 67108864   # TCP发送缓冲区
net.core.netdev_max_backlog = 10000    # 网卡队列大小
net.ipv4.tcp_window_scaling = 1        # 启用TCP窗口缩放
EOF

# 立即生效
sysctl -p

# 验证
sysctl net.core.rmem_max net.core.wmem_max
# 应显示 67108864 67108864

5.2 开机启动优化(硬件 offload)

# 修复 rc.local 语法并添加硬件优化
cat > /etc/rc.local <<'EOF'
#!/bin/sh -e
#
# rc.local - 开机启动脚本
#

# 开启硬件 offload(网卡硬件加速)
/usr/sbin/ethtool -K eth0 tx on rx on tso on gso on gro on > /var/log/ethtool.log 2>&1
/usr/sbin/ethtool -K eth0 tx-udp-segmentation on >> /var/log/ethtool.log 2>&1
/usr/sbin/ethtool -G eth0 rx 1024 tx 1024 >> /var/log/ethtool.log 2>&1

exit 0
EOF

# 赋予执行权限
chmod +x /etc/rc.local

# 测试执行
/etc/rc.local

六、OpenWrt LXC 容器配置

6.1 下载 OpenWrt rootfs

cd /tmp
wget https://downloads.immortalwrt.org/releases/24.10.0/targets/armsr/armv8/immortalwrt-24.10.0-armsr-armv8-rootfs.tar.gz

6.2 创建容器并解压

mkdir -p /var/lib/lxc/openwrt/rootfs
tar -xzf immortalwrt-*.tar.gz -C /var/lib/lxc/openwrt/rootfs/

6.3 容器配置文件

cat > /var/lib/lxc/openwrt/config <<'EOF'
# 容器基本配置
lxc.include = /usr/share/lxc/config/common.conf
lxc.arch = arm64
lxc.uts.name = openwrt
lxc.tty.max = 2
lxc.start.auto = 1
lxc.start.delay = 5

# rootfs路径
lxc.rootfs.path = dir:/var/lib/lxc/openwrt/rootfs

# WAN口 - 物理直通
lxc.net.0.type = phys
lxc.net.0.name = eth0
lxc.net.0.flags = up
lxc.net.0.link = wan

# LAN口 - 连接宿主机网桥
lxc.net.1.type = veth
lxc.net.1.name = eth1
lxc.net.1.flags = up
lxc.net.1.link = br-mgmt
lxc.net.1.hwaddr = 00:16:3e:01:02:03

# PPP 设备授权(PPPoE必须)
lxc.cgroup2.devices.allow = c 108:0 rwm
lxc.mount.entry = /dev/ppp dev/ppp none bind,create=file

# 基础设备挂载
lxc.mount.auto = proc:mixed sys:ro cgroup:mixed
EOF

6.4 启动并配置 OpenWrt

# 启动容器
lxc-start -n openwrt
lxc-ls -f  # 确认状态为 RUNNING

# 进入容器
lxc-attach -n openwrt

6.5 OpenWrt 内部网络配置

# 编辑 /etc/config/network
cat > /etc/config/network <<'EOF'
config interface 'loopback'
    option device 'lo'
    option proto 'static'
    option ipaddr '127.0.0.1'
    option netmask '255.0.0.0'

config interface 'wan'
    option device 'eth0'
    option proto 'pppoe'
    option username '你的宽带账号'
    option password '你的宽带密码'
    option ipv6 '1'
    option delegate '1'
    option reqprefix 'auto'

config interface 'lan'
    option device 'br-lan'
    option proto 'static'
    option ipaddr '192.168.2.1'
    option netmask '255.255.255.0'
    option ip6assign '60'

config device
    option name 'br-lan'
    option type 'bridge'
    list ports 'eth1'

config interface 'wan6'
    option proto 'dhcpv6'
    option device '@wan'
    option reqaddress 'try'
    option reqprefix 'auto'
EOF

6.6 OpenWrt DHCP 配置

# 编辑 /etc/config/dhcp
cat > /etc/config/dhcp <<'EOF'
config dnsmasq
    option domainneeded '1'
    option boguspriv '1'
    option filterwin2k '0'
    option localise_queries '1'
    option rebind_protection '1'
    option rebind_localhost '1'
    option local '/lan/'
    option domain 'lan'
    option expandhosts '1'
    option authoritative '1'
    option leasefile '/tmp/dhcp.leases'

config dhcp 'lan'
    option interface 'lan'
    option start '100'
    option limit '150'
    option leasetime '12h'
    option ra 'disabled'
    option dhcpv6 'disabled'
    option dhcpv4 'server'

config dhcp 'wan'
    option interface 'wan'
    option ignore '1'

config host
    option name 'armbian'
    list mac '5E:B4:EC:05:2F:29'
    option ip '192.168.2.150'
    option leasetime 'infinite'
EOF

# 重启服务并退出
/etc/init.d/network restart
/etc/init.d/dnsmasq restart
exit

七、Docker 环境配置

7.1 安装 Docker

# 添加 Docker 官方 GPG 密钥
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# 添加 Docker 阿里云源
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null

# 安装 Docker
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

# 验证
docker version
docker compose version

7.2 配置 Docker 镜像加速

# 创建 Docker 配置(华为云 + 网易镜像源)
cat > /etc/docker/daemon.json <<EOF
{
  "storage-driver": "overlay2",
  "data-root": "/var/lib/docker",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "registry-mirrors": [
    "https://05f073ad3c0010ea0f4bc00b7105ec20.mirror.swr.myhuaweicloud.com",
    "https://hub-mirror.c.163.com"
  ]
}
EOF

# 重启 Docker
systemctl restart docker
docker info | grep -A 2 "Registry Mirrors"

7.3 迁移 Docker 到傲腾(保留数据)

# 查看傲腾设备
lsblk -f | grep nvme0n1

# 创建挂载点并挂载
mkdir -p /mnt/optane
mount /dev/nvme0n1 /mnt/optane

# 验证数据(应看到 overlay2, containers 等目录)
ls -la /mnt/optane/

# 添加到 fstab 实现开机自动挂载
cp /etc/fstab /etc/fstab.bak
echo "UUID=$(blkid -s UUID -o value /dev/nvme0n1) /mnt/optane ext4 defaults,noatime 0 2" >> /etc/fstab

# 停止 Docker 并迁移数据
systemctl stop docker
[ -d /var/lib/docker.bak ] && rm -rf /var/lib/docker.bak
mv /var/lib/docker /var/lib/docker.bak
ln -s /mnt/optane /var/lib/docker
chown -R root:root /var/lib/docker

# 启动 Docker
systemctl start docker

# 验证
docker info | grep "Docker Root Dir"  # 应显示 /var/lib/docker
docker ps -a  # 应该能看到原来的容器
df -h /var/lib/docker  # 应显示傲腾的空间

八、Web 环境部署 (Docker Compose)

8.1 创建项目目录

# 在傲腾上创建网站目录
mkdir -p /mnt/optane/webstack
cd /mnt/optane/webstack

# 创建子目录
mkdir -p mysql/{data,conf} www php/conf.d nginx/conf.d

8.2 创建 docker-compose.yml(ARM64 优化版)

cat > docker-compose.yml <<'EOF'
name: webstack

services:
  mysql:
    image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/biarms/mysql:5.7.30-linux-arm64v8-linuxarm64
    container_name: web-mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: root123456
      MYSQL_DATABASE: appdb
      MYSQL_USER: appuser
      MYSQL_PASSWORD: app123456
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/conf:/etc/mysql/conf.d
    ports:
      - "3306:3306"
    networks:
      - webnet

  php:
    image: php:8.3-fpm-alpine
    container_name: web-php
    restart: unless-stopped
    depends_on:
      - mysql
    volumes:
      - ./www:/var/www/html
      - ./php/conf.d:/usr/local/etc/php/conf.d
    environment:
      - TZ=Asia/Shanghai
    networks:
      - webnet
    command: >
      sh -c "
      apk add --no-cache php83-mysqli php83-pdo_mysql php83-gd php83-zip php83-opcache php83-ctype php83-dom php83-iconv php83-xml php83-xmlreader php83-xmlwriter pcre2 libzip &&
      php-fpm
      "

  nginx:
    image: nginx:alpine
    container_name: web-nginx
    restart: unless-stopped
    depends_on:
      - php
    ports:
      - "80:80"
    volumes:
      - ./www:/var/www/html
      - ./nginx/conf.d:/etc/nginx/conf.d
    networks:
      - webnet

  adminer:
    image: adminer:latest
    container_name: web-adminer
    restart: unless-stopped
    ports:
      - "8080:8080"
    networks:
      - webnet

networks:
  webnet:
    driver: bridge
EOF

8.3 MySQL 优化配置

cat > /mnt/optane/webstack/mysql/conf/my.cnf <<'EOF'
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

user = mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp

# InnoDB
innodb_buffer_pool_size = 512M
innodb_log_file_size = 48M
innodb_flush_log_at_trx_commit = 2
innodb_file_per_table = 1
innodb_flush_method = O_DIRECT

# Query Cache
query_cache_type = 1
query_cache_size = 24M
query_cache_limit = 1M

# Connections
max_connections = 50
max_user_connections = 30
thread_cache_size = 8
thread_stack = 192K

# Temp Tables
tmp_table_size = 64M
max_heap_table_size = 64M
table_open_cache = 1600

# MyISAM
key_buffer_size = 16M
myisam-recover-options = BACKUP

# Packet
max_allowed_packet = 16M

# Slow Log
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/slow.log
long_query_time = 2
log_queries_not_using_indexes = 1
log_slow_admin_statements = 1
log_slow_slave_statements = 1

# Error Log
log_error = /var/lib/mysql/error.log
log_warnings = 2

# Binary Log
skip-log-bin

# Timeouts
wait_timeout = 300
interactive_timeout = 600

# Performance
expire_logs_days = 10
max_binlog_size = 100M
performance_schema = OFF

[client]
default-character-set = utf8mb4

[mysql]
default-character-set = utf8mb4

[mysqldump]
quick
quote-names
max_allowed_packet = 16M
EOF

8.4 Nginx 站点配置

cat > /mnt/optane/webstack/nginx/conf.d/default.conf <<'EOF'
server {
    listen 80;
    listen [::]:80;
    server_name localhost;
    root /var/www/html;
    index index.php index.html;

    access_log off;
    error_log /dev/null crit;
    client_max_body_size 100M;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location /uploads {
        alias /var/www/html/uploads;
        client_max_body_size 100M;
        autoindex off;
    }

    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        fastcgi_param PHP_VALUE "
            upload_max_filesize = 100M
            post_max_size = 100M
            max_execution_time = 300
            max_input_time = 300
            memory_limit = 256M
            log_errors = Off
            display_errors = On
        ";

        include fastcgi_params;
    }

    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }

    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg|eot)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
        access_log off;
    }
}
EOF

8.5 启动服务

cd /mnt/optane/webstack
docker compose up -d
docker compose ps

九、多站点配置

# 创建站点目录
cd /mnt/optane/webstack
mkdir -p www/site1 www/site2 www/site3

# 创建测试页面
echo "<h1>Site 1 - 主站点</h1>" > www/site1/index.html
echo "<h1>Site 2 - 第二个站点</h1>" > www/site2/index.html
echo "<h1>Site 3 - 第三个站点</h1>" > www/site3/index.html

# 创建 Nginx 多站点配置
cat > /mnt/optane/webstack/nginx/conf.d/sites.conf <<'EOF'
server {
    listen 80;
    server_name site1.local;
    root /var/www/html/site1;
    index index.html index.php;

    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

server {
    listen 80;
    server_name site2.local;
    root /var/www/html/site2;
    index index.html index.php;

    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

server {
    listen 80;
    server_name site3.local;
    root /var/www/html/site3;
    index index.html index.php;

    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}
EOF

# 重启 Nginx
docker compose restart nginx

十、系统检查脚本

cat > /root/check-system.sh <<'EOF'
#!/bin/bash
echo "══════════════════════════════════════════════"
echo "【1. 系统负载】"
uptime
free -h
echo ""

echo "【2. 网络状态】"
ip addr show br-mgmt | grep "inet "
bridge link show | grep -E "lan|veth"
ip route show default
echo ""

echo "【3. 容器状态】"
lxc-ls -f
echo ""
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
echo ""

echo "【4. 存储使用】"
df -h | grep -E "(/dev/mmc|nvme|Filesystem|/mnt/optane)"
echo ""

echo "【5. CPU 温度】"
cat /sys/class/thermal/thermal_zone*/temp 2>/dev/null | awk '{print $1/1000 "°C"}' || echo "N/A"
echo ""

echo "【6. 内核优化参数】"
sysctl net.core.rmem_max net.core.wmem_max 2>/dev/null || echo "N/A"
echo "══════════════════════════════════════════════"
EOF

chmod +x /root/check-system.sh

十一、常用命令速查

用途 命令
启动 OpenWrt lxc-start -n openwrt
进入 OpenWrt lxc-attach -n openwrt
重启网络 systemctl restart networking
应用内核优化 sysctl -p
启动 Web 环境 cd /mnt/optane/webstack && docker compose up -d
查看 Docker 容器 docker ps
重启 Nginx docker restart web-nginx
重启 MySQL docker restart web-mysql
查看 MySQL 日志 docker logs web-mysql --tail 50
系统检查 /root/check-system.sh
速度测试 time curl -o /dev/null -s -w "%{time_total}s\n" http://localhost/

十二、最终验证

# 1. 运行检查脚本
/root/check-system.sh

# 2. 测试网络
ping -c 3 baidu.com
ping -c 3 8.8.8.8

# 3. 测试 OpenWrt
lxc-ls -f
ping -c 3 192.168.2.1

# 4. 测试 Web 服务
time curl -o /dev/null -s -w "首页速度: %{time_total}s\n" http://localhost/
time curl -o /dev/null -s -w "PHP速度: %{time_total}s\n" http://localhost/test.php
curl -I http://localhost:8080 2>/dev/null | head -n 1  # Adminer

# 5. 验证内核优化
sysctl net.core.rmem_max net.core.wmem_max
# 应显示 67108864 67108864

十三、客户端访问配置

如需在电脑上测试多站点,修改 hosts 文件:

Windows: C:\Windows\System32\drivers\etc\hosts
Linux/Mac: /etc/hosts

添加以下行:

192.168.2.2 site1.local site2.local site3.local

十四、系统资源统计

组件 规格 已用 用途
eMMC 229GB ~2.1GB 系统
傲腾 14GB ~3.3GB Docker 数据
内存 8GB 充足 运行服务

运行中的服务:

  • ✅ OpenWrt 容器 – 主路由 (PPPoE + DHCP)
  • ✅ MySQL – 数据库
  • ✅ PHP-FPM – 动态页面
  • ✅ Nginx – Web 服务器
  • ✅ Adminer – 数据库管理

十五、故障排查

如果首页慢,但 test.php 快

# 检查 WordPress 插件
ls -la /mnt/optane/webstack/www/wp-content/plugins/ | wc -l

# 查看慢查询
docker exec web-mysql cat /var/lib/mysql/slow.log | tail -20

如果 OpenWrt 慢

lxc-attach -n openwrt -- top
lxc-attach -n openwrt -- logread | tail -20

如果网络不通

# 检查路由
ip route show

# 检查网桥
brctl show

十六、结语

恭喜!您的 RK3568 已经成为一台功能强大的:

主路由器(OpenWrt + PPPoE + DHCP)
Docker 容器平台(傲腾加速)
Web 服务器(Nginx + PHP + MySQL)
多站点支持(3个虚拟主机)
数据库管理(Adminer)

核心优化已生效:

  • net.core.rmem_max = 64MB – 大流量不丢包
  • net.core.wmem_max = 64MB – 发送数据更顺畅
  • rc.local 硬件 offload – 网卡硬件加速

总计占用: 系统盘仅用 2.1GB,傲腾用 3.3GB,剩余 226GB 可用于 NAS 存储!


Posted in Resourse | Leave a comment

RK3568 路由器 DD 命令刷机教程

DD 命令刷机,无需 USB 数据线,条件是已经刷过机,且能通过 SSH 链接。

准备:
1、SSH 软件,推荐 MobaXterm_CHS
2、准备好固件,将固件解压,并将固件改名为 op.img

刷机方法:
1、通过 SSH 软件 SSH 登录路由器。
2、SSH 登录成功后,在左手边的文件管理器窗口,找到 tmp 目录,并双击 tmp 目录进入 tmp 目录。
3、将 op.img 镜像拖入这个目录(tmp)。
4、在右手的命令窗口输入刷机命令(可复制粘贴):dd if=/tmp/op.img of=/dev/mmcblk0 bs=1M

命令输入完成后,很快就能刷机完成,等待 写玩(时间取决于emmc的速率),断电重启。

Posted in Resourse | Leave a comment