资料地址文档GitHub Release 页面EasyTier 管理器EasyTier 游戏联机启动器EasyTier 鸿蒙版安装 (命令行程序)1、手动下载命令行程序访问 ⬇️下载页面 下载适用于您操作系统和硬件架构的 EasyTier 命令行程序。下载后为 ZIP 压缩包，解压后既可直接使用。Linux / MacOS / FreeBSD:./easytier-core --versionWindows:.\easytier-core.exe --version2、DockerHubDockerHub 镜像地址# docker.io 镜像 docker pull easytier/easytier:latest docker run -d --privileged --network host easytier/easytier:latest # 国内用户可以使用 DaoCloud 镜像 docker pull m.daocloud.io/docker.io/easytier/easytier:latest docker run -d --privileged --network host m.daocloud.io/docker.io/easytier/easytier:latest请继续阅读 快速组网 文档以了解参数含义和使用方法。3、通过Docker Compose安装不含Web控制台的docker-compose.ymlservices: watchtower: # 用于自动更新easytier镜像，若不需要请删除这部分 image: containrrr/watchtower container_name: watchtower restart: unless-stopped environment: - TZ=Asia/Shanghai - WATCHTOWER_NO_STARTUP_MESSAGE volumes: - /var/run/docker.sock:/var/run/docker.sock command: --interval 3600 --cleanup --label-enable easytier: image: easytier/easytier:latest # 国内用户可以使用 m.daocloud.io/docker.io/easytier/easytier:latest hostname: easytier container_name: easytier labels: com.centurylinklabs.watchtower.enable: 'true' restart: unless-stopped network_mode: host cap_add: - NET_ADMIN - NET_RAW environment: - TZ=Asia/Shanghai devices: - /dev/net/tun:/dev/net/tun volumes: - /etc/easytier:/root - /etc/machine-id:/etc/machine-id:ro # 映射宿主机机器码 command: -d --network-name <用户> --network-secret <密码> -p tcp://public.easytier.cn:11010含Web控制台的docker-compose.ymlversion: '3.8' services: easytier-web: image: easytier/easytier:latest container_name: easytier-web restart: always network_mode: host # 使用主机网络，注意：host模式下ports映射不生效 labels: com.centurylinklabs.watchtower.enable: 'true' # 启用watchtower自动更新 entrypoint: easytier-web-embed command: - "-a" - "12121" # web网页打开端口 - "-l" - "12121" # web网页里面的api端口，注册和验证码需要 - "-c" - "21212" # web服务端给设备下发信息用的端口，也是设备连接web用的端口，使用方式看最后一段 watchtower: # 用于自动更新easytier镜像，若不需要请删除这部分 image: containrrr/watchtower container_name: watchtower restart: unless-stopped environment: - TZ=Asia/Shanghai - WATCHTOWER_NO_STARTUP_MESSAGE volumes: - /var/run/docker.sock:/var/run/docker.sock command: --interval 3600 --cleanup --label-enable easytier: image: easytier/easytier:latest # 国内用户可以使用 m.daocloud.io/docker.io/easytier/easytier:latest hostname: "tencent-vm" # web端显示的设备名字，可自行修改 container_name: easytier labels: com.centurylinklabs.watchtower.enable: 'true' # 启用watchtower自动更新 restart: unless-stopped network_mode: host cap_add: - NET_ADMIN - NET_RAW environment: - TZ=Asia/Shanghai devices: - /dev/net/tun:/dev/net/tun volumes: - /etc/easytier:/root - /etc/machine-id:/etc/machine-id:ro # 映射宿主机机器码 command: --config-server udp://127.0.0.1:21212/admin # 修改为自己的IP地址和注册账号4、一键安装脚本（仅 Linux）注意：一键脚本依赖 unzip，请提前下载并安装。wget -O /tmp/easytier.sh "https://raw.githubusercontent.com/EasyTier/EasyTier/main/script/install.sh" && sudo bash /tmp/easytier.sh install --gh-proxy https://ghfast.top/脚本执行成功后，EasyTier 的二进程程序会安装到 /opt/easytier 目录下，配置文件位于 /opt/easytier/config/default.conf。配置文件可通过 配置文件生成器 生成。EasyTier 会被注册为系统服务，可以通过以下命令管理：systemctl start easytier@default5、通过源码安装cargo install --git https://github.com/EasyTier/EasyTier.git easytier源码安装需要 Rust 环境，并且安装 LLVM。安装 (图形界面)EasyTier GUI访问 ⬇️下载页面 下载适用于您操作系统和硬件架构的图形界面程序，并安装即可。安装成功后可阅读 公共服务器组网 文档以了解图形界面工具的使用方法。需要注意，EasyTier GUI 依赖 WebView，可能有以下常见问题：在低版本 Windows 上 WebView 下载失败，由于国内网络环境问题，可能无法下载 WebView 组件。请手动安装 WebView2 或 Edge 浏览器。在低版本 Android 上样式丢失，显示混乱。请在应用商店中手动更新 WebView 组件。快速组网利用共享节点组网当没有公网 IP 时，可使用 EasyTier 社区提供的免费共享节点快速组网。节点间会自动尝试 NAT 穿透并建立 P2P 连接，P2P 失败时由共享节点中转数据。通过以下步骤可以构建一个简单的双节点虚拟网络：#mermaid-12{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-12 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-12 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-12 .error-icon{fill:#552222;}#mermaid-12 .error-text{fill:#552222;stroke:#552222;}#mermaid-12 .edge-thickness-normal{stroke-width:1px;}#mermaid-12 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-12 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-12 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-12 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-12 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-12 .marker{fill:#333333;stroke:#333333;}#mermaid-12 .marker.cross{stroke:#333333;}#mermaid-12 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-12 p{margin:0;}#mermaid-12 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-12 .cluster-label text{fill:#333;}#mermaid-12 .cluster-label span{color:#333;}#mermaid-12 .cluster-label span p{background-color:transparent;}#mermaid-12 .label text,#mermaid-12 span{fill:#333;color:#333;}#mermaid-12 .node rect,#mermaid-12 .node circle,#mermaid-12 .node ellipse,#mermaid-12 .node polygon,#mermaid-12 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-12 .rough-node .label text,#mermaid-12 .node .label text,#mermaid-12 .image-shape .label,#mermaid-12 .icon-shape .label{text-anchor:middle;}#mermaid-12 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-12 .rough-node .label,#mermaid-12 .node .label,#mermaid-12 .image-shape .label,#mermaid-12 .icon-shape .label{text-align:center;}#mermaid-12 .node.clickable{cursor:pointer;}#mermaid-12 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-12 .arrowheadPath{fill:#333333;}#mermaid-12 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-12 .flowchart-link{stroke:#333333;fill:none;}#mermaid-12 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-12 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-12 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-12 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-12 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-12 .cluster text{fill:#333;}#mermaid-12 .cluster span{color:#333;}#mermaid-12 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-12 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-12 rect.text{fill:none;stroke-width:0;}#mermaid-12 .icon-shape,#mermaid-12 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-12 .icon-shape p,#mermaid-12 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-12 .icon-shape rect,#mermaid-12 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-12 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-12 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-12 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}虚拟网 abc连接连接P2P直连公共共享节点节点A节点B步骤示例假设有两个节点 A 和 B：1. 在节点 A 上运行Linux:# 管理员权限运行 ./easytier-core -d --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010Windows:# 管理员权限运行 .\easytier-core.exe -d --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010-d 自动分配虚拟 IP，默认分配 10.126.126.0/24 网段，可使用 -i 10.11.11.0/24 指定其他虚拟 IP。--network-name 指定虚拟网络名称（支持中文）。注意：若与其他用户网络名冲突，可能导致组网失败。--network-secret 指定虚拟网络的密码，用于保护网络安全。-p 指定节点地址，此处为官方共享节点，也可用其他公共节点2. 在节点 B 上运行Linux:# 管理员权限运行 ./easytier-core -d --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010Windows:# 管理员权限运行 .\easytier-core.exe -d --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010-d 表示 DHCP 模式，自动分配虚拟 IP-p 需指定与节点 A 使用相同的共享节点3. 测试组网在节点 B 上测试与节点 A 的连通性：ping 10.126.126.1 ping 10.126.126.2!> 注意如无法 ping 通，可能是防火墙阻止入站流量。请关闭防火墙或添加放行规则。4. 加入更多节点可以继续在其他节点上运行相同命令，加入同一虚拟网络。查看虚拟网络状态EasyTier 启动后，可用 easytier-cli 管理和查看状态。查看虚拟网中的节点信息：easytier-cli peeripv4hostnamecostlat_msloss_raterx_bytestx_bytestunnel_protonat_typeid10.144.144.1abc-dec13.452017.33kB20.42kBudpFullCone390879727查看虚拟网路由信息：easytier-cli routeipv4hostnameproxy_cidrsnext_hop_ipv4next_hop_hostnamenext_hop_latcost10.144.144.1abc-decDIRECT3.6461查看本节点信息：easytier-cli node同时使用多个共享节点组网为提升可用性，可同时连接多个共享节点，只需指定多个 -p 参数：-p tcp://1.1.1.1:11010 -p udp://1.1.1.2:11011建议所有节点指定相同的共享节点列表。组网原理示意下图展示了多个共享节点集群下的组网模式：#mermaid-260{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-260 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-260 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-260 .error-icon{fill:#552222;}#mermaid-260 .error-text{fill:#552222;stroke:#552222;}#mermaid-260 .edge-thickness-normal{stroke-width:1px;}#mermaid-260 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-260 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-260 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-260 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-260 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-260 .marker{fill:#333333;stroke:#333333;}#mermaid-260 .marker.cross{stroke:#333333;}#mermaid-260 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-260 p{margin:0;}#mermaid-260 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-260 .cluster-label text{fill:#333;}#mermaid-260 .cluster-label span{color:#333;}#mermaid-260 .cluster-label span p{background-color:transparent;}#mermaid-260 .label text,#mermaid-260 span{fill:#333;color:#333;}#mermaid-260 .node rect,#mermaid-260 .node circle,#mermaid-260 .node ellipse,#mermaid-260 .node polygon,#mermaid-260 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-260 .rough-node .label text,#mermaid-260 .node .label text,#mermaid-260 .image-shape .label,#mermaid-260 .icon-shape .label{text-anchor:middle;}#mermaid-260 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-260 .rough-node .label,#mermaid-260 .node .label,#mermaid-260 .image-shape .label,#mermaid-260 .icon-shape .label{text-align:center;}#mermaid-260 .node.clickable{cursor:pointer;}#mermaid-260 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-260 .arrowheadPath{fill:#333333;}#mermaid-260 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-260 .flowchart-link{stroke:#333333;fill:none;}#mermaid-260 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-260 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-260 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-260 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-260 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-260 .cluster text{fill:#333;}#mermaid-260 .cluster span{color:#333;}#mermaid-260 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-260 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-260 rect.text{fill:none;stroke-width:0;}#mermaid-260 .icon-shape,#mermaid-260 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-260 .icon-shape p,#mermaid-260 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-260 .icon-shape rect,#mermaid-260 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-260 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-260 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-260 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}节点 D节点 C共享节点共享节点 A网络名: Public共享节点 B网络名: Public节点 C网络名: abc节点 D网络名: abc即使出现网络分区，C 只能连到 A，D 只能连到 B，C 和 D 依然可通信：#mermaid-264{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-264 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-264 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-264 .error-icon{fill:#552222;}#mermaid-264 .error-text{fill:#552222;stroke:#552222;}#mermaid-264 .edge-thickness-normal{stroke-width:1px;}#mermaid-264 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-264 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-264 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-264 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-264 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-264 .marker{fill:#333333;stroke:#333333;}#mermaid-264 .marker.cross{stroke:#333333;}#mermaid-264 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-264 p{margin:0;}#mermaid-264 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-264 .cluster-label text{fill:#333;}#mermaid-264 .cluster-label span{color:#333;}#mermaid-264 .cluster-label span p{background-color:transparent;}#mermaid-264 .label text,#mermaid-264 span{fill:#333;color:#333;}#mermaid-264 .node rect,#mermaid-264 .node circle,#mermaid-264 .node ellipse,#mermaid-264 .node polygon,#mermaid-264 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-264 .rough-node .label text,#mermaid-264 .node .label text,#mermaid-264 .image-shape .label,#mermaid-264 .icon-shape .label{text-anchor:middle;}#mermaid-264 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-264 .rough-node .label,#mermaid-264 .node .label,#mermaid-264 .image-shape .label,#mermaid-264 .icon-shape .label{text-align:center;}#mermaid-264 .node.clickable{cursor:pointer;}#mermaid-264 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-264 .arrowheadPath{fill:#333333;}#mermaid-264 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-264 .flowchart-link{stroke:#333333;fill:none;}#mermaid-264 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-264 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-264 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-264 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-264 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-264 .cluster text{fill:#333;}#mermaid-264 .cluster span{color:#333;}#mermaid-264 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-264 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-264 rect.text{fill:none;stroke-width:0;}#mermaid-264 .icon-shape,#mermaid-264 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-264 .icon-shape p,#mermaid-264 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-264 .icon-shape rect,#mermaid-264 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-264 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-264 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-264 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}节点 D共享节点节点 C节点 C网络名: abc共享节点 A网络名: Public共享节点 B网络名: Public节点 D网络名: abc同时加入/组建多个虚拟网络EasyTier 支持同一设备运行多个进程，每个进程加入不同虚拟网。注意：不同虚拟网的虚拟 IP 网段不能重叠，否则路由冲突；启动多个实例时需指定不同监听端口，否则端口冲突。示例：# 管理员权限运行 ./easytier-core --network-name net1 -p tcp://public.easytier.cn:11010 -l 11010 ./easytier-core --network-name net2 -p tcp://public.easytier.cn:11010 -l 21010-l 指定监听端口。使用 Web 控制台EasyTier 支持使用 Web 控制台 来管理 EasyTier 节点，包括查看节点状态、配置节点参数、查看节点日志等。运行 EasyTier 节点如果希望 EasyTier 节点可以被 Web 控制台管理，需要在启动时指定 --config-server 或 -w 参数，例如：sudo ./easytier-core -w <你的用户名>请将 <你的用户名> 替换为你在 Web 控制台注册的用户名。如果终端出现类似 “连接成功” 或 “已连接服务器” 的提示，则表示 Easytier Core 已成功连接到 Web 控制台的服务器。[scode type="green"]提示Web 后端通过机器唯一码来识别设备和持久化配置，默认情况下 EasyTier 会自动从系统中获取机器唯一码。若机器码获取失败会导致重启后配置丢失，建议使用 --machine-id 参数指定机器码，例如：sudo ./easytier-core -w <你的用户名> --machine-id abc123请确保机器码在所有设备中唯一且不变。 强烈建议 Docker 环境下手动指定机器码。[/scode][scode type="red"]注意一台机器只能有一个 EasyTier 进程被 Web 控制台管理，如果有多个进程可能会导致奇怪的问题。[/scode][scode type="green"]提示可以通过 --hostname <自定义主机名> 参数指定控制台上显示的主机名。[/scode]自建 Web 控制台EasyTier 支持自建 Web 控制台来管理 EasyTier 节点，EasyTier Web 控制台采用前后端分离的架构，在设计上共有3个服务Web 前端（默认11211端口）Web API 后端（默认11211端口）配置下发端（默认22020端口，UDP协议）其中，web前端与web api后端默认绑定在同一端口，配置下发端则是web api后端的一部分。EasyTier的web控制台有2个版本easytier-web（仅web api后端）easytier-web-embed（web前端 + web api后端）下面举一个用easytier-web-embed前后端同时部署的例子./easytier-web-embed \ --api-server-port 11211 \ --api-host "http://127.0.0.1:11211" \ --config-server-port 22020 \ --config-server-protocol udp运行后若无任何内容显示则成功。下面是easytier-web-embed常用参数的说明：--api-server-port: web前后端的端口--api-host: 在web前端指定web api后端的访问地址（不设你就只能在web前端手动指定api后端地址）--config-server-port: 用于easytier-core连接的配置下发端的端口--config-server-protocol: 用于easytier-core连接的配置下发端的协议（实测支持 tcp, udp, ws）--web-server-port: 额外监听web前端的端口（注：此设置不受 --no-web 影响）--no-web: 不运行web前端（关闭 --api-server-port 端口的前端功能）之后打开 Web 控制台 http://127.0.0.1:11211就可以看到页面。接入自建 Web 控制台前面我们本地搭建好了web控制台，并且配置下发端口为22020，协议UDP，那么easytier接入自建控制台的指令就是./easytier-core -w udp://127.0.0.1:22020/<你在自建web控制台上的用户名>!> 注意Web 控制台有了两个默认账户，用户名与密码分别为admin和user。虽然这属于普通账户，但仍需留意其存在。开机自启（注册服务）一键注册服务EasyTier Cli 提供注册服务命令，可以在大部分系统上一键将 EasyTier 注册为系统服务。注册后，EasyTier 会在系统启动时自动启动，并在后台运行。使用该命令需要 easytier-core 和 easytier-cli 在同一目录下。进入该目录后，运行以下命令：Linux:# 假设 EasyTier 的启动参数为 -w abc sudo ./easytier-cli service install -w abcWindows:# 假设 EasyTier 的启动参数为 -w abc .\easytier-cli.exe service install -w abcinstall 后的部分会作为 easytier-core 的启动参数。完整示例:Linux:# 假设 EasyTier 的启动参数为 -w abc sudo ./easytier-cli service install \ --description "自定义服务描述" \ # 可选，默认使用包描述 --display-name "显示名称" \ # 可选，服务显示名称 --disable-autostart \ # 可选，禁用开机自启（默认启用） --core-path /path/to/easytier-core \ # 可选，指定二进制路径 --service-work-dir /工作目录路径 \ # 可选，指定工作目录 -- -w abc # 可选，传递给 easytier-core 的参数Windows:# 假设 EasyTier 的启动参数为 -w abc .\easytier-cli.exe service install ` --description "自定义服务描述" ` # 可选，默认使用包描述 --display-name "显示名称" ` # 可选，服务显示名称 --disable-autostart ` # 可选，禁用开机自启（默认启用） --core-path /path/to/easytier-core ` # 可选，指定二进制路径 --service-work-dir /工作目录路径 ` # 可选，指定工作目录 -- -w abc # 可选，传递给 easytier-core 的参数服务安装成功后，可以使用以下命令对服务进行管理：启动服务：Linux:sudo ./easytier-cli service startWindows:.\easytier-cli.exe service start停止服务：Linux:sudo ./easytier-cli service stopWindows:.\easytier-cli.exe service stop查看状态：Linux:sudo ./easytier-cli service statusWindows:.\easytier-cli.exe service status卸载服务：Linux:sudo ./easytier-cli service uninstallWindows:.\easytier-cli.exe service uninstall安装为 macOS 服务下载并安装 serviceman。打开终端，运行如下命令注册服务：# 使用配置文件注册 easytier 服务 sudo serviceman add --name easytier --daemon \ --workdir /var/log/easytier \ --group wheel --user root \ -- easytier-core -c ~/.config/easytier.toml# 不使用配置文件注册 easytier 服务 sudo serviceman add --name easytier --daemon \ --workdir /var/log/easytier \ --group wheel --user root \ -- easytier-core --ipv4 x.x.x.x --network-name xxx --network-secret yyy --peers tcp://peer_host:11010启动 easytier 服务：sudo launchctl start easytier # 加载任务, -w选项会在下次登录/重新启动时重新启动。 launchctl load -w /Library/LaunchDaemons/easytier.plist关闭 easytier 服务：sudo launchctl stop easytier # 卸载任务, -w选项会在下次登录/重新启动时不再执行。 launchctl unload -w /Library/LaunchDaemons/easytier.plist查看运行日志：sudo tail -f /var/log/easytier.log配置选项完整配置选项可使用 easytier-core --help 查看全部配置项。配置服务器参数说明-w, --config-server配置服务器地址。允许格式：- 完整URL：--config-server udp://127.0.0.1:22020/admin- 仅用户名：--config-server admin，将使用官方的服务器[env: ET_CONFIG_SERVER=]--machine-idWeb 配置服务器通过 machine id 来识别机器，用于断线重连后的配置恢复，需要保证唯一且固定不变。默认从系统获得。 [env: ET_MACHINE_ID=]-c, --config-file配置文件路径，注意：命令行中的配置的选项会覆盖配置文件中的选项 [env: ET_CONFIG_FILE=]网络设置参数说明--network-name用于标识此VPN网络的网络名称 [env: ET_NETWORK_NAME=]--network-secret网络密钥，用于验证此节点属于VPN网络 [env: ET_NETWORK_SECRET=]-i, --ipv4此VPN节点的IPv4地址。如果为空，则此节点将仅转发数据包，不会创建TUN设备 [env: ET_IPV4=]-d, --dhcp由Easytier自动确定并设置IP地址，默认从10.0.0.1开始。警告：在使用DHCP时，如果网络中出现IP冲突，IP将自动更改。 [env: ET_DHCP=]-p, --peers最初要连接的对等节点 [env: ET_PEERS=]-e, --external-node使用公共共享节点来发现对等节点 [env: ET_EXTERNAL_NODE=]-n, --proxy-networks将本地网络导出到VPN中的其他对等节点，例如：10.0.0.0/24。支持映射到其他CIDR，例如：10.0.0.0/24->192.168.0.0/24 [env: ET_PROXY_NETWORKS=]RPC 设置参数说明-r, --rpc-portal用于管理的RPC门户地址。支持以下格式：- 0 表示随机端口- 12345 表示在localhost的12345上监听- 0.0.0.0:12345 表示在所有接口的12345上监听默认是 0，首先尝试 15888[env: ET_RPC_PORTAL=]--rpc-portal-whitelistRPC门户白名单，仅允许这些地址访问RPC门户，例如：127.0.0.1/32,127.0.0.0/8,::1/128 [env: ET_RPC_PORTAL_WHITELIST=]监听器设置参数说明-l, --listeners监听器用于接受连接，支持以下格式：- 端口号：<11010>，意味着tcp/udp将在11010端口监听，ws/wss将在11010和11011端口监听，wg将在11011端口监听。- URL：<tcp://0.0.0.0:11010>，其中tcp可以是tcp、udp、ring、wg、ws、wss协议。- 协议和端口对：<proto:port>，例如wg:11011，表示使用WireGuard协议在11011端口监听。[env: ET_LISTENERS=]--mapped-listeners手动指定监听器的公网地址，其他节点可以使用该地址连接到本节点。例如：tcp://123.123.123.123:11223，可以指定多个。 [env: ET_MAPPED_LISTENERS=]--no-listener不监听任何端口，只连接到对等节点 [env: ET_NO_LISTENER=]其他设置参数说明--hostname用于标识此设备的主机名 [env: ET_HOSTNAME=]-m, --instance-name实例名称，用于在同一台机器上标识此VPN节点 [env: ET_INSTANCE_NAME=]--vpn-portal定义VPN门户的URL，允许其他VPN客户端连接。例如：wg://0.0.0.0:11010/10.14.14.0/24 [env: ET_VPN_PORTAL=]--default-protocol连接到对等节点时使用的默认协议 [env: ET_DEFAULT_PROTOCOL=]-u, --disable-encryption禁用对等节点通信的加密，默认为false，必须与对等节点相同 [env: ET_DISABLE_ENCRYPTION=]--multi-thread使用多线程运行时，默认为单线程 [env: ET_MULTI_THREAD=]--disable-ipv6不使用IPv6 [env: ET_DISABLE_IPV6=]--dev-name可选的TUN接口名称 [env: ET_DEV_NAME=]--mtuTUN设备的MTU，默认为非加密时为1380，加密时为1360 [env: ET_MTU=]--latency-first延迟优先模式，将尝试使用最低延迟路径转发流量，默认使用最短路径 [env: ET_LATENCY_FIRST=]--exit-nodes转发所有流量的出口节点，虚拟IPv4地址，优先级由列表顺序决定 [env: ET_EXIT_NODES=]--enable-exit-node允许此节点成为出口节点 [env: ET_ENABLE_EXIT_NODE=]--proxy-forward-by-system通过系统内核转发子网代理数据包，禁用内置NAT [env: ET_PROXY_FORWARD_BY_SYSTEM=]--no-tun不创建TUN设备，可以使用子网代理访问节点 [env: ET_NO_TUN=]--use-smoltcp为子网代理和 KCP 代理启用smoltcp堆栈 [env: ET_USE_SMOLTCP=]--manual-routes手动分配路由CIDR，将禁用子网代理和从对等节点传播的wireguard路由。例如：192.168.0.0/16 [env: ET_MANUAL_ROUTES=]--relay-network-whitelist仅转发白名单网络的流量，支持通配符字符串。多个网络名称间可以使用英文空格间隔。 [env: ET_RELAY_NETWORK_WHITELIST=]--disable-p2p禁用P2P通信，只通过--peers指定的节点转发数据包 [env: ET_DISABLE_P2P=]--disable-udp-hole-punching禁用UDP打洞功能 [env: ET_DISABLE_UDP_HOLE_PUNCHING=]--relay-all-peer-rpc转发所有对等节点的RPC数据包，即使对等节点不在转发网络白名单中。 [env: ET_RELAY_ALL_PEER_RPC=]--socks5启用 socks5 服务器，允许 socks5 客户端访问虚拟网络。格式: <端口>，例如：1080 [env: ET_SOCKS5=]--compression要使用的压缩算法，支持 none、zstd。默认为 none [env: ET_COMPRESSION=]--bind-device将连接器的套接字绑定到物理设备以避免路由问题。 [env: ET_BIND_DEVICE=]--enable-kcp-proxy使用 KCP 代理 TCP 流，提高在 UDP 丢包网络上的延迟和吞吐量。 [env: ET_ENABLE_KCP_PROXY=]--disable-kcp-input不允许其他节点使用 KCP 代理 TCP 流到此节点。 [env: ET_DISABLE_KCP_INPUT=]--enable-quic-proxy使用 QUIC 代理 TCP 流，提高在 UDP 丢包网络上的延迟和吞吐量。 [env: ET_ENABLE_QUIC_PROXY=]--disable-quic-input不允许其他节点使用 QUIC 代理 TCP 流到此节点。 [env: ET_DISABLE_QUIC_INPUT=]--port-forward将本地端口转发到虚拟网络中的远程端口。例如：udp://0.0.0.0:12345/10.126.126.1:23456 [env: ET_PORT_FORWARD=]--accept-dns如果为true，则启用魔法DNS。使用魔法DNS，您可以使用域名访问其他节点，例如：<hostname>.et.net [env: ET_ACCEPT_DNS=]--private-mode如果为true，则不允许使用了与本网络不相同的网络名称和密码的节点通过本节点进行握手或中转 [env: ET_PRIVATE_MODE=]--foreign-relay-bps-limit限制转发流量的带宽 [env: ET_FOREIGN_RELAY_BPS_LIMIT=]--console-log-level控制台日志级别 [env: ET_CONSOLE_LOG_LEVEL=]--file-log-level文件日志级别 [env: ET_FILE_LOG_LEVEL=]--file-log-dir存储日志文件的目录 [env: ET_FILE_LOG_DIR=]配置文件支持使用 -c 参数指定配置文件路径。easytier-core -c ./config.yaml!> 注意：配置文件中的参数可以被命令行覆盖，比如配置文件中指定了 --hostname abc，但在命令行中使用 --hostname xyz，则会使用命令行中的主机名参数 xyz。使用参数运行可以获得对应参数的配置文件。配置文件会打印在命令行中，可以手动复制对应配置保存为toml文件即可。在不使用参数的情况下直接运行 easytier-core 可以获得最小配置文件。多配置文件启动可以通过 -c 参数指定多个配置文件，EasyTier 会在一个进程中加载多个配置文件并启动多个虚拟网络。easytier-core -c ./config1.yaml -c ./config2.yaml配置文件生成工具官网提供了配置文件生成工具，可以通过访问 配置文件生成工具 来生成配置文件。常见问题Q：Windows 7 无法创建网络，程序崩溃或者报错无法创建虚拟网？A： Windows 7 需为 SP1 及以上版本，并安装以下补丁：KB3063858KB4474419Q：Linux 命令行帮助是英文，如何切换为中文？A： 设置环境变量 LANG=zh_CN，命令如下：export LANG=zh_CNQ：启动后提示 TunError 怎么办？A： 请确认 TUN 驱动已正确加载，并且 /dev/net/tun 文件存在。如果在 Docker 中运行，请确保开启特权模式。Linux 下加载 TUN 驱动的命令如下：modprobe tun mkdir -p /dev/net sudo mknod /dev/net/tun c 10 200Q：启动后报错 Address already in use？A： 可能是端口冲突。请检查 11010 端口或启动参数指定的端口（如 -l tcp:12345）是否被其他程序占用。Q：一定需要WEB控制台吗？A： EasyTier 是去中心化的，所以其没有也不需要一个类似 ZeroTier、Tailscale 的中心控制面板来管理所有设备。Q：WEB 控制台到底是干什么的？A： WEB 控制台的作用是远程管理/下发配置，即如果使用-w <web控制台用户名>参数启动，机器会尝试连接到指定的服务器，并申明自己为指定的用户名。此时，在面板，你可以通过这个用户名登录，并管理声明为该用户的机器。也就是说，你无须担心有人冒名使用你的用户名开启 core，因为只要你不对该机器下发与其他机器相同的配置，就不会泄露你的网络信息。相反，这反而会使它的所有内网服务暴露在你面前。Q：WEB 控制台的用户名与网络名称相同吗？A： 不相同。网络名称 + 网络密码 完全相同才能成功建立网络，传递有效信息。Q：WEB 控制台里看不到其他组网的机器怎么办？A： 如果这些机器是通过其他方式启动的，那么它的配置文件就是通过机器本身管理的，自然是无法在 web 面板里看到的。如果需要检测其是否成功连接，你可以在机器中输入easytier-cli node easytier-cli peers来查看。或者你也可以随便打开 web 面板里受控机器 (与要查看是否连接的机器使用的是同一个网络名称)，其已连接目录里是否存在你要检测是否连接的机器。Q：如何启动多个实例？A： 一台机器可能需要同时为多个网络提供转发服务，但这可能导致端口冲突报错。只要设置监听器 (listener) 的端口没有与其他已启动的实例冲突即可。如果设置了 rpc，也需要保证端口不冲突。
第一步引入依赖 <dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.5</version> </dependency>第二步application.yml中增加jasypt配置# 如果密文加盐，需要配置盐值 jasypt: encryptor: password: # 密钥（该值不应该写在配置文件中，使用时建议通过【-Djasypt.encryptor.password=盐值】在启动命令传入） algorithm: PBEWITHHMACSHA512ANDAES_256 # 算法 iv-generator-classname: org.jasypt.iv.RandomIvGenerator # 盐生成器第三步加密数据通过Jasypt Jar包加密数据获取Jar包：首先需要拥有 jasypt-x.x.x.jar 文件。如果你使用Maven，可以在本地仓库（通常是 ~/.m2/repository/org/jasypt/jasypt/）找到；也可以在Maven中央仓库下载。执行加密命令：在命令行中运行以下命令（参数根据实际情况替换）：java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="明文" password="密钥" algorithm="算法"java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="yourpassword" password="yourMasterKey" algorithm=PBEWITHHMACSHA512ANDAES_256 ivGeneratorClassName=org.jasypt.iv.RandomIvGenerator解密：java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringDecryptionCLI input="密文" password="密钥" algorithm="算法"PBEWITHHMACSHA512ANDAES_256算法加密时需要指定盐生成器否则会报错，如：ivGeneratorClassName=org.jasypt.iv.RandomIvGenerator获取结果：命令执行后，在 OUTPUT 部分输出的字符串即为加密后的密文通过Spring Boot 编程方式加密数据@Autowired private StringEncryptor encryptor; public void testEncrypt() { String plainText = "yourpassword"; String encryptedText = encryptor.encrypt(plainText); System.out.println("加密后的密文: " + encryptedText); // 例如输出：qzVuB4VpQYoW0CkIUPNV1A== }第四步在配置文件中使用加密值格式：将密文用 ENC() 包裹起来# application.properties 示例 my.secret.property=ENC(qzVuB4VpQYoW0CkIUPNV1A==)# application.yml 示例 spring: datasource: password: ENC(qzVuB4VpQYoW0CkIUPNV1A==)关键配置：为了让Jasypt在应用启动时能自动解密，必须为其提供用于解密的主密码（Master Password）。出于安全考虑，主密码不应直接写在配置文件中。推荐的做法是通过系统属性、环境变量或启动命令传入java -jar your-application.jar -Djasypt.encryptor.password=yourMasterKey
自动计算从A向量变换到B向量的旋转矩阵和缩放因子
1. 基础数学原理对于两个向量 v₁ 和 v₂，我们要找到旋转矩阵 R 和缩放因子 s，使得：v₂ = s × R × v₁2. Java实现public class VectorTransform { private double[][] rotationMatrix; private double scaleFactor; public VectorTransform(double[] sourceVector, double[] targetVector) { if (sourceVector.length != targetVector.length) { throw new IllegalArgumentException("Vectors must have same dimension"); } calculateTransform(sourceVector, targetVector); } private void calculateTransform(double[] v1, double[] v2) { int n = v1.length; // 计算缩放因子 (向量模长的比值) double normV1 = norm(v1); double normV2 = norm(v2); if (normV1 == 0) { throw new IllegalArgumentException("Source vector cannot be zero vector"); } this.scaleFactor = normV2 / normV1; // 计算旋转矩阵 if (n == 2) { calculateRotation2D(v1, v2); } else if (n == 3) { calculateRotation3D(v1, v2); } else { calculateRotationND(v1, v2); } } // 计算二维旋转 private void calculateRotation2D(double[] v1, double[] v2) { double[] u1 = normalize(v1); double[] u2 = normalize(v2); double cosTheta = dot(u1, u2); double sinTheta = u1[0] * u2[1] - u1[1] * u2[0]; // 2D叉积 rotationMatrix = new double[][]{ {cosTheta, -sinTheta}, {sinTheta, cosTheta} }; } // 计算三维旋转 (使用罗德里格斯旋转公式) private void calculateRotation3D(double[] v1, double[] v2) { double[] u1 = normalize(v1); double[] u2 = normalize(v2); double[] axis = cross(u1, u2); double axisNorm = norm(axis); if (axisNorm < 1e-10) { // 向量平行或反平行 if (dot(u1, u2) > 0) { // 平行 - 单位矩阵 rotationMatrix = new double[][]{ {1, 0, 0}, {0, 1, 0}, {0, 0, 1} }; } else { // 反平行 - 180度旋转 (任意轴) rotationMatrix = new double[][]{ {-1, 0, 0}, {0, -1, 0}, {0, 0, -1} }; } } else { double[] unitAxis = normalize(axis); double cosTheta = dot(u1, u2); double sinTheta = axisNorm; // 罗德里格斯旋转公式 double x = unitAxis[0], y = unitAxis[1], z = unitAxis[2]; double c = cosTheta; double s = sinTheta; double t = 1 - c; rotationMatrix = new double[][]{ {t*x*x + c, t*x*y - s*z, t*x*z + s*y}, {t*x*y + s*z, t*y*y + c, t*y*z - s*x}, {t*x*z - s*y, t*y*z + s*x, t*z*z + c} }; } } // 计算N维旋转 (使用Householder变换) private void calculateRotationND(double[] v1, double[] v2) { int n = v1.length; double[] u = normalize(v1); double[] v = normalize(v2); double[] w = subtract(u, v); double wNorm = norm(w); if (wNorm < 1e-10) { // 向量相同 - 单位矩阵 rotationMatrix = identityMatrix(n); return; } w = normalize(w); // Householder 反射矩阵: H = I - 2wwᵀ rotationMatrix = identityMatrix(n); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { rotationMatrix[i][j] -= 2 * w[i] * w[j]; } } } // 应用变换到新向量 public double[] transform(double[] vector) { if (vector.length != rotationMatrix.length) { throw new IllegalArgumentException("Vector dimension mismatch"); } double[] rotated = multiply(rotationMatrix, vector); return multiply(rotated, scaleFactor); } // 获取变换参数 public double[][] getRotationMatrix() { return copyMatrix(rotationMatrix); } public double getScaleFactor() { return scaleFactor; } // 工具方法 public double norm(double[] v) { return Math.sqrt(dot(v, v)); } public double[] normalize(double[] v) { double n = norm(v); if (n == 0) return v; return multiply(v, 1.0 / n); } private double dot(double[] a, double[] b) { double result = 0; for (int i = 0; i < a.length; i++) { result += a[i] * b[i]; } return result; } private double[] cross(double[] a, double[] b) { if (a.length != 3 || b.length != 3) { throw new IllegalArgumentException("Cross product only for 3D vectors"); } return new double[]{ a[1]*b[2] - a[2]*b[1], a[2]*b[0] - a[0]*b[2], a[0]*b[1] - a[1]*b[0] }; } private double[] subtract(double[] a, double[] b) { double[] result = new double[a.length]; for (int i = 0; i < a.length; i++) { result[i] = a[i] - b[i]; } return result; } private double[] multiply(double[] v, double scalar) { double[] result = new double[v.length]; for (int i = 0; i < v.length; i++) { result[i] = v[i] * scalar; } return result; } private double[] multiply(double[][] matrix, double[] vector) { int n = matrix.length; double[] result = new double[n]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { result[i] += matrix[i][j] * vector[j]; } } return result; } private double[][] identityMatrix(int n) { double[][] I = new double[n][n]; for (int i = 0; i < n; i++) { I[i][i] = 1; } return I; } private double[][] copyMatrix(double[][] matrix) { double[][] copy = new double[matrix.length][]; for (int i = 0; i < matrix.length; i++) { copy[i] = matrix[i].clone(); } return copy; } }3. 使用示例public class VectorTransformExample { public static void main(String[] args) { // 示例1: 2D向量变换 double[] source2D = {1, 0}; // 原始向量 double[] target2D = {0, 2}; // 目标向量 VectorTransform transform2D = new VectorTransform(source2D, target2D); // 对新向量应用相同的变换 double[] newVector2D = {2, 1}; double[] transformed2D = transform2D.transform(newVector2D); System.out.println("2D Transform:"); System.out.println("Scale factor: " + transform2D.getScaleFactor()); System.out.println("Original: " + Arrays.toString(newVector2D)); System.out.println("Transformed: " + Arrays.toString(transformed2D)); // 示例2: 3D向量变换 double[] source3D = {1, 0, 0}; double[] target3D = {0, 1, 0}; VectorTransform transform3D = new VectorTransform(source3D, target3D); double[] newVector3D = {1, 1, 1}; double[] transformed3D = transform3D.transform(newVector3D); System.out.println("\n3D Transform:"); System.out.println("Scale factor: " + transform3D.getScaleFactor()); System.out.println("Original: " + Arrays.toString(newVector3D)); System.out.println("Transformed: " + Arrays.toString(transformed3D)); } }4. 高级版本（支持变换组合）public class AdvancedVectorTransform { private double[][] transformationMatrix; public AdvancedVectorTransform(double[] sourceVector, double[] targetVector) { // 直接计算完整的仿射变换矩阵 this.transformationMatrix = calculateTransformationMatrix(sourceVector, targetVector); } private double[][] calculateTransformationMatrix(double[] v1, double[] v2) { int n = v1.length; double[][] matrix = new double[n][n]; // 这里可以使用更复杂的算法，如SVD分解 // 简化为缩放和旋转的组合 VectorTransform basicTransform = new VectorTransform(v1, v2); double scale = basicTransform.getScaleFactor(); double[][] rotation = basicTransform.getRotationMatrix(); // 组合缩放和旋转 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { matrix[i][j] = scale * rotation[i][j]; } } return matrix; } public double[] transform(double[] vector) { return multiply(transformationMatrix, vector); } // 矩阵乘法工具方法 private double[] multiply(double[][] matrix, double[] vector) { int n = matrix.length; double[] result = new double[n]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { result[i] += matrix[i][j] * vector[j]; } } return result; } }
Proxifier注册码序列号(包括最新4.0以上版本均可注册。)L6Z8A-XY2J4-BTZ3P-ZZ7DF-A2Q9C（Portable Edition）#免安装版本 5EZ8G-C3WL5-B56YG-SCXM9-6QZAP（Standard Edition）#安装版本 P427L-9Y552-5433E-8DSR3-58Z68（MAC） #mac版本Proxifier下载Proxifier Downloads 需科学上网下载兼容以下系统Windows (x86, x64, ARM) 7, 8, 10, 11Server 2008, 2012, 2016, 2019, 2022macOS Big Sur, Monterey, Ventura
一、pyppeteer是什么？pyppeteer是Puppeteer的Python非官方端口，通过CFFI技术绑定Chromium浏览器，实现自动化操作。它专为现代网页设计，能高效处理动态内容加载、JavaScript渲染等反爬虫机制，是爬虫工程师对抗反爬的利器。二、核心特性1、无头浏览器控制支持无界面模式运行，节省资源且隐蔽性强：browser = await launch(headless=True) # 默认无头模式2、动态内容渲染完美处理SPA（单页应用）和AJAX加载内容：await page.waitForSelector('.dynamic-content') # 等待动态元素加载3、反爬虫对抗代理支持：await page.authenticate({ 'username': 'proxyUser', 'password': 'proxyPass' })指纹伪装：await page.evaluateOnNewDocument(''' Object.defineProperty(navigator, 'webdriver', {get: () => false}) ''')4、高性能异步基于asyncio实现原生异步，支持高并发请求：async def main(): browser = await launch() # 并行处理多个页面...三、安装指南系统要求Python 3.6+（推荐3.8+）Linux/macOS/Windows（Windows需安装Visual Studio Build Tools）安装步骤pip install pyppeteer验证安装import asyncio from pyppeteer import launch async def check(): browser = await launch() page = await browser.newPage() await page.goto('https://example.com') print(await page.title()) await browser.close() asyncio.run(check()) # 应输出"Example Domain"四、基础用法详解启动浏览器# 指定Chromium路径（如使用系统自带Chrome） browser = await launch( executablePath='/usr/bin/google-chrome', headless=False # 显示浏览器界面调试 )页面操作# 导航与截图 await page.goto('https://httpbin.org/get', waitUntil='networkidle2') await page.screenshot({ 'path': 'screenshot.png', 'fullPage': True }) # 执行JavaScript dimension = await page.evaluate('''() => { return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight } }''')元素交互# 输入与点击 await page.type('#username', 'my_user') await page.click('#submit', delay=100) # 添加100ms延迟模拟人类操作 # 下拉框选择 await page.select('#country', 'US')五、高级特性解析动态内容处理# 无限滚动加载 async def scroll_to_bottom(page): while True: await page.evaluate('window.scrollBy(0, 1000)') await asyncio.sleep(1) if await page.evaluate('document.documentElement.scrollTop >= document.body.scrollHeight - 1000'): break网络请求拦截# 拦截并修改请求 async def intercept_request(page): await page.setRequestInterception(True) page.on('request', lambda req: asyncio.create_task(handle_request(req))) async def handle_request(request): if request.url.endswith('.js'): await request.abort() # 阻止JS文件加载 else: await request.continue_()多标签页管理# 创建新标签页 new_page = await browser.newPage() await new_page.goto('https://example.com/new') # 切换标签页 pages = await browser.pages() await pages[1].bringToFront()六、实战案例：知乎反爬虫突破import asyncio from pyppeteer import launch async def scrape_zhihu(): browser = await launch(headless=False) page = await browser.newPage() # 设置代理与伪装 await page.authenticate({ 'username': 'proxy_user', 'password': 'proxy_pass' }) await page.evaluateOnNewDocument(''' Object.defineProperty(navigator, 'webdriver', {get: () => false}) ''') # 访问并登录 await page.goto('https://www.zhihu.com/signin') await page.type('#account', 'your_username') await page.type('#password', 'your_password') await page.click('.SignFlow-submitButton') # 等待登录完成 await page.waitForNavigation() # 抓取问题与回答 questions = await page.querySelectorAll('.QuestionItem-title') for q in questions: print(await q.getProperty('textContent')) await browser.close() asyncio.run(scrape_zhihu())七、性能优化技巧1、连接复用复用浏览器实例减少资源消耗：browser = await launch() # 多次使用同一个browser实例...2、并行处理使用asyncio.gather实现并发请求：async def main(): browser = await launch() tasks = [fetch_data(browser, url) for url in urls] await asyncio.gather(*tasks)3、内存管理及时释放资源：await page.close() # 关闭标签页 await browser.close() # 关闭浏览器八、常见问题解答Q1: 安装时提示"Chromium revision is not downloaded"A: 手动下载Chromium：python -c "import pyppeteer; pyppeteer.chromium_downloader.download_chromium()"Q2: 如何处理验证码？A: 结合OCR或第三方服务：# 使用Tesseract OCR from PIL import Image import pytesseract img = await page.screenshot() text = pytesseract.image_to_string(Image.open(img))Q3: 如何避免被检测到是爬虫？A: 综合使用以下策略：随机延迟（await asyncio.sleep(random.uniform(1,3))）伪装User-Agent旋转代理IP模拟人类操作（如滚动、鼠标移动）
监控指定后缀的文件import logging from logging.handlers import TimedRotatingFileHandler import os import shutil import time # 配置日志基本设置 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') # 日志文件路径 log_file_path = 'log/output.log' # 确保日志目录存在 log_directory = os.path.dirname(log_file_path) if not os.path.exists(log_directory): os.makedirs(log_directory) # 创建TimedRotatingFileHandler，每天轮换日志文件 handler = TimedRotatingFileHandler(log_file_path, encoding='utf-8', when="midnight", interval=1, backupCount=30) # 使用日志格式器 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) # 为日志记录系统添加handler logging.getLogger().addHandler(handler) try: from watchdog.observers import Observer from watchdog.events import * except ImportError: logging.info("错误：未找到watchdog模块。") logging.info("请通过以下命令安装：pip install watchdog") sys.exit(1) # 退出程序，返回非0状态码表示错误 # 处理器 class FileEventHandler(PatternMatchingEventHandler): def __init__(self, base_path, save_path): # 设置匹配模式 # 关键参数说明： # patterns：一个字符串列表，定义要监控的文件模式，例如 ['*.txt', '*.csv']。 # ignore_patterns：一个字符串列表，定义要忽略的文件模式。 # ignore_directories：设置为 True 可以忽略目录事件。 # case_sensitive：设置为 True 则进行大小写敏感匹配。 super().__init__(patterns=['*.dat', '*.jpg', '*.png', '*.mp3', '*.mp4'], ignore_directories=True, case_sensitive=False) self.base_path = base_path # 监控的根目录 self.save_path = save_path # 保存的根目录 def is_file_ready(self, file_path): """检查文件是否准备就绪（未被其他进程占用）""" try: # 尝试以读写方式打开文件，如果文件被占用，则会失败 with open(file_path, 'a+b'): return True except IOError: return False def on_moved(self, event): if event.is_directory: logging.info("directory moved from {0} to {1}".format(event.src_path, event.dest_path)) else: logging.info("file moved from {0} to {1}".format(event.src_path, event.dest_path)) localtime = time.strftime("%Y-%m-%d_%H_%M_%S",time.localtime(time.time())) i = 1 while i <= 5: i += 1 # os.system("copy \"{0}\" \"F:\image\{1}_moved_{2}\"".format(event.dest_path, localtime, os.path.basename(event.dest_path))) # command = "copy \"{0}\" \"{1}\moved{2}\"".format(event.dest_path, self.save_path, os.path.relpath(event.dest_path, self.base_path)) target_path = "{0}\moved{1}".format(self.save_path, os.path.relpath(event.dest_path, self.base_path)) # 确保目标目录存在 target_dir = os.path.dirname(target_path) os.makedirs(target_dir, exist_ok=True) if os.path.exists(event.dest_path) and self.is_file_ready(event.dest_path): try: # logging.info(command) # os.system(command) # 使用shutil.copy进行拷贝 shutil.copy2(event.dest_path, target_path) break except Exception as e: logging.info(f"发生错误：{e}") break else: logging.info(f"{event.dest_path} 文件不存在或被占用") time.sleep(0.5) # if event.src_path.endswith(".dat") and os.access(event.src_path, os.R_OK): # #logging.info(event.src_path.endswith(".png")) # logging.info("File[{0}] is accessible to read".format(os.path.basename(event.src_path))) # os.system("copy \"{0}\" \"F:\image\{1}\"".format(event.src_path, os.path.basename(event.src_path))) def on_created(self, event): if event.is_directory: logging.info("directory created:{0}".format(event.src_path)) else: logging.info("file created:{0}".format(event.src_path)) def on_deleted(self, event): if event.is_directory: logging.info("directory deleted:{0}".format(event.src_path)) else: logging.info("file deleted:{0}".format(event.src_path)) def on_modified(self, event): if event.is_directory: logging.info("directory modified:{0}".format(event.src_path)) else: logging.info("file modified:{0}".format(event.src_path)) localtime = time.strftime("%Y-%m-%d_%H_%M_%S",time.localtime(time.time())) i = 1 while i <= 5: i += 1 # os.system("copy \"{0}\" \"F:\image\{1}_modified_{2}\"".format(event.src_path, localtime, os.path.basename(event.src_path))) # command = "copy \"{0}\" \"{1}\modified{2}\"".format(event.src_path, self.save_path, os.path.relpath(event.src_path, self.base_path)) target_path = "{0}\modified{1}".format(self.save_path, os.path.relpath(event.src_path, self.base_path)) # 确保目标目录存在 target_dir = os.path.dirname(target_path) os.makedirs(target_dir, exist_ok=True) if os.path.exists(event.src_path) and self.is_file_ready(event.src_path): try: # logging.info(command) # os.system(command) # 使用shutil.copy进行拷贝 shutil.copy2(event.src_path, target_path) break except Exception as e: logging.info(f"发生错误：{e}") break else: logging.info(f"{event.src_path} 文件不存在或被占用") time.sleep(0.5) # shutil.copyfile(event.src_path, "F:\image\{0}".format(os.path.basename(event.src_path))) # if event.src_path.endswith(".dat") and os.access(event.src_path, os.R_OK): # #logging.info(event.src_path.endswith(".png")) # logging.info("File[{0}] is accessible to read".format(os.path.basename(event.src_path))) # os.system("copy \"{0}\" \"F:\image\{1}\"".format(event.src_path, os.path.basename(event.src_path))) if __name__ == '__main__': observer = Observer() # 监控的目录 path_to_watch = r"D:\WeChat Files\wxid_\FileStorage\MsgAttach" # 保存的目录 path_to_save = r"F:\image\MsgAttach" # 处理器初始化时候这里可以传些参进去 event_handler = FileEventHandler(path_to_watch, path_to_save) # 监控的文件夹 observer.schedule(event_handler, path_to_watch, True) path_to_watch1 = r"D:\xwechat_files\wxid_\msg" path_to_save1 = r"F:\image\msg" event_handler1 = FileEventHandler(path_to_watch1, path_to_save1) observer.schedule(event_handler1, path_to_watch1, True) observer.start() observer.join()
redis安装 https://download.redis.io/releases/解压reids tartar -zxvf redis-7.2.0.tar.gz安装make make install PREFIX=/usr/local/redis修改配置或直接使用配置文件cp /redis-7.2.0/redis.conf /usr/local/redis/bin/vim /usr/local/redis/bin/redis.conf （1）bind 127.0.0.1 -::1 ---> bind 0.0.0.0 （2）daemonize no ---> daemonize yes （3）requirepass foobared注释去掉并在后加上密码（注意中间加个空格）requirepass foobared ---> requirepass 123456（4）设置redis记录日志，默认不记录日志（redis.log为文件名）logfile " " ---> logfile "redis.log" 运行./redis-server redis.confcentos防火墙开放端口firewall-cmd --statefirewall-cmd --zone=public --add-port=16379/tcp --permanent firewall-cmd --reloadredis更新查询程序ps aux | grep redis停止sudo kill redis-server将旧版本移动到新目录备份sudo mv /usr/local/redis /usr/local/redis_old执行redis安装步骤进行更新
https://treehousegames.notion.site/How-to-Host-Dedicated-Servers-1debc50b8edb803b9d5bd4dbfdb32c2c#271bc50b8edb80a18b4bc1ea4eaf674fsteamcmd.exe +login anonymous +force_install_dir ./voyagersOfNera +app_update 3937860 validate +quit《Nera远征者》默认使用7777端口，因此您可能需要在路由器上将协议为UDP的7777端口进行端口转发至运行专用服务器的主机。要在单独端口上运行，请使用命令行参数 -port=<端口> 启动服务器。默认情况下，您的专用服务器应用程序会广播其运行设备的公网IP。如果需要客户端连接至其他IP地址，必须设置 宿主机/容器操作系统环境变量 EOS_OVERRIDE_HOST_IP=<IP>。若以命令行参数形式提供，该设置将无效。Download via SteamCMDTo download the dedicated server build without purchasing the game, you must use SteamCMD, a command line tool often used for downloading dedicated servers. To install from SteamCMD, run### In steamcmd.exe console login anonymous app_update 3937860How to Run the ServerAssuming your install directory is C:\Nera, you’ll find the server executable shortcut at C:\Nera\BoatGameServer.exe. Or to find the actual binary: C:\Nera\BoatGame\Binaries\Win64\BoatGameServer-Win64-Shipping.exe.By default when you run the Server application it will not create a console or log output. To get a console output, supply the command line argument -log. Additionally if you want to enable writing log files (located at C:\Nera\BoatGame\Saved\Logs ) supply the command line argument -LoggingInShippingEnabled=true. Just note that log files can get very large, especially as your server runs for long periods of time. If you use this setting, we recommend periodically restarting your server and cleaning up old or very large log files.Note: Verify your server has write access to your save directory on your machine! If the game does not have write access, it will not be able to save your progress. Your save directory is within the server install directory: {INSTALL_DIRECTORY}\BoatGame\Saved\PersistedData .Configuring Your ServerDedicated Server settings are configured in different INI files, which are special text files the game uses to configure things like Server Display Name, Passwords, Gameplay Settings, and more. Follow these steps when setting up your server for the first time:Run your Dedicated Server application, which will generate the initial configuration files for you. Let the app run until you see the folder mentioned in the next step created on your hard drive. Then close the server application.Locate the folder which contains the INI configuration files at {InstallRoot}\BoatGame\Saved\PersistedData\CustomConfig\ in Windows Explorer.Use the table below to view all existing supported settings. To configure a setting, open the INI file in a text editor application (such as Notepad). Each line will either have a bracketed name like [/Script/BoatGame.BGGameUserSettings] or will look like someKey=someValue. The lines with an = are used to set values of different configurations.Save any changes to your INI file, and the next time you start your server they will be used!Setting NameFileINI Section HeaderValue TypeAllowed ValuesRecommended Value(s)DescriptionCustom Host-Server SettingsHostServerDisplayNameCustomHostServerUserSettings.ini[/Script/BoatGame.BGCustomHostServerSettings]StringAlphanumeric characters only.Max 30 characters.Whatever you want your server name to display asThe public name of your server other players will see when browsing servers.HostServerPasswordCustomHostServerUserSettings.ini[/Script/BoatGame.BGCustomHostServerSettings]StringAlphanumeric characters only.Default blank (no password) or generate a password for them.The password required for players to join your game.A blank value means the server will not be password-protected and is joinable by any player.MaxPlayersCustomHostServerUserSettings.ini[/Script/BoatGame.BGCustomHostServerSettings]IntegerMust be greater than 0.1 - 10Default 10.The maximum number of players allowed to be connected to your server at once.AutosaveTimerSecondsCustomHostServerUserSettings.ini[/Script/BoatGame.BGCustomHostServerSettings]IntegerMust be greater than 0.10 - 900Default 300 (5min)How often your server will automatically save. It’s not recommended to adjust this setting unless you specifically have a reason to.Custom Game-Server SettingsGatheringRateMultiplierCustomGameUserSettings.ini[/Script/BoatGame.BGCustomGameSettings]Float0.01 → 10.0Default: 1.0Resource yield multiplier - increases quantity gathered per gathering interaction.EnemyDamageMultiplierCustomGameUserSettings.ini[/Script/BoatGame.BGCustomGameSettings]Float0.01 → 10.0Default: 1.0Enemy attack damage multiplier.PlayerDamageMultiplierCustomGameUserSettings.ini[/Script/BoatGame.BGCustomGameSettings]Float0.01 → 10.0Default: 1.0Player attack damage multiplier.DisableEquipmentDurabilityCustomGameUserSettings.ini[/Script/BoatGame.BGCustomGameSettings]BooleanTrue / FalseDefault: FalseWhen enabled, equipment never breaks or loses durability.DisableDropItemsOnDeathCustomGameUserSettings.ini[/Script/BoatGame.BGCustomGameSettings]BooleanTrue / FalseDefault: FalseWhen enabled, players keep inventory items after death.
Windows查看docker信息docker info解决方案(官方文档找到了答案)：新增wsl全局配置文件(.wslconfig文件)：windows路径栏输入：%UserProfile%，找到目录C:\Users\Administrator，默认是没有这个配置文件的，可以自己新增增加配置项：# 设置在wsl2上运行 [wsl2] # 设置分配给WSL VM 的内存大小（默认是1/2的电脑内存） memory=8GB # 设置要分配给 WSL 2 VM 的逻辑处理器数（默认和Windows 上相同数量的逻辑处理器） processors=10其它的WSL配置可以去WSL官方文档查找，文档很全
https://blog.csdn.net/Lwehne/article/details/135867512如果提示需要登录Oracle账号，这里提供一个：# 提醒：为了大伙的方便，请不要随便用该邮箱重新注册Oracle账号！ 账号：yawoniu@163.com 密码：Oracle.123