博客

  • “魔改”it-tools前端界面与Docker镜像构建实录

    “魔改”it-tools前端界面与Docker镜像构建实录

    “都是自己人,咱就捞干的说。”兄弟们,不知道你们有没有这种体验:网上找个JSON格式化、密码生成之类的小工具,要么广告满天飞,要么就得扫码关注,用起来那叫一个憋屈。反正李哥我是受够了!这不,就在Github上发现了这个叫 it-tools​ 的宝贝,一个集成了50多种开发者工具的纯前端网站,干净、免费、即开即用。官方提供了Docker镜像,一键部署是爽,但默认全是英文界面,Logo、标题啥的也都是人家的,用着总感觉是“寄人篱下”,不够得劲。

    所以,李哥今儿个要干票啥?咱不满足于“能用”,咱要的是“好用”,而且是“完全属于自己”的好用!这篇笔记,就手把手带你“扒掉”it-tools的官方外衣,给它换上咱自己的“皮肤”。从怎么克隆源码、修改标题Logo,到如何亲手构建一个写着咱自己大名的Docker镜像,最后部署上线。整个过程,说白了就是一次轻量级的“二次开发”。你放心,不需要你有多深的前端功底,跟着李哥的步骤,改改文字、换换图片,就跟给WordPress换个主题差不多简单。但做完之后,成就感绝对拉满——访问自己服务器上那个完全定制化的工具站,那感觉,就跟开盲盒开出了隐藏款一样爽!“工具嘛,就得驯服得贴贴心心,用起来才顺手。”​ 废话不多说,开整!

    一、核心思路

    it-tools是纯前端项目(Vue.js框架),所有界面内容都在源码的src/目录中。修改流程为: 克隆源码 → 修改前端文件 → 本地构建Docker镜像 → 运行自定义镜像

    二、详细步骤

    1. 准备环境

    确保安装Git、Node.js(16+)、Docker:

    # 安装依赖(Ubuntu示例)
    sudo apt install -y git nodejs npm docker.io

    2. 克隆源码并修改内容

    # 克隆仓库
    git clone https://github.com/CorentinTh/it-tools.git
    cd it-tools

    # 安装前端依赖
    npm install

    根据红框位置修改对应文件(以常见修改为例):

    修改内容文件路径示例操作
    顶部标题/Logosrc/App.vue搜索<header>标签,替换<h1>内容或<img>的src属性(Logo建议用Base64编码)
    工具分类名称src/tools/index.ts修改categories数组中的name字段(如将”Converter”改为”格式转换”)
    工具卡片名称/描述src/tools/[工具类型]/[工具名].ts如修改JSON格式化工具:src/tools/formatters/json-formatter.ts中的name和description
    页脚版权信息src/components/layout/Footer.vue修改<footer>标签内的文本

    修改后可本地预览效果:npm run dev(访问http://localhost:5173)。

    3. 构建自定义Docker镜像

    修改完成后,用官方Dockerfile构建镜像:

    # 在源码目录执行构建(约5分钟,依赖网络环境)
    docker build -t my-it-tools:custom . # 镜像名自定义,如my-it-tools:custom

    构建成功后,本地镜像列表会新增my-it-tools:custom。

    4. 运行自定义镜像

    用修改后的镜像启动容器,命令与官方类似:

    # 先停止并删除旧容器(如有)
    docker stop it-tools && docker rm it-tools

    # 启动自定义镜像
    docker run -d \
    --name it-tools \
    --restart unless-stopped \
    -p 9580:80 \ # 主机端口9580,可自定义
    my-it-tools:custom # 使用本地构建的镜像名

    三、验证与更新

    1. 访问验证:浏览器打开http://IP:9580,确认红框内容已修改。
    2. 后续更新:若官方源码更新,需重新克隆并合并修改(git pull),再重复构建步骤。

    四、注意事项

    1. 不建议直接修改容器内文件:通过docker exec进入容器修改文件会随容器删除丢失,且无法同步到新镜像。
    2. 保留官方镜像:自定义镜像命名避免与官方corentinth/it-tools冲突,便于区分。
    3. 复杂修改需前端基础:如调整样式需修改src/assets/styles/下的CSS文件,或通过src/main.ts注入全局样式。

    这种方式本质是“二次开发”,适合需要深度定制的场景。若仅需简单修改(如标题),10分钟即可完成;若涉及工具功能逻辑调整,需参考Vue.js和项目的开发文档。修改后的镜像可推送到私有仓库,方便多服务器部署。


    进阶容器持久化:

    通过Docker安装it-tools(开发者在线工具集)仅需三步,官方提供了两种镜像源选择,适用于Linux、Windows和macOS系统。以下是基于官方文档的完整部署指南。

    一、环境准备

    首先确保已安装Docker和Docker Compose。验证命令:

    docker -v          # 需显示20.10+版本
    docker compose version # 需显示v2.0+版本

    未安装可参考Docker官方文档,或使用简化脚本(Ubuntu示例):

    sudo apt update && sudo apt install -y docker.io docker-compose
    sudo systemctl enable docker --now

    二、核心部署步骤

    选项1:从Docker Hub拉取镜像(推荐)

    官方镜像体积约150MB,包含所有工具功能。执行以下命令启动容器:

    docker run -d \
    --name it-tools \         # 容器名称
    --restart unless-stopped \ # 异常退出时自动重启
    -p 8080:80 \               # 端口映射(主机端口:容器端口)
    corentinth/it-tools:latest # 镜像名称:版本标签
    • 参数说明:-d后台运行,-p 8080:80将主机8080端口映射到容器80端口(可修改主机端口避免冲突,如-p 9000:80)。

    选项2:从GitHub Packages拉取镜像

    若Docker Hub访问缓慢,可使用GitHub容器仓库:

    docker run -d \
    --name it-tools \
    --restart unless-stopped \
    -p 8080:80 \
    ghcr.io/corentinth/it-tools:latest

    三、验证与访问

    检查容器状态

      docker ps | grep it-tools  # 应显示STATUS为Up
      docker logs it-tools       # 查看启动日志,无报错即可

      访问工具网站: 在浏览器输入 http://服务器IP:8080(本地部署用http://localhost:8080),即可看到it-tools的工具列表,包含Base64编码、JSON格式化、正则表达式测试等50+开发者工具。

        四、进阶配置(可选)

        1. 数据持久化(针对未来版本)

        当前官方镜像未提供持久化目录(工具数据保存在浏览器本地存储),若需保存配置,可关注项目后续更新或通过Docker卷挂载:

        docker run -d \
        --name it-tools \
        --restart unless-stopped \
        -p 8080:80 \
        -v ./it-tools-data:/app/data \ # 挂载本地目录(需提前创建)
        corentinth/it-tools:latest

        2. HTTPS配置(生产环境)

        配合Nginx反向代理实现SSL加密:

        安装Nginx并配置虚拟主机,示例配置(/etc/nginx/sites-available/it-tools):

          server {
              listen 443 ssl;
              server_name tools.yourdomain.com;
             
              ssl_certificate /path/to/cert.pem;
              ssl_certificate_key /path/to/key.pem;
             
              location / {
                proxy_pass http://localhost:8080;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
              }
            }

          重启Nginx:sudo systemctl restart nginx。

            五、更新与卸载

            • 更新镜像
            docker stop it-tools && docker rm it-tools
            docker pull corentinth/it-tools:latest # 拉取最新版本
            # 重新执行docker run命令
            • 完全卸载
            docker stop it-tools && docker rm it-tools  # 停止并删除容器
            docker rmi corentinth/it-tools:latest       # 删除镜像

            通过Docker部署的it-tools保持与官方同步更新,单容器设计使其资源占用极低( idle状态内存<50MB),适合个人开发者或小型团队内部部署。若需自定义工具或贡献代码,可参考项目README中的开发指南。访问时遇到端口冲突,只需修改-p参数的主机端口即可快速解决。


            其它说明:

            sudo mkdir -p /www/wwwroot/it-tools-data && sudo chmod 777 /www/wwwroot/it-tools-data  #先手动创建主机目录(避免权限问题)
            docker run -d \
              --name it-tools \
              --restart unless-stopped \
              -p 9090:80 \  # 主机端口9090 → 容器端口80(正确映射)
              -v /www/wwwroot/it-tools-data:/data \  # 绝对路径挂载(当前无实际作用)
              corentinth/it-tools:latest
            docker exec -it it-tools /bin/sh   #进入容器
            docker ps | grep it-tools   #查看容器运行状态(应显示 Up X分钟)
            docker logs it-tools   #若状态异常,查看错误日志
            docker stop it-tools && docker rm it-tools     #通过容器名称删除
            docker ps -a | grep it-tools  # 找到名为"it-tools"的容器
            docker images | grep it-tools  #  查看it-tools镜像ID
            docker rmi def67890  #删除镜像(替换def67890为实际镜像ID)
            docker images | grep it-tools # 应无输出(镜像已删除)
            ls -la /www/wwwroot/it-tools  #先检查目录内容(避免误删)
            sudo rm -rf /www/wwwroot/it-tools   #确认无重要文件后删除

            通过GIT项目安装:

            IT-TOOLS如果需要定制LOGO修改前端内容最好是通过GIT在本地修改后再用DOCKER进行构建再次部署。因为你直接部署的话就不能自定义一些自己的内容

            sudo mkdir -p /www/wwwroot/it-tools-dev && sudo chmod 777 /www/wwwroot/it-tools-dev
            cd /www/wwwroot/it-tools-dev
            git clone https://github.com/CorentinTh/it-tools.git
            cd it-tools
            apt install npm
            npm install

            构建DOCKER镜像:

            docker build -t leepoet-tools:custom .

            如果遇到问题解决方案:

            # 1. 删除错误的 Dockerfile
            rm Dockerfile
            
            # 2. 创建正确的 Dockerfile
            cat > Dockerfile << 'EOF'
            FROM node:20.19.5-alpine AS builder
            WORKDIR /app
            COPY package.json pnpm-lock.yaml ./
            RUN npm install -g pnpm && pnpm install
            COPY . .
            ENV NODE_OPTIONS="--max_old_space_size=8192"
            RUN npx vite build
            FROM nginx:alpine
            COPY --from=builder /app/dist /usr/share/nginx/html
            EXPOSE 80
            CMD ["nginx", "-g", "daemon off;"]
            EOF
            
            # 3. 验证文件内容
            cat Dockerfile
            
            # 4. 构建镜像
            docker build -t leepoet-tools:custom .
            
            # 5. 运行容器
            docker run -d -p 3000:80 --name it-tools-container leepoet-tools:custom

            后续管理指令:

            # 停止容器
            docker stop it-tools-container
            
            # 启动容器
            docker start it-tools-container
            
            # 重启容器
            docker restart it-tools-container
            
            # 进入容器内部(调试用)
            docker exec -it it-tools-container sh
            
            # 删除容器
            docker rm it-tools-container
            
            # 查看容器详细信息
            docker inspect it-tools-container
            
            # 查看容器使用的资源
            docker stats it-tools-container

            构建完镜像后打包:

            使用镜像保存/加载(适合内网环境)

            # 保存镜像为文件
            docker save -o leepoet-tools.tar leepoet-tools:custom

            传输到服务器

            # 使用 scp 传输(替换为你的服务器信息)
            scp leepoet-tools.tar root@你的服务器IP:/tmp/

            在服务器上加载运行

            # 在服务器上加载镜像
            docker load -i /tmp/leepoet-tools.tar
            
            # 运行容器
            docker run -d -p 3000:80 --name it-tools-container leepoet-tools:custom
            
            # 停止并删除现有容器(如果存在)
            docker stop it-tools-container
            docker rm it-tools-container
            
              # 假设容器内网站根目录是/usr/share/nginx/html
            # 重新运行容器并挂载目录
            docker run -d \
              -p 3000:80 \
              --name it-tools-container \
              -v /www/wwwroot/tools.leepoet.cn:/usr/share/nginx/html \
              leepoet-tools:custom


          1. 垃圾佬的基操:白嫖cpolar让老旧手机焕发新生实现内透穿透

            垃圾佬的基操:白嫖cpolar让老旧手机焕发新生实现内透穿透

            推了推眼角的黑框眼镜,手指在旧手机屏幕上划出一道自信的弧光,我说各位垃圾佬啊,你们抽屉里那台卡到怀疑人生的旧手机,别急着换脸盆——今儿个李哥就带你们玩点骚操作,让这老伙计化身24小时在线的私人服务器!

            一场零成本的“电子废品文艺复兴”

            1. 核心玩法拆解

            • 前期准备:给手机刷上Termux(安卓端的Linux终端),配好SSH远程操控,相当于给老古董植入赛博神经。
            • 关键一步:白嫖cpolar内网穿透服务(这玩意儿免费还不限流量),把家门口的旧手机变成能在外网访问的“云主机”。
            • 终极形态:开机自启+服务化部署,实现插电即用,彻底解放双手。

            2. 能干啥?

            • 深夜躺床上用电脑远程调取手机里的学习资料(正经的)
            • 把旧手机改造成轻量级网站服务器/云盘监控中转站
            • 为后续部署机器人、爬虫脚本铺路(李哥后续会更新实战教程)

            3. 李哥划重点

            • 全程零费用,但需要一丢丢命令行基础(跟着复制粘贴就行)
            • 避开坑点:记得给Termux换国内源,否则下载速度堪比祖传2G网
            • 隐私警告:别用来看不该看的东西,cpolar后台能查隧道日志(笑)

            安装 Termux

            下载地址:https://github.com/termux/termux-app

            选择相应的版本安装,安装后换源,选择一个国内的源如清华、阿里源

            termux-change-repo

            选中国源

            安装 SSH

            pkg update && pkg upgrade   #更新软件包列表
            pkg install openssh
            passwd       # 设置当前用户密码       
            sshd         # 启动sshd服务

            设置登录密码

            为了通过SSH登录,你需要为当前的Termux用户设置一个密码。执行passwd命令,然后根据提示输入两次新密码

            passwd

            查询Android设备的IP地址

            你需要知道手机在当前Wi-Fi网络中的IP地址。在Termux中执行:

            ifconfig

            在输出信息中找到wlan0网络接口,其inet地址就是手机的局域网IP,通常格式为192.168.x.x

            从客户端电脑进行SSH连接

            在使用电脑登陆SSH前,手机端需要打开TERMUX并启动命令:sshd即可。

            打开你电脑的终端,使用以下命令格式进行连接。请将<手机的IP地址>替换为上一步查到的IP。

            • 如果使用密钥认证,通常可以直接连接:
            ssh -p 8022 u0_aXXX@<手机的IP地址>

            如果使用密码认证,则需要显式启用密码登录选项(确保已执行上述设置密码的步骤)

            ssh -o PreferredAuthentications=password -p 8022 u0_aXXX@<手机的IP地址>

            注意:这里的用户名u0_aXXX是你的Termux用户名,可以通过在Termux中执行whoami命令查看

            管理服务:您可以使用一系列命令来管理服务:

            • sv up sshd:启动服务。
            • sv down sshd:停止服务。
            • sv status sshd:查看服务状态。

            ⚙️ 设置开机自启动

            如果你希望每次手机重启后SSH服务都能自动启动,可以借助Termux的termux-boot包。

            安装termux-boot

            pkg install termux-boot

            创建启动脚本

            在Termux中创建 ~/.termux/boot/目录,然后在该目录下创建一个可执行脚本文件,例如 start-sshd

            mkdir -p ~/.termux/boot
            nano ~/.termux/boot/start-sshd

            在文件中输入以下内容:

            #!/data/data/com.termux/files/usr/bin/sh
            sshd

            保存退出后,给脚本添加执行权限

            chmod +x ~/.termux/boot/start-sshd

            数据安全:修复或重置 Termux 对你手机共享存储空间的访问链接,这不会删除你手机存储(如下载、图片、文档等目录)中的实际文件,它只影响 Termux 内 ~/storage这个链接目录的结构

            termux-setup-storage 

            脚本执行完毕后,你的 ~/storage目录会包含指向手机各个公共存储文件夹的符号链接(例如 downloadsdcimshared等),方便你在 Termux 和手机存储之间传输文件。

            完成后可进行的操作

            设置成功后,你就可以很方便地在 Termux 和手机存储之间移动或复制文件了。例如:

            将 Termux 中的文件复制到手机下载目录

            cp myfile.txt ~/storage/downloads/

            从手机的共享存储空间访问文件

            ls ~/storage/shared/

            1.1 安装cpolar内网穿透

            cpolar它支持http/https/tcp协议,可以永久免费使用不限制流量,无需公网IP,也不用设置路由器。用过一段时间后你会发现,它甚至还可以直接在家中实现小型的云服务。

            cpolar官网:https://www.cpolar.com

            创建一个sources.list.d的文件夹:

            mkdir -p $PREFIX/etc/apt/sources.list.d

            添加cpolar下载源文件

            echo "deb [trusted=yes] http://termux.cpolar.com termux extras" >> $PREFIX/etc/apt/sources.list.d/cpolar.list

            更新仓库

            pkg update

            安装cpolar

            pkg install cpolar

            安装Termux服务,注意: 安装完成后记得关闭重启一下Termux才生效!!

            pkg install termux-services

            重启完Termux后,然后启动cpolar

            sv up cpolar

            设置开机自启

            sv-enable cpolar

            这个是停止cpolar服务

            sv down cpolar

            cpolar.yml主配置文件路径位置

            $PREFIX/etc/cpolar/cpolar.yml

            然后在手机浏览器我们输入http://localhost:9200即可看到cpolar管理界面,使用在cpolar官网注册的账号即可登陆。我们用电脑端打开手机的cpolar服务是通过手机的IP。

            登陆后台并注册一个cpolar账号:

            注册后选择0元套餐。

            认证token

            在cpolar后台复制你自己的token值

            搞定。

            启动服务。

            如我现在手机端有个服务端口是1122

            那么我cpolar http 1122

            启动这个服务并穿透。

            原地址:

            那么我们可以通过cpolar的两条隧道穿透到它提供的这种二级域名

            相关文章:零成本建站攻略:如何将废旧手机变成Hexo博客服务器并实现公网访问


            💡 技术宅的哲学思考

            “所谓垃圾,只是放错位置的资源——就像当年诺基亚砸核桃,如今咱用它跑服务,这波叫赛博朋克式环保!

            哲学系的李诗人(naver.19901117.xyz/)—— 专注让电子垃圾发光发热的硬核技术诗人

          2. 立省万元!李哥教你如何使用translate.js实现WP菜单的JavaScript翻译

            立省万元!李哥教你如何使用translate.js实现WP菜单的JavaScript翻译

            你是不是也在为WordPress网站加个多语言菜单而发愁?一想到那些名声在外的翻译插件,脑海里

            是不是立刻浮现出每年上千元的续费账单?

            是不是受够了那个一旦启用就让网站速度变慢的“庞然大物”?

            是不是在面对复杂的设置界面时,感觉像是在操作一架航天飞机,而你只是想骑个自行车出门买个菜?

            兄弟,我懂你。这种感觉,就像是只想拧一颗螺丝,却被迫买下整个五金店。别再忍受了!今天,李哥(LeePoet.cn)就带你彻底摆脱这种“高消费、低体验”的困境。这篇文章,将为你揭开一条“技术平权”之路。

            我们不用求助于任何重型插件,不花一分钱授权费,仅凭几行简洁的JavaScript代码,就能为你的WordPress菜单实现流畅、优雅的多语言切换功能。我们要用的神器,就是轻量级前端翻译库——translate.js

            学完这一招,你不仅能立刻省下未来几年上万元的插件费用,更能收获一个速度更快、完全受你掌控的网站。从此,插件更新、兼容性冲突、账单提醒,这些都与你无关。这不仅是省钱的技巧,更是一种技术的自由。跟着我的步骤,让我们一起对臃肿的翻译插件说“不”,亲手打造属于你自己的极速多语言方案!

            接下来,我们将从第一步“准备translate.js库文件”开始……

            1.先把translate.js上传到服务器,本地调用比https://cdn.staticfile.net/translate.js/3.18.66/translate.js速度更快。

            2.安装插入代码插件,如 “Header and Footer Scripts” 或 “Insert Headers and Footers”。把translate.js的JavaScript代码放到footer或才head里。

            <script src="https://www.你的域名及JS路径.cn/leepoetjs/translate.js"></script> 
            
            <script>
            translate.selectLanguageTag.languages = 'english,chinese_simplified,japanese,spanish,deutsch,french,korean';
            translate.request.listener.delayExecuteTime = 500;
            translate.service.use('client.edge'); //设置机器翻译服务通道
            translate.listener.start(); //开启页面元素动态监控
            translate.selectLanguageTag.show = false;//false隐藏选择框,true打开
            translate.execute();//完成翻译初始化,进行翻译
            </script>

            3.通过函数文件添加需要调用的translate.js,在主题的functions.php文件中添加:

            //调用翻译translate.js
            function add_language_switcher_script() {
                ?>
                <script>
            //添加switchToEnglish()英文函数
                function switchToEnglish() {
                    if (typeof translate !== 'undefined' && typeof translate.changeLanguage === 'function') {
                        translate.changeLanguage('english');
                    }
                }
            //添加switchToChineseSimplified()中文函数
                function switchTochinese_simplified() {
                    if (typeof translate !== 'undefined' && typeof translate.changeLanguage === 'function') {
                        translate.changeLanguage('chinese_simplified');
                    }
                }		
                </script>
                <?php
            }
            add_action('wp_head', 'add_language_switcher_script');

            2.进入WordPress后台 → 外观​ → 菜单,点击创建新菜单或选择现有菜单,在左侧自定义链接部分添加:

            点击添加到菜单,在菜单编辑器中,点击该菜单项展开设置

            • URL:#
            • 链接文字:English或您想要的文字
            • URL字段中替换为:javascript:translate.changeLanguage(‘english’);
            #导航标签字段
            <li href="#" onclick="javascript:switchToEnglish(); return false;">English</li>
            <li href="#" onclick="javascript:switchTochinese_simplified(); return false;">简体中文</li>

            然后保存菜单。*如果需要其它语言,可以继续在主题文件functions.php里继续填加调用。

            然后刷新前台基本搞定。

            相关文章:translate.js:两行代码实现网页全自动多语言内容动态翻译

            现在的WordPress多语言解决方案,尤其是主流插件,确实让人又爱又恨,痛苦主要集中在以下几点:

            沉重的年度费用,如同“数字税”,像WPML、Polylang Premium这类插件,需要每年支付高昂的授权费来获得更新和支持。对于一个长期运营的网站来说,这成了一笔沉重的、持续的固定支出。网站不赚钱时,这笔钱尤其肉疼;网站赚钱了,又会觉得为什么我要一直为这个“基础设施”付钱?感觉就像被“套牢”了。

            “全家桶”式捆绑,资源浪费严重,大型插件功能大而全,但你可能只需要其中最核心的菜单和静态内容翻译功能。它却强行塞给你一整套复杂的翻译管理系统、字符串翻译、媒体翻译等。这导致插件异常臃肿,严重拖慢网站速度,为了一个“点”的需求,背上了整个“面”的负担。

            架构复杂,学习成本高,配置过程繁琐,需要在不同标签页之间来回切换,理解“字符串”、“翻译编辑器”等专业概念。对于只是想给网站加个中英文菜单的博主来说,学习成本太高,一不小心就配错了。

            与主题/插件兼容性噩梦,一旦主题更新或更换,或者安装了新的插件,兼容性问题就可能出现,导致翻译失效或页面错乱。排查问题非常困难,往往需要深度技术知识。

            机器翻译API的隐藏成本,一些方案鼓励你接入Google Translate或DeepL的API来自动翻译。初期感觉方便,但随着内容增长,API调用的费用会悄无声息地累积,成为一个不可预测的成本黑洞。

            总结来说,当下的痛苦是:我们只是想要一个“轻量、快速、便宜、能控制”的翻译方案,尤其是针对菜单和前端静态内容。但市场给出的答案往往是“沉重、昂贵、复杂、受制于人”的庞然大物。

          3. translate.js:两行代码实现网页全自动多语言内容动态翻译

            translate.js:两行代码实现网页全自动多语言内容动态翻译

            在全球化日益深入的今天,为网站提供多语言支持已成为提升用户体验和扩大受众范围的关键。传统多语言解决方案通常需要复杂的配置、大量的翻译文件以及繁琐的代码修改,而translate.js的出现彻底改变了这一现状。

            寻找简单高效的网站翻译方案?translate.js 仅用两行JavaScript代码,即可为你的网站添加全自动多语言翻译功能,支持动态内容翻译,是WordPress插件和Google API的轻量级替代方案。

            什么是translate.js?

            translate.js是一款创新的开源JavaScript库,专注于网页多语言切换。它采用MIT开源协议,完全免费商用,只需两行核心代码即可实现HTML页面的全自动翻译,无需修改页面结构,无需语言配置文件,无需API Key,同时对SEO友好。

            核心优势

            1. 极简接入:无需复杂配置,快速集成
            2. 全自动翻译:智能识别页面内容并自动翻译
            3. 无侵入设计:对现有代码几乎零影响
            4. SEO友好:保持搜索引擎优化效果
            5. 开源免费:MIT协议,商业应用无忧

            translate.js是一个免费、开源的翻译工具库,支持多种语言之间的互译,如中英文、法文、日语、韩语、德语等。它避免了API调用次数的限制和费用问题,且使用方法简单,方便快捷。

            普通网页快速测试示例

            1. 随便打开一个网页
            2. F12(审查元素)
            3. 在控制台页签下粘贴入以下代码: 
              var head= document.getElementsByTagName('head')[0];
             var script= document.createElement('script');
             script.type= 'text/javascript';
             script.src= 'https://res.zvo.cn/translate/inspector_v2.js';
             head.appendChild(script);

            普通网页快速接入示例

            <script src="https://cdn.staticfile.net/translate.js/3.18.66/translate.js"></script>
            <script>
            translate.service.use('client.edge'); //设置机器翻译服务通道,相关说明参考 http://translate.zvo.cn/545867.html
            translate.listener.start(); //开启页面元素动态监控,js改变的内容也会被翻译,参考文档: http://translate.zvo.cn/4067.html
            translate.execute();//完成翻译初始化,进行翻译
            </script>

            在网页最末尾, </html> 之前,加入以下代码,一般在页面的最底部,就会出现选择语言的 select 切换标签。添加上述代码后,页面底部将自动出现语言选择下拉框,用户可以轻松切换界面语言。    详细解说

            工作原理

            translate.js的工作原理十分巧妙:

            1. 内容扫描:对页面所有DOM元素进行扫描,识别可翻译文本
            2. 文本抽离:提取需要翻译的文本内容
            3. API翻译:通过配置的翻译服务通道进行翻译
            4. 结果渲染:将翻译结果重新赋予对应元素

            整个过程自动完成,无需人工干预。

            高级功能与微调

            虽然基础使用极其简单,但translate.js提供了丰富的微调指令,满足各种复杂需求:

            语言控制

            translate.changeLanguage('english'); // 主动切换语言
            translate.language.setLocal('chinese_simplified'); // 设置本地语种
            translate.language.getCurrent(); // 获取当前显示语种

            内容过滤

            translate.ignore.text.push('你好'); // 忽略特定文本
            translate.ignore.id.push('test'); // 忽略特定ID元素
            translate.ignore.class.push('test'); // 忽略特定class元素

            高级配置

            translate.setAutoDiscriminateLocalLanguage(); // 自动识别用户语种
            translate.language.setUrlParamControl(); // URL参数控制显示语种
            translate.selectionTranslate.start(); // 启用划词翻译

            除了基础翻译功能,translate.js还提供了一系列高级特性,例如自动识别并翻译图片中的文字内容,监控AJAX请求,实时翻译动态加载的内容,自定义翻译术语,确保专业词汇准确一致,且监控翻译性能,优化用户体验。

            最佳实践建议

            1. 自托管JS文件:将translate.js下载到自己的服务器,确保稳定加载
            2. 合理配置缓存:根据业务特点设置合适的缓存策略
            3. 选择性翻译:对不需要翻译的内容进行排除,提升性能
            4. 渐进式实施:先在小范围测试,再全面推广

            translate.js重新定义了网页多语言实现的范式,将复杂的技术难题简化为几行优雅的代码。无论是个人项目还是企业级应用,它都能提供高效、稳定、易用的多语言解决方案。其开源免费的特性更是降低了技术门槛,让更多开发者能够轻松实现产品的国际化。随着人工智能技术的不断发展,translate.js也在持续进化,集成更先进的翻译引擎,提供更精准的翻译结果。未来,它将继续致力于降低多语言实现的复杂度,助力更多产品走向全球市场。


            DEMO:

            <!DOCTYPE html>
            <html lang="zh-CN">
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <title>多语言网站示例</title>
                <!-- 引入本地的 translate.js -->
                <script src="./translate.js"></script>
                <style>
                    body {
                        font-family: Arial, sans-serif;
                        max-width: 800px;
                        margin: 0 auto;
                        padding: 20px;
                    }
                    .language-switcher {
                        margin: 20px 0;
                        padding: 10px;
                        background: #f5f5f5;
                        border-radius: 5px;
                    }
                    .language-btn {
                        padding: 8px 16px;
                        margin: 0 5px;
                        border: 1px solid #ddd;
                        background: white;
                        cursor: pointer;
                        border-radius: 3px;
                    }
                    .language-btn.active {
                        background: #007bff;
                        color: white;
                        border-color: #007bff;
                    }
                    .content {
                        margin-top: 20px;
                        padding: 20px;
                        border: 1px solid #eee;
                        border-radius: 5px;
                    }
                </style>
            </head>
            <body>
                <div class="language-switcher">
                    <h3>选择语言 / Select Language:</h3>
                    <button class="language-btn active" onclick="switchLanguage('chinese_simplified')">中文</button>
                    <button class="language-btn" onclick="switchLanguage('english')">English</button>
                    <button class="language-btn" onclick="switchLanguage('japanese')">日本語</button>
                    <button class="language-btn" onclick="switchLanguage('korean')">한국어</button>
                    <button class="language-btn" onclick="switchLanguage('french')">Français</button>
                    <button class="language-btn" onclick="switchLanguage('german')">Deutsch</button>
                </div>
            
                <div class="content" id="translatable-content">
                    <h1>欢迎来到我的多语言网站</h1>
                    <p>这是一个演示如何使用 translate.js 实现多语言切换的示例。</p>
                    
                    <h2>功能特点</h2>
                    <ul>
                        <li>支持多种语言实时切换</li>
                        <li>无需刷新页面</li>
                        <li>自动翻译页面内容</li>
                        <li>简单易用的API</li>
                    </ul>
                    
                    <h2>联系我们</h2>
                    <p>电子邮件:contact@example.com</p>
                    <p>电话:+86 123-4567-8900</p>
                    
                    <div class="no-translate">
                        <p><em>这个区域的内容不会被翻译(class="no-translate")</em></p>
                    </div>
                </div>
            
                <script>
                    // 页面加载完成后初始化翻译功能
                    document.addEventListener('DOMContentLoaded', function() {
                        // 初始化翻译配置
                        translate.service.use('client.edge'); // 使用浏览器内置翻译(免费)
                        // 或者使用自有翻译服务:translate.service.use('translate.service');
                        
                        // 设置页面默认语言
                        translate.language.setLocal('chinese_simplified');
                        
                        // 设置忽略翻译的元素(可选)
                        translate.ignore.class.push('no-translate');
                        
                        // 启动监听器,自动检测页面变化
                        translate.listener.start();
                        
                        // 根据浏览器语言自动切换(可选)
                        autoDetectLanguage();
                        
                        console.log('translate.js 初始化完成!');
                    });
            
                    // 语言切换函数
                    function switchLanguage(lang) {
                        // 更新按钮状态
                        document.querySelectorAll('.language-btn').forEach(btn => {
                            btn.classList.remove('active');
                        });
                        event.target.classList.add('active');
                        
                        // 切换语言并执行翻译
                        translate.changeLanguage(lang);
                        translate.execute();
                        
                        // 保存语言偏好到本地存储
                        localStorage.setItem('preferred-language', lang);
                        
                        console.log('切换到语言:', lang);
                    }
            
                    // 自动检测浏览器语言
                    function autoDetectLanguage() {
                        const savedLang = localStorage.getItem('preferred-language');
                        if (savedLang) {
                            // 使用用户之前选择的语言
                            setActiveButton(savedLang);
                            return;
                        }
                        
                        // 检测浏览器语言
                        const browserLang = navigator.language.toLowerCase();
                        let detectedLang = 'chinese_simplified'; // 默认中文
                        
                        if (browserLang.includes('en')) {
                            detectedLang = 'english';
                        } else if (browserLang.includes('ja')) {
                            detectedLang = 'japanese';
                        } else if (browserLang.includes('ko')) {
                            detectedLang = 'korean';
                        } else if (browserLang.includes('fr')) {
                            detectedLang = 'french';
                        } else if (browserLang.includes('de')) {
                            detectedLang = 'german';
                        }
                        
                        // 如果检测到的语言不是中文,自动切换
                        if (detectedLang !== 'chinese_simplified') {
                            setTimeout(() => {
                                switchLanguage(detectedLang);
                            }, 1000);
                        }
                    }
            
                    // 设置活动按钮样式
                    function setActiveButton(lang) {
                        document.querySelectorAll('.language-btn').forEach(btn => {
                            btn.classList.remove('active');
                            if (btn.textContent.toLowerCase().includes(getLanguageText(lang).toLowerCase())) {
                                btn.classList.add('active');
                            }
                        });
                    }
            
                    // 获取语言显示文本
                    function getLanguageText(lang) {
                        const langMap = {
                            'chinese_simplified': '中文',
                            'english': 'English',
                            'japanese': '日本語',
                            'korean': '한국어',
                            'french': 'Français',
                            'german': 'Deutsch'
                        };
                        return langMap[lang] || lang;
                    }
            
                    // 手动触发翻译函数(如果需要)
                    function manualTranslate() {
                        translate.execute();
                    }
            
                    // 重置翻译(恢复原始语言)
                    function resetTranslation() {
                        translate.reset();
                    }
                </script>
            </body>
            </html>

            [members_only]

            <div style="text-align: center;">
                <span id="timeDate"></span><span id="times"></span> 
            </div>
            
            <script language="javascript"> 
            var now = new Date();
            function createtime(){
                var grt= new Date("01/01/2024 00:00:00");
                now.setTime(now.getTime()+250);
                days = (now - grt ) / 1000 / 60 / 60 / 24;
                dnum = Math.floor(days);
                hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum);
                hnum = Math.floor(hours);
                if(String(hnum).length ==1 ){hnum = "0" + hnum;}
                minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
                mnum = Math.floor(minutes);
                if(String(mnum).length ==1 ){mnum = "0" + mnum;}
                seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
                snum = Math.round(seconds);
                if(String(snum).length ==1 ){snum = "0" + snum;}
                document.getElementById("timeDate").innerHTML = "本网站己运行"+dnum+"天";
                document.getElementById("times").innerHTML = hnum + "小时" + mnum + "分" + snum + "秒";
            }
            setInterval("createtime()",250); 
            </script>
            
            <div style="text-align: center;"><strong>
            	切换语言:</strong>
            	<a class="ignore" href="javascript:translate.changeLanguage('english');">English</a> | 
            	<a class="ignore" href="javascript:translate.changeLanguage('chinese_simplified');">简体中文</a>
            </div>
            <script src="http://naver.19901117.xyz/translate.js/translate.js"></script>
            
            <script>
            translate.selectLanguageTag.languages = 'english,chinese_simplified,japanese,spanish,deutsch,french,korean';
            translate.request.listener.delayExecuteTime = 500;
            translate.service.use('client.edge'); //设置机器翻译服务通道
            translate.listener.start(); //开启页面元素动态监控
            translate.selectLanguageTag.show = false;//false隐藏选择框,true打开
            translate.execute();//完成翻译初始化,进行翻译
            </script>

            支持的语种表 – translate.service通道

            corsican:科西嘉语
            guarani:瓜拉尼语
            kinyarwanda:卢旺达语
            hausa:豪萨语
            norwegian:挪威语
            dutch:荷兰语
            yoruba:约鲁巴语
            english:英语
            gongen:贡根语
            latin:拉丁语
            nepali:尼泊尔语
            french:法语
            czech:捷克语
            hawaiian:夏威夷语
            georgian:格鲁吉亚语
            russian:俄语
            chinese_simplified:简体中文
            persian:波斯语
            bhojpuri:博杰普尔语
            hindi:印地语
            belarusian:白俄罗斯语
            swahili:斯瓦希里语
            icelandic:冰岛语
            yiddish:意第绪语
            twi:契维语
            irish:爱尔兰语
            gujarati:古吉拉特语
            khmer:高棉语
            slovak:斯洛伐克语
            hebrew:希伯来语
            kannada:卡纳达语
            hungarian:匈牙利语
            tamil:泰米尔语
            arabic:阿拉伯语
            bengali:孟加拉语
            azerbaijani:阿塞拜疆语
            samoan:萨摩亚语
            afrikaans:南非荷兰语
            indonesian:印尼语
            danish:丹麦语
            shona:修纳语
            bambara:班巴拉语
            lithuanian:立陶宛语
            vietnamese:越南语
            maltese:马耳他语
            turkmen:土库曼语
            assamese:阿萨姆语
            catalan:加泰罗尼亚语
            singapore:僧伽罗语
            cebuano:宿务语
            scottish_gaelic:苏格兰盖尔语
            sanskrit:梵语
            polish:波兰语
            galician:加利西亚语
            latvian:拉脱维亚语
            ukrainian:乌克兰语
            tatar:鞑靼语
            welsh:威尔士语
            japanese:日语
            filipino:菲律宾语
            aymara:艾马拉语
            lao:老挝语
            telugu:泰卢固语
            romanian:罗马尼亚语
            haitian_creole:海地克里奥尔语
            dogrid:多格来语
            swedish:瑞典语
            maithili:迈蒂利语
            thai:泰语
            armenian:亚美尼亚语
            burmese:缅甸语
            pashto:普什图语
            hmong:苗语
            dhivehi:迪维希语
            chinese_traditional:繁體中文
            luxembourgish:卢森堡语
            sindhi:信德语
            kurdish:库尔德语(库尔曼吉语)
            turkish:土耳其语
            macedonian:马其顿语
            bulgarian:保加利亚语
            malay:马来语
            luganda:卢干达语
            marathi:马拉地语
            estonian:爱沙尼亚语
            malayalam:马拉雅拉姆语
            deutsch:德语
            slovene:斯洛文尼亚语
            urdu:乌尔都语
            portuguese:葡萄牙语
            igbo:伊博语
            kurdish_sorani:库尔德语(索拉尼)
            oromo:奥罗莫语
            greek:希腊语
            spanish:西班牙语
            frisian:弗里西语
            somali:索马里语
            amharic:阿姆哈拉语
            nyanja:齐切瓦语
            punjabi:旁遮普语
            basque:巴斯克语
            italian:意大利语
            albanian:阿尔巴尼亚语
            korean:韩语
            tajik:塔吉克语
            finnish:芬兰语
            kyrgyz:吉尔吉斯语
            ewe:埃维语
            croatian:克罗地亚语
            creole:克里奥尔语
            quechua:克丘亚语
            bosnian:波斯尼亚语
            maori:毛利语

            [/members_only]

            相关文章:立省万元!李哥教你如何使用translate.js实现WP菜单的JavaScript翻译

          4. Custom_Nodes篇:ComfyUI-QwenVL反推节点

            Custom_Nodes篇:ComfyUI-QwenVL反推节点

            这个插件对于 ComfyUI 用户来说,实用性非常高,而且考虑到了不同的硬件配置需求

            技术整合很到位:它直接把像 Qwen3-VL 这样领先的视觉语言模型带到了 ComfyUI 的节点式工作流中,让用户能以更直观的方式使用多模态能力,无论是图片分析还是未来的视频处理(根据介绍),都提供了强大的基础。它不仅追赶最新的 Qwen3-VL 模型,还保持对早期 Qwen2.5 的支持,这让拥有不同模型的用户都能受益。最值得称赞的是它加入了多种量化(4-bit/8-bit)和模型缓存的选项。这意味着开发者清晰地认识到 VRAM 是限制许多 AI 玩家的主要瓶颈,通过这些优化,可以让更多配置没那么“壕”的用户也能体验到高性能 VL 模型,这大大提升了它的普及潜力。

            总体来看,这是一个强大、灵活且注重实用的节点集,为 ComfyUI 的多模态能力添加了重要的一块拼图。

            节点官方介绍:

            ComfyUI-QwenVL 自定义节点集成了 Qwen-VL 系列,包括最新的 Qwen3-VL 型号,以及 Qwen2.5-VL 和最新的 Qwen3-VL,从而实现了用于生成文本、图像理解和视频分析的高级多模态 AI。


            开源地址:https://github.com/1038lab/ComfyUI-QwenVL

            ComfyUI-QwenVL 自定义节点集成了阿里云强大的 Qwen-VL 系列视觉语言模型 (LVLM),包括最新的 Qwen3-VL 和 Qwen2.5-VL。该高级节点可在 ComfyUI 工作流程中实现无缝的多模态 AI 功能,从而高效地生成文本、理解图像和分析视频。

            ✨ 特点

            • 标准节点和高级节点:包含一个用于快速使用的简单 QwenVL 节点和一个可对生成进行精细控制的 QwenVL(高级)节点。
            • 预设和自定义提示:您可以从一系列便捷的预设提示中进行选择,也可以编写自己的提示以实现完全控制。
            • 多型号支持:轻松切换各种官方 Qwen-VL 型号。
            • 自动模型下载:模型会在首次使用时自动下载。
            • 智能量化:通过 4 位、8 位和 FP16 选项平衡 VRAM 和性能。
            • 硬件感知:自动检测 GPU 功能,防止与不兼容的型号(例如 FP8)发生错误。
            • 可复现生成:使用种子参数以获得一致的输出。
            • 内存管理:“保持模型加载”选项可将模型保留在 VRAM 中,以加快处理速度。
            • 图像和视频支持:接受单个图像和视频帧序列作为输入。
            • 强大的错误处理能力:针对硬件或内存问题提供清晰的错误消息。
            • 简洁的控制台输出:运行期间输出简洁明了的控制台日志。Flash -Attention v2 集成:可用时自动启用,以加快注意力层速度。Torch 编译优化:可选的 JIT 编译,use_torch_compile以提高吞吐量。 高级设备处理:自动检测 CUDA、Apple Silicon (MPS) 或 CPU;可手动覆盖。 动态内存强制执行:根据 VRAM 可用性自动调整量化级别。

            🚀 安装

            1. 将此仓库克隆到您的 ComfyUI/custom_nodes 目录:cd ComfyUI/custom\_nodes git clone https://github.com/1038lab/ComfyUI-QwenVL.git
            2. 安装所需的依赖项:cd ComfyUI/custom_nodes/ComfyUI-QwenVL pip install -r requirements.txt
            3. 重启ComfyUI。

            📥 下载模型

            首次使用时,模型将自动下载。如果您希望手动下载,请将其放置在 ComfyUI/models/LLM/Qwen-VL/ 目录中。

            模型关联
            Qwen3-VL-2B-指令下载
            Qwen3-VL-2B-思考下载
            Qwen3-VL-2B-指令-FP8下载
            Qwen3-VL-2B-思考-FP8下载
            Qwen3-VL-4B-指令下载
            Qwen3-VL-4B-思考下载
            Qwen3-VL-4B-指令-FP8下载
            Qwen3-VL-4B-思考-FP8下载
            Qwen3-VL-8B-指令下载
            Qwen3-VL-8B-思考下载
            Qwen3-VL-8B-指令-FP8下载
            Qwen3-VL-8B-思考-FP8下载
            Qwen3-VL-32B-指令下载
            Qwen3-VL-32B-思考下载
            Qwen3-VL-32B-指令-FP8下载
            Qwen3-VL-32B-思考-FP8下载
            Qwen2.5-VL-3B-指令下载
            Qwen2.5-VL-7B-指令下载

            📖 用法

            基本用法

            1. 从 🧪AILab/QwenVL 类别中添加“QwenVL”节点。
            2. 选择您要使用的模型名称。
            3. 将图像或视频(图像序列)源连接到节点。
            4. 您可以使用预设字段或自定义字段编写提示信息。
            5. 运行工作流程。

            高级用法

            如需更精细的控制,请使用“QwenVL(高级)”节点。该节点可让您访问详细的生成参数,例如温度、top_p、光束搜索和器件选择。

            ⚙️ 参数

            范围描述默认范围节点
            模型名称要使用的Qwen-VL模型。Qwen3-VL-4B-指令标准版和高级版
            量化即时量化。对于预量化模型(例如 FP8),此操作将被忽略。8 位(平衡)4 位、8 位、无标准版和高级版
            注意模式注意后端。auto如果可用,则尝试使用 Flash-Attn v2,否则回退到 SDPA。汽车自动、闪光灯_注意_2、SDPA标准版和高级版
            使用 torch 编译启用torch.compile('reduce-overhead')以提高 CUDA 吞吐量(Torch 2.1+)。仅限高级用户
            设备取消自动设备选择。汽车自动、CUDA、CPU仅限高级用户
            预设提示针对常见任务的一系列预定义提示。请描述一下……任何文本标准版和高级版
            自定义提示如果提供了预设提示,则覆盖该提示。任何文本标准版和高级版
            最大令牌数要生成的新代币的最大数量。102464-2048标准版和高级版
            保持模型加载将模型保存在显存中,以便后续运行速度更快。真的真/假标准版和高级版
            种子可重复结果的种子。11 – 2^64-1标准版和高级版
            温度控制随机性。数值越高,随机性越强。(当 num_beams 为 1 时使用)。0.60.1-1.0仅限高级用户
            顶部_p细胞核采样阈值。(当 num_beams 为 1 时使用)。0.90.0-1.0仅限高级用户
            光束数用于光束搜索的光束数量。大于 1 则禁用温度/top_p 采样。11-10仅限高级用户
            重复惩罚不鼓励重复使用令牌。1.20.0-2.0仅限高级用户
            帧数要从视频输入中采样的帧数。161-64仅限高级用户

            💡 量化选项

            模式精确内存使用情况速度质量推荐用于
            无(FP16)16 位浮点数高的最快最好的高显存显卡(16GB+)
            8 位(平衡)8位整数中等的快速地非常好均衡性能(8GB+)
            4 位(对显存友好)4位整数低的慢点*好的低显存显卡(<8GB)

            *关于 4 位速度的说明:4 位量化可以显著降低 VRAM 使用量,但由于实时反量化的计算开销,可能会导致某些系统的性能变慢。

            🤔 设置技巧

            环境推荐
            模型选择对于大多数用户来说,Qwen3-VL-4B-Instruct 是一个很好的起点。如果您使用的是 40 系列 GPU,请尝试使用 -FP8 版本以获得更好的性能。
            内存模式如果您计划多次运行该节点,请保持 keep_model_loaded 启用(True)以获得最佳性能。仅当其他节点的显存不足时才禁用它。
            量子化首先使用默认的 8 位模式。如果显存充足(>16GB),请切换到“无”(FP16)模式以获得最佳速度和质量。如果显存不足,请使用 4 位模式。
            表现首次使用特定量化方式加载模型时,速度可能会较慢。后续运行(启用 keep_model_loaded 参数)速度会快得多。

            🧠 关于模型

            该节点采用阿里云Qwen团队开发的Qwen-VL系列模型。这些强大的开源大型视觉语言模型(LVLM)旨在理解和处理视觉和文本信息,因此非常适合图像和视频详细描述等任务。

          5. Custom_Nodes篇:ComfyUI-QwenVL3-image反推节点

            Custom_Nodes篇:ComfyUI-QwenVL3-image反推节点

            这是一个 ComfyUI 自定义节点,它集成了阿里巴巴的 Qwen3-VL-4B-Instruct-FP8 视觉语言模型。

            核心功能: 它的主要作用是分析输入的图像并生成相应的文本描述

            主要特点是高效运行,由于使用了 FP8 量化技术,该模型运行所需的显存较低,大约 10GB 左右。开发者提到,它适合用在图像放大的工作流程中,作为图像理解(“看懂”图片内容)的工具。需要注意的是,因为它依赖 FP8 技术,所以它有特定的硬件门槛,要求使用 NVIDIA RTX 4090 或计算能力更强的 GPU。简单来说,它是一个能帮你“读取”图片内容并将其转换成文字的工具节点,特别适合显存有限但又想使用先进视觉模型的用户(前提是你的 GPU 支持 FP8)。

            节点官方介绍:


            开源地址:https://github.com/yamanacn/ComfyUI-QwenVL3-image

            使用qwen3vl-4b快速分析图片内容,用于在放大中替代florence

            📖 简介

            一个为 ComfyUI 设计的自定义节点,集成了 Qwen3-VL-4B-Instruct-FP8 视觉语言模型,用于高效的图像理解和描述。

            ✨ 主要特性

            • 🚀 高效 FP8 量化:仅需约 10GB 显存
            • 📦 批量处理支持:一次处理多张图片
            • 💾 智能内存管理:可选模型保持加载,优化显存
            • 🔧 辅助工具链:提供文本分割、列表处理等节点

            📋 硬件要求

            • GPU: NVIDIA RTX 4090 或更高(计算能力 ≥ 8.9)
            • 显存: ≥ 10GB
            • 系统内存: 8GB+

            ⚠️ 重要提示: 此插件仅支持 FP8 量化模型,需要计算能力 8.9 或更高的 GPU。

            🔧 安装方法

            使用 Git Clone(推荐)

            cd ComfyUI/custom_nodes/
            git clone https://github.com/yamanacn/ComfyUI-QwenVL3-image.git
            cd ComfyUI-QwenVL3-image
            pip install -r requirements.txt

            使用 ComfyUI Manager

            1. 在 ComfyUI 中打开 Manager
            2. 搜索 “QwenVL3”
            3. 点击安装

            📦 模型下载

            模型会在首次使用时自动下载。你也可以从 HuggingFace 手动下载模型,并将其放置在 ComfyUI/models/Qwen/ 目录下。

            🎮 基础工作流

            对于批量处理,可连接 Text Batch Splitter 和 List Selector 节点来分别查看每张图片的描述。

            图片输入 → QwenVL3 Image (FP8) → 文本输出

          6. 数字囤积症救星!LeePoet教你用AI智能整理海量图片

            数字囤积症救星!LeePoet教你用AI智能整理海量图片

            图片太多了怎么办?LeePoet一站式智能整理方案

            💡 LeePoet技术理念:复杂的科技,应该以最简单的方式服务每个普通人。

            你是否也曾在数千张照片中翻找一张重要合影?是否因为图片太多而放弃整理?本文是数字时代每个人的“图片救星”。无论你是拥有上万张照片的摄影爱好者,还是被工作图片淹没的职场人,LeePoet将用AI技术为你提供从简单到专业的全系列解决方案。

            从人物识别到内容分类,Python自动化搞定图片管理,每个人手机里几千张照片的普遍现象,手动整理的痛苦:耗时、低效、容易遗漏,LeePoet的解决方案理念:技术让生活更简单。”隐私保护+内容管理,一键分类敏感图片”,多层级风险评估。

            包含开箱即用的Python脚本、详细配置教程和常见问题解决方案。LeePoet用实际案例证明,技术不应该高高在上,而要真正服务于生活。

            UBUNTU创建虚拟环境操作

            1.创建虚拟环境
            # 进入你的项目文件夹
            cd /path/to/your/project
            
            # 创建虚拟环境(会在当前文件夹创建 venv 目录)
            python3 -m venv myenv
            
            2.激活虚拟环境
            source myenv/bin/activate
            
            3.安装依赖
            python -m pip install opencv-python
            

            如果要使用增强版本,还需要安装:

            python -m pip install torch ultralytics
            
            4.基本完成。

            初级检测:

            创建一个名为 extract_people_images.py的文件完整代码:

            import os
            import shutil
            import cv2
            from pathlib import Path
            
            def detect_people_in_image(image_path, face_cascade=None):
                """
                检测图片中是否包含人物
                """
                if face_cascade is None:
                    face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
                
                try:
                    img = cv2.imread(image_path)
                    if img is None:
                        return False
                    
                    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
                    
                    faces = face_cascade.detectMultiScale(
                        gray,
                        scaleFactor=1.1,
                        minNeighbors=5,
                        minSize=(30, 30)
                    )
                    
                    return len(faces) > 0
                    
                except Exception as e:
                    print(f"处理图片 {image_path} 时出错: {e}")
                    return False
            
            def extract_images_with_people(source_dir, target_dir):
                """
                从源文件夹提取带人物的图片到目标文件夹
                """
                supported_formats = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp']
                
                # 创建目标文件夹
                Path(target_dir).mkdir(parents=True, exist_ok=True)
                
                # 加载人脸检测器
                face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
                
                # 统计信息
                total_images = 0
                images_with_people = 0
                
                print("开始扫描图片...")
                
                for filename in os.listdir(source_dir):
                    file_path = os.path.join(source_dir, filename)
                    
                    # 检查文件格式
                    if os.path.isfile(file_path) and any(filename.lower().endswith(fmt) for fmt in supported_formats):
                        total_images += 1
                        print(f"处理第 {total_images} 张图片: {filename}")
                        
                        # 检测是否包含人物
                        if detect_people_in_image(file_path, face_cascade):
                            images_with_people += 1
                            
                            # 构建目标路径
                            target_path = os.path.join(target_dir, filename)
                            
                            # 处理重名文件
                            counter = 1
                            while os.path.exists(target_path):
                                name, ext = os.path.splitext(filename)
                                target_path = os.path.join(target_dir, f"{name}_{counter}{ext}")
                                counter += 1
                            
                            # 复制文件
                            shutil.copy2(file_path, target_path)
                            print(f"✓ 检测到人物: {filename}")
                        else:
                            print(f"✗ 未检测到人物: {filename}")
                
                print("\n" + "="*50)
                print("处理完成!")
                print(f"总共处理图片: {total_images}张")
                print(f"检测到包含人物的图片: {images_with_people}张")
                print(f"图片已保存到: {target_dir}")
            
            def main():
                # 设置路径
                current_dir = os.getcwd()
                target_dir = os.path.join(current_dir, "images_with_people")
                
                print(f"当前文件夹: {current_dir}")
                print(f"目标文件夹: {target_dir}")
                print("-" * 50)
                
                # 执行提取
                extract_images_with_people(current_dir, target_dir)
            
            if __name__ == "__main__":
                main()
            
            5. 运行脚本
            python extract_people_images.py
            
            6. 退出虚拟环境

            完成后,可以退出虚拟环境:

            其它说明:安装所需库(上面装完了可以不看这)
            # 升级pip(可选)
            python -m pip install --upgrade pip
            
            # 安装OpenCV(基础版本)
            python -m pip install opencv-python
            
            # 或者安装增强版本所需的库
            python -m pip install opencv-python torch ultralytics
            
            创建 requirements.txt 文件(可选)

            你可以创建一个依赖文件,方便以后重新安装:

            # 生成requirements.txt
            python -m pip freeze requirements.txt
            

            requirements.txt 内容示例:

            opencv-python==4.8.1.78
            numpy==1.24.3
            

            以后重新安装依赖:

            python -m pip install -r requirements.txt
            
            完整的项目结构建议
            your_project/
            ├── venv/                    # 虚拟环境(不要提交到版本控制)
            ├── extract_people_images.py # 主程序
            ├── requirements.txt         # 依赖列表
            └── images_with_people/      # 输出文件夹(程序自动创建)
            

            库装不上的其它操作

            python -m pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple –verbose

            中级检测:

            正常激活虚拟环境

            cd myenv  #即虚拟环境目录
            source myenv/bin/activate   # 进入虚拟环境(如果还没激活)
            pip install opencv-python    # 确保已安装所需库
            

            安装依赖的完整步骤

            # 1. 激活虚拟环境
            source myenv/bin/activate
            
            # 2. 安装依赖(如果网络慢可以使用清华镜像)
            python -m pip install torch torchvision ultralytics opencv-python
            
            # 或者使用国内镜像加速安装
            python -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple torch torchvision ultralytics opencv-python
            
            # 直接使用python命令运行,不需要执行权限
            python extract_people_images.py
            
            # 给脚本执行权限:
            chmod +x extract_people_images.py
            

            ##

            一、增强检测人物图片:

            使用YOLOv8模型进行更准确的人物检测,增强版人物图片检测

            模型说明

            • yolov8n.pt:最小最快,适合快速检测
            • yolov8s.pt:平衡速度和精度
            • yolov8m.pt:中等精度
            • yolov8l.pt:高精度
            • yolov8x.pt:最高精度,最慢

            特性

            1. 更准确的人物检测:YOLO可以检测各种姿势、大小的人物
            2. 可调置信度:根据需求调整检测敏感度
            3. 检测预览:可选保存带检测框的图片
            4. 详细统计:显示检测数量、置信度、处理时间等
            5. 错误处理:完善的异常处理,不会因为单张图片失败而停止
            6. 进度显示:实时显示处理进度

            这个增强版本比基础版本准确得多,特别是对于侧面、远处、遮挡的人物检测效果更好!

            完整的Python代码

            #!/usr/bin/env python3
            """
            增强版人物图片检测程序
            使用YOLOv8模型进行更准确的人物检测
            """
            
            import os
            import shutil
            import argparse
            from pathlib import Path
            import time
            
            def setup_environment():
                """检查并导入所需的库"""
                try:
                    from ultralytics import YOLO
                    import cv2
                    return YOLO, cv2
                except ImportError as e:
                    print("错误: 缺少必要的库")
                    print("请安装以下依赖:")
                    print("pip install torch torchvision ultralytics opencv-python")
                    print("\n如果安装缓慢,可以使用清华镜像:")
                    print("pip install -i https://pypi.tuna.tsinghua.edu.cn/simple torch torchvision ultralytics opencv-python")
                    return None, None
            
            def extract_images_with_people_enhanced(source_dir, target_dir, confidence_threshold=0.3, 
                                                  model_size='yolov8n.pt', device='cpu', 
                                                  save_detection_preview=False):
                """
                使用YOLO模型检测并提取包含人物的图片
                
                参数:
                source_dir: 源文件夹路径
                target_dir: 目标文件夹路径
                confidence_threshold: 置信度阈值(0.0-1.0),值越小检测越敏感
                model_size: 模型大小 ('yolov8n.pt', 'yolov8s.pt', 'yolov8m.pt', 'yolov8l.pt', 'yolov8x.pt')
                device: 运行设备 ('cpu' 或 'cuda')
                save_detection_preview: 是否保存带检测框的预览图
                """
                
                # 检查库是否可用
                YOLO, cv2 = setup_environment()
                if YOLO is None:
                    return
                
                # 创建目标文件夹
                Path(target_dir).mkdir(parents=True, exist_ok=True)
                
                # 如果启用预览,创建预览文件夹
                preview_dir = None
                if save_detection_preview:
                    preview_dir = os.path.join(target_dir, "detection_previews")
                    Path(preview_dir).mkdir(parents=True, exist_ok=True)
                
                # 支持的图片格式
                supported_formats = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp', '.JPG', '.JPEG', '.PNG']
                
                print("=" * 60)
                print("增强版人物图片检测程序")
                print("=" * 60)
                print(f"源文件夹: {source_dir}")
                print(f"目标文件夹: {target_dir}")
                print(f"模型: {model_size}")
                print(f"置信度阈值: {confidence_threshold}")
                print(f"设备: {device}")
                print(f"保存检测预览: {save_detection_preview}")
                print("-" * 60)
                
                try:
                    # 加载YOLO模型(会自动下载如果不存在)
                    print("正在加载YOLO模型...")
                    start_time = time.time()
                    model = YOLO(model_size)
                    load_time = time.time() - start_time
                    print(f"✓ 模型加载完成,耗时: {load_time:.2f}秒")
                    
                except Exception as e:
                    print(f"✗ 模型加载失败: {e}")
                    print("请检查网络连接或手动下载模型")
                    return
                
                # 统计信息
                total_images = 0
                images_with_people = 0
                processed_files = []
                detection_times = []
                
                print("\n开始扫描图片...")
                
                # 获取所有图片文件
                image_files = []
                for filename in os.listdir(source_dir):
                    file_path = os.path.join(source_dir, filename)
                    if os.path.isfile(file_path) and any(filename.lower().endswith(fmt) for fmt in supported_formats):
                        image_files.append(filename)
                
                total_files = len(image_files)
                print(f"找到 {total_files} 张待处理图片")
                
                # 处理每张图片
                for i, filename in enumerate(image_files, 1):
                    file_path = os.path.join(source_dir, filename)
                    
                    print(f"\n[{i}/{total_files}] 处理: {filename}")
                    total_images += 1
                    
                    try:
                        # 记录开始时间
                        start_time = time.time()
                        
                        # 使用YOLO进行检测
                        results = model(file_path, conf=confidence_threshold, device=device, verbose=False)
                        detection_time = time.time() - start_time
                        detection_times.append(detection_time)
                        
                        # 检查是否检测到人物(YOLO中person类的ID是0)
                        has_people = False
                        people_count = 0
                        max_confidence = 0.0
                        
                        for result in results:
                            if result.boxes is not None and len(result.boxes) > 0:
                                for box in result.boxes:
                                    class_id = int(box.cls)
                                    confidence = float(box.conf)
                                    
                                    # class_id 0 对应 'person'
                                    if class_id == 0:
                                        has_people = True
                                        people_count += 1
                                        max_confidence = max(max_confidence, confidence)
                        
                        if has_people:
                            images_with_people += 1
                            
                            # 构建目标文件路径
                            target_path = os.path.join(target_dir, filename)
                            
                            # 处理重名文件
                            counter = 1
                            base_target_path = target_path
                            while os.path.exists(target_path):
                                name, ext = os.path.splitext(filename)
                                target_path = os.path.join(target_dir, f"{name}_{counter}{ext}")
                                counter += 1
                            
                            # 复制原图到目标文件夹
                            shutil.copy2(file_path, target_path)
                            
                            # 如果启用预览,保存带检测框的图片
                            if save_detection_preview and has_people:
                                try:
                                    # 绘制检测结果
                                    plotted_image = results[0].plot()
                                    preview_filename = f"preview_{os.path.splitext(filename)[0]}.jpg"
                                    preview_path = os.path.join(preview_dir, preview_filename)
                                    cv2.imwrite(preview_path, plotted_image)
                                except Exception as e:
                                    print(f"  警告: 无法保存检测预览图: {e}")
                            
                            print(f"  ✓ 检测到 {people_count} 个人物 (最高置信度: {max_confidence:.3f})")
                            print(f"  ✓ 检测时间: {detection_time:.2f}秒")
                            print(f"  ✓ 已复制到: {os.path.basename(target_path)}")
                            
                            processed_files.append({
                                'filename': filename,
                                'people_count': people_count,
                                'max_confidence': max_confidence,
                                'detection_time': detection_time
                            })
                            
                        else:
                            print(f"  ✗ 未检测到人物 (检测时间: {detection_time:.2f}秒)")
                            
                    except Exception as e:
                        print(f"  ✗ 处理图片时出错: {e}")
                        continue
                
                # 输出统计信息
                print("\n" + "=" * 60)
                print("处理完成!")
                print("=" * 60)
                
                if total_images > 0:
                    print(f"总共处理图片: {total_images}张")
                    print(f"检测到包含人物的图片: {images_with_people}张")
                    print(f"检测率: {(images_with_people/total_images*100):.1f}%")
                    
                    if detection_times:
                        avg_detection_time = sum(detection_times) / len(detection_times)
                        print(f"平均检测时间: {avg_detection_time:.2f}秒/张")
                        print(f"总检测时间: {sum(detection_times):.2f}秒")
                    
                    if images_with_people > 0:
                        print(f"\n包含人物的图片已保存到: {target_dir}")
                        if save_detection_preview:
                            print(f"检测预览图已保存到: {preview_dir}")
                        
                        # 显示检测到最多人物的前几张图片
                        if processed_files:
                            processed_files.sort(key=lambda x: x['people_count'], reverse=True)
                            print(f"\n检测结果最好的图片:")
                            for i, file_info in enumerate(processed_files[:3]):
                                print(f"  {i+1}. {file_info['filename']} - {file_info['people_count']}人 "
                                      f"(置信度: {file_info['max_confidence']:.3f})")
                else:
                    print("未找到任何可处理的图片文件")
            
            def main():
                """主函数"""
                parser = argparse.ArgumentParser(description='增强版人物图片检测程序')
                parser.add_argument('--source', '-s', default='.', 
                                   help='源文件夹路径 (默认: 当前文件夹)')
                parser.add_argument('--target', '-t', default='images_with_people',
                                   help='目标文件夹路径 (默认: images_with_people)')
                parser.add_argument('--confidence', '-c', type=float, default=0.3,
                                   help='检测置信度阈值 (0.0-1.0, 默认: 0.3)')
                parser.add_argument('--model', '-m', default='yolov8n.pt',
                                   choices=['yolov8n.pt', 'yolov8s.pt', 'yolov8m.pt', 'yolov8l.pt', 'yolov8x.pt'],
                                   help='YOLO模型大小 (默认: yolov8n.pt)')
                parser.add_argument('--device', '-d', default='cpu',
                                   choices=['cpu', 'cuda'],
                                   help='运行设备 (默认: cpu)')
                parser.add_argument('--preview', '-p', action='store_true',
                                   help='保存带检测框的预览图')
                
                args = parser.parse_args()
                
                # 检查源文件夹是否存在
                if not os.path.exists(args.source):
                    print(f"错误: 源文件夹 '{args.source}' 不存在")
                    return
                
                # 运行检测程序
                extract_images_with_people_enhanced(
                    source_dir=args.source,
                    target_dir=args.target,
                    confidence_threshold=args.confidence,
                    model_size=args.model,
                    device=args.device,
                    save_detection_preview=args.preview
                )
            
            # 简化版本函数(不需要命令行参数)
            def simple_extract():
                """简化版本,使用默认参数"""
                source_dir = os.getcwd()  # 当前文件夹
                target_dir = os.path.join(source_dir, "images_with_people")
                
                print("使用简化模式运行...")
                extract_images_with_people_enhanced(source_dir, target_dir)
            
            if __name__ == "__main__":
                # 如果没有命令行参数,使用简化版本
                import sys
                if len(sys.argv) == 1:
                    simple_extract()
                else:
                    main()
            

            步骤1:保存文件

            在nano编辑器中:

            • Ctrl+X退出
            • Y确认保存
            • Enter确认文件名

            步骤2:运行程序

            python extract_people_images_enhanced_copy.py
            

            方法2:使用命令行参数

            # 基本使用
            python extract_people_images_enhanced_copy.py --source /path/to/images --target ./output
            
            # 调整检测敏感度(值越小越敏感)
            python extract_people_images_enhanced_copy.py --confidence 0.2
            
            # 使用更大的模型(更准确但更慢)
            python extract_people_images_enhanced_copy.py --model yolov8s.pt
            
            # 保存检测预览图
            python extract_people_images_enhanced_copy.py --preview
            
            # 使用GPU加速(如果有NVIDIA显卡)
            python extract_people_images_enhanced_copy.py --device cuda
            
            # 查看所有选项
            python extract_people_images_enhanced_copy.py --help
            

            如果还是有问题,使用这个简单版本:

            # 先删除所有有问题的文件
            rm -f *.py
            
            # 创建简单版本
            cat > simple_detection.py << 'EOF'
            import os
            import shutil
            import cv2
            from pathlib import Path
            
            def detect_people():
                print("开始检测人物图片...")
                
                # 检查OpenCV
                try:
                    face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
                except:
                    print("请先安装OpenCV: pip install opencv-python")
                    return
                
                # 创建目标文件夹
                target_dir = "images_with_people"
                Path(target_dir).mkdir(exist_ok=True)
                
                # 支持的图片格式
                formats = ['.jpg', '.jpeg', '.png']
                count = 0
                
                for filename in os.listdir('.'):
                    if any(filename.lower().endswith(fmt) for fmt in formats):
                        print(f"处理: {filename}")
                        img = cv2.imread(filename)
                        if img is not None:
                            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
                            faces = face_cascade.detectMultiScale(gray, 1.1, 5)
                            if len(faces) > 0:
                                shutil.copy2(filename, os.path.join(target_dir, filename))
                                print(f"✓ 检测到人物")
                                count += 1
                            else:
                                print(f"✗ 未检测到人物")
                
                print(f"\n完成! 找到 {count} 张包含人物的图片")
            
            if __name__ == "__main__":
                detect_people()
            EOF
            
            # 运行简单版本
            python simple_detection.py
            

            请按照步骤1-4操作,应该能解决问题!

            二、增强检测人物图片(自定义文件夹):

            这个版本实现了把检测到的图片直接移动到新的文件夹而不是复制,也可以通过后面的加参数定义移动或者复制。

            主要修改 shutil.copy2()shutil.move()

            #!/usr/bin/env python3
            """
            增强版人物图片检测程序(移动版本)
            使用YOLOv8模型进行更准确的人物检测,并将图片移动到新文件夹
            """
            
            import os
            import shutil
            import argparse
            from pathlib import Path
            import time
            
            def setup_environment():
                """检查并导入所需的库"""
                try:
                    from ultralytics import YOLO
                    import cv2
                    return YOLO, cv2
                except ImportError as e:
                    print("错误: 缺少必要的库")
                    print("请安装以下依赖:")
                    print("pip install torch torchvision ultralytics opencv-python")
                    print("\n如果安装缓慢,可以使用清华镜像:")
                    print("pip install -i https://pypi.tuna.tsinghua.edu.cn/simple torch torchvision ultralytics opencv-python")
                    return None, None
            
            def extract_images_with_people_enhanced(source_dir, target_dir, confidence_threshold=0.3, 
                                                  model_size='yolov8n.pt', device='cpu', 
                                                  save_detection_preview=False, move_instead_of_copy=True):
                """
                使用YOLO模型检测并移动包含人物的图片
                
                参数:
                source_dir: 源文件夹路径
                target_dir: 目标文件夹路径
                confidence_threshold: 置信度阈值(0.0-1.0),值越小检测越敏感
                model_size: 模型大小 ('yolov8n.pt', 'yolov8s.pt', 'yolov8m.pt', 'yolov8l.pt', 'yolov8x.pt')
                device: 运行设备 ('cpu' 或 'cuda')
                save_detection_preview: 是否保存带检测框的预览图
                move_instead_of_copy: 是否移动而不是复制图片
                """
                
                # 检查库是否可用
                YOLO, cv2 = setup_environment()
                if YOLO is None:
                    return
                
                # 创建目标文件夹
                Path(target_dir).mkdir(parents=True, exist_ok=True)
                
                # 如果启用预览,创建预览文件夹
                preview_dir = None
                if save_detection_preview:
                    preview_dir = os.path.join(target_dir, "detection_previews")
                    Path(preview_dir).mkdir(parents=True, exist_ok=True)
                
                # 支持的图片格式
                supported_formats = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp', '.JPG', '.JPEG', '.PNG']
                
                print("=" * 60)
                print("增强版人物图片检测程序(移动版本)")
                print("=" * 60)
                print(f"源文件夹: {source_dir}")
                print(f"目标文件夹: {target_dir}")
                print(f"操作模式: {'移动' if move_instead_of_copy else '复制'}")
                print(f"模型: {model_size}")
                print(f"置信度阈值: {confidence_threshold}")
                print(f"设备: {device}")
                print(f"保存检测预览: {save_detection_preview}")
                print("-" * 60)
                
                # 警告信息
                if move_instead_of_copy:
                    print("⚠️  警告: 此操作将移动图片文件,原文件夹中的图片将被删除!")
                    response = input("是否继续?(y/N): ")
                    if response.lower() != 'y':
                        print("操作已取消")
                        return
                
                try:
                    # 加载YOLO模型(会自动下载如果不存在)
                    print("正在加载YOLO模型...")
                    start_time = time.time()
                    model = YOLO(model_size)
                    load_time = time.time() - start_time
                    print(f"✓ 模型加载完成,耗时: {load_time:.2f}秒")
                    
                except Exception as e:
                    print(f"✗ 模型加载失败: {e}")
                    print("请检查网络连接或手动下载模型")
                    return
                
                # 统计信息
                total_images = 0
                images_with_people = 0
                processed_files = []
                detection_times = []
                
                print("\n开始扫描图片...")
                
                # 获取所有图片文件
                image_files = []
                for filename in os.listdir(source_dir):
                    file_path = os.path.join(source_dir, filename)
                    if os.path.isfile(file_path) and any(filename.lower().endswith(fmt) for fmt in supported_formats):
                        image_files.append(filename)
                
                total_files = len(image_files)
                print(f"找到 {total_files} 张待处理图片")
                
                # 处理每张图片
                for i, filename in enumerate(image_files, 1):
                    file_path = os.path.join(source_dir, filename)
                    
                    print(f"\n[{i}/{total_files}] 处理: {filename}")
                    total_images += 1
                    
                    try:
                        # 记录开始时间
                        start_time = time.time()
                        
                        # 使用YOLO进行检测
                        results = model(file_path, conf=confidence_threshold, device=device, verbose=False)
                        detection_time = time.time() - start_time
                        detection_times.append(detection_time)
                        
                        # 检查是否检测到人物(YOLO中person类的ID是0)
                        has_people = False
                        people_count = 0
                        max_confidence = 0.0
                        
                        for result in results:
                            if result.boxes is not None and len(result.boxes) > 0:
                                for box in result.boxes:
                                    class_id = int(box.cls)
                                    confidence = float(box.conf)
                                    
                                    # class_id 0 对应 'person'
                                    if class_id == 0:
                                        has_people = True
                                        people_count += 1
                                        max_confidence = max(max_confidence, confidence)
                        
                        if has_people:
                            images_with_people += 1
                            
                            # 构建目标文件路径
                            target_path = os.path.join(target_dir, filename)
                            
                            # 处理重名文件
                            counter = 1
                            base_target_path = target_path
                            while os.path.exists(target_path):
                                name, ext = os.path.splitext(filename)
                                target_path = os.path.join(target_dir, f"{name}_{counter}{ext}")
                                counter += 1
                            
                            # 移动或复制文件到目标文件夹
                            if move_instead_of_copy:
                                shutil.move(file_path, target_path)
                                operation_text = "移动"
                            else:
                                shutil.copy2(file_path, target_path)
                                operation_text = "复制"
                            
                            # 如果启用预览,保存带检测框的图片
                            if save_detection_preview and has_people:
                                try:
                                    # 重新读取图片用于预览(如果是移动操作,需要重新加载)
                                    if move_instead_of_copy:
                                        preview_source_path = target_path
                                    else:
                                        preview_source_path = file_path
                                        
                                    preview_results = model(preview_source_path, conf=confidence_threshold, device=device, verbose=False)
                                    plotted_image = preview_results[0].plot()
                                    preview_filename = f"preview_{os.path.splitext(filename)[0]}.jpg"
                                    preview_path = os.path.join(preview_dir, preview_filename)
                                    cv2.imwrite(preview_path, plotted_image)
                                except Exception as e:
                                    print(f"  警告: 无法保存检测预览图: {e}")
                            
                            print(f"  ✓ 检测到 {people_count} 个人物 (最高置信度: {max_confidence:.3f})")
                            print(f"  ✓ 检测时间: {detection_time:.2f}秒")
                            print(f"  ✓ 已{operation_text}到: {os.path.basename(target_path)}")
                            
                            processed_files.append({
                                'filename': filename,
                                'people_count': people_count,
                                'max_confidence': max_confidence,
                                'detection_time': detection_time,
                                'operation': operation_text
                            })
                            
                        else:
                            print(f"  ✗ 未检测到人物 (检测时间: {detection_time:.2f}秒)")
                            
                    except Exception as e:
                        print(f"  ✗ 处理图片时出错: {e}")
                        continue
                
                # 输出统计信息
                print("\n" + "=" * 60)
                print("处理完成!")
                print("=" * 60)
                
                if total_images > 0:
                    print(f"总共处理图片: {total_images}张")
                    print(f"检测到包含人物的图片: {images_with_people}张")
                    print(f"检测率: {(images_with_people/total_images*100):.1f}%")
                    
                    if detection_times:
                        avg_detection_time = sum(detection_times) / len(detection_times)
                        print(f"平均检测时间: {avg_detection_time:.2f}秒/张")
                        print(f"总检测时间: {sum(detection_times):.2f}秒")
                    
                    if images_with_people > 0:
                        operation_mode = "移动" if move_instead_of_copy else "复制"
                        print(f"\n包含人物的图片已{operation_mode}到: {target_dir}")
                        if save_detection_preview:
                            print(f"检测预览图已保存到: {preview_dir}")
                        
                        # 显示检测到最多人物的前几张图片
                        if processed_files:
                            processed_files.sort(key=lambda x: x['people_count'], reverse=True)
                            print(f"\n检测结果最好的图片:")
                            for i, file_info in enumerate(processed_files[:3]):
                                print(f"  {i+1}. {file_info['filename']} - {file_info['people_count']}人 "
                                      f"(置信度: {file_info['max_confidence']:.3f})")
                            
                        # 安全提示
                        if move_instead_of_copy:
                            remaining_files = len([f for f in os.listdir(source_dir) 
                                                 if any(f.lower().endswith(fmt) for fmt in supported_formats)])
                            print(f"\n⚠️  原文件夹剩余图片: {remaining_files}张")
                            print("⚠️  检测到的图片已被移动到目标文件夹,原文件已删除")
                else:
                    print("未找到任何可处理的图片文件")
            
            def main():
                """主函数"""
                parser = argparse.ArgumentParser(description='增强版人物图片检测程序(移动版本)')
                parser.add_argument('--source', '-s', default='.', 
                                   help='源文件夹路径 (默认: 当前文件夹)')
                parser.add_argument('--target', '-t', default='images_with_people',
                                   help='目标文件夹路径 (默认: images_with_people)')
                parser.add_argument('--confidence', '-c', type=float, default=0.3,
                                   help='检测置信度阈值 (0.0-1.0, 默认: 0.3)')
                parser.add_argument('--model', '-m', default='yolov8n.pt',
                                   choices=['yolov8n.pt', 'yolov8s.pt', 'yolov8m.pt', 'yolov8l.pt', 'yolov8x.pt'],
                                   help='YOLO模型大小 (默认: yolov8n.pt)')
                parser.add_argument('--device', '-d', default='cpu',
                                   choices=['cpu', 'cuda'],
                                   help='运行设备 (默认: cpu)')
                parser.add_argument('--preview', '-p', action='store_true',
                                   help='保存带检测框的预览图')
                parser.add_argument('--copy', action='store_true',
                                   help='使用复制模式而不是移动模式')
                
                args = parser.parse_args()
                
                # 检查源文件夹是否存在
                if not os.path.exists(args.source):
                    print(f"错误: 源文件夹 '{args.source}' 不存在")
                    return
                
                # 运行检测程序
                extract_images_with_people_enhanced(
                    source_dir=args.source,
                    target_dir=args.target,
                    confidence_threshold=args.confidence,
                    model_size=args.model,
                    device=args.device,
                    save_detection_preview=args.preview,
                    move_instead_of_copy=not args.copy  # 如果指定--copy则使用复制模式
                )
            
            # 简化版本函数(不需要命令行参数)
            def simple_extract():
                """简化版本,使用默认参数(移动模式)"""
                source_dir = os.getcwd()  # 当前文件夹
                target_dir = os.path.join(source_dir, "images_with_people")
                
                print("使用简化模式运行(移动模式)...")
                print("此操作将把检测到人物的图片移动到新文件夹!")
                
                response = input("是否继续?(y/N): ")
                if response.lower() != 'y':
                    print("操作已取消")
                    return
                
                extract_images_with_people_enhanced(source_dir, target_dir, move_instead_of_copy=True)
            
            if __name__ == "__main__":
                # 如果没有命令行参数,使用简化版本
                import sys
                if len(sys.argv) == 1:
                    simple_extract()
                else:
                    main()
            

            主要修改内容:

            1. shutil.copy2()改为 shutil.move()
            2. 添加了移动模式的安全确认,避免误操作
            3. 增加了 move_instead_of_copy参数,可以控制是移动还是复制
            4. 添加了 --copy命令行参数,如果需要复制模式可以加上这个参数
            5. 改进了预览图生成逻辑,适应移动操作
            6. 增加了操作完成后的统计信息,显示剩余文件数量

            使用方法:

            移动模式(默认):

            # 简单使用(移动图片)
            python extract_people_images_enhanced_move.py
            
            # 带参数使用
            python extract_people_images_enhanced_move.py --source ./图片文件夹 --target ./人物图片
            

            复制模式(如果需要保留原文件):

            # 使用复制模式
            python extract_people_images_enhanced_move.py --copy
            
            # 带预览的复制模式
            python extract_people_images_enhanced_move.py --copy --preview
            

            其他参数:

            # 调整检测敏感度
            python extract_people_images_enhanced_move.py --confidence 0.2
            
            # 使用更大的模型
            python extract_people_images_enhanced_move.py --model yolov8s.pt
            
            # 保存检测预览图
            python extract_people_images_enhanced_move.py --preview
            

            安全特性:

            1. 运行前确认:移动模式会要求用户确认
            2. 进度显示:实时显示处理进度
            3. 结果统计:完成后显示移动/复制的文件数量
            4. 剩余文件提示:显示原文件夹剩余图片数量

            这样修改后,检测到人物的图片会被直接移动到目标文件夹,原文件会被删除,节省磁盘空间。如果你需要保留原文件,使用 --copy参数即可。

            高级检测:

            方案1:基于YOLO的人体部位检测

            专门检测女性人体暴露或色情内容。这里有几个不同级别的实现方案:

            #!/usr/bin/env python3
            """
            女性人体暴露/色情图片检测程序
            使用YOLO模型检测暴露的身体部位
            """
            
            import os
            import shutil
            import argparse
            from pathlib import Path
            import time
            
            def setup_environment():
                """检查并导入所需的库"""
                try:
                    from ultralytics import YOLO
                    import cv2
                    return YOLO, cv2
                except ImportError as e:
                    print("错误: 缺少必要的库")
                    print("请安装以下依赖:")
                    print("pip install torch torchvision ultralytics opencv-python")
                    return None, None
            
            class NudityDetector:
                """暴露内容检测器"""
                
                def __init__(self, confidence_threshold=0.3):
                    self.confidence_threshold = confidence_threshold
                    # 定义敏感部位类别(基于COCO数据集)
                    self.sensitive_parts = {
                        'person': 0,      # 人物
                        'face': 1,        # 脸部(某些数据集有)
                    }
                    
                    # 定义暴露风险等级的关键词(用于文件名和路径分析)
                    self.risky_keywords = [
                        'nude', 'naked', 'bikini', 'swimsuit', 'lingerie', 'sexy', 'hot',
                        '暴露', '性感', '内衣', '比基尼', '泳装', '裸体', '色情'
                    ]
                
                def detect_exposure_level(self, results, filename=""):
                    """
                    检测图片的暴露等级
                    返回: (暴露等级, 置信度, 检测到的部位)
                    """
                    exposure_level = 0  # 0: 安全, 1: 轻度, 2: 中度, 3: 高度暴露
                    max_confidence = 0
                    detected_parts = []
                    
                    for result in results:
                        if result.boxes is not None and len(result.boxes) > 0:
                            for box in result.boxes:
                                class_id = int(box.cls)
                                confidence = float(box.conf)
                                
                                # 检测人物
                                if class_id == 0:  # person
                                    detected_parts.append('person')
                                    max_confidence = max(max_confidence, confidence)
                                    
                                    # 如果有高置信度的人物检测,基础暴露等级为1
                                    if confidence > 0.5:
                                        exposure_level = max(exposure_level, 1)
                    
                    # 基于文件名分析
                    filename_risk = self.analyze_filename_risk(filename.lower())
                    exposure_level = max(exposure_level, filename_risk)
                    
                    return exposure_level, max_confidence, detected_parts
                
                def analyze_filename_risk(self, filename):
                    """分析文件名的风险等级"""
                    risk_level = 0
                    for keyword in self.risky_keywords:
                        if keyword in filename:
                            if keyword in ['nude', 'naked', '裸体', '色情']:
                                risk_level = max(risk_level, 3)
                            elif keyword in ['bikini', 'swimsuit', 'lingerie', '比基尼', '泳装', '内衣']:
                                risk_level = max(risk_level, 2)
                            else:
                                risk_level = max(risk_level, 1)
                    return risk_level
                
                def analyze_skin_ratio(self, image_path):
                    """简单的肤色比例分析(基础版本)"""
                    try:
                        import cv2
                        import numpy as np
                        
                        # 读取图片
                        img = cv2.imread(image_path)
                        if img is None:
                            return 0
                            
                        # 转换到HSV颜色空间进行肤色检测
                        hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
                        
                        # 定义肤色范围(需要根据实际情况调整)
                        lower_skin = np.array([0, 20, 70], dtype=np.uint8)
                        upper_skin = np.array([20, 255, 255], dtype=np.uint8)
                        
                        # 创建肤色掩码
                        mask = cv2.inRange(hsv, lower_skin, upper_skin)
                        
                        # 计算肤色像素比例
                        skin_ratio = np.sum(mask > 0) / (img.shape[0] * img.shape[1])
                        
                        return skin_ratio
                        
                    except Exception as e:
                        print(f"肤色分析错误: {e}")
                        return 0
            
            def extract_sensitive_images(source_dir, target_dir, confidence_threshold=0.3, 
                                       model_size='yolov8n.pt', device='cpu', 
                                       save_detection_preview=False, move_instead_of_copy=True,
                                       min_exposure_level=1):
                """
                检测并提取敏感图片
                
                参数:
                source_dir: 源文件夹路径
                target_dir: 目标文件夹路径
                confidence_threshold: 置信度阈值
                model_size: 模型大小
                device: 运行设备
                save_detection_preview: 是否保存检测预览
                move_instead_of_copy: 是否移动文件
                min_exposure_level: 最小暴露等级 (1-3)
                """
                
                # 检查库是否可用
                YOLO, cv2 = setup_environment()
                if YOLO is None:
                    return
                
                # 创建目标文件夹
                Path(target_dir).mkdir(parents=True, exist_ok=True)
                
                # 按暴露等级创建子文件夹
                level_folders = {
                    1: os.path.join(target_dir, "level1_mild"),
                    2: os.path.join(target_dir, "level2_moderate"),
                    3: os.path.join(target_dir, "level3_high")
                }
                
                for folder in level_folders.values():
                    Path(folder).mkdir(parents=True, exist_ok=True)
                
                # 如果启用预览,创建预览文件夹
                preview_dir = None
                if save_detection_preview:
                    preview_dir = os.path.join(target_dir, "detection_previews")
                    Path(preview_dir).mkdir(parents=True, exist_ok=True)
                
                # 支持的图片格式
                supported_formats = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp']
                
                print("=" * 70)
                print("女性人体暴露/色情图片检测程序")
                print("=" * 70)
                print(f"源文件夹: {source_dir}")
                print(f"目标文件夹: {target_dir}")
                print(f"操作模式: {'移动' if move_instead_of_copy else '复制'}")
                print(f"最小暴露等级: {min_exposure_level}")
                print(f"模型: {model_size}")
                print(f"置信度阈值: {confidence_threshold}")
                print("-" * 70)
                
                # 警告信息
                if move_instead_of_copy:
                    print("⚠️  警告: 此操作将移动图片文件!")
                    response = input("是否继续?(y/N): ")
                    if response.lower() != 'y':
                        print("操作已取消")
                        return
                
                # 初始化检测器
                detector = NudityDetector(confidence_threshold)
                
                try:
                    # 加载YOLO模型
                    print("正在加载YOLO模型...")
                    start_time = time.time()
                    model = YOLO(model_size)
                    load_time = time.time() - start_time
                    print(f"✓ 模型加载完成,耗时: {load_time:.2f}秒")
                    
                except Exception as e:
                    print(f"✗ 模型加载失败: {e}")
                    return
                
                # 统计信息
                total_images = 0
                detected_images = 0
                level_stats = {1: 0, 2: 0, 3: 0}
                processed_files = []
                detection_times = []
                
                print("\n开始扫描图片...")
                
                # 获取所有图片文件
                image_files = []
                for filename in os.listdir(source_dir):
                    file_path = os.path.join(source_dir, filename)
                    if os.path.isfile(file_path) and any(filename.lower().endswith(fmt) for fmt in supported_formats):
                        image_files.append(filename)
                
                total_files = len(image_files)
                print(f"找到 {total_files} 张待处理图片")
                
                # 处理每张图片
                for i, filename in enumerate(image_files, 1):
                    file_path = os.path.join(source_dir, filename)
                    
                    print(f"\n[{i}/{total_files}] 分析: {filename}")
                    total_images += 1
                    
                    try:
                        start_time = time.time()
                        
                        # 使用YOLO进行检测
                        results = model(file_path, conf=confidence_threshold, device=device, verbose=False)
                        detection_time = time.time() - start_time
                        detection_times.append(detection_time)
                        
                        # 检测暴露等级
                        exposure_level, max_confidence, detected_parts = detector.detect_exposure_level(results, filename)
                        
                        # 简单的肤色比例分析
                        skin_ratio = detector.analyze_skin_ratio(file_path)
                        if skin_ratio > 0.3:  # 如果肤色比例较高,提高暴露等级
                            exposure_level = max(exposure_level, 2)
                        
                        if exposure_level >= min_exposure_level:
                            detected_images += 1
                            level_stats[exposure_level] = level_stats.get(exposure_level, 0) + 1
                            
                            # 确定目标文件夹
                            target_level_folder = level_folders.get(exposure_level, target_dir)
                            
                            # 构建目标文件路径
                            target_path = os.path.join(target_level_folder, filename)
                            
                            # 处理重名文件
                            counter = 1
                            while os.path.exists(target_path):
                                name, ext = os.path.splitext(filename)
                                target_path = os.path.join(target_level_folder, f"{name}_{counter}{ext}")
                                counter += 1
                            
                            # 移动或复制文件
                            if move_instead_of_copy:
                                shutil.move(file_path, target_path)
                                operation_text = "移动"
                            else:
                                shutil.copy2(file_path, target_path)
                                operation_text = "复制"
                            
                            # 保存检测预览
                            if save_detection_preview:
                                try:
                                    preview_results = model(target_path if move_instead_of_copy else file_path, 
                                                          conf=confidence_threshold, device=device, verbose=False)
                                    plotted_image = preview_results[0].plot()
                                    preview_filename = f"L{exposure_level}_preview_{os.path.splitext(filename)[0]}.jpg"
                                    preview_path = os.path.join(preview_dir, preview_filename)
                                    cv2.imwrite(preview_path, plotted_image)
                                except Exception as e:
                                    print(f"  警告: 无法保存检测预览图: {e}")
                            
                            # 暴露等级描述
                            level_descriptions = {
                                1: "轻度暴露",
                                2: "中度暴露", 
                                3: "高度暴露"
                            }
                            
                            print(f"  ✓ 检测结果: {level_descriptions.get(exposure_level, '未知')} (等级{exposure_level})")
                            print(f"  ✓ 肤色比例: {skin_ratio:.3f}")
                            print(f"  ✓ 检测部位: {', '.join(detected_parts) if detected_parts else '无'}")
                            print(f"  ✓ 检测时间: {detection_time:.2f}秒")
                            print(f"  ✓ 已{operation_text}到: level{exposure_level}/{os.path.basename(target_path)}")
                            
                            processed_files.append({
                                'filename': filename,
                                'exposure_level': exposure_level,
                                'skin_ratio': skin_ratio,
                                'detected_parts': detected_parts,
                                'detection_time': detection_time,
                                'operation': operation_text
                            })
                            
                        else:
                            print(f"  ✗ 安全图片 (暴露等级: {exposure_level}, 检测时间: {detection_time:.2f}秒)")
                            
                    except Exception as e:
                        print(f"  ✗ 处理图片时出错: {e}")
                        continue
                
                # 输出统计信息
                print("\n" + "=" * 70)
                print("检测完成!")
                print("=" * 70)
                
                if total_images > 0:
                    print(f"总共处理图片: {total_images}张")
                    print(f"检测到敏感图片: {detected_images}张")
                    print(f"检测率: {(detected_images/total_images*100):.1f}%")
                    
                    print(f"\n暴露等级分布:")
                    for level, count in level_stats.items():
                        if count > 0:
                            level_desc = {1: "轻度暴露", 2: "中度暴露", 3: "高度暴露"}.get(level, "未知")
                            print(f"  等级{level} ({level_desc}): {count}张")
                    
                    if detected_images > 0:
                        operation_mode = "移动" if move_instead_of_copy else "复制"
                        print(f"\n敏感图片已{operation_mode}到对应等级的文件夹")
                        
                        # 显示检测结果
                        if processed_files:
                            processed_files.sort(key=lambda x: x['exposure_level'], reverse=True)
                            print(f"\n风险最高的图片:")
                            for i, file_info in enumerate(processed_files[:5]):
                                print(f"  {i+1}. {file_info['filename']} - 等级{file_info['exposure_level']} "
                                      f"(肤色比例: {file_info['skin_ratio']:.3f})")
                else:
                    print("未找到任何可处理的图片文件")
            
            def main():
                """主函数"""
                parser = argparse.ArgumentParser(description='女性人体暴露/色情图片检测程序')
                parser.add_argument('--source', '-s', default='.', 
                                   help='源文件夹路径 (默认: 当前文件夹)')
                parser.add_argument('--target', '-t', default='sensitive_images',
                                   help='目标文件夹路径 (默认: sensitive_images)')
                parser.add_argument('--confidence', '-c', type=float, default=0.3,
                                   help='检测置信度阈值 (0.0-1.0, 默认: 0.3)')
                parser.add_argument('--model', '-m', default='yolov8n.pt',
                                   choices=['yolov8n.pt', 'yolov8s.pt', 'yolov8m.pt', 'yolov8l.pt', 'yolov8x.pt'],
                                   help='YOLO模型大小 (默认: yolov8n.pt)')
                parser.add_argument('--device', '-d', default='cpu',
                                   choices=['cpu', 'cuda'],
                                   help='运行设备 (默认: cpu)')
                parser.add_argument('--preview', '-p', action='store_true',
                                   help='保存带检测框的预览图')
                parser.add_argument('--copy', action='store_true',
                                   help='使用复制模式而不是移动模式')
                parser.add_argument('--level', type=int, default=1, choices=[1, 2, 3],
                                   help='最小暴露等级 (1:轻度, 2:中度, 3:高度, 默认: 1)')
                
                args = parser.parse_args()
                
                if not os.path.exists(args.source):
                    print(f"错误: 源文件夹 '{args.source}' 不存在")
                    return
                
                extract_sensitive_images(
                    source_dir=args.source,
                    target_dir=args.target,
                    confidence_threshold=args.confidence,
                    model_size=args.model,
                    device=args.device,
                    save_detection_preview=args.preview,
                    move_instead_of_copy=not args.copy,
                    min_exposure_level=args.level
                )
            
            if __name__ == "__main__":
                import sys
                if len(sys.argv) == 1:
                    # 简化模式运行
                    source_dir = os.getcwd()
                    target_dir = os.path.join(source_dir, "sensitive_images")
                    
                    print("女性人体暴露图片检测程序")
                    print("此操作将检测并分类暴露程度的图片!")
                    
                    response = input("是否继续?(y/N): ")
                    if response.lower() == 'y':
                        extract_sensitive_images(source_dir, target_dir, move_instead_of_copy=True)
                    else:
                        print("操作已取消")
                else:
                    main()
            

            方案2:使用专门的NSFW检测模型

            如果需要更准确的结果,可以使用专门的NSFW检测模型:

            # 需要额外安装的库
            # pip install tensorflow keras
            
            def setup_nsfw_detector():
                """设置专门的NSFW检测器"""
                try:
                    # 这里可以使用预训练的NSFW检测模型
                    # 例如: https://github.com/rockyzhengwu/nsfw
                    print("正在加载NSFW检测模型...")
                    # 实际实现需要下载预训练模型
                    return None
                except ImportError:
                    print("请安装TensorFlow: pip install tensorflow")
                    return None
            

            使用方法:

            # 基本使用(移动模式,检测所有暴露等级)
            python sensitive_detector.py
            
            # 只检测高度暴露的图片
            python sensitive_detector.py --level 3
            
            # 复制模式(保留原文件)
            python sensitive_detector.py --copy
            
            # 指定源文件夹和目标文件夹
            python sensitive_detector.py --source ./图片库 --target ./敏感图片
            
            # 保存检测预览
            python sensitive_detector.py --preview
            
            # 使用更准确的模型
            python sensitive_detector.py --model yolov8s.pt
            

            暴露等级说明:

            • 等级1(轻度): 检测到人物,可能有轻微暴露
            • 等级2(中度): 检测到较多肤色区域或敏感关键词
            • 等级3(高度): 高度暴露内容,包含大量肤色区域或明确敏感内容

            注意事项:

            1. 准确率限制: 这种检测方法有一定误判率
            2. 隐私保护: 请确保合法使用,尊重他人隐私
            3. 法律合规: 在不同地区使用此类工具可能受到法律限制
            4. 人工审核: 建议最终结果由人工审核确认

            这个程序会按暴露程度将图片分类到不同的文件夹,方便你进一步处理。

            正确的运行方法:

            方法1:直接运行Python脚本

            # 进入脚本所在目录
            cd /home/leepoet/Documents/env/myenv/甜心
            
            # 直接运行Python脚本
            python 女性人体暴露.py
            

            方法2:给脚本添加执行权限后直接运行

            # 进入脚本所在目录
            cd /home/leepoet/Documents/env/myenv/甜心
            
            # 给脚本添加执行权限
            chmod +x 女性人体暴露.py
            
            # 直接运行脚本
            ./女性人体暴露.py
            

            设置自定义检测路径

            你可以通过命令行参数灵活指定源文件夹和目标文件夹:

            # 检测指定文件夹,结果保存到自定义目录
            python your_script.py --source /path/to/your/images --target /path/to/output

            # 完整参数示例
            python your_script.py --source /home/user/photos --target /home/user/sorted_photos --confidence 0.5 --model yolov8s.pt --preview --copy

            4. 关键参数说明

            • --source: 指定需要检测的图片文件夹路径
            • --target: 指定结果输出文件夹路径
            • --confidence: 调整检测灵敏度(0.1-1.0)
            • --model: 选择不同的YOLO模型(影响精度和速度)
            • --preview: 保存带检测框的预览图
            • --copy: 使用复制模式(默认是移动文件)
          7. 解决同一服务器上两个WordPress网站共用Redis导致数据混乱的问题

            解决同一服务器上两个WordPress网站共用Redis导致数据混乱的问题

            Redis采用单线程模型处理客户端请求,这种设计避免了多线程环境下的锁竞争和上下文切换开销。通过I/O多路复用技术,Redis能够高效处理大量并发连接,在内存操作的基础上实现了极高的吞吐量。通常我们在WORDPRESS时,安装“Redis Object Cache”​ 插件的同时,也必须在服务器上安装一个REDIS的服务组件。在WordPress后台的“设置”中,找到该插件的页面,点击“Enable”按钮。如果连接成功,会显示“Connected”状态。这样才算成功启用。服务器上的Redis为WordPress提供了一个高性能的内存缓存后端,而像“Redis Object Cache”这样的插件则充当了连接两者的桥梁。这种结合通过将频繁访问的数据存储在内存中,从根本上减少了数据库查询,是提升中大型WordPress网站性能和扩展性的标准且至关重要的优化手段

            如果您在同一个服务器(例如同一台VPS或云服务器)上运行了两个独立的WordPress网站,并且都为它们配置了Redis对象缓存以提升性能,那么很可能会遇到一个棘手的问题:两个网站的数据出现了“串门”。具体表现为,访问A网站却显示了B网站的文章、页面或菜单,甚至无法正常登录后台。所以同一服务器上的多个WordPress站点连接同一个Redis实例,必须进行隔离。方法就是通过设置唯一的WP_CACHE_KEY_SALT(缓存键盐)或使用不同的WP_REDIS_DATABASE编号。这相当于给每个网站的数据贴上不同的“标签”或放进不同的“房间”。

            问题根源:这个问题的根本原因在于,默认情况下,两个WordPress网站在连接Redis时,使用的缓存键(Key)前缀和数据库编号是相同的。这就好比两个租客(网站A和B)被分配到了同一个房间(Redis数据库),并且他们的物品(缓存数据)都直接堆在房间的公共区域,没有贴名字标签。当其中一位租客想找自己的东西时,很容易就错拿了另一位租客的。Redis无法区分这些数据属于哪个网站,从而导致数据混乱。下图清晰地展示了问题根源与核心解决方案:

            接下来,我们将通过三个步骤,为您的两个网站在Redis中划定清晰的“界限”,彻底解决数据混乱问题。

            解决方法

            请按照以下步骤,分别对两个WordPress网站进行操作。

            1. 设置唯一的Redis缓存前缀

            这是最关键的一步。缓存前缀就像是给每个网站的数据贴上专属“标签”,从而在Redis中区分开来。

            • 操作:​ 分别打开两个WordPress网站根目录下的 wp-config.php文件。
            • 位置:​ 在 /* 好了!请停止编辑。愉快的发布。 */这行代码之前添加。
            • 代码示例:

            第一个网站(Site A)的配置:

            // 为第一个网站设置唯一的前缀,例如 'my_site_a_'
            define('WP_CACHE_KEY_SALT', 'my_site_a_');

            第二个网站(Site B)的配置:

            // 为第二个网站设置一个不同的唯一前缀,例如 'my_site_b_'
            define('WP_CACHE_KEY_SALT', 'my_site_b_');

            注意:​ 请务必将 'my_site_a_'和 'my_site_b_'替换为您自己喜欢的、有区分度的字符串。


            2. 指定不同的Redis数据库编号

            Redis服务默认支持多个数据库(编号从0到15)。我们可以利用这一点,将两个网站的数据存放在不同的“房间”里。

            • 操作:​ 同样在各自的 wp-config.php文件中添加配置。
            • 代码示例:

            第一个网站(Site A)的配置:

            // 指定使用0号数据库
            define('WP_REDIS_DATABASE', 0);

            第二个网站(Site B)的配置:

            // 指定使用1号数据库
            define('WP_REDIS_DATABASE', 1);

            注意:​ 确保两个站点使用的数据库编号不同,且在Redis服务允许的范围内(通常是0-15)。


            3. 检查并重置Redis Object Cache插件

            在修改了配置文件后,需要让缓存插件重新加载配置,以建立新的、隔离的连接。

            1. 分别登录两个WordPress网站的后台管理界面。
            2. 进入 “插件”​ -> “已安装插件”
            3. 找到您正在使用的 “Redis Object Cache”​ 插件。
            4. 禁用该插件,然后再次启用它。
            5. 启用后,检查插件状态页面,确认显示“Connected”(已连接)。这表示插件已经根据新的配置成功连接到了Redis。

            清理旧缓存:​ 在完成上述设置后,为了彻底清除之前残留的混乱缓存数据,最好能清空一次Redis服务器。您可以在服务器命令行执行 redis-cli flushall命令(注意:此操作会清空Redis中的所有数据,如果服务器上还有其他应用在使用Redis,请谨慎操作)。完成所有配置后,请分别访问两个网站的前台和后台,检查显示和功能是否正常。在一个网站发布新内容或更新设置,确认另一个网站不会受到任何影响。

            通过以上三步操作,您就可以完美解决两个WordPress网站因共用Redis而引发的数据混乱问题,让它们既能享受Redis带来的高速体验,又能保持数据独立,互不干扰。


            优化前奏:LeePoet针对2H2G服务器性能优化LNMP+Redis配置详解

          8. 如何为你的网站挑选合适的服务器?(附VPS/云服务器/虚拟主机详解)

            如何为你的网站挑选合适的服务器?(附VPS/云服务器/虚拟主机详解)

            兄弟,是不是一看到“服务器”这三个字,就觉得那是机房里的黑盒子,是运维大神才搞得定的玩意儿?别慌!当年我哲学系毕业,转头扎进这数字世界,也是从一脸懵圈开始的。

            今天,咱不整那些虚头巴脑的概念,就像老朋友聊天一样,我来给你把这“服务器的门道”一次捋清楚。这篇文章,是我摸爬滚打多年攒下的“实战心得”。从服务器到底是个啥(它可比你想象的要简单),到VPS、云服务器、虚拟主机这些花名该怎么选(这直接关系到你的钱和网站的未来),再到怎么像回家开门一样轻松登录它、管理它——我都给你备齐了。

            我的目标就一个:让你读完就能心里有底,手上不慌,像个老手一样做出明智选择。​ 咱们这就开始,一起把你网站的“家”安顿得明明白白!

            什么是服务器?

            简单来说,服务器就是一台24小时待命的计算机,它负责存储网站文件与数据,并专门处理用户的访问请求。

            云服务器机房
            云服务器机房

            可以肯定地说,网站的正常运行完全离不开服务器。没有它,网站将无法存储任何信息,更谈不上响应你的点击和浏览。

            每台服务器都有一个唯一的IP地址,它就像是服务器的“门牌号”。

            我们虽然输入域名来访问网站,但系统会利用域名系统(DNS)将该域名解析成对应的IP地址,从而确保用户设备能准确连接到目标服务器。

            服务器的构成

            那么,服务器到底由哪些部分组成呢?

            其实,服务器就像普通电脑一样,主要由硬件和软件两大部分组成。

            核心硬件

            服务器硬件涉及的方面很多,但我们通常重点关注以下几项:

            • CPU(中央处理器):服务器的“大脑”,负责执行各种计算任务,速度极快,核心数量直接决定处理效率。
            • 内存(RAM):高速缓存,用于临时存储正在运行的程序和数据,直接影响服务器的响应速度。
            • 存储(硬盘HDD/SSD):用于持久保存操作系统、应用程序和数据。目前更常用的是速度更快的固态硬盘SSD
            • 带宽:衡量服务器数据传输能力的重要指标,影响网站访问速度和数据吞吐量。
            服务器选购界面
            服务器选购界面

            软件架构

            服务器的软件主要由操作系统和各种应用程序组成,它们协同工作,将底层硬件资源转化为对外提供的各项服务。

            操作系统

            在云服务器中,操作系统又被称为镜像。它相当于我们个人电脑上的WindowsmacOS,但它是经过预配置、专为服务器环境设计的。

            • 功能:它的主要职责是管理和调度服务器的底层资源(如CPU、内存、存储),从而为所有应用提供稳定可靠的运行环境。
            • 主流选择:常见的服务器操作系统包括Linux(如:CentOSUbuntu ServerDebianRed Hat)和Windows Server
            • 特点:这类操作系统通常非常注重安全性、稳定性,不仅有高效的网络处理能力,还具备强大的命令行和远程管理功能。

            应用程序

            服务器的应用程序是为满足不同业务需求而安装的软件。通过它们,服务器具备了特定的服务能力。

            Web 服务器

            例如ApacheNginx,它们负责接收用户的访问请求,从服务器文件系统中读取相应的网站文件(如HTMLCSSJS等),并通过网络将网页内容发送到用户的浏览器,从而实现网站的正常显示和访问。

            数据库

            例如MySQLPostgreSQLSQL Server等,它们作为数据“仓库”,负责安全、高效地存储、组织和检索网站运行所需的各种核心数据。

            常见的服务器类型

            当搭建网站时,常会遇到一些名词如VPS、云服务器和虚拟主机。对初学者来说,容易一头雾水,不知从何下手。

            简单来说,它们都是基于一台真实的物理服务器,通过技术手段将其分割成不同的“空间”,再租给不同的用户使用。

            核心区别在于:

            这些“空间”在资源分配模式、用户权限、运行速度和价格定位上存在显著差异,因此分别适用于不同的建站需求。

            虚拟主机:“合租房”

            就像你和别人合租的一套房子,你只拥有其中的一个房间。

            你只需管理好自己的房间(网站文件),无需操心大楼的公共设施(服务器维护)。

            因为所有资源(CPU、内存)都是共享的,所以一旦“邻居”的网站流量过大,你的网站性能也可能跟着受到影响。通常,一个虚拟主机账户只能运行一个网站。

            • 优势:价格最便宜,管理最简单,适合预算有限或刚入门的个人网站/博客。
            • 操作:配置简单,直接上传网站程序即可运行;文件管理可通过FTP等工具完成。

            VPS:“独享套间”

            你拥有一个独立的套间,带有你的专属IP地址。你的计算资源(CPU、内存)是独享的,完全不受其他用户的影响。

            你还拥有管理员权限,可以自由选择“装修风格”(操作系统)并安装任何软件。只要配置允许,一个VPS实例可以承载多个网站。

            • 优势:性能稳定,自主权高,性价比高,适合需要特定环境的中小型网站。
            • 操作:用户需要自行配置操作系统、环境和安全设置,因此要求具备一定的技术基础。

            云服务器:“独栋别墅”

            你租的不再是固定大小的房子,而是可以随时扩建的别墅。在业务忙碌时,你可以轻松“加盖”(弹性伸缩),立即获得更多资源。

            • 优势:高度灵活,高可用性,按需付费(用多少付多少),适合流量波动大、对稳定性有极高要求的大型企业。
            • 操作:与VPS类似,用户仍需要自行配置操作系统、环境和安全设置,因此要求具备较高的技术基础。

            通过刚才的比喻,相信您已经对三者的区别有了清晰的认识。

            如果仅从性能和稳定性的角度排序,通常是:云服务器 > VPS  > 虚拟主机

            当然,存在即合理,服务器的选择没有绝对的好坏之分,只有是否最适合您的需求。

            如何选择合适的服务器?

            服务器的选择主要取决于您的网站规模、性能需求、预算以及技术能力

            对于个人博客或小型企业展示网站,成本较低、易于管理的虚拟主机通常就足够了。

            对于开发者、运维人员等,当您需要部署多个站点并追求高性能时,VPS或云服务器会是更理想的选择。

            其他需要考虑的因素

            地理位置(加速访问):

            服务器的物理位置应靠近目标用户群,这是显著提升网站访问速度的关键。

            例如,若用户群体集中在新加坡或东南亚,服务器应相应选择部署在当地。

            配置选择(根据需求):

            • 展示型网站(预算有限):选虚拟主机即可。
            • VPS/云主机(通用推荐):建议初期选择2核4G配置,可支持多个访问量较小的网站。

            带宽选择(速度与成本):

            带宽越大,加载速度越快,但费用也越高。

            • 流量小:建议选择按流量计费。
            • 流量稳定且大:建议选择固定带宽,带宽数值越高越好。

            操作系统(稳定安全):

            优先推荐Linux,因为它在性能、稳定性和安全性方面通常优于Windows

             

            服务器性能与配置成正比。 建议按实际需求选购,避免资源浪费。

            知名服务商

            购买服务器,请务必选择信誉良好、规模较大的服务商,这是服务质量和售后保障的根本。

            常见服务商推荐

            • 国外知名厂商:HostingerSitegroundCloudwaysGoDaddy、搬瓦工、亚马逊云(AWS)等。
            • 国内主流厂商:阿里云、腾讯云等。

            外贸站点服务器服务商的选择

            搭建外贸独立站时,许多人倾向国外服务商。但如果您的业务没有特殊网络要求,建议优先考虑国内大型平台。

            这是因为国内平台更贴合您的使用习惯和时差,能确保您在遇到问题时,更快速便捷地获得技术支持和解决。

            如何挑选最具性价比的服务器?

            确定配置后,建议您多平台对比同规格产品价格,以选出最具性价比的方案。

            ✅ 稳定压倒一切

            无论选择国内还是国外,都应优先挑选大型服务商。这是因为服务器的稳定性对任何网站都至关重要。

            频繁的宕机,对用户体验和搜索引擎爬虫来说,都是极其致命的打击。

            服务器新手入门

            很多新手购买了服务器后,不知道该做什么。其实流程很简单:只要你手上有服务器的IP地址、账号和密码,就可以开始登录管理了。

            具体的登录方式取决于你的服务器安装的是Windows系统还是Linux系统(如常见的CentOS)。我们分别来看看:

            登录和管理 Windows 服务器

            Windows服务器通常使用远程桌面连接(RDP)进行图形化管理。

            你需要准备

            • 服务器的公网IP地址(可能带有端口号,如123.45.67.89:3389
            • 管理员用户名(Windows默认为Administrator
            • 管理员密码

            登录操作步骤

            打开“远程桌面连接”:在你的Windows电脑上,按下Win + R键,输入mstsc后回车,或直接在“开始”菜单搜索打开。

            输入地址并连接:在弹出的窗口中,输入服务器的公网IP地址(如果服务商提供了端口号,请一并加上)。

            点击 “连接”。
            点击 “连接”。

            登录:在登录窗口输入用户名和密码。

            为了方便下次登录,你可以选择勾选“记住我的凭据”。

            确认信息无误后,点击“确定”或“是”即可完成连接。
            确认信息无误后,点击“确定”或“是”即可完成连接。

            如果信息正确,你将看到服务器的桌面界面。现在,你可以在服务器上安装软件、配置服务或部署网站。

            登录后,你可以根据需要更改用户名和密码。

            登录管理Linux服务器(SSH命令行)

            Linux服务器主要通过 SSH (Secure Shell) 协议进行命令行管理。这意味着你将通过输入命令而非图形界面来操作服务器。

            你需要准备:

            • 服务器的 IP地址 或域名
            • SSH 端口号(默认通常是 22
            • 用户名(例如root或普通用户账户)
            • 对应的密码

            登录工具推荐:

            Windows系统上,推荐使用 Xshell 或 FinalShell 等流行的SSH客户端。它们的登录原理类似于Windows的远程桌面连接。

            FinalShell登录服务器演示

            官网:https://www.hostbuf.com/

            根据您的电脑系统,选择并下载合适的版本。

            打开 FinalShell 后,新建 SSH 连接
            打开 FinalShell 后,新建 SSH 连接
            输入服务器 IP 地址、用户名和密码
            输入服务器 IP 地址、用户名和密码
            首次连接会出现“主机密钥未注册”的安全警告。请直接选择 【接受并保存】。
            首次连接会出现“主机密钥未注册”的安全警告。请直接选择 【接受并保存】。
            登录成功后,您将看到命令行界面(Terminal),以及服务器的实时运行状态(如 CPU、内存使用情况)。
            登录成功后,您将看到命令行界面(Terminal),以及服务器的实时运行状态(如 CPU、内存使用情况)。
            通过命令行开始管理您的服务器
            通过命令行开始管理您的服务器

            常见的服务器命令行

            以下是按功能分类的最常用和最重要的Linux服务器命令:

            文件和目录管理

            ls: 列出当前目录的内容。
            ls -l: 显示详细信息(权限、所有者、大小、修改日期等)。
            ls -a: 显示所有文件,包括隐藏文件(以 . 开头的文件)。
            cd: 切换目录 (Change Directory)。
            cd /path/to/directory: 切换到指定路径。
            cd ..: 返回上一级目录。
            cd ~: 返回用户主目录。
            pwd: 显示当前工作目录的完整路径 (Print Working Directory)。
            mkdir: 创建新目录。
            mkdir mynewdir: 创建一个名为 mynewdir 的目录。
            mkdir -p /path/to/parent/newdir: 递归创建目录(如果上级目录不存在也会创建)。
            rm: 删除文件或目录。
            rm myfile.txt: 删除文件。
            rm -r mydir: 递归删除目录及其内容。
            rm -rf mydir: 强制递归删除目录及其内容(慎用,不可恢复)。
            cp: 复制文件或目录。
            cp file1.txt file2.txt: 复制文件并重命名。
            cp -r dir1 dir2: 递归复制目录。
            mv: 移动文件或目录,也可以用于重命名。
            mv file.txt /path/to/newlocation/: 移动文件到新位置。
            mv oldname.txt newname.txt: 重命名文件。
            touch: 创建空文件或更新文件的时间戳。
            touch newfile.txt: 创建一个名为 newfile.txt 的空文件。
            cat: 查看文件内容。
            cat myfile.log: 显示 myfile.log 的所有内容。
            less / more: 分页查看文件内容,适合大文件。less 功能更强大,可以向上翻页。
            less largefile.txt
            head: 查看文件开头几行(默认前10行)。
            head -n 5 myfile.txt: 查看前5行。
            tail: 查看文件末尾几行(默认后10行),常用于查看日志文件。
            tail -f /var/log/messages: 实时跟踪日志文件的新增内容。

            系统信息和网络

            df: 查看磁盘空间使用情况 (Disk Free)。
            df -h: 以人类可读的格式显示。
            du: 查看文件或目录的磁盘使用空间 (Disk Usage)。
            du -sh mydir: 显示 mydir 目录的总大小。
            free: 查看内存使用情况。
            free -h: 以人类可读的格式显示。
            uname: 显示系统信息。
            uname -a: 显示所有系统信息。
            hostname: 显示或设置主机名。
            ip addr / ifconfig: 查看和配置网络接口(ifconfig 在新版 Linux 中可能被 ip addr 取代)。
            ping: 测试网络连通性。
            ping www.baidu.com: 测试与百度的连通性。
            netstat: 显示网络连接、路由表、接口统计等(在新版 Linux 中部分功能被 ss 或 ip 命令取代)。
            netstat -tulnp: 显示所有监听的 TCP 和 UDP 端口及其对应的进程。
            ss: 更快速地查看套接字统计信息。
            ss -tulnp: 与 netstat -tulnp 类似。
            wget / curl: 从网络下载文件。
            wget https://example.com/file.zip: 下载文件。
            curl -O https://example.com/file.json: 下载文件。

            其他重要类别

            除了上述内容外,服务器管理还涉及:

            • 用户和权限管理(创建用户、设置权限,确保安全)
            • 进程管理(查看及终止运行中的程序)
            • 软件包管理(使用yum/dnfapt等工具安装、更新软件)

            新手不必试图记住所有命令及其参数。

            最有效的方法是:

            精通基础常用命令后,遇到不熟悉的细节,再随时通过搜索或查阅文档学习具体用法。

            最专业的Linux命令大全
            最专业的Linux命令大全

            在线Linux命令手册:

            https://wangchujiang.com/linux-command/index.html

             

            重要提示:执行文件删除操作时,务必做好数据备份,以避免重要数据丢失。

            服务器管理面板

            对于不熟悉命令行的用户,服务器管理面板是简化管理的理想选择。

            这类面板提供直观的可视化界面,让你无需掌握复杂的命令行,即可轻松完成Web环境配置和日常网站维护。

            以下是一些常见的服务器管理面板:

            宝塔面板

            官方:https://www.bt.cn/

            宝塔面板在国内用户群体中想必已经非常熟悉了。

            它凭借极简的安装、直观的界面和强大的功能,极大地降低了服务器管理难度,深受个人站长和小型企业青睐。

            宝塔面板
            宝塔面板

            通过宝塔面板,无论是Linux还是Windows服务器,用户都能轻松实现Web环境的一键部署,并高效地完成网站、文件、数据库等各项管理任务。

            cPanel面板

            官网:https://www.cpanel.net/

            cPanel是广受Linux用户喜爱的一款面板,它的核心优势是利用图形化界面简化复杂的服务器管理。

            所有用户都可以通过它直观地操作文件、数据库、电子邮件账户、域名等各项功能,实现快速上手。

            cPanel面板
            cPanel面板

            重要提示:cPanel是一款商业付费软件,需要授权。

            CyberPanel

            官网:https://cyberpanel.net/

            CyberPanel是一款免费开源、功能丰富的服务器管理面板,尤其适合追求高性能网站托管的用户。

            CyberPanel面板
            CyberPanel面板

            它的核心优势是默认深度集成了OpenLiteSpeed/LiteSpeed Enterprise这两款高性能Web服务器。

            同时,CyberPanel还自带缓存优化和一键WordPress部署功能,非常适合有技术背景的朋友。

            小皮面板(原 phpStudy)

            官网:https://m.xp.cn/

            说到PHP开发环境,小皮面板是一款非常优秀的工具,它免费、轻便,且支持跨平台使用(WindowsmacOSLinux)。

            小皮面板不同于宝塔、CyberPanel等面板,它的核心功能是快速搭建和管理本地PHP开发环境,非常适合新手在本地学习和练手。

            除了上述介绍的面板,常见的选择还包括1PanelPleskWebmin/VirtualminAMHWDCP等。

            最后还是要看你自己,哪个面板用起来顺手,就选哪个。

            新手建议直接用服务器管理面板。

            这样可以绕开那些复杂的命令行,让你把时间和精力放在网站的快速搭建和内容运营上。

            结语

            掌握了以上这些服务器基础知识,您就足以应对大多数中小型网站的日常需求。

            对于内容管理人员来说,服务器管理其实很简单,就是文件上传和替换。只要您选对了型号和管理面板,日常维护工作就会非常简单轻松。

            你可能感兴趣的

            •  

             

          9. 从本地部署好的it-tools项目再部署到服务器并上线

            从本地部署好的it-tools项目再部署到服务器并上线

            随着开发工作的日益复杂,高效、集成的开发工具成为提升生产力的关键。IT-Tools 作为一个开源的工具集合项目,将多种常用开发工具(如编码转换、格式化、加密解密等)聚合于一体,为开发者提供了极大的便利。

            之前在本地部署了一个it-tools,因为感觉它比较轻便,于是想部署到服务器上来。

            1.首先,在本地的it-tools目录中构建镜像文件并在本地保存镜像:

            # 在当前it-tools根目录下构建并取名为leepoet-tools
            docker build -t leepoet-tools:custom .
            
            # 打包镜像为leepoet-tools.tar
            docker save -o leepoet-tools.tar leepoet-tools:custom

            2.然后把镜像上传到服务器的/tmp目录下:

            # 使用 scp 传输(替换为你的服务器信息)
            scp leepoet-tools.tar root@你的服务器IP:/tmp/

            3.在服务器上用docker加载镜像:

            # 在服务器上加载镜像
            docker load -i /tmp/leepoet-tools.tar

            可以通过docker ps -a查看一下:

            4.在服务器上通过docker生成容器并运行:

            # 运行容器
            docker run -d -p 3000:80 --name leepoet-toolkit leepoet-tools:custom

            *注:

            这里有几个点要注意,因为it-tools镜像是一个基于 Alpine Linux​ 的 Nginx​ 镜像,并且将 /app/dist目录的内容复制到了 /usr/share/nginx/html(标准的 Nginx 静态文件目录)。目前我无法通过:

            # 假设容器内网站根目录是/usr/share/nginx/html
            # 重新运行容器并挂载目录
            docker run -d \
              -p 3000:80 \
              --name it-tools-container \
              -v /www/wwwroot/tools.leepoet.cn:/usr/share/nginx/html \
              leepoet-tools:custom
            
            或者
            
            docker run -d \
              --name it-tools \
              -p 3000:3000 \
              -v /www/wwwroot/tools.leepoet.cn:/data \
              corentinth/it-tools:latest

            这样的命令去持久化生成它。

            如果直接用这个目录/usr/share/nginx/html挂载到/www/wwwroot/tools.leepoet.cn,网站打不开。

            如果用/data目录挂载到/www/wwwroot/tools.leepoet.cn的话,网站是可以打开的。但是 /www/wwwroot/tools.leepoet.cn是空的,因为 Nginx 默认不向 /data写入数据,它只从 /usr/share/nginx/html读取静态文件。所以挂载到/data目录没有什么意义。最后我还是直接:

            docker run -d -p 3000:80 --name leepoet-toolkit leepoet-tools:custom

            生成了容器并运行起来。

            然后通过复制容器内的文件到宿主机:

            docker cp leepoet-toolkit:/usr/share/nginx/html/. /www/wwwroot/tools.leepoet.cn/

            这样的话我是想直接通过正向代理到这个目录。

            他现在读取的路径就是/www/wwwroot/tools.leepoet.cn/

            https://tools.leepoet.cn/ 然后通过BING提交验证,并通过

            为什么要这么操作?

            因为之前我是直接把上传的镜像生成在服务器的容器内部,并用域名反向代理到这个内部的容器。也就是DOCKER里的/usr/share/nginx/html这个目录。如果想做SEO验证就必须把XML的文件个人感觉还是比较麻烦。所以如果把/usr/share/nginx/html/直接复制到/www/wwwroot/tools.leepoet.cn/这里的话,再上传BING的XLM验证文件会更合适。当然这是我个人的理解与实践结果。