分类: Web

  • OpenVidu:快速集成视频通话的利器

    在当今数字化时代,实时视频通话已经成为许多应用的核心功能之一。无论是远程医疗、在线教育、客户服务,还是虚拟会议,视频通话的需求都在不断增加。今天,我要向大家介绍的是一款强大的开源平台——OpenVidu,它能帮助开发者快速且低成本地将视频通话功能集成到他们的应用中。

    什么是 OpenVidu?

    OpenVidu 是一个旨在简化视频通话集成的开源平台。它提供了一整套技术栈,方便开发者快速将实时通讯功能添加到他们的应用中。无论你是开发网页应用还是移动应用,OpenVidu 都能满足你的需求。

    主要特性

    1. WebRTC 视频会议:支持一对一、一对多以及多对多的各种组合,几乎可以实现你能想到的任何场景。
    2. 开源:OpenVidu 是一个开源项目,使用 Apache License v2 许可证,完全免费。
    3. 多平台兼容:支持 Chrome、Firefox、Safari、Opera、Edge、Android、iOS 以及桌面应用,所有这些平台都能相互兼容。
    4. 易于使用:提供即用型组件,只需简单地粘贴代码即可快速实现视频通话。如果你需要更多的自定义,OpenVidu 的 API 也是非常简单且强大的。
    5. 易于部署:支持在最流行的云服务提供商上进行快速部署,或是通过 Docker 进行本地部署,过程非常简便。

    快速入门

    开始使用 OpenVidu 非常简单。你可以参考 OpenVidu 文档 中的“Getting started”部分,了解如何安装和配置 OpenVidu。以下是一些关键步骤:

    1. 安装 OpenVidu Server:你可以选择在 AWS 上一键部署 OpenVidu,也可以使用 Docker 在本地部署。
    2. 集成前端和后端:OpenVidu 提供了多种前端技术的示例,如 JavaScript、Angular、React、Vue.js 等。后端技术则包括 Java、Node.js 以及 REST API,方便你选择适合的技术栈。

    开发你的视频应用

    OpenVidu 提供了丰富的教程和示例,帮助你快速上手。以下是一些推荐的步骤:

    1. 学习基础知识:文档中提供了“Hello World”示例,帮助你快速了解基本的 API 调用和使用方法。
    2. 探索高级功能:你可以查看“Advanced features”部分,了解如何实现录制视频、屏幕共享、音视频滤镜等高级功能。
    3. 使用现成组件:如果你希望快速实现某些功能,可以使用 OpenVidu 提供的即用型组件,如自定义 UI、自定义工具栏等。

    安全性和隐私保护

    OpenVidu 非常重视用户的隐私和安全。它通过 WebRTC 加密、服务器 API 和客户端基于角色的系统,确保所有通话内容都是完全私密的。此外,OpenVidu 还允许你限制客户端的能力,通过预定义角色来决定用户是否可以订阅、发布或管理视频流。

    适用场景

    OpenVidu 的应用场景非常广泛,包括但不限于以下几种:

    • 客户服务:集成一对一视频通话中心,提供面对面的客户服务。
    • 远程医疗:医生可以通过视频通话直接与患者进行交流,确保私密和安全。
    • 在线教育:教师可以通过视频通话向学生讲解课程,支持多名学生同时在线。
    • 会议服务:支持演讲者实时应用音视频滤镜,提高会议质量。
    • 安防系统:接收来自安防摄像头的视频流,实现监控功能。

    结语

    无论你是想开发一个简单的视频聊天应用,还是一个复杂的视频会议系统,OpenVidu 都能提供强大的支持。它不仅简化了开发过程,还提供了丰富的功能和高水平的安全性,是你开发视频通话应用的不二选择。

    更多详细信息和教程,请访问 OpenVidu 文档


    参考文献:

  • 了解 Caddy 的分布式 HTTP 缓存模块

    Caddy 是一款功能强大的网络服务器,而 caddyserver/cache-handler 模块为其提供了强大的分布式 HTTP 缓存功能。本文将带你了解这个模块的特点、基本配置以及一些高级用法。

    模块简介

    caddyserver/cache-handler 是一个基于 Souin 缓存的分布式 HTTP 缓存模块。它具备以下主要特点:

    • 遵循 RFC 7234 标准的 HTTP 缓存。
    • 设置 Cache-Status HTTP 响应头。
    • 提供 REST API 来清除缓存和列出存储的资源。
    • 支持 ESI 标签处理(使用 go-esi 包)。
    • 内置分布式缓存支持。

    基本配置

    使用最小配置,响应会被缓存 120 秒。以下是一个简单的例子:

    {
        cache
    }
    
    example.com {
        cache
        reverse_proxy your-app:8080
    }

    这个配置中,只需添加 cache 指令,所有请求的响应将被缓存 120 秒。

    全局选项语法

    全局选项允许你更细粒度地控制缓存行为。以下是一些常用的配置选项:

    {
        log {
            level debug
        }
        cache {
            allowed_http_verbs GET POST PATCH
            api {
                basepath /some-basepath
                prometheus
                souin {
                    basepath /souin-changed-endpoint-path
                }
            }
            badger {
                path the_path_to_a_file.json
            }
            cache_keys {
                .*\.css {
                    disable_body
                    disable_host
                    disable_method
                    disable_query
                    headers X-Token Authorization
                    hide
                }
            }
            cache_name Another
            cdn {
                api_key XXXX
                dynamic
                email darkweak@protonmail.com
                hostname domain.com
                network your_network
                provider fastly
                strategy soft
                service_id 123456_id
                zone_id anywhere_zone
            }
            etcd {
                configuration {
                    # Your etcd configuration here
                }
            }
            key {
                disable_body
                disable_host
                disable_method
                headers Content-Type Authorization
            }
            log_level debug
            mode bypass
            nuts {
                path /path/to/the/storage
            }
            olric {
                url url_to_your_cluster:3320
                path the_path_to_a_file.yaml
                configuration {
                    # Your olric configuration here
                }
            }
            regex {
                exclude /test2.*
            }
            stale 200s
            ttl 1000s
            default_cache_control no-store
        }
    }
    
    :4443
    respond "Hello World!"

    指令选项

    缓存指令允许你在更具体的请求路径上应用缓存策略。例如:

    @match path /path
    
    handle @match {
        cache {
            cache_name ChangeName
            cache_keys {
                (host1|host2).*\.css {
                    disable_body
                    disable_host
                    disable_method
                    disable_query
                    headers X-Token Authorization
                }
            }
            cdn {
                api_key XXXX
                dynamic
                email darkweak@protonmail.com
                hostname domain.com
                network your_network
                provider fastly
                strategy soft
                service_id 123456_id
                zone_id anywhere_zone
            }
            key {
                disable_body
                disable_host
                disable_method
                disable_query
                headers Content-Type Authorization
            }
            log_level debug
            regex {
                exclude /test2.*
            }
            stale 200s
            ttl 1000s
            default_cache_control no-store
        }
    }

    缓存提供者配置

    caddyserver/cache-handler 支持多种缓存提供者,包括 Badger、Etcd、NutsDB、Olric 和 Redis。以下是一些示例配置:

    Badger

    badger-path.com {
        cache {
            badger {
                path /tmp/badger/first-match
            }
        }
    }

    Redis

    redis-url.com {
        cache {
            redis {
                url 127.0.0.1:6379
            }
        }
    }

    结论

    caddyserver/cache-handler 模块为 Caddy 提供了强大的分布式 HTTP 缓存能力。通过灵活的配置选项和多种缓存提供者的支持,你可以根据具体需求优化网站性能。如果你正在寻找一种高效的缓存解决方案,这个模块无疑是一个值得尝试的选择。

    参考文献:GitHub – caddyserver/cache-handler: Distributed HTTP caching module for Caddy

  • Caddy 2.8 Web 服务器:重大改进与更新

    Caddy 2.8 作为一个强大的跨平台开源 Web 和反向代理服务器,已经正式发布了。这次发布带来了许多令人瞩目的改进,包括对 ACME Renewal Information (ARI) 的支持、通过 HTTP/3 代理到后端的功能以及核心依赖项的重要改进,如 ACMEz 和 CertMagic。

    主要改进概览

    证书处理方式的变化

    在之前的版本中,Caddy 自动使用 Let’s Encrypt 和 ZeroSSL 提供证书而无需配置。然而,在新版中,由于 ZeroSSL 政策的变化,用户现在必须在配置中包含电子邮件地址以继续使用 ZeroSSL。这一调整虽然与 Caddy 过去几年的推荐一致,但可能需要一些用户进行配置更新以维持当前设置。

    核心技术的升级

    开发人员对 Caddy 的底层技术进行了精细优化,包括 CertMagic 和 ACMEz 的重大升级,这对证书管理和自动化至关重要。这些变化虽然对终端用户来说可能不太明显,但对于在高流量环境中增强 Caddy 的操作效率至关重要。

    新功能与弃用功能

    Caddy 2.8 引入了多个重要的破坏性更改:

    • ZeroSSL 发行模块不再具备 ACME 能力;它需要 API 密钥才能继续使用。
    • lego_deprecated DNS 提供模块已被移除,取而代之的是更灵活、更精简的 caddy-dns 模块。
    • 一些过时的选项,如 remote_ip 匹配器中的 forwarded 选项,已被移除,推动用户采用新的配置。

    用户体验的提升

    Caddy 2.8 非常重视用户体验,许多改进直接来源于社区反馈。这些改进包括配置简化和文档更新,这些文档将在 Caddy 网站上很快更新,以反映新的变化。

    此外,发布日志中还列出了许多较小的更新,供用户查看。

    总结

    Caddy 2.8 版本不仅在功能上进行了重大更新,还在用户体验和操作效率上进行了全面提升。对于那些依赖 Caddy 进行高流量管理和自动化证书管理的用户来说,这些改进将是非常有价值的。

    更多详细更新和配置说明,请访问 Caddy 官方网站或查看发布日志。


    参考文献

    • Bobby Borisov, “Caddy 2.8 Web Server Is Here with Many Improvements,” Linuxiac, May 30, 2024. 链接
  • 为apache2 开启https

    要在Apache2上启用HTTPS,您需要完成以下步骤:

    • 安装所需的软件包:
    • 在Ubuntu上,使用以下命令安装Apache2:sudo apt install apache2
    • 在CentOS上,使用以下命令安装Apache2:sudo yum install httpd
    • 生成SSL证书和密钥:
    • 您可以使用自签名证书进行测试,或者从证书颁发机构获取有效的SSL证书。
    • 使用以下命令生成自签名证书和密钥:
      sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/apache-selfsigned.key -out /etc/ssl/certs/apache-selfsigned.crt
    • 配置Apache2以使用SSL:
    • 在Ubuntu上,使用以下命令启用SSL模块:sudo a2enmod ssl
    • 在CentOS上,SSL模块已经默认启用。
    • 创建SSL虚拟主机配置文件:
    • 在Ubuntu上,使用以下命令创建新的配置文件:sudo nano /etc/apache2/sites-available/default-ssl.conf
    • 在CentOS上,使用以下命令创建新的配置文件:sudo nano /etc/httpd/conf.d/ssl.conf
    • 将以下内容添加到配置文件中: <VirtualHost _default_:443> ServerAdmin your_email@example.com DocumentRoot /var/www/html ServerName your_domain.com SSLEngine on SSLCertificateFile /etc/ssl/certs/apache-selfsigned.crt SSLCertificateKeyFile /etc/ssl/private/apache-selfsigned.key &lt;FilesMatch "\.(cgi|shtml|phtml|php)"> SSLOptions +StdEnvVars </FilesMatch> <Directory /usr/lib/cgi-bin> SSLOptions +StdEnvVars </Directory> ErrorLog{APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost>
    • 启用SSL虚拟主机配置:
    • 在Ubuntu上,使用以下命令启用配置文件:sudo a2ensite default-ssl.conf
    • 在CentOS上,无需启用配置文件。
    • 重新加载Apache2配置:
    • 在Ubuntu上,使用以下命令重新加载配置:sudo systemctl reload apache2
    • 在CentOS上,使用以下命令重新加载配置:sudo systemctl reload httpd
    • 防火墙设置:
    • 在Ubuntu上,使用以下命令允许HTTPS流量:sudo ufw allow 'Apache Full'
    • 在CentOS上,使用以下命令允许HTTPS流量:sudo firewall-cmd --permanent --add-service=https
    • 重新加载防火墙规则:
      • 在Ubuntu上,使用以下命令重新加载防火墙:sudo ufw reload
      • 在CentOS上,使用以下命令重新加载防火墙:sudo firewall-cmd --reload

    现在,您的Apache2服务器已经配置为使用HTTPS。您可以通过访问https://your_domain.com来测试它。


    Learn more:

    1. Ubuntu下安装和配置apache2并启用https_ubuntu apache2 配置443反向代理-CSDN博客
    2. Apache配置HTTPS功能 – 温柔易淡 – 博客园
    3. 下载并在Ubuntu Apache2配置SSL证书实现网站HTTPS加密_数字证书管理服务(原SSL证书)(SSL Certificate)-阿里云帮助中心
  • 揭秘Caddy 2 + PHP-FPM 部署WordPress的卡顿谜团

    大家好,今天我将带您走进一个神秘的领域——Caddy 2 服务器与 PHP-FPM 部署的 WordPress 的卡顿现象。为什么我们辛辛苦苦搭建的网站会卡顿?让我们一探究竟。

    1. 初识Caddy 2与PHP-FPM

    首先,让我们简单介绍一下Caddy 2和PHP-FPM。Caddy 2 是一款现代化的开源Web服务器,因其自动HTTPS配置和简洁的配置文件赢得了广泛的好评。PHP-FPM(PHP FastCGI Process Manager)则是一个专为处理PHP请求优化的进程管理器,常用于提高网站性能。

    2. 卡顿现象的初步排查

    2.1 服务器资源不足

    当我们发现WordPress网站卡顿,首要怀疑的就是服务器资源不足。检查CPU使用率和内存占用情况是否过高。如果是的话,可能需要升级服务器配置。

    2.2 网络带宽问题

    网络带宽不足同样会造成网站卡顿。使用工具如 pingtraceroute 检查网络延迟和丢包率。如果网络状况不佳,可以尝试联系服务提供商解决。

    2.3 数据库性能

    WordPress的数据库性能也至关重要。使用 SHOW FULL PROCESSLIST; 命令检查MySQL数据库是否有慢查询,或者配置查询缓存来提高性能。

    3. 深入探讨Caddy 2与PHP-FPM的优化

    3.1 Caddy 2 的优化

    Caddy 2 默认的配置已经相当不错,但我们可以进一步优化:

    {
        auto_https off
        http_port 80
        https_port 443
    }
    
    example.com {
        root * /var/www/html
        php_fastcgi unix//run/php/php7.4-fpm.sock
        file_server
        encode gzip
    }
    • gzip压缩:启用gzip压缩可以减少传输数据量,提高加载速度。
    • 缓存:配置缓存以减少对服务器的压力。

    3.2 PHP-FPM 的优化

    PHP-FPM 的配置文件通常位于 /etc/php/7.4/fpm/pool.d/www.conf

    pm = dynamic
    pm.max_children = 50
    pm.start_servers = 5
    pm.min_spare_servers = 5
    pm.max_spare_servers = 35
    pm.max_requests = 500
    • pm.max_children:设置能够处理的最大并发请求数。
    • pm.max_requests:设置每个子进程在重启前处理的最大请求数,避免内存泄漏。

    4. WordPress的优化

    4.1 插件管理

    尽量减少并优化插件,特别是那些影响性能的插件。使用插件如 Query Monitor 来监控性能瓶颈。

    4.2 缓存插件

    使用缓存插件如 W3 Total Cache 或 WP Super Cache,可以显著提高页面加载速度。

    4.3 图像优化

    图像通常是页面加载时间的主要因素之一。使用插件如 Smush 来优化图像大小。

    5. CDN加速

    将静态资源(如图片、CSS、JS)托管到CDN(内容分发网络)上,可以显著提高加载速度,减轻服务器负担。

    结论

    通过以上措施,我们可以有效解决Caddy 2 + PHP-FPM 部署的WordPress网站卡顿问题。当然,实际情况可能千差万别,您需要根据具体情况进行调整和优化。希望这篇文章对您有所帮助!


    参考文献:

  • Caddy 2中配置缓存

    在Caddy 2中配置缓存可以使用cache-handler插件。以下是配置Caddy 2缓存的步骤:

    1. 安装cache-handler插件:首先,确保已安装Caddy 2,并且可以使用插件。然后,下载并安装cache-handler插件。可以从GitHub的caddyserver/cache-handler仓库中获取插件的最新版本。
    2. 配置全局缓存选项:在Caddyfile中配置全局缓存选项。这些选项将适用于所有站点。以下是一些常用的全局选项:
    • order cache before rewrite:确保缓存处理在重写之前执行。
    • cache:启用缓存处理。
    • cache.allowed_http_verbs:允许被缓存的HTTP方法,默认为GET和HEAD。
    • cache.api.basepath:缓存管理API的基本路径。
    • cache.badger.path:配置Badger缓存存储的路径。
    • cache.cache_name:覆盖在Cache-Status响应头中使用的缓存名称。
    • cache.default_cache_control:如果上游未设置Cache-Control响应头,则设置默认值。 你可以根据需要配置其他选项。完整的全局选项列表可以在cache-handler插件的文档中找到[1]
    1. 配置站点缓存选项:在Caddyfile中为每个站点配置缓存选项。以下是一个示例配置:
       example.com {
         cache
         reverse_proxy your-app:8080
       }

    在这个示例中,cache指令启用了缓存处理,并将请求代理到your-app:8080

    1. 配置缓存键:你可以使用cache_keys指令为特定的URI配置缓存键生成规则。例如:
       cache_keys {
         .+\.css {
           disable_body
           disable_host
           disable_method
           disable_query
           headers X-Token Authorization
         }
       }

    在这个示例中,所有以.css结尾的URI将使用指定的规则生成缓存键。

    1. 配置CDN管理(可选):如果你使用CDN代理请求,可以配置CDN管理选项。这些选项将与缓存处理一起使用。例如:
       cdn {
         provider fastly
         api_key XXXX
         dynamic
         email darkweak@protonmail.com
         hostname domain.com
         network your_network
         strategy soft
         service_id 123456_id
         zone_id anywhere_zone
       }

    在这个示例中,使用Fastly作为CDN提供商,并配置了相关的API密钥、邮箱、主机名等信息。

    1. 保存并重新加载Caddy配置:完成配置后,保存Caddyfile并重新加载Caddy配置,以使更改生效。

    这些是配置Caddy 2缓存的基本步骤。你可以根据需要调整和扩展这些配置选项。请参考cache-handler插件的文档以获取更多详细信息和选项[1]


    Learn more:

    1. GitHub – caddyserver/cache-handler: Distributed HTTP caching module for Caddy
    2. State of HTTP Caching in Caddy 2 – Help – Caddy Community
    3. 【Caddy2】最新Caddy2配置文件解析 – 詩 – 博客园

    darkweak/souin 是一个HTTP缓存系统,符合RFC标准,并与@tyktechnologies、@traefik、@caddyserver、@go-chi、@bnkamalesh、@beego、@devfeel、@labstack、@gofiber、@go-goyave、@go-kratos、@gin-gonic、@roadrunner-server、@zalando、@zeromicro、@nginx和@apache兼容[1]。它可以作为反向代理缓存系统,可以放置在当前反向代理(如Apache、Nginx)的顶部,也可以作为Træfik、Caddy或Tyk等喜爱的反向代理的插件使用。由于它是用Go语言编写的,可以部署在任何服务器上,并且由于具有Docker集成,可以轻松安装在Swarm或Kubernetes实例上。它支持Vary、请求合并、陈旧的Cache-Control和其他与RFC-7234相关的规范,还支持新编写的RFC(目前处于草案阶段)http-cache-groups和http-invalidation。它还支持ESI标签,感谢go-esi包的支持[1]

    Souin的配置文件存储在/anywhere/configuration.yml中,可以根据需要提供自己的配置文件。配置文件中包含了各种配置选项,如缓存时间、反向代理URL、API配置、缓存键配置等[1]

    Souin还提供了各种插件,如Beego过滤器、Caddy模块、Chi中间件、Dotweb中间件、Echo中间件、Fiber中间件、Gin中间件、Goa中间件、Go-zero中间件、Goyave中间件、Hertz中间件、Kratos过滤器、Roadrunner中间件、Skipper过滤器、Træfik插件、Tyk插件、Webgo中间件、Prestashop插件和Wordpress插件等[1]

    Souin还支持Prometheus API、Souin API和Security API,可以用于监控和管理缓存系统[1]

    总结:darkweak/souin是一个符合RFC标准的HTTP缓存系统,可以作为反向代理缓存系统使用,也可以作为插件嵌入到其他反向代理中。它支持各种配置选项和插件,并提供了API用于监控和管理缓存系统。


    Learn more:

    1. GitHub – darkweak/souin: An HTTP cache system, RFC compliant, compatible with @tyktechnologies, @traefik, @caddyserver, @go-chi, @bnkamalesh, @beego, @devfeel, @labstack, @gofiber, @go-goyave, @go-kratos, @gin-gonic, @roadrunner-server, @zalando, @zeromicro, @nginx and @apache
    2. darkweak · GitHub
    3. Releases · darkweak/souin · GitHub
  • 提高MySQL性能:修改事务隔离级别的最佳实践

    在实际生产环境中,合理选择和调整MySQL的事务隔离级别可以显著提升系统的性能。然而,事务隔离级别的调整需要结合实际业务需求和系统的并发访问情况,因此需要对其特点及适用场景有充分的了解。本文将详细介绍如何通过修改MySQL的事务隔离级别来提高性能,并提供相关的操作方法。

    了解事务隔离级别的特点和适用场景

    MySQL定义了四种常见的事务隔离级别:

    1. 读未提交(Read Uncommitted):允许一个事务读取另一个事务未提交的数据,可能导致脏读问题。不推荐在生产环境中使用。
    2. 读提交(Read Committed):一个事务只能读取已经提交的数据,避免了脏读问题,但可能导致不可重复读问题。适用于大多数场景。
    3. 可重复读(Repeatable Read):一个事务在执行期间多次读取同一数据时,能够保证读取到的结果一致,避免了脏读和不可重复读问题,但可能存在幻读问题。是InnoDB的默认隔离级别。
    4. 串行化(Serializable):最高的隔离级别,强制事务串行执行,避免了脏读、不可重复读和幻读问题,但降低了并发性能。

    评估当前系统的性能瓶颈

    在修改事务隔离级别之前,需要先评估当前系统的性能瓶颈。通过性能监控工具(如MySQL的Explain、Slow Query Log等)来分析系统的性能问题,确定是否由于事务隔离级别不当导致性能瓶颈。

    修改事务隔离级别的方法

    临时修改

    可以在当前会话中临时修改事务隔离级别,使用以下命令:

    SET SESSION TRANSACTION ISOLATION LEVEL <isolation_level>;

    例如,将隔离级别设置为读提交:

    SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

    全局修改

    全局修改事务隔离级别,需要重启MySQL服务才能生效,使用以下命令:

    SET GLOBAL TRANSACTION ISOLATION LEVEL <isolation_level>;

    例如,将隔离级别设置为可重复读:

    SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

    选择合适的事务隔离级别

    根据实际业务需求和并发访问情况选择合适的事务隔离级别:

    • 读提交(Read Committed):适用于大多数场景,可以避免脏读问题。
    • 可重复读(Repeatable Read):适用于需要保证数据一致性,但不介意幻读问题的场景。
    • 串行化(Serializable):适用于需要最高数据一致性的场景,但要注意可能导致的并发性能问题。

    监控和调优

    在修改事务隔离级别后,需要进行性能监控和调优,确保系统的性能得到提升。可以使用MySQL的性能监控工具,如Explain、Slow Query Log等,来分析和优化查询语句的性能。

    参考资料

    1. MySQL事务隔离级别详解及默认设置_mysql默认事务隔离级别-CSDN博客
    2. MySQL事务隔离级别的最佳实践(上)-阿里云开发者社区
    3. MySQL:修改事务隔离级别和常用语句 – 划水的鱼dm – 博客园

    通过合理选择和调整事务隔离级别,可以在保证数据一致性的同时,提高系统的并发性能。但需要注意的是,事务隔离级别的调整应该根据实际业务需求和并发访问情况进行权衡,以确保系统的稳定和高效运行。


    要查询当前MySQL全局事务隔离级别,可以使用以下SQL命令:

    SELECT @@GLOBAL.transaction_isolation;

    这个命令会返回当前MySQL实例的全局事务隔离级别。以下是一个示例输出:

    mysql> SELECT @@GLOBAL.transaction_isolation;
    +------------------------------+
    | @@GLOBAL.transaction_isolation |
    +------------------------------+
    | REPEATABLE-READ              |
    +------------------------------+
    1 row in set (0.00 sec)

    在这个例子中,MySQL的全局事务隔离级别是“REPEATABLE-READ”。

    另外,你也可以查询当前会话的事务隔离级别,使用以下命令:

    SELECT @@SESSION.transaction_isolation;

    这个命令会返回当前会话的事务隔离级别。

    mysql> SELECT @@SESSION.transaction_isolation;
    +-------------------------------+
    | @@SESSION.transaction_isolation |
    +-------------------------------+
    | REPEATABLE-READ               |
    +-------------------------------+
    1 row in set (0.00 sec)

    在这个例子中,当前会话的事务隔离级别也是“REPEATABLE-READ”。

    通过这些命令,你可以方便地查看MySQL的事务隔离级别设置,以便进行相应的调整和优化。


    什么是幻读问题?

    幻读(Phantom Read)是指在一个事务中,多次执行同一个查询时,结果集的行数可能不一致。具体来说,当一个事务在查询某一范围的数据时,另一个事务在该范围内插入了新的数据,导致前一个事务再次查询时,结果集中出现了“幻影”数据。这种现象通常发生在并发事务处理中,是一种并发一致性问题。

    幻读问题的示例

    假设有一个表 heros_temp,记录了许多英雄的信息。以下是一个简单的示例来说明幻读问题:

    1. 事务A:开始一个事务并查询所有英雄: BEGIN; SELECT * FROM heros_temp WHERE id BETWEEN 1 AND 20; 此时,假设查询结果包含10条记录。
    2. 事务B:在 heros_temp 表中插入一个新的英雄记录,该记录的 id 在事务A查询的范围内: BEGIN; INSERT INTO heros_temp (id, name) VALUES (15, '吕布'); COMMIT;
    3. 事务A:再次执行相同的查询:
      sql SELECT * FROM heros_temp WHERE id BETWEEN 1 AND 20;
      此时,查询结果会包含11条记录,因为事务B插入的新记录也被包含进来。这就产生了幻读现象。

    InnoDB 如何解决幻读问题

    InnoDB存储引擎默认的事务隔离级别是 可重复读(Repeatable Read)。虽然可重复读能够避免脏读(Dirty Read)和不可重复读(Non-repeatable Read)问题,但它不能完全避免幻读问题。

    使用Next-Key Locks

    InnoDB通过使用一种称为 Next-Key Locks 的锁机制来部分解决幻读问题。Next-Key Locks结合了行锁和间隙锁,锁定了索引记录以及索引记录之间的间隙,从而阻止其他事务在锁定范围内插入新记录。

    例如,在上述示例中,当事务A执行查询时,InnoDB会锁定 id 为1到20之间的所有记录,以及这些记录之间的间隙。这意味着在事务A提交之前,事务B无法在 id 为1到20之间插入新的记录,从而避免幻读问题。

    完全避免幻读

    要完全避免幻读,可以将事务隔离级别设置为 串行化(Serializable)。在串行化隔离级别下,事务将按照顺序执行,确保事务之间完全隔离,从而避免所有并发一致性问题,包括幻读。

    SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

    然而,串行化隔离级别会显著降低系统的并发性能,因此需要根据实际业务需求进行权衡。

    总结

    幻读是并发事务处理中常见的一种一致性问题,指的是一个事务在多次执行相同查询时,结果集中出现了其他事务新插入的记录。InnoDB通过使用Next-Key Locks部分解决了幻读问题,但要完全避免幻读,需要将事务隔离级别设置为串行化。选择合适的事务隔离级别,需要在性能和数据一致性之间进行权衡。


  • 探索 ActivityPub:去中心化社交网络协议

    引言

    随着互联网的不断发展,去中心化的社交网络逐渐成为热点话题。ActivityPub 正是在这种背景下应运而生的,它是一种去中心化的社交网络协议,基于 ActivityStreams 2.0 数据格式。本文将详细介绍 ActivityPub 的工作原理及其实现方式。

    什么是 ActivityPub?

    ActivityPub 是一种去中心化的社交网络协议,旨在通过客户端到服务器和服务器到服务器的 API 实现内容的创建、更新和删除,以及通知和内容的分发。它的主要目标是让去中心化的网站能够共享信息,并让用户通过各种客户端与自己的账户进行互动。

    ActivityPub 的两层协议

    服务器到服务器的联邦协议

    这种联邦协议使得不同的去中心化网站能够共享信息。例如,当一个用户在一个服务器上发布内容时,其他服务器上的用户也能够看到并互动。

    客户端到服务器的协议

    这种协议允许用户(包括真实用户、机器人和其他自动化进程)通过各种客户端(如手机、桌面应用或 Web 应用)与他们在服务器上的账户进行通信。

    用户和演员

    在 ActivityPub 中,用户通过服务器上的账户表示为“演员”。每个演员都有一个收件箱和一个发件箱,用于接收和发送消息。这些都是通过 URL 进行标识的。

    示例

    {
      "@context": "https://www.w3.org/ns/activitystreams",
      "type": "Person",
      "id": "https://social.example/alyssa/",
      "name": "Alyssa P. Hacker",
      "preferredUsername": "alyssa",
      "summary": "Lisp enthusiast hailing from MIT",
      "inbox": "https://social.example/alyssa/inbox/",
      "outbox": "https://social.example/alyssa/outbox/",
      "followers": "https://social.example/alyssa/followers/",
      "following": "https://social.example/alyssa/following/",
      "liked": "https://social.example/alyssa/liked/"
    }

    如何发送和接收消息?

    发送消息

    • POST 到收件箱:将消息发送到某人的收件箱(仅适用于服务器到服务器的通信)。
    • POST 到发件箱:将消息发送到全世界(客户端到服务器)。
    • GET 从发件箱:查看某人发送的消息(客户端到服务器和/或服务器到服务器)。

    接收消息

    • GET 从收件箱:查看最新收到的消息(客户端到服务器)。

    一个完整的示例

    假设 Alyssa 想给她的朋友 Ben 发送一条消息,询问他是否还记得归还一本借来的书。她可以创建一个 ActivityStreams 对象并将其发送到她的发件箱。

    {
      "@context": "https://www.w3.org/ns/activitystreams",
      "type": "Note",
      "to": ["https://chatty.example/ben/"],
      "attributedTo": "https://social.example/alyssa/",
      "content": "Say, did you finish reading that book I lent you?"
    }

    服务器会将这条消息包装在一个 Create 活动中,并发送到 Ben 的收件箱。

    {
      "@context": "https://www.w3.org/ns/activitystreams",
      "type": "Create",
      "id": "https://social.example/alyssa/posts/a29a6843-9feb-4c74-a7f7-081b9c9201d3",
      "to": ["https://chatty.example/ben/"],
      "actor": "https://social.example/alyssa/",
      "object": {
        "type": "Note",
        "id": "https://social.example/alyssa/posts/49e2d03d-b53a-4c4c-a95c-94a6abf45a19",
        "attributedTo": "https://social.example/alyssa/",
        "to": ["https://chatty.example/ben/"],
        "content": "Say, did you finish reading that book I lent you?"
      }
    }

    安全性和验证

    在 ActivityPub 中,服务器应该验证收到的内容,以防止内容欺骗攻击。例如,当服务器接收到一个 Like 活动时,应该验证该活动的 id 是否存在并且是一个有效对象。

    结论

    ActivityPub 通过提供灵活的客户端到服务器和服务器到服务器的协议,使得创建、更新和分发内容变得更加简单和高效。它的去中心化特性也为社交网络带来了更多的自由和可能性。如果您对构建去中心化社交网络感兴趣,ActivityPub 将是一个非常有价值的工具。

    希望这篇文章能帮助您更好地理解 ActivityPub 及其在去中心化社交网络中的重要作用。

  • WordPress插件的安全性

    在WordPress插件开发的过程中,安全性是一个至关重要的方面。插件代码可能会在数百万个WordPress站点上运行,因此确保代码的安全性是开发者不可忽视的责任。在《WordPress插件开发教程手册》的插件安全部分中,我们可以学习到如何检查用户能力、验证输入和清理输出等安全措施。

    检查用户能力

    为了确保插件的安全性,首先需要检查用户的能力。如果插件允许用户提交数据(无论是管理员还是游客),一定要检查用户权限。WordPress以用户角色和能力的形式提供了这个能力系统。比如,管理员角色会拥有“manage_options”的能力,这让他们有权限查看、编辑、保存网站选项,而其他角色则没有这个权限。

    示例代码

    以下代码在前端创建了一个删除文章的链接,但没有检查用户能力,导致所有访问网站的用户都可以使用这个链接删除文章:

    function wporg_generate_delete_link(content) {     if (is_single() && in_the_loop() && is_main_query()) {url = add_query_arg(
                [
                    'action' => 'wporg_frontend_delete',
                    'post'   => get_the_ID(),
                ],
                home_url()
            );
            return content . ' <a href=' . esc_url(url) . '>' . esc_html__('Delete Post', 'wporg') . '</a>';
        }
        return null;
    }
    
    function wporg_delete_post()
    {
        if (isset(_GET['action']) &&_GET['action'] === 'wporg_frontend_delete') {
            post_id = (isset(_GET['post'])) ? (_GET['post']) : (null);post = get_post((int)post_id);         if (empty(post)) {
                return;
            }
            wp_trash_post(post_id);redirect = admin_url('edit.php');
            wp_safe_redirect($redirect);
            die;
        }
    }
    
    add_filter('the_content', 'wporg_generate_delete_link');
    add_action('init', 'wporg_delete_post');

    为了确保只有拥有编辑能力的用户可以看到这个链接并删除文章,我们需要在显示链接时检查用户是否拥有“edit_others_posts”能力:

    if (current_user_can('edit_others_posts')) {
        add_filter('the_content', 'wporg_generate_delete_link');
        add_action('init', 'wporg_delete_post');
    }

    数据验证

    数据验证是确保输入数据符合预期的一项重要措施。它可以防止恶意数据的输入和潜在的安全漏洞。数据验证应尽早执行,可以在前端使用JavaScript进行初步验证,但绝不能代替后端的PHP验证。

    常见的验证方法

    1. 必填字段检查: 确保输入的必填字段不为空。
    2. 格式检查: 确保输入的数据符合预期的格式,比如电话号码、邮编、Email等。
    3. 范围检查: 确保输入数据在预期的范围内,比如数量字段要大于0。

    在WordPress中,我们可以使用多种方法来验证数据,包括PHP内置函数、WordPress核心函数以及自定义函数。以下是一些常用的PHP内置函数:

    • isset()empty():检查变量是否存在或为空。
    • mb_strlen()strlen():检查字符串长度。
    • preg_match():检查字符串是否匹配指定的正则表达式。

    通过这些方法,我们可以有效地确保数据的安全性,防止潜在的安全漏洞。

    结论

    确保WordPress插件的安全性不仅是对用户负责,也是对自己的代码负责。通过检查用户能力和验证输入数据,可以大大减少安全风险,提升插件的可靠性和安全性。希望每一位开发者都能在开发过程中重视安全性,为用户提供安全可靠的插件。

  • WordPress 插件开发

    学习 WordPress 插件开发是一个非常有益且实用的技能,可以帮助你扩展和定制 WordPress 网站的功能。以下是一个详细的指南,帮助你从零开始学习 WordPress 插件开发。

    1. 基础知识准备

    在开始插件开发之前,你需要具备一些基本知识:

    • HTML/CSS:了解基本的网页结构和样式。
    • JavaScript:掌握基本的编程逻辑和DOM操作。
    • PHP:WordPress 插件主要使用 PHP 编写,需要了解 PHP 的基本语法和功能。
    • MySQL:了解基本的数据库操作,因为 WordPress 使用 MySQL 作为数据库。

    2. 设置开发环境

    为插件开发设置一个合适的开发环境:

    • 本地服务器:使用工具如 XAMPP、MAMP 或 Local by Flywheel 来设置一个本地开发服务器。
    • 文本编辑器或 IDE:选择一个代码编辑器,如 Visual Studio Code、Sublime Text 或 PHPStorm。
    • WordPress 安装:在本地服务器上安装最新版本的 WordPress。

    3. 了解 WordPress 插件结构

    一个简单的 WordPress 插件通常包含以下文件和文件夹:

    • 插件主文件:通常命名为plugin-name.php,包含插件的主要功能代码。
    • README 文件:包含插件的基本信息和说明。
    • 其他文件:如 CSS、JavaScript、图片等资源文件。

    4. 创建第一个插件

    下面是创建一个简单插件的步骤:

    4.1 创建插件目录和主文件

    wp-content/plugins 目录下创建一个新文件夹,例如 my-first-plugin。在该文件夹中创建一个 PHP 文件,例如 my-first-plugin.php

    4.2 插件头部信息

    my-first-plugin.php 文件中添加插件的头部信息:

    <?php
    /*
    Plugin Name: My First Plugin
    Plugin URI: https://example.com/my-first-plugin
    Description: This is my first WordPress plugin.
    Version: 1.0
    Author: Your Name
    Author URI: https://example.com
    License: GPL2
    */

    4.3 插件的基本功能

    添加一些基本功能,如在 WordPress 后台显示一个简单的信息:

    function my_first_plugin_admin_notice() {
        echo '<div class="notice notice-success is-dismissible">
            <p>My First Plugin is activated!</p>
        </div>';
    }
    add_action('admin_notices', 'my_first_plugin_admin_notice');

    5. 学习 WordPress 钩子(Hooks)

    WordPress 插件开发的核心是钩子(Hooks),它包括动作钩子(Action Hooks)和过滤器钩子(Filter Hooks)。通过钩子,你可以在特定的时间点执行代码:

    • 动作钩子(Actions):允许你在 WordPress 的执行过程中插入自定义代码。
    • 过滤器钩子(Filters):允许你修改 WordPress 数据或输出。

    例子:

    // 动作钩子
    add_action('wp_footer', 'my_first_plugin_footer_message');
    function my_first_plugin_footer_message() {
        echo '<p>Thank you for visiting my website!</p>';
    }
    
    // 过滤器钩子
    add_filter('the_content', 'my_first_plugin_content_filter');
    function my_first_plugin_content_filter(content) {     returncontent . '<p>Custom content added by My First Plugin.</p>';
    }

    6. 学习 WordPress 插件 API

    WordPress 提供了丰富的 API 供插件开发者使用:

    • Settings API:管理插件设置页面。
    • Shortcode API:创建短代码。
    • Widgets API:创建自定义小工具。
    • REST API:创建自定义 REST 端点。

    7. 学习插件国际化和本地化

    为了使你的插件可以被翻译成不同的语言,了解如何国际化和本地化插件是很重要的:

    // 加载插件文本域
    function my_first_plugin_load_textdomain() {
        load_plugin_textdomain( 'my-first-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); 
    }
    add_action('plugins_loaded', 'my_first_plugin_load_textdomain');

    8. 探索插件开发资源

    • 官方文档:阅读 WordPress 插件开发手册
    • 教程和书籍:查找相关的在线教程和书籍,如《Professional WordPress Plugin Development》。

    9. 配置插件设置页面

    为插件添加一个设置页面是常见的需求。以下是如何使用 Settings API 创建一个简单的插件设置页面的步骤:

    9.1 添加菜单项

    首先,需要在 WordPress 管理菜单中添加一个新的菜单项:

    function my_first_plugin_menu() {
        add_menu_page(
            'My First Plugin Settings', // 页面标题
            'My First Plugin', // 菜单标题
            'manage_options', // 用户权限
            'my-first-plugin', // 菜单别名
            'my_first_plugin_settings_page', // 回调函数
            'dashicons-admin-generic' // 图标
        );
    }
    add_action('admin_menu', 'my_first_plugin_menu');

    9.2 创建设置页面

    在回调函数 my_first_plugin_settings_page 中定义设置页面的内容:

    function my_first_plugin_settings_page() {
        ?>
        <div class="wrap">
            <h1>My First Plugin Settings</h1>
            <form method="post" action="options.php">
                <?php
                settings_fields('my_first_plugin_options_group');
                do_settings_sections('my-first-plugin');
                submit_button();
                ?>
            </form>
        </div>
        <?php
    }

    9.3 注册设置

    使用 Settings API 注册插件设置:

    function my_first_plugin_settings_init() {
        register_setting('my_first_plugin_options_group', 'my_first_plugin_option_name');
    
        add_settings_section(
            'my_first_plugin_settings_section', // ID
            'My First Plugin Settings', // 标题
            'my_first_plugin_settings_section_callback', // 回调函数
            'my-first-plugin' // 页面
        );
    
        add_settings_field(
            'my_first_plugin_field', // ID
            'Sample Field', // 标签
            'my_first_plugin_field_callback', // 回调函数
            'my-first-plugin', // 页面
            'my_first_plugin_settings_section' // 部分
        );
    }
    add_action('admin_init', 'my_first_plugin_settings_init');
    
    function my_first_plugin_settings_section_callback() {
        echo 'Enter your settings below:';
    }
    
    function my_first_plugin_field_callback() {
        option = get_option('my_first_plugin_option_name');     echo '<input type="text" name="my_first_plugin_option_name" value="' . esc_attr(option) . '" />';
    }

    10. 创建短代码

    短代码允许用户在文章和页面中插入自定义内容。以下是如何创建一个简单的短代码:

    function my_first_plugin_shortcode(atts) {atts = shortcode_atts(
            array(
                'text' => 'Hello, World!',
            ),
            atts,         'my_shortcode'     );      return '<div class="my-shortcode">' . esc_html(atts['text']) . '</div>';
    }
    add_shortcode('my_shortcode', 'my_first_plugin_shortcode');

    11. 创建小工具

    小工具可以在 WordPress 侧边栏等小工具区域显示内容。以下是如何创建一个简单的小工具:

    “`php
    class My_First_Plugin_Widget extends WP_Widget {
    function construct() { parent::_construct( ‘my_first_plugin_widget’, // ID (‘My First Plugin Widget’, ‘text_domain’), // 名称
    array(‘description’ => _
    (‘A simple widget’, ‘text_domain’)) // 描述
    );
    }

    public function widget(args,instance) {
        echo args['before_widget'];     echoargs['before_title'] . apply_filters('widget_title', instance['title']) .args['after_title'];
        echo '<p>' . esc_html(instance['text']) . '';     echoargs['after_widget'];
    }
    
    public function form(instance) {title = !empty(instance['title']) ?instance['title'] : __('New title', 'text_domain');
        text = !empty(instance['text']) ? instance['text'] : __('Hello, World!', 'text_domain');     ?>              <label for="<?php echo esc_attr(this->get_field_id('title')); ?>"><?php _e('Title:'); ?></label>
            <input class="widefat" id="<?php echo esc_attr(this->get_field_id('title')); ?>" name="<?php echo esc_attr(this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr(title); ?>">                   <label for="<?php echo esc_attr(this->get_field_id('text')); ?>"><?php _e('Text:'); ?></label>
    
    

    11. 创建小工具(续)

    11.1 完成小工具的表单和更新方法

    在上面的代码基础上,继续完成小工具的表单和更新方法:

            <textarea class="widefat" id="<?php echo esc_attr(this->get_field_id('text')); ?>" name="<?php echo esc_attr(this->get_field_name('text')); ?>"><?php echo esc_attr(text); ?></textarea>                  <?php     }      public function update(new_instance, old_instance) {instance = array();
            instance['title'] = (!empty(new_instance['title'])) ? strip_tags(new_instance['title']) : '';instance['text'] = (!empty(new_instance['text'])) ? strip_tags(new_instance['text']) : '';
    
            return instance;     } }  // 注册小工具 function register_my_first_plugin_widget() {     register_widget('My_First_Plugin_Widget'); } add_action('widgets_init', 'register_my_first_plugin_widget');</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">12. 使用 WordPress REST API</h3> <!-- /wp:heading -->  <!-- wp:paragraph --> WordPress REST API 允许你创建和访问自定义端点,从而使你的插件能够与外部应用程序进行通信。 <!-- /wp:paragraph -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">12.1 创建自定义 REST 端点</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 以下是如何创建一个简单的自定义 REST 端点: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>function my_first_plugin_register_routes() {     register_rest_route('my-first-plugin/v1', '/data/', array(         'methods' => 'GET',         'callback' => 'my_first_plugin_get_data',     )); } add_action('rest_api_init', 'my_first_plugin_register_routes');  function my_first_plugin_get_data(request) {
        return new WP_REST_Response(array('message' => 'Hello, World!'), 200);
    }

    13. 安全性和最佳实践

    确保你的插件是安全的,并遵循最佳实践:

    13.1 数据验证和清理

    在处理用户输入时,确保对数据进行验证和清理:

    // 验证和清理输入数据
    function my_first_plugin_validate_data(data) {     return sanitize_text_field(data);
    }

    13.2 使用非ces (Nonces) 进行安全验证

    在处理表单提交时,使用非ces 来防止跨站请求伪造 (CSRF) 攻击:

    // 创建一个 nonce
    function my_first_plugin_create_nonce() {
        return wp_create_nonce('my_first_plugin_nonce_action');
    }
    
    // 验证 nonce
    function my_first_plugin_verify_nonce(nonce) {     return wp_verify_nonce(nonce, 'my_first_plugin_nonce_action');
    }

    13.3 避免直接文件访问

    在插件的每个文件顶部添加以下代码,防止直接访问:

    if (!defined('ABSPATH')) {
        exit; // 退出如果直接访问
    }

    14. 插件国际化和本地化(续)

    确保你的插件可以被翻译成不同的语言:

    14.1 创建语言文件

    在插件目录下创建一个 languages 文件夹,并使用 .pot 文件保存插件的翻译字符串。例如,创建一个 my-first-plugin.pot 文件。

    14.2 加载语言文件

    在插件初始化时加载语言文件:

    function my_first_plugin_load_textdomain() {
        load_plugin_textdomain('my-first-plugin', false, dirname(plugin_basename(__FILE__)) . '/languages');
    }
    add_action('plugins_loaded', 'my_first_plugin_load_textdomain');

    15. 发布和分发插件

    当你完成插件开发后,可以考虑将插件发布到 WordPress 插件目录中。

    15.1 准备插件

    确保你的插件包含以下文件:

    • 主插件文件:包含插件头部信息和主要功能代码。
    • README 文件:详细描述插件的功能、安装方法和使用说明。
    • 语言文件:包含 .pot 文件和翻译文件。
    • 其他资源文件:如 CSS、JavaScript、图片等资源文件。

    15.2 创建 README 文件

    使用 Markdown 编写 README 文件,并确保它包含以下部分:

    • Plugin Name: 插件名称
    • Description: 插件描述
    • Installation: 安装说明
    • Frequently Asked Questions: 常见问题解答
    • Changelog: 更新日志

    15.3 提交插件

    访问 WordPress 插件提交页面 并按照指南提交你的插件。一旦审核通过,你的插件将出现在 WordPress 插件目录中。

    16. 进一步学习资源

    要深入学习 WordPress 插件开发,以下资源非常有用:

    16.1 官方文档

    16.2 在线课程和教程

    • Udemy: 提供了许多关于 WordPress 插件开发的在线课程。
    • Lynda/LinkedIn Learning: 提供了高质量的 WordPress 开发视频教程。

    16.3 开发者社区

    16.4 博客和文章

    • Tuts+: 提供了大量关于 WordPress 开发的文章和教程。
    • Smashing Magazine: 定期发布关于 WordPress 的开发技巧和最佳实践的文章。

    17. 高级插件开发技巧

    17.1 面向对象编程 (OOP)

    使用面向对象编程风格可以使你的插件代码更具可维护性和可扩展性。以下是一个简单的 OOP 插件示例:

    class My_First_Plugin {
        public function __construct() {
            add_action('init', array(this, 'init'));     }      public function init() {         // 初始化代码     }      public function register_post_type() {args = array(
                'public' => true,
                'label'  => 'Custom Post',
            );
            register_post_type('custom_post', args);     } }my_first_plugin = new My_First_Plugin();

    17.2 使用命名空间

    使用命名空间可以避免类名和函数名冲突:

    namespace MyFirstPlugin;
    
    class Main {
        public function __construct() {
            add_action('init', array(this, 'init'));     }      public function init() {         // 初始化代码     }      public function register_post_type() {args = array(
                'public' => true,
                'label'  => 'Custom Post',
            );
            register_post_type('custom_post', args);     } }main = new Main();

    17.3 自动加载

    使用自动加载器可以简化类文件的加载:

    spl_autoload_register(function (class) {prefix = 'MyFirstPlugin\\';
        base_dir = __DIR__ . '/inc/';len = strlen(prefix);     if (strncmp(prefix, class,len) !== 0) {
            return;
        }
    
        relative_class = substr(class, len);file = base_dir . str_replace('\\', '/',relative_class) . '.php';
    
        if (file_exists(file)) {         requirefile;
        }
    });

    17.4 单例模式

    使用单例模式确保插件的主类只实例化一次:

    class My_First_Plugin {
        private static instance = null;      private function __construct() {         add_action('init', array(this, 'init'));
        }
    
        public static function get_instance() {
            if (self::instance == null) {             self::instance = new self();
            }
            return self::instance;     }      public function init() {         // 初始化代码     } }  My_First_Plugin::get_instance();</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">18. 性能优化</h3> <!-- /wp:heading -->  <!-- wp:paragraph --> 确保你的插件不会对网站性能产生负面影响: <!-- /wp:paragraph -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">18.1 使用缓存</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 利用 WordPress 内置的缓存功能: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>function get_cached_data() {cache_key = 'my_first_plugin_cached_data';
        data = wp_cache_get(cache_key);
    
        if (data === false) {         // 获取数据data = '...';
            wp_cache_set(cache_key,data, '', 3600);
        }
    
        return data; }</code></pre> <!-- /wp:code -->  <!-- wp:paragraph -->  <!-- /wp:paragraph -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">19. 数据库操作</h3> <!-- /wp:heading -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">19.1 创建自定义数据库表</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 有时候,插件需要创建自定义数据库表来存储特定的数据。以下是创建自定义表的示例: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>function my_first_plugin_create_table() {     globalwpdb;
        table_name =wpdb->prefix . 'my_first_plugin_table';
    
        charset_collate =wpdb->get_charset_collate();
    
        sql = "CREATE TABLEtable_name (
            id mediumint(9) NOT NULL AUTO_INCREMENT,
            name tinytext NOT NULL,
            email text NOT NULL,
            date datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
            PRIMARY KEY  (id)
        ) charset_collate;";      require_once(ABSPATH . 'wp-admin/includes/upgrade.php');     dbDelta(sql);
    }
    
    register_activation_hook(__FILE__, 'my_first_plugin_create_table');

    19.2 插入数据

    function my_first_plugin_insert_data(name,email) {
        global wpdb;table_name = wpdb->prefix . 'my_first_plugin_table';wpdb->insert(
            table_name,         array(             'name' =>name,
                'email' => email,             'date' => current_time('mysql'),         )     ); }</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">19.3 获取数据</h4> <!-- /wp:heading -->  <!-- wp:code --> <pre class="wp-block-code"><code>function my_first_plugin_get_data() {     globalwpdb;
        table_name =wpdb->prefix . 'my_first_plugin_table';
    
        results =wpdb->get_results("SELECT * FROM table_name", ARRAY_A);     returnresults;
    }

    20. AJAX 操作

    使用 AJAX 可以在不刷新页面的情况下与服务器进行交互。

    20.1 在前端调用 AJAX

    jQuery(document).ready(function() {('#my-button').click(function() {
            .ajax({             url: my_first_plugin_ajax_object.ajax_url,             type: 'POST',             data: {                 action: 'my_first_plugin_action',                 data: 'some_data'             },             success: function(response) {                 console.log(response);             }         });     }); });</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">20.2 在后端处理 AJAX 请求</h4> <!-- /wp:heading -->  <!-- wp:code --> <pre class="wp-block-code"><code>function my_first_plugin_ajax_handler() {data = _POST['data'];      // 处理数据response = array('message' => 'Success', 'data' => data);      wp_send_json_success(response);
    }
    
    add_action('wp_ajax_my_first_plugin_action', 'my_first_plugin_ajax_handler');
    add_action('wp_ajax_nopriv_my_first_plugin_action', 'my_first_plugin_ajax_handler');

    21. 使用自定义钩子和过滤器

    创建自定义钩子和过滤器,使其他开发者可以扩展你的插件功能。

    21.1 创建自定义动作钩子

    function my_first_plugin_do_custom_action() {
        do_action('my_first_plugin_custom_action');
    }
    
    function my_first_plugin_add_custom_action() {
        echo 'Custom Action Triggered!';
    }
    
    add_action('my_first_plugin_custom_action', 'my_first_plugin_add_custom_action');

    21.2 创建自定义过滤器

    function my_first_plugin_apply_custom_filter(content) {     return apply_filters('my_first_plugin_custom_filter',content);
    }
    
    function my_first_plugin_modify_content(content) {     returncontent . ' - Modified by custom filter';
    }
    
    add_filter('my_first_plugin_custom_filter', 'my_first_plugin_modify_content');

    22. 使用第三方库

    有时,你可能需要在插件中使用第三方库。你可以通过 Composer 来管理依赖关系。

    22.1 安装 Composer

    确保在你的开发环境中安装了 Composer

    22.2 创建 composer.json 文件

    在插件根目录下创建一个 composer.json 文件:

    {
        "name": "yourname/yourplugin",
        "description": "A description of your plugin",
        "require": {
            "some/package": "^1.0"
        }
    }

    22.3 安装依赖

    在终端中运行以下命令:

    composer install

    22.4 在插件中加载 Composer 自动加载器

    在插件的主文件中添加以下代码:

    require_once __DIR__ . '/vendor/autoload.php';

    23. 测试和调试(续)

    23.1 使用 WP_DEBUG

    wp-config.php 文件中启用调试模式:

    define('WP_DEBUG', true);
    define('WP_DEBUG_LOG', true);
    define('WP_DEBUG_DISPLAY', false);
    @ini_set('display_errors', 0);

    此设置会将错误记录到 wp-content/debug.log 文件中,而不是直接显示在页面上。

    23.2 使用 PHPStorm 和 Xdebug 进行调试

    1. 安装 Xdebug: 根据你的 PHP 版本和操作系统安装 Xdebug。
    2. 配置 php.ini: 添加以下行到你的 php.ini 文件: zend_extension="path/to/xdebug.so" xdebug.remote_enable=1 xdebug.remote_host=127.0.0.1 xdebug.remote_port=9000 xdebug.remote_handler=dbgp
    3. 配置 PHPStorm: 在 PHPStorm 中配置 Xdebug 远程调试。在 “Preferences” -> “Languages & Frameworks” -> “PHP” -> “Debug” 中设置 Xdebug 端口为 9000。
    4. 设置断点: 在 PHPStorm 中打开你的插件代码并设置断点。
    5. 启动调试: 启动 PHPStorm 的调试监听,然后在浏览器中访问你的插件页面。代码将在断点处暂停,你可以逐步调试。

    24. 国际化与本地化

    24.1 准备插件以进行翻译

    使用 __()_e() 函数准备你的插件代码:

    echo __('Hello, World!', 'my-first-plugin');
    _e('Hello, World!', 'my-first-plugin');

    24.2 创建 POT 文件

    使用 xgettext 或 Poedit 等工具生成 POT 文件:

    xgettext --from-code=UTF-8 -k__ -k_e -o languages/my-first-plugin.pot (find . -name "*.php")</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">24.3 加载翻译文件</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 在插件初始化时加载翻译文件: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>function my_first_plugin_load_textdomain() {     load_plugin_textdomain('my-first-plugin', false, dirname(plugin_basename(__FILE__)) . '/languages'); } add_action('plugins_loaded', 'my_first_plugin_load_textdomain');</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">25. 安全性</h3> <!-- /wp:heading -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">25.1 数据验证和清理</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 确保所有用户输入的数据都经过验证和清理: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>cleaned_data = sanitize_text_field(_POST['data']);</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">25.2 权限检查</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 在处理敏感操作之前检查用户权限: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>if (!current_user_can('manage_options')) {     wp_die(__('You do not have sufficient permissions to access this page.', 'my-first-plugin')); }</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">25.3 防止 SQL 注入</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 使用 <code>wpdb->prepare() 函数来防止 SQL 注入:

    global wpdb;sql = wpdb->prepare("SELECT * FROM {wpdb->prefix}my_table WHERE id = %d", id);results = wpdb->get_results(sql);

    26. 创建设置页面

    26.1 添加设置页面

    function my_first_plugin_add_admin_menu() {
        add_options_page(
            __('My First Plugin Settings', 'my-first-plugin'),
            __('My First Plugin', 'my-first-plugin'),
            'manage_options',
            'my-first-plugin',
            'my_first_plugin_options_page'
        );
    }
    add_action('admin_menu', 'my_first_plugin_add_admin_menu');

    26.2 创建设置页面内容

    function my_first_plugin_options_page() {
        ?>
        <div class="wrap">
            <h1><?php _e('My First Plugin Settings', 'my-first-plugin'); ?></h1>
            <form action="options.php" method="post">
                <?php
                settings_fields('my_first_plugin_options');
                do_settings_sections('my-first-plugin');
                submit_button();
                ?>
            </form>
        </div>
        <?php
    }

    26.3 注册设置

    ```php
    function my_first_plugin_settings_init() {
    register_setting('my_first_plugin_options', 'my_first_plugin_options');

    add_settings_section(
        'my_first_plugin_section',
        __('General Settings', 'my-first-plugin'),
        'my_first_plugin_section_callback',
        'my-first-plugin'
    );
    
    add_settings_field(
        'my_first_plugin_field_text',
        __('Sample Text Field', 'my-first-plugin'),
        'my_first_plugin_field_text_callback',
        '

    26. 创建设置页面(续)

    26.3 注册设置(续)

        add_settings_field(
            'my_first_plugin_field_text',
            __('Sample Text Field', 'my-first-plugin'),
            'my_first_plugin_field_text_callback',
            'my-first-plugin',
            'my_first_plugin_section'
        );
    }
    
    add_action('admin_init', 'my_first_plugin_settings_init');
    
    function my_first_plugin_section_callback() {
        echo __('Enter your settings below:', 'my-first-plugin');
    }
    
    function my_first_plugin_field_text_callback() {
        options = get_option('my_first_plugin_options');     ?>     <input type="text" name="my_first_plugin_options[my_first_plugin_field_text]" value="<?php echo isset(options['my_first_plugin_field_text']) ? esc_attr(options['my_first_plugin_field_text']) : ''; ?>">     <?php }</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">27. 创建自定义小工具</h3> <!-- /wp:heading -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">27.1 定义小工具类</h4> <!-- /wp:heading -->  <!-- wp:code --> <pre class="wp-block-code"><code>class My_First_Plugin_Widget extends WP_Widget {     public function __construct() {         parent::__construct(             'my_first_plugin_widget', // Base ID             __('My First Plugin Widget', 'my-first-plugin'), // Name             array('description' => __('A sample widget for showing text', 'my-first-plugin')) // Args         );     }      public function widget(args, instance) {         echoargs['before_widget'];
            if (!empty(instance['title'])) {             echoargs['before_title'] . apply_filters('widget_title', instance['title']) .args['after_title'];
            }
            echo __('Hello, World!', 'my-first-plugin');
            echo args['after_widget'];     }      public function form(instance) {
            title = !empty(instance['title']) ? instance['title'] : __('New title', 'my-first-plugin');         ?>                      <label for="<?php echothis->get_field_id('title'); ?>"><?php _e('Title:', 'my-first-plugin'); ?></label>
                <input class="widefat" id="<?php echo this->get_field_id('title'); ?>" name="<?php echothis->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr(title); ?>">                  <?php     }      public function update(new_instance, old_instance) {instance = array();
            instance['title'] = (!empty(new_instance['title'])) ? strip_tags(new_instance['title']) : '';         returninstance;
        }
    }

    27.2 注册小工具

    function my_first_plugin_register_widgets() {
        register_widget('My_First_Plugin_Widget');
    }
    
    add_action('widgets_init', 'my_first_plugin_register_widgets');

    28. 使用 REST API

    28.1 创建自定义 REST API 路由

    function my_first_plugin_register_rest_route() {
        register_rest_route('my-first-plugin/v1', '/data/', array(
            'methods' => 'GET',
            'callback' => 'my_first_plugin_get_data',
        ));
    }
    
    add_action('rest_api_init', 'my_first_plugin_register_rest_route');
    
    function my_first_plugin_get_data(request) {data = array('message' => 'Hello, World!');
        return new WP_REST_Response(data, 200); }</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">28.2 使用 REST API</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 在前端使用 JavaScript 访问自定义 REST API 路由: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>fetch('/wp-json/my-first-plugin/v1/data/')     .then(response => response.json())     .then(data => console.log(data))     .catch(error => console.error('Error:', error));</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">29. 自定义短代码</h3> <!-- /wp:heading -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">29.1 注册短代码</h4> <!-- /wp:heading -->  <!-- wp:code --> <pre class="wp-block-code"><code>function my_first_plugin_shortcode(atts = [], content = null) {atts = array_change_key_case((array)atts, CASE_LOWER);a = shortcode_atts([
            'title' => 'Default Title',
        ], atts);      return '<div class="my-first-plugin">' . esc_html(a['title']) . '</div>';
    }
    
    add_shortcode('my_first_plugin', 'my_first_plugin_shortcode');

    29.2 使用短代码

    在文章或页面中使用短代码:

    [my_first_plugin title="Hello, World!"]

    30. 单元测试(续)

    30.1 设置 PHPUnit(续)

    tests 目录中创建 bootstrap.php 文件以初始化测试环境:

    <?php
    _tests_dir = getenv('WP_TESTS_DIR');  if (!_tests_dir) {
        _tests_dir = rtrim(sys_get_temp_dir(), '/\\') . '/wordpress-tests-lib'; }  require_once_tests_dir . '/includes/functions.php';
    
    function _manually_load_plugin() {
        require dirname(__DIR__) . '/my-first-plugin.php';
    }
    
    tests_add_filter('muplugins_loaded', '_manually_load_plugin');
    
    require _tests_dir . '/includes/bootstrap.php';</code></pre> <!-- /wp:code -->  <!-- wp:paragraph --> 在 <code>tests</code> 目录中创建 <code>test-sample.php</code> 文件以编写第一个测试: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code><?php class SampleTest extends WP_UnitTestCase {      function test_sample() {this->assertTrue(true);
        }
    }

    30.2 运行测试

    使用以下命令运行 PHPUnit 测试:

    phpunit --bootstrap tests/bootstrap.php tests/test-sample.php

    31. 使用自定义表单

    31.1 创建自定义表单

    在插件中使用 HTML 和 PHP 创建自定义表单:

    function my_first_plugin_form() {
        ?>
        <form action="<?php echo esc_url(admin_url('admin-post.php')); ?>" method="post">
            <input type="hidden" name="action" value="my_first_plugin_form_action">
            <?php wp_nonce_field('my_first_plugin_form_nonce', 'my_first_plugin_nonce'); ?>
            <label for="name"><?php _e('Name:', 'my-first-plugin'); ?></label>
            <input type="text" name="name" id="name" required>
            <input type="submit" value="<?php _e('Submit', 'my-first-plugin'); ?>">
        </form>
        <?php
    }

    31.2 处理表单提交

    在插件中处理表单提交:

    function my_first_plugin_form_handler() {
        if (!isset(_POST['my_first_plugin_nonce']) || !wp_verify_nonce(_POST['my_first_plugin_nonce'], 'my_first_plugin_form_nonce')) {
            wp_die(__('Nonce verification failed', 'my-first-plugin'));
        }
    
        if (!current_user_can('manage_options')) {
            wp_die(__('You do not have sufficient permissions to access this page.', 'my-first-plugin'));
        }
    
        name = sanitize_text_field(_POST['name']);
        // 处理表单数据,例如保存到数据库
        wp_redirect(admin_url('options-general.php?page=my-first-plugin&message=success'));
        exit;
    }
    
    add_action('admin_post_my_first_plugin_form_action', 'my_first_plugin_form_handler');

    32. 使用 AJAX

    32.1 注册 AJAX 动作

    function my_first_plugin_ajax_handler() {
        check_ajax_referer('my_first_plugin_nonce', 'security');
    
        if (!current_user_can('manage_options')) {
            wp_send_json_error(__('You do not have sufficient permissions to access this page.', 'my-first-plugin'));
        }
    
        response = array('message' => __('Hello, World!', 'my-first-plugin'));     wp_send_json_success(response);
    }
    
    add_action('wp_ajax_my_first_plugin_action', 'my_first_plugin_ajax_handler');

    32.2 在前端使用 AJAX

    在前端脚本中使用 AJAX 进行请求:

    jQuery(document).ready(function() {('#my-button').on('click', function() {
            
    *** QuickLaTeX cannot compile formula:
    .ajax({
                url: ajaxurl,
                type: 'POST',
                data: {
                    action: 'my_first_plugin_action',
                    security: my_first_plugin_vars.nonce
                },
                success: function(response) {
                    if (response.success) {
                        alert(response.data.message);
                    } else {
                        alert('Error: ' + response.data);
                    }
                }
            });
        });
    });</code></pre>
    <!-- /wp:code -->
    
    <!-- wp:paragraph -->
    在插件中注册和本地化脚本:
    <!-- /wp:paragraph -->
    
    <!-- wp:code -->
    <pre class="wp-block-code"><code>function my_first_plugin_enqueue_scripts() {
        wp_enqueue_script('my-first-plugin-script', plugins_url('js/my-first-plugin.js', __FILE__), array('jquery'), null, true);
        wp_localize_script('my-first-plugin-script', 'my_first_plugin_vars', array(
            'nonce' => wp_create_nonce('my_first_plugin_nonce')
        ));
    }
    
    add_action('admin_enqueue_scripts', 'my_first_plugin_enqueue_scripts');</code></pre>
    <!-- /wp:code -->
    
    <!-- wp:paragraph -->
    
    <!-- /wp:paragraph -->
    
    <!-- wp:heading {"level":3} -->
    <h3 class="wp-block-heading">33. 创建自定义数据库表</h3>
    <!-- /wp:heading -->
    
    <!-- wp:heading {"level":4} -->
    <h4 class="wp-block-heading">33.1 激活时创建表</h4>
    <!-- /wp:heading -->
    
    <!-- wp:paragraph -->
    在插件激活时创建自定义数据库表:
    <!-- /wp:paragraph -->
    
    <!-- wp:code -->
    <pre class="wp-block-code"><code>function my_first_plugin_create_db_table() {
        global
    
    *** Error message:
    Cannot download image from QuickLaTeX server: cURL error 28: Operation timed out after 5000 milliseconds with 16139 out of 32963 bytes received
    Please make sure your server/PHP settings allow HTTP requests to external resources ("allow_url_fopen", etc.)
    These links might help in finding solution:
    http://wordpress.org/extend/plugins/core-control/
    http://wordpress.org/support/topic/an-unexpected-http-error-occurred-during-the-api-request-on-wordpress-3?replies=37
    wpdb; table_name =wpdb->prefix . 'my_first_plugin_table'; charset_collate =wpdb->get_charset_collate(); sql = "CREATE TABLEtable_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, name tinytext NOT NULL, value text NOT NULL, PRIMARY KEY (id) ) charset_collate;";      require_once(ABSPATH . 'wp-admin/includes/upgrade.php');     dbDelta(sql); } register_activation_hook(__FILE__, 'my_first_plugin_create_db_table');

    33.2 插入数据

    在插件中插入数据到自定义表:

    function my_first_plugin_insert_data(name,value) {
        global wpdb;table_name = wpdb->prefix . 'my_first_plugin_table';wpdb->insert(
            table_name,         array(             'name' =>name,
                'value' => value,         )     ); }</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">33.3 查询数据</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 从自定义表中查询数据: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>function my_first_plugin_get_data() {     globalwpdb;
        table_name =wpdb->prefix . 'my_first_plugin_table';
    
        results =wpdb->get_results("SELECT * FROM table_name", OBJECT);      returnresults;
    }

    34. 本地化

    34.1 加载插件的翻译文件

    在插件中加载翻译文件:

    function my_first_plugin_load_textdomain() {
        load_plugin_textdomain('my-first-plugin', false, dirname(plugin_basename(__FILE__)) . '/languages');
    }
    
    add_action('plugins_loaded', 'my_first_plugin_load_textdomain');

    34.2 创建翻译文件

    使用 Poedit 或其他翻译工具创建 .po.mo 文件,并将它们放在 languages 目录中。例如,创建 my-first-plugin-zh_CN.pomy-first-plugin-zh_CN.mo 文件。

    35. 安全性

    35.1 数据验证和清理

    在处理用户输入时,确保数据的验证和清理:

    name = sanitize_text_field(_POST['name']);

    35.2 非可信数据的输出

    在输出非可信数据时,使用适当的 WordPress 函数进行转义:

    echo esc_html(name);</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">35.3 使用非ces</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 使用 Nonces 来验证请求的合法性: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>if (!isset(_POST['my_first_plugin_nonce']) || !wp_verify_nonce(_POST['my_first_plugin_nonce'], 'my_first_plugin_form_nonce')) {     wp_die(__('Nonce verification failed', 'my-first-plugin')); }</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">36. 优化性能</h3> <!-- /wp:heading -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">36.1 减少数据库查询</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 缓存频繁使用的数据以减少数据库查询次数: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>data = wp_cache_get('my_first_plugin_data');
    
    if (data === false) {data = my_first_plugin_get_data();
        wp_cache_set('my_first_plugin_data', data); }</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">36.2 异步处理</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 使用异步方式处理耗时操作: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>function my_first_plugin_enqueue_async_script(url) {
        if (strpos(url, '#async') === false) {         returnurl;
        } else if (is_admin()) {
            return str_replace('#async', '', url);     } else {         return str_replace('#async', '',url) . "' async='async";
        }
    }
    
    add_filter('clean_url', 'my_first_plugin_enqueue_async_script', 11, 1);

    37. 与第三方服务集成

    37.1 使用 API 密钥

    存储和使用 API 密钥:

    api_key = get_option('my_first_plugin_api_key');response = wp_remote_get("https://api.example.com/data?api_key={api_key}");</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">37.2 处理 API 响应</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 处理第三方 API 的响应: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>if (is_wp_error(response)) {
        error_message =response->get_error_message();
        echo "Something went wrong: error_message"; } else {body = wp_remote_retrieve_body(response);data = json_decode(body); }</code></pre> <!-- /wp:code -->  <!-- wp:paragraph -->  <!-- /wp:paragraph -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">38. 版本控制</h3> <!-- /wp:heading -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">38.1 使用 Git</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 使用 Git 进行版本控制是现代开发的最佳实践。以下是一些基本步骤来初始化和使用 Git: <!-- /wp:paragraph -->  <!-- wp:list {"ordered":true} --> <ol><!-- wp:list-item --> <li><strong>初始化 Git 仓库</strong>: <code>git init</code></li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>添加 <code>.gitignore</code> 文件</strong>: 创建一个 <code>.gitignore</code> 文件,以忽略不需要版本控制的文件和目录。例如: <code>/vendor/ /node_modules/ /wp-content/uploads/ .env</code></li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>添加并提交文件</strong>: <code>git add . git commit -m "Initial commit"</code></li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>推送到远程仓库</strong>: <code>git remote add origin <remote-repository-URL> git push -u origin master</code></li> <!-- /wp:list-item --></ol> <!-- /wp:list -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">38.2 使用 GitHub Actions 进行 CI/CD</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> GitHub Actions 可以帮助你自动化测试、构建和部署插件。 <!-- /wp:paragraph -->  <!-- wp:list {"ordered":true} --> <ol><!-- wp:list-item --> <li><strong>创建 GitHub Actions 工作流</strong>: 在你的仓库中创建 <code>.github/workflows/main.yml</code> 文件: <code>name: CI on: push: branches: - master pull_request: branches: - master jobs: build: runs-on: ubuntu-lateststeps: - name: Checkout code uses: actions/checkout@v2 - name: Set up PHP uses: shivammathur/setup-php@v2 with: php-version: '7.4' - name: Install dependencies run: composer install - name: Run tests run: vendor/bin/phpunit</code></pre></li></code></li> <!-- /wp:list-item --></ol> <!-- /wp:list -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">39. 钩子和过滤器</h3> <!-- /wp:heading -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">39.1 自定义钩子</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 创建自定义钩子,让其他开发者可以扩展你的插件功能: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>do_action('my_first_plugin_custom_action',arg1, arg2);</code></pre> <!-- /wp:code -->  <!-- wp:paragraph --> 使用自定义钩子: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>add_action('my_first_plugin_custom_action', 'my_custom_function', 10, 2);  function my_custom_function(arg1, arg2) {     // 处理自定义钩子 }</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">39.2 自定义过滤器</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 创建自定义过滤器以便于修改数据: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>value = apply_filters('my_first_plugin_custom_filter', value);</code></pre> <!-- /wp:code -->  <!-- wp:paragraph --> 使用自定义过滤器: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>add_filter('my_first_plugin_custom_filter', 'my_custom_filter_function');  function my_custom_filter_function(value) {
        // 修改并返回数据
        return value . ' modified'; }</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">40. REST API</h3> <!-- /wp:heading -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">40.1 注册自定义 REST API 路由</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 使用 WordPress REST API 注册自定义路由: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>function my_first_plugin_register_rest_route() {     register_rest_route('my-first-plugin/v1', '/data', array(         'methods' => 'GET',         'callback' => 'my_first_plugin_rest_callback',     )); }  add_action('rest_api_init', 'my_first_plugin_register_rest_route');</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">40.2 处理 REST API 请求</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 处理 REST API 请求并返回响应: <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>function my_first_plugin_rest_callback(request) {
        data = array(         'message' => __('Hello, World!', 'my-first-plugin'),     );      return new WP_REST_Response(data, 200);
    }

    41. Gutenberg 块

    41.1 创建自定义 Gutenberg 块

    继续创建和注册自定义 Gutenberg 块。

    1. 创建编辑器脚本(续): 在 src 目录中继续创建 index.js 文件的内容: import { registerBlockType } from '@wordpress/blocks'; import { useBlockProps, RichText } from '@wordpress/block-editor'; registerBlockType('my-first-plugin/my-custom-block', { edit({ attributes, setAttributes }) { const blockProps = useBlockProps(); return ( <div {...blockProps}> <RichText tagName="p" onChange={(content) => setAttributes({ content })} value={attributes.content} placeholder="Write your content here..." /> </div> ); }, save({ attributes }) { const blockProps = useBlockProps.save(); return ( <div {...blockProps}> <RichText.Content tagName="p" value={attributes.content} /> </div> ); }, attributes: { content: { type: 'string', source: 'html', selector: 'p' } } });
    2. 编译脚本: 更新 package.json 以添加构建脚本: { "scripts": { "build": "wp-scripts build", "start": "wp-scripts start" }, "devDependencies": { "@wordpress/scripts": "^23.0.0" } } 然后运行构建命令: npm run build
    3. 加载脚本: 在插件的 PHP 文件中加载编译后的 JavaScript 文件: function my_first_plugin_register_block() { wp_register_script( 'my-first-plugin-editor-script', plugins_url('build/index.js', __FILE__), array('wp-blocks', 'wp-element', 'wp-editor') );register_block_type('my-first-plugin/my-custom-block', array( 'editor_script' =&gt; 'my-first-plugin-editor-script', ));} add_action('init', 'my_first_plugin_register_block');

    42. 使用 WP-CLI

    WP-CLI 是一个强大的命令行工具,可以用于管理 WordPress 安装,包括插件的开发和调试。

    42.1 创建自定义 WP-CLI 命令

    1. 注册自定义命令: 在插件的主文件中注册一个自定义 WP-CLI 命令: if (defined('WP_CLI') && WP_CLI) { WP_CLI::add_command('my_first_plugin', 'My_First_Plugin_CLI_Command'); } class My_First_Plugin_CLI_Command { public function hello(args,assoc_args) { WP_CLI::success('Hello, World!'); } }
    2. 运行命令: 在终端中运行自定义 WP-CLI 命令: wp my_first_plugin hello

    43. 单元测试

    43.1 使用 PHPUnit 进行单元测试

    1. 安装 PHPUnit: 使用 Composer 安装 PHPUnit: composer require --dev phpunit/phpunit
    2. 设置测试环境: 创建 phpunit.xml.dist 文件以配置测试环境: <phpunit bootstrap="tests/bootstrap.php"> <testsuites> <testsuite name="My First Plugin Test Suite"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
    3. 编写测试: 在 tests 目录中创建测试文件,例如 test-sample.phpclass SampleTest extends WP_UnitTestCase { public function test_sample() { this->assertTrue(true); } }</code></li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>运行测试</strong>: 在终端中运行 PHPUnit: <code>vendor/bin/phpunit</code></li> <!-- /wp:list-item --></ol> <!-- /wp:list -->  <!-- wp:paragraph -->  <!-- /wp:paragraph -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">44. 代码质量和静态分析</h3> <!-- /wp:heading -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">44.1 使用 PHPCS 检查代码规范</h4> <!-- /wp:heading -->  <!-- wp:list {"ordered":true,"start":2} --> <ol start="2"><!-- wp:list-item --> <li><strong>配置 PHPCS</strong>: 创建 <code>phpcs.xml.dist</code> 文件,以定义代码检查规则: <code><?xml version="1.0"?> <ruleset name="My First Plugin"> <description>WordPress Plugin Coding Standards</description> <rule ref="WordPress-Core"/> <rule ref="WordPress-Docs"/> <rule ref="WordPress-Extra"/> <file>./</file> <exclude-pattern>*/vendor/*</exclude-pattern> </ruleset></code></li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>运行 PHPCS</strong>: 在终端中运行 PHP_CodeSniffer 以检查代码规范: <code>vendor/bin/phpcs</code></li> <!-- /wp:list-item --></ol> <!-- /wp:list -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">44.2 使用 PHPStan 进行静态分析</h4> <!-- /wp:heading -->  <!-- wp:list {"ordered":true} --> <ol><!-- wp:list-item --> <li><strong>安装 PHPStan</strong>: 使用 Composer 安装 PHPStan: <code>composer require --dev phpstan/phpstan</code></li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>配置 PHPStan</strong>: 创建 <code>phpstan.neon</code> 文件: <code>includes: - vendor/phpstan/phpstan/conf/bleedingEdge.neon parameters: level: max paths: - %currentWorkingDirectory%/src excludePaths: - %currentWorkingDirectory%/vendor</code></li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>运行 PHPStan</strong>: 在终端中运行 PHPStan 以进行静态分析: <code>vendor/bin/phpstan analyse</code></li> <!-- /wp:list-item --></ol> <!-- /wp:list -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">45. 安全性</h3> <!-- /wp:heading -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">45.1 数据验证和清理</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 为了防止安全漏洞,必须对所有用户输入进行验证和清理。 <!-- /wp:paragraph -->  <!-- wp:list {"ordered":true} --> <ol><!-- wp:list-item --> <li><strong>验证输入</strong>: 使用 <code>sanitize_*</code> 系列函数来清理输入数据: <code>email = sanitize_email(_POST['email']);url = esc_url(_POST['url']);</code></li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>非ces验证</strong>: 使用 <code>wp_verify_nonce</code> 和 <code>check_admin_referer</code> 来验证非ces: <code>if (!wp_verify_nonce(_POST['_wpnonce'], 'my_action')) { die('Security check failed'); }

    45.2 SQL 注入防护

    使用 prepare 方法来防止 SQL 注入:

    global wpdb;wpdb->query(wpdb->prepare("SELECT * FROMwpdb->posts WHERE post_title = %s", title));</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">46. 国际化和本地化</h3> <!-- /wp:heading -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">46.1 准备插件进行翻译</h4> <!-- /wp:heading -->  <!-- wp:list {"ordered":true} --> <ol><!-- wp:list-item --> <li><strong>加载文本域</strong>: 在插件主文件中加载文本域: <code>function my_first_plugin_load_textdomain() { load_plugin_textdomain('my-first-plugin', false, dirname(plugin_basename(__FILE__)) . '/languages'); } add_action('plugins_loaded', 'my_first_plugin_load_textdomain');</code></li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>使用翻译函数</strong>: 使用 <code>__</code>, <code>_e</code>, <code>_n</code> 等函数进行国际化: <code>message = __('Hello, World!', 'my-first-plugin'); _e('Welcome to my plugin!', 'my-first-plugin');
  • 生成 POT 文件: 使用 wp i18n 工具来生成 POT 文件: npx wp i18n make-pot . languages/my-first-plugin.pot
  • 47. 优化性能

    47.1 缓存

    1. 使用 Transients API: 缓存临时数据,以减少数据库查询: data = get_transient('my_first_plugin_data'); if (data === false) { data = costly_database_query(); set_transient('my_first_plugin_data',data, 12 * HOUR_IN_SECONDS); }
    2. 使用对象缓存: 使用 wp_cache_setwp_cache_get 进行对象缓存: wp_cache_set('my_cache_key', data);cached_data = wp_cache_get('my_cache_key');

    47.2 优化数据库查询

    确保数据库查询高效,避免不必要的查询和数据加载:

    global wpdb;results = wpdb->get_results(wpdb->prepare("SELECT * FROM wpdb->posts WHERE post_status = %s", 'publish'));</code></pre> <!-- /wp:code -->  <!-- wp:paragraph -->  <!-- /wp:paragraph -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">48. 高级插件架构</h3> <!-- /wp:heading -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">48.1 使用面向对象编程(OOP)</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 通过面向对象编程(OOP),可以使插件的代码更加模块化、可维护和可扩展。 <!-- /wp:paragraph -->  <!-- wp:list {"ordered":true} --> <ol><!-- wp:list-item --> <li><strong>创建基础类</strong>: 创建一个基础类来管理插件的初始化和加载: <code>class MyFirstPlugin { protected staticinstance = null;public static function get_instance() { if (null === self::instance) { self::instance = new self(); } return self::instance; } private function __construct() {this-&gt;setup_hooks(); } private function setup_hooks() { add_action('init', array(this, 'init')); } public function init() { // 初始化代码 }} MyFirstPlugin::get_instance();</code></li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>模块化插件功能</strong>: 将插件的不同功能模块化,以便更好地管理和扩展: <code>class MyFirstPlugin_Admin { public function __construct() { add_action('admin_menu', array(this, 'add_admin_menu')); }public function add_admin_menu() { add_menu_page('My First Plugin', 'My First Plugin', 'manage_options', 'my-first-plugin', array(this, 'admin_page')); } public function admin_page() { echo '<h1>My First Plugin Settings</h1>'; }} if (is_admin()) { new MyFirstPlugin_Admin(); }</code></li> <!-- /wp:list-item --></ol> <!-- /wp:list -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">48.2 使用依赖注入(DI)</h4> <!-- /wp:heading -->  <!-- wp:paragraph --> 依赖注入(DI)是一种设计模式,它可以使类的依赖更显式,并且更容易进行单元测试。 <!-- /wp:paragraph -->  <!-- wp:list {"ordered":true} --> <ol><!-- wp:list-item --> <li><strong>创建依赖注入容器</strong>: 使用一个简单的 DI 容器来管理类的实例: <code>class DIContainer { protectedinstances = array();public function set(name,instance) { this->instances[name] = instance; } public function get(name) { return this->instances[name]; }} container = new DIContainer();</code></li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>使用 DI 容器</strong>: 注册和使用依赖: <code>container->set('admin', new MyFirstPlugin_Admin()); admin =container->get('admin');
    
    
    
    
    

    49. REST API

    49.1 创建自定义 REST API 端点

    WordPress 提供了一个内置的 REST API,可以用来创建自定义端点。

    1. 注册自定义端点: 使用 register_rest_route 函数注册一个自定义 REST API 端点: function my_first_plugin_register_api_routes() { register_rest_route('my-first-plugin/v1', '/data', array( 'methods' => 'GET', 'callback' => 'my_first_plugin_get_data', )); } add_action('rest_api_init', 'my_first_plugin_register_api_routes'); function my_first_plugin_get_data(request) { return new WP_REST_Response(array('data' => 'Hello, World!'), 200); }</code></li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>测试端点</strong>: 访问 <code>http://your-site/wp-json/my-first-plugin/v1/data</code> 以测试自定义端点。</li> <!-- /wp:list-item --></ol> <!-- /wp:list -->  <!-- wp:paragraph -->  <!-- /wp:paragraph -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">50. 多站点支持</h3> <!-- /wp:heading -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">50.1 多站点激活和停用</h4> <!-- /wp:heading -->  <!-- wp:code --> <pre class="wp-block-code"><code>```php         foreach (blog_ids as blog_id) {             switch_to_blog(blog_id); my_first_plugin_single_deactivate(); restore_current_blog(); } } else { my_first_plugin_single_deactivate(); } } register_deactivation_hook(__FILE__, 'my_first_plugin_deactivate'); ```

    50.2 处理多站点特定功能

    1. 存储全局设置: 在多站点环境中,可以使用 get_site_optionupdate_site_option 来存储和检索全局设置: function my_first_plugin_get_global_setting(key) { return get_site_option(key); } function my_first_plugin_set_global_setting(key,value) { update_site_option(key,value); }
    2. 跨站点数据同步: 在多站点网络中,可以通过 switch_to_blogrestore_current_blog 函数在不同站点之间切换,以同步数据: function my_first_plugin_sync_data_across_sites(data) { globalwpdb; blog_ids =wpdb->get_col("SELECT blog_id FROM wpdb->blogs"); foreach (blog_ids as blog_id) { switch_to_blog(blog_id); update_option('my_first_plugin_data', data); restore_current_blog(); } }</code></li> <!-- /wp:list-item --></ol> <!-- /wp:list -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">51. 单元测试</h3> <!-- /wp:heading -->  <!-- wp:heading {"level":4} --> <h4 class="wp-block-heading">51.1 使用 PHPUnit 进行测试</h4> <!-- /wp:heading -->  <!-- wp:list {"ordered":true} --> <ol><!-- wp:list-item --> <li><strong>安装 PHPUnit</strong>: 使用 Composer 安装 PHPUnit: <code>composer require --dev phpunit/phpunit</code></li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>配置 PHPUnit</strong>: 创建 <code>phpunit.xml.dist</code> 文件: <code><phpunit bootstrap="tests/bootstrap.php"> <testsuites> <testsuite name="My Plugin Test Suite"> <directory>tests/</directory> </testsuite> </testsuites> </phpunit></code></li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>编写测试用例</strong>: 创建测试用例文件,例如 <code>tests/test-sample.php</code>: <code>class SampleTest extends WP_UnitTestCase { public function test_sample() {this->assertTrue(true); } }
    3. 运行测试: 运行 PHPUnit 以执行测试: vendor/bin/phpunit

    52. 钩子和过滤器

    52.1 创建自定义钩子

    1. 创建自定义动作钩子: 在插件代码中创建一个自定义动作钩子: do_action('my_first_plugin_custom_action', arg1,arg2);
    2. 创建自定义过滤器钩子: 在插件代码中创建一个自定义过滤器钩子: value = apply_filters('my_first_plugin_custom_filter',value, arg1,arg2);

    52.2 使用钩子和过滤器

    1. 添加动作钩子: 使用 add_action 函数来挂载自定义动作钩子: add_action('my_first_plugin_custom_action', 'my_custom_action_function', 10, 2); function my_custom_action_function(arg1,arg2) { // 自定义动作处理逻辑 }
    2. 添加过滤器钩子: 使用 add_filter 函数来挂载自定义过滤器钩子: add_filter('my_first_plugin_custom_filter', 'my_custom_filter_function', 10, 3); function my_custom_filter_function(value,arg1, arg2) { // 自定义过滤逻辑 returnvalue; }

  • 无用户名登录的 WebAuthn 实现

    无用户名登录是一种通过 WebAuthn 进行身份验证的改进方法,其目标是进一步简化用户的登录过程,消除输入用户名的需求。这一特性主要依赖于 Resident Key(驻留密钥)功能,允许认证器在本地存储私钥,从而实现用户身份的无缝识别。下面是这一过程的详细解释:

    为什么普通的 WebAuthn 不能实现无用户名登录?

    在传统的 WebAuthn 流程中,依赖方(如网站)在验证用户身份时需要提供凭证 ID(Credential ID)给认证器,认证器依赖该凭证 ID 计算出相应的私钥。通常,认证器并不存储私钥,而是通过 Key Warp 等技术加密私钥并将其包含在凭证 ID 中。这意味着认证器可以无限制地生成公私钥对,而无需维护庞大的存储空间。

    然而,这也导致了一个问题:在登录时,依赖方必须通过用户名找到对应的凭证 ID,并将其发送给认证器。这就要求用户在验证时必须输入用户名。

    Resident Key(驻留密钥)解决方案

    Resident Key 功能允许认证器在本地永久存储私钥,从而消除对凭证 ID 的依赖。通过这种方式,认证器可以直接根据依赖方 ID 找到对应的私钥进行身份验证,无需用户输入用户名。

    无用户名登录的具体流程

    注册时(启用 Resident Key):

    1. 依赖方请求新建凭证
      • 依赖方请求认证器生成一对公私钥,并要求启用 Resident Key。
    2. 认证器生成密钥对
      • 认证器生成一对公私钥,并将私钥存储在永久内存中,与依赖方 ID 和用户 ID 绑定。
    3. 发送公钥给依赖方
      • 认证器将公钥发送给依赖方,依赖方将公钥与用户 ID 绑定并存储。

    验证时(无用户名登录):

    1. 依赖方请求验证
      • 依赖方请求验证用户身份,只需提供依赖方 ID。
    2. 用户选择认证器
      • 用户选择用于验证的认证器。
    3. 认证器查找私钥
      • 认证器根据依赖方 ID 查找对应的私钥。如果有多个对应的私钥,认证器会提示用户选择使用哪个身份信息进行登录
    4. 认证器签名挑战
      • 认证器使用找到的私钥签名依赖方发送的挑战(challenge),并将签名结果和用户 ID 返回给依赖方。
    5. 依赖方验证签名
      • 依赖方根据返回的用户 ID 查找对应的公钥,验证签名的正确性。如果签名有效,则允许用户登录。

    无用户名登录的示意图

    依赖方                  客户端                 认证器
      |-- 验证请求(依赖方 ID) -->|                          |
      |                           |-- 用户选择认证器 ------>|
      |                           |                          |-- 查找私钥
      |                           |                          |-- 签名挑战
      |                           |<-- 返回签名和用户 ID --|
      |<-- 验证签名(公钥验证) --|                          |
      |-- 登录成功(如验证通过) -->|                          |

    重要注意事项

    • 存储限制:认证器能永久存储的私钥数量是有限的,因此 Resident Key 功能应仅在真正需要无用户名登录时启用。
    • 兼容性:目前尚无统一的方法检测认证器是否支持 Resident Key 功能,因此在无用户名验证失败时,应回退至常规的 WebAuthn 验证流程,即向用户询问用户名。
    • 安全性:驻留密钥功能要求认证器支持用户身份的安全管理,包括对用户 ID 的安全存储和私钥的安全签名操作。

    结论

    Resident Key 功能为 WebAuthn 提供了无用户名登录的可能性,进一步简化了用户的登录体验。在未来,随着更多认证器对 Resident Key 的支持及其在实际应用中的普及,无用户名登录有望成为一种常见的身份验证方法。然而,在实施这一特性时,需要注意认证器的存储限制和兼容性问题,以确保用户体验的平滑过渡。

  • Server-Sent Events(服务器发送事件)和WebSocket(网络套接字)

    Server-Sent Events(服务器发送事件)和WebSocket(网络套接字)是两种常用的实时通信协议,用于在应用程序中进行快速高效的数据传输。它们在实时体验方面的期望随着时间的推移不断增长,随着技术的改进和对可能性的理解而不断提高。本文将比较这两种流行的实时协议——WebSockets和Server-Sent Event(SSE)API。您将了解到它们各自的功能、优缺点以及何时使用它们。

    WebSockets是建立在设备的TCP/IP协议栈之上的一种轻量级传输层,提供了服务器和浏览器之间全双工、低延迟、事件驱动的连接。这对于实时应用程序非常理想,因为在初始的HTTP握手之后,单个WebSocket连接可以处理一个会话的所有消息,无需进一步的握手。当会话结束时,连接应该作为清理的一部分关闭。[2]

    WebSockets的优点包括:

    • 使用自定义的ws协议传输消息,工作在比HTTP更低的层级上。
    • 连接是双向的,因此WebSockets非常适用于需要从服务器读取和写入数据的应用程序,例如聊天应用程序或多人游戏。
    • 它可以复杂地从头开始实现WebSocket,但有许多库可用于简化此过程。
    • 基于事件的,无需轮询即可获取消息,有助于减少延迟。
    • RFC 6455 – WebSocket协议于2011年发布在IETF网站上,现在所有主流浏览器都支持它。[2]

    WebSockets的缺点包括:

    • 防火墙阻塞:一些企业防火墙在处理WebSockets时可能会出现问题(特别是SophosXG防火墙、WatchGuard和McAfee Web Gateway)。
    • 没有内置的重新连接支持:当WebSocket连接关闭时(例如由于网络问题),客户端不会尝试重新连接到服务器,这意味着您需要编写额外的代码来轮询服务器,在可用时重新建立连接。或者,您可以使用Server-Sent Events或具有重新连接支持的库,如Socket.IO。[2]

    Server-Sent Events(SSE)基于Server-Sent DOM Events(服务器发送的DOM事件)。浏览器可以使用EventSource接口订阅服务器生成的事件流,每当发生新事件时,就会接收到更新。EventSource接受来自特定URL的HTTP事件流连接,并在检索到可用数据时保持连接打开。Server-Sent Events是一种标准,描述了服务器在建立初始客户端连接后如何保持数据传输到客户端。它提供了一种内存高效的XHR流实现。与原始的XHR连接不同,原始XHR连接在连接断开之前会缓冲整个接收到的响应,而SSE连接可以在不累积所有消息的情况下丢弃已处理的消息。[3]

    Server-Sent Events的优点包括:

    • 可以在不支持它的浏览器中使用JavaScript进行polyfill。这对于向后兼容性非常有用,因为您可以依赖现有的实现而不必编写替代实现。
    • 内置的重新连接支持:Server-Sent Event连接在连接丢失后会重新建立连接,这意味着需要编写的代码更少,以实现基本的行为。
    • 不会被防火墙阻塞:SSE在进行数据包检查的企业防火墙中没有问题,这对于在企业环境中支持应用程序非常重要。

    Server-Sent Events(SSE)是一种基于服务器发送的DOM事件的协议。浏览器可以通过EventSource接口订阅由服务器生成的事件流,每当发生新事件时,浏览器就会接收到更新。SSE使用XHR流传输消息,连接是单向的,适用于只需要从服务器读取数据的应用程序,例如实时股票或新闻滚动。

    WebSocket是建立在设备的TCP/IP协议栈之上的一种轻量级传输层,提供了全双工、低延迟、事件驱动的服务器与浏览器之间的连接。WebSocket适用于需要从服务器读取和写入数据的应用程序,例如聊天应用程序或多人游戏。

    下面是Server-Sent Events和WebSocket的一些比较:

    Server-Sent Events:

    • 使用XHR流传输消息,连接是单向的。
    • 不需要复杂的实现,但相关的库较少。
    • 事件驱动,无需轮询即可拦截消息。
    • 支持自动重新连接,当连接丢失时会重新建立连接。
    • 可以通过JavaScript进行polyfill,以支持不支持SSE的浏览器。
    • 不受企业防火墙的阻塞,适用于企业环境。

    WebSocket:

    • 使用自定义的ws协议传输消息,工作在比HTTP更低的层级。
    • 连接是双向的,适用于需要从服务器读取和写入数据的应用程序。
    • 实现WebSocket可能较为复杂,但有许多库可用于简化实现。
    • 事件驱动,无需轮询即可拦截消息。
    • 不受企业防火墙的阻塞,但某些企业防火墙可能无法处理WebSocket。
    • 没有内置的重新连接支持,需要额外的代码来重新建立连接。

    根据具体的应用场景和需求,可以选择使用Server-Sent Events或WebSocket。如果只需要从服务器读取数据,并且需要自动重新连接的功能,可以选择Server-Sent Events。如果需要双向通信,并且需要更高级的功能和更大的灵活性,可以选择WebSocket。


    Learn more:

    1. Server-sent events vs. WebSockets – LogRocket Blog
    2. WebSockets vs Server-Sent Events: Key differences and which to use in 2024
    3. WebSocket vs Server-Sent Events: In-Depth Comparison, Use Cases, Pros & Cons, and Best Practices for Real-Time Communication
  • 在WordPress网站中启用Passkey认证

    要在WordPress网站中启用Passkey认证,你可以按照以下步骤操作:

    1. 安装插件:首先,你需要安装一个支持WebAuthn和Passkey认证的WordPress插件。WP-WebAuthn是一个可以在WordPress站点上启用WebAuthn的插件
    2. 配置插件:安装插件后,你需要进入WordPress管理后台的“设置”部分,找到WP-WebAuthn插件的设置页面,并按照插件的文档说明进行配置。
    3. 启用HTTPS:由于WebAuthn和Passkey认证需要安全的连接,确保你的WordPress网站使用HTTPS连接。如果尚未启用,请配置SSL证书以启用HTTPS。
    4. 用户注册身份验证器:用户需要在他们的个人资料页面注册身份验证器。这可能涉及到使用生物识别技术,如指纹或面部识别,或者是通过USB设备、NFC等方式。
    5. 测试认证流程:配置完成后,尝试注册和使用Passkey进行登录,以确保一切工作正常。
    6. 用户教育:由于Passkey是一种较新的技术,你可能需要向你的用户解释如何使用它,以及它如何增强账户的安全性。
    7. 技术支持:如果你遇到任何问题,可以查看插件的文档,或者在插件的GitHub页面上寻求帮助。
    8. 保持更新:定期检查插件更新,以确保你的Passkey认证功能保持最新和安全。

    请注意,启用Passkey认证可能需要一定的技术知识,特别是关于服务器配置和插件安装的部分。如果你不熟悉这些操作,可能需要寻求专业的技术支持。

    WP-WebAuthn 是一个为你的 WordPress 站点启用 WebAuthn 的插件。只需下载并安装,你就进入了 Web 身份验证的未来。
    WP-WebAuthn 也支持无用户名验证。
    这个插件有 4 个内置短代码和 4 个对应的内置 Gutenberg 区块,因此你可以将注册表单等组件添加到前端页面。

    WebAuthn(Web Authentication)是一个开放的无密码标准,由万维网联盟(W3C)和FIDO联盟共同制定,旨在为用户提供一种更安全、更便捷的网络认证方式。WebAuthn 允许用户使用生物识别技术、智能卡或移动设备等作为认证手段,而不是仅仅依赖传统的用户名和密码。

    截至2023年,WebAuthn 规范的主要版本是 Level 1,即 WebAuthn Level 1,它在2019年3月成为W3C的官方推荐标准。WebAuthn Level 1 定义了客户端和服务器之间进行无密码认证的流程,包括注册(注册新的认证器)和认证(使用已有的认证器进行认证)两个主要过程。

    关于 WebAuthn Level 2 的规范,截至2023年,并没有官方的 Level 2 版本发布。通常,技术规范的更新会通过补丁或小版本迭代来进行,而不是直接跳到 Level 2。WebAuthn 的发展和更新可能会通过一系列的改进提案(如 RFCs)来进行,这些提案会逐步集成到核心规范中。

    为了获取最新的WebAuthn规范信息,建议访问W3C官方网站或FIDO联盟的相关资源,查阅最新的文档和公告。


    https://flyhigher.top/develop/2160.html

    走进 WebAuthn 的世界:无密码时代的身份认证

    随着互联网的发展,传统密码认证方式的安全性越来越让人担忧。幸运的是,WebAuthn 技术的出现让我们看到了希望。WebAuthn 是一种新的身份认证方式,它让我们可以抛弃繁琐的密码,通过更加安全和便捷的方式来进行身份验证。在本文中,我们将一起走进 WebAuthn 的世界,了解它的角色、过程和关键概念。

    WebAuthn 认证的四大角色

    在 WebAuthn 的认证流程中,有四个重要的角色:

    1. 依赖方 (Relying Party, RP)
      • 这是提供服务的那一方,比如一个网站。
      • 依赖方负责提供注册和登录的接口。
    2. 用户 (User)
      • 也就是你,准备登录网站的那个人。
      • 用户是整个流程的主体,通过生物识别或其他方式进行身份验证。
    3. 认证器 (Authenticator)
      • 认证器可以是 USB Key、设备内置的指纹扫描器、虹膜扫描器、面部识别装置等。
      • 认证器在使用过程中替代了传统的密码。
    4. 用户代理 (User Agent)
      • 通常是浏览器或操作系统。
      • 用户代理负责与认证器交互,帮助完成认证流程。

    认证过程:注册和验证

    WebAuthn 的认证过程主要分为两个阶段:

    1. 注册仪式 (Registration Ceremony)
      • 这是用户将认证器添加到他们的账户中的过程。
      • 用户在此过程中注册他们的认证器,使其可以在未来进行身份验证。
    2. 验证仪式 (Authentication Ceremony)
      • 用户通过已注册的认证器进行身份验证。
      • 这是用户登录时的过程,通过之前注册的认证器来验证身份。

    认证过程中的关键内容

    在 WebAuthn 的认证过程中,有一些重要的内容需要理解:

    1. 挑战 (Challenge)
      • 通常是一串随机字符串。
      • 由依赖方生成并发送给用户,以防止重放攻击。
    2. 公钥凭证 (Public Key Credential)
      • 由认证器生成的凭证,技术上代替了密码。
      • 包含公钥和其他必要的信息,用于验证用户身份。
    3. 证明 (Attestation)
      • 注册时认证器产生的验证数据。
      • 用于证明认证器的可靠性和安全性。
    4. 断言 (Assertion)
      • 验证时认证器产生的验证数据。
      • 用于证明用户是注册时的同一个用户。

    区分证明和断言

    在 WebAuthn 中,“证明 (Attestation)”和“断言 (Assertion)”是两个容易混淆的概念,但它们有着不同的用途:

    • 证明 (Attestation):在注册过程中产生,用于证明认证器的安全性和真实性。
    • 断言 (Assertion):在登录过程中产生,用于证明用户的身份。

    总结

    WebAuthn 技术为我们带来了更加安全、便捷的身份认证方式。通过了解依赖方、用户、认证器和用户代理这四大角色,以及注册和验证两个过程中的关键内容,我们可以更好地理解和应用 WebAuthn。随着这种无密码身份认证方式的普及,我们的网络生活将变得更加安全和舒适。

    让我们一起迎接无密码时代的到来,享受更加安全和便捷的网络体验吧!


    非对称加密与 WebAuthn 的安全性

    在数字时代,密码曾经是我们保护在线身份和隐私的主要工具。然而,随着技术的进步和黑客手段的多样化,传统的密码认证方式变得越来越不可靠。WebAuthn 的出现让我们看到了新的希望,它是一种基于非对称加密的身份认证技术,旨在提供更加安全和便捷的认证体验。

    非对称加密的基础

    要理解 WebAuthn 的安全性,我们首先需要了解非对称加密的基本概念。非对称加密使用一对密钥,即公钥和私钥。这对密钥有以下特点:

    • 公钥 (Public Key):可以公开给任何人,用于加密信息。
    • 私钥 (Private Key):必须严格保密,用于解密信息。

    公钥和私钥是互相关联但几乎无法互相推导的。在这种加密体系中,使用私钥加密的信息只能由对应的公钥解密,反之亦然。

    对称加密的局限性

    为了更好地理解非对称加密的优势,我们先来看一个对称加密的例子:

    假设小明和小红互相写信,但见不到对方。小红想确认给自己写信的人真的是小明,于是他们商量出一套方案:

    1. 小明和小红在身份验证的情况下商量一个统一的密码和密钥。
    2. 一段时间后,小红要求小明验证身份时,发送一段文本给小明。
    3. 小明用商量好的密码和密钥加密文本后发回给小红。
    4. 小红使用相同的密码和密钥解密文本,如果得到的文本和之前发出的一致,就能确定对方是小明。

    这种方法的最大问题在于密钥的交换。如果两人在开始时不能见面,那么他们必须通过某种方式以明文交换密码,一旦密码在传输过程中被窃取,这个认证方法就失效了。

    非对称加密解决信任问题

    非对称加密完美地解决了密钥泄露的问题。我们再来看小明和小红的例子,这次他们使用非对称加密:

    1. 在小明的身份已经验证的情况下,小明生成一对公私钥,将公钥发送给小红,私钥自己保管,同时商量好统一的密码。
    2. 一段时间后,小红要求小明验证身份时,发送一段文本给小明。
    3. 小明使用商量好的密码和自己的私钥加密文本,发送给小红。
    4. 小红使用相同的密码和小明的公钥解密文本,如果得到的文本和之前发出的一致,就能确定对方是小明。

    在这个过程中,私钥从未离开过小明的手中,也没有经过传输,几乎没有泄露的风险。小红可以通过解密文本确认对方的身份,而无需担心密钥被窃取。

    WebAuthn 的安全性

    WebAuthn 采用了非对称加密的基本原理来确保用户身份的安全性。具体而言,WebAuthn 的认证过程包括以下步骤:

    1. 注册 (Registration)
      • 用户在依赖方(如网站)注册时,认证器生成一对公私钥,并将公钥发送给依赖方。
      • 私钥保存在认证器中,从不离开设备。
    2. 验证 (Authentication)
      • 用户登录时,依赖方生成一个随机的挑战 (Challenge) 并发送给用户。
      • 认证器使用私钥对挑战进行签名,并将签名发送回依赖方。
      • 依赖方使用事先保存的公钥验证签名,如果验证通过,则表明用户的身份是合法的。

    通过这种方式,WebAuthn 确保了认证过程的安全性,因为私钥永远不会被传输或暴露,即使网络通信被截获,攻击者也无法伪造用户的身份。

    结论

    WebAuthn 的核心在于利用非对称加密技术,通过公钥和私钥的配合,确保认证器生成的凭证是用户的认证器,而非第三方伪造的。这种机制大大提高了身份验证的安全性,解决了传统密码认证方式的诸多问题,让我们的数字生活更加安全和便捷。通过 WebAuthn,我们正迈向一个无密码的安全新时代。


    非对称加密与 WebAuthn 的安全性

    在数字时代,密码曾经是我们保护在线身份和隐私的主要工具。然而,随着技术的进步和黑客手段的多样化,传统的密码认证方式变得越来越不可靠。WebAuthn 的出现让我们看到了新的希望,它是一种基于非对称加密的身份认证技术,旨在提供更加安全和便捷的认证体验。

    非对称加密的基础

    要理解 WebAuthn 的安全性,我们首先需要了解非对称加密的基本概念。非对称加密使用一对密钥,即公钥和私钥。这对密钥有以下特点:

    • 公钥 (Public Key):可以公开给任何人,用于加密信息。
    • 私钥 (Private Key):必须严格保密,用于解密信息。

    公钥和私钥是互相关联但几乎无法互相推导的。在这种加密体系中,使用私钥加密的信息只能由对应的公钥解密,反之亦然。

    对称加密的局限性

    为了更好地理解非对称加密的优势,我们先来看一个对称加密的例子:

    假设小明和小红互相写信,但见不到对方。小红想确认给自己写信的人真的是小明,于是他们商量出一套方案:

    1. 小明和小红在身份验证的情况下商量一个统一的密码和密钥。
    2. 一段时间后,小红要求小明验证身份时,发送一段文本给小明。
    3. 小明用商量好的密码和密钥加密文本后发回给小红。
    4. 小红使用相同的密码和密钥解密文本,如果得到的文本和之前发出的一致,就能确定对方是小明。

    这种方法的最大问题在于密钥的交换。如果两人在开始时不能见面,那么他们必须通过某种方式以明文交换密码,一旦密码在传输过程中被窃取,这个认证方法就失效了。

    非对称加密解决信任问题

    非对称加密完美地解决了密钥泄露的问题。我们再来看小明和小红的例子,这次他们使用非对称加密:

    1. 在小明的身份已经验证的情况下,小明生成一对公私钥,将公钥发送给小红,私钥自己保管,同时商量好统一的密码。
    2. 一段时间后,小红要求小明验证身份时,发送一段文本给小明。
    3. 小明使用商量好的密码和自己的私钥加密文本,发送给小红。
    4. 小红使用相同的密码和小明的公钥解密文本,如果得到的文本和之前发出的一致,就能确定对方是小明。

    在这个过程中,私钥从未离开过小明的手中,也没有经过传输,几乎没有泄露的风险。小红可以通过解密文本确认对方的身份,而无需担心密钥被窃取。

    WebAuthn 的安全性

    WebAuthn 采用了非对称加密的基本原理来确保用户身份的安全性。具体而言,WebAuthn 的认证过程包括以下步骤:

    1. 注册 (Registration)
      • 用户在依赖方(如网站)注册时,认证器生成一对公私钥,并将公钥发送给依赖方。
      • 私钥保存在认证器中,从不离开设备。
    2. 验证 (Authentication)
      • 用户登录时,依赖方生成一个随机的挑战 (Challenge) 并发送给用户。
      • 认证器使用私钥对挑战进行签名,并将签名发送回依赖方。
      • 依赖方使用事先保存的公钥验证签名,如果验证通过,则表明用户的身份是合法的。

    通过这种方式,WebAuthn 确保了认证过程的安全性,因为私钥永远不会被传输或暴露,即使网络通信被截获,攻击者也无法伪造用户的身份。

    结论

    WebAuthn 的核心在于利用非对称加密技术,通过公钥和私钥的配合,确保认证器生成的凭证是用户的认证器,而非第三方伪造的。这种机制大大提高了身份验证的安全性,解决了传统密码认证方式的诸多问题,让我们的数字生活更加安全和便捷。通过 WebAuthn,我们正迈向一个无密码的安全新时代。


    WebAuthn 注册流程

    WebAuthn 的注册流程旨在建立用户、认证器和依赖方(如网站)之间的信任关系。通过这个流程,认证器生成公私钥对,公钥被传递给依赖方以便后续认证使用。以下是详细的注册流程:

    1. 用户发起注册请求
      • 用户在依赖方的注册页面上发起注册请求。依赖方会生成一个注册挑战(challenge),并返回给用户的客户端(通常是浏览器)。
    2. 客户端与认证器交互
      • 客户端将注册挑战发送给用户的认证器(如安全密钥、手机的生物识别系统等)。
    3. 认证器生成密钥对
      • 认证器生成一对公私钥。私钥保存在认证器中,永不离开设备。
      • 同时,认证器生成一个包含公钥和其他元数据(如认证器的标识、使用的加密算法等)的凭证。
    4. 用户验证
      • 认证器要求用户进行验证(如指纹扫描、面部识别或输入 PIN 码),以确保是合法用户在进行注册操作。
    5. 返回注册响应
      • 认证器将公钥和其他相关数据(如认证器 ID、签名等)封装在注册响应中,并返回给客户端。
    6. 客户端发送注册响应给依赖方
      • 客户端将注册响应发送回依赖方服务器。
    7. 依赖方验证并存储公钥
      • 依赖方验证注册响应(例如,检查认证器签名是否有效)。
      • 验证通过后,依赖方会将公钥和其他相关信息与用户账户关联存储起来,以便后续的验证使用。

    WebAuthn 注册流程示意图

    用户                 依赖方              客户端                   认证器
      |-- 注册请求 -->|                    |                           |
      |               |-- 注册挑战 ------->|                           |
      |               |                    |-- 注册挑战发送给认证器 -->|
      |               |                    |                           |-- 生成密钥对
      |               |                    |                           |-- 用户验证
      |               |                    |<-- 返回注册响应 ---------|
      |               |<-- 注册响应 -------|                           |
      |               |-- 验证并存储公钥 -->|                           |

    具体细节

    • 注册挑战(challenge):这是一个随机生成的字符串,用于防止重放攻击。每次注册请求都会生成一个新的挑战。
    • 元数据(metadata):包括认证器的标识、支持的加密算法等。这些信息帮助依赖方了解如何验证响应。
    • 签名(signature):认证器用私钥对注册响应中的数据进行签名,依赖方用公钥验证签名的有效性。

    结论

    WebAuthn 的注册流程通过非对称加密技术,确保了在注册过程中公私钥的安全生成和存储,防止密钥泄露或被篡改。同时,通过挑战-应答模型和用户验证步骤,WebAuthn 提供了强大的安全保障,为后续的身份验证打下了坚实的基础。接下来,我们可以详细探讨 WebAuthn 的验证流程。


    WebAuthn 验证流程

    WebAuthn 验证流程旨在确保用户的身份真实性,通过基于挑战-应答的模型,使用之前注册时存储的公钥验证用户。以下是详细的验证流程:

    1. 用户发起验证请求
      • 用户在依赖方(如网站)上发起登录请求。依赖方生成一个验证挑战(challenge),并返回给用户的客户端(通常是浏览器)。
    2. 依赖方发送挑战
      • 依赖方将挑战发送给用户的客户端。
    3. 客户端与认证器交互
      • 客户端将验证挑战、依赖方信息(如 Relying Party ID)和客户端信息(如 origin)发送给用户的认证器。
    4. 认证器请求用户动作
      • 认证器提示用户进行验证(如指纹扫描、面部识别或输入 PIN 码)。
      • 用户验证通过后,认证器找到对应的私钥,并使用私钥对挑战进行签名,生成一个签名断言(assertion)。
    5. 认证器返回签名断言
      • 认证器将签名断言连同认证器的元数据(如认证器 ID、计数器值等)返回给客户端。
    6. 客户端发送签名断言给依赖方
      • 客户端将签名断言发送回依赖方服务器。
    7. 依赖方验证签名断言
      • 依赖方使用之前存储的公钥,验证签名断言的有效性。
      • 依赖方检查计数器值是否合理,以防止重放攻击。
      • 如果签名有效且计数器值合理,依赖方确认用户身份验证通过。

    WebAuthn 验证流程示意图

    用户                 依赖方              客户端                   认证器
      |-- 验证请求 -->|                    |                           |
      |               |-- 验证挑战 ------->|                           |
      |               |                    |-- 验证挑战发送给认证器 -->|
      |               |                    |                           |-- 用户验证
      |               |                    |                           |-- 签名挑战
      |               |                    |<-- 返回签名断言 ---------|
      |               |<-- 签名断言 -------|                           |
      |               |-- 验证签名断言 -->|                           |
      |<-- 验证成功 --|                    |                           |

    具体细节

    • 验证挑战(challenge):这是一个随机生成的字符串,用于防止重放攻击。每次验证请求都会生成一个新的挑战。
    • 依赖方信息(Relying Party ID):这是依赖方的标识,用于确保签名是由正确的私钥生成的。
    • 客户端信息(origin):包括客户端的原点信息(URI),用于确保请求的合法性。
    • 签名断言(assertion):认证器用私钥对挑战进行签名生成的断言,包含签名数据和其他元数据。
    • 计数器值:认证器维护的计数器,用于防止重放攻击。每次成功验证后计数器都会递增。

    结论

    WebAuthn 的验证流程通过挑战-应答模型确保了用户身份的真实性。通过非对称加密技术,认证器生成的私钥永不离开设备,极大地提升了安全性。同时,通过用户验证步骤和计数器机制,WebAuthn 提供了强大的防重放攻击能力。这个流程与注册流程相辅相成,确保了从注册到验证的整体安全性和可靠性。


    https://github.com/topics/webauthn


  • 优化高负载TCP服务器的一些常见策略和诊断方法

    1. 优化内核参数

    优化内核参数是提高高负载TCP服务器性能的一个重要方面。以下是一些常见的内核参数,可以通过调整这些参数来增强TCP服务器的处理能力:

    • net.core.somaxconn:定义了系统中每个监听套接字可排队的最大连接数。这影响的是全连接(accept队列)。
    • net.ipv4.tcp_max_syn_backlog:定义了在SYN_RECV状态下最多可以有多少个连接请求。这影响的是半连接(SYN队列)。
    • net.ipv4.tcp_fin_timeout:设置TCP连接在FIN_WAIT_2状态下的超时时间,可以帮助更快地释放资源。
    • net.ipv4.tcp_tw_reusenet.ipv4.tcp_tw_recycle:这两个参数可以帮助快速回收处于TIME_WAIT状态的连接(注意:在某些情况下,启用这些参数可能会有安全和可靠性问题,需慎重考虑)。

    2. 半连接和全连接队列满的影响

    • 半连接队列(SYN队列):当一个客户端发起TCP连接时,服务器会在接收到SYN包时将其放入半连接队列。如果这个队列满了,服务器会直接丢弃新的SYN请求,导致客户端无法建立连接。
    • 全连接队列(accept队列):当TCP连接完成三次握手后,连接会被移至全连接队列。如果这个队列满了,服务器将无法接受新的连接,导致客户端连接超时。

    3. 从系统日志中分析

    系统日志可以提供有价值的信息来判断是哪种队列满了。日志中可能会有关于丢包、连接超时或其他网络异常的提示信息,这些信息可以帮助定位问题的根源。

    4. 抓包分析

    如果上述方法无法解决问题,抓包是下一步的诊断措施。通过抓包,可以分析网络流量,查看是否存在以下情况:

    • SYN包无响应:可能是服务器没有及时处理SYN请求,或者是网络问题导致的丢包。
    • ACK包无响应:可能是三次握手中的ACK没有被正确处理,导致连接无法建立。
    • RST包:查看是否有大量的重置包,可能是服务器主动拒绝连接。

    抓包可以使用工具如 tcpdumpWireshark,并结合服务器日志进行综合分析。

    结论

    以上建议涵盖了优化TCP服务器和诊断连接问题的核心方法。通过调整内核参数,可以提升服务器在高负载下的处理能力;通过分析系统日志,可以初步判断问题;通过抓包,可以深入了解网络通信的具体问题。这一系列步骤相辅相成,可以有效地提升服务器的性能和稳定性。


    优化内核参数时,具体数值需要根据服务器的硬件配置、网络环境以及应用的负载情况来调整。以下是一些常见的内核参数及其参考值,可以作为初始配置进行尝试:

    1. net.core.somaxconn

    这个参数定义了系统中每个监听套接字可排队的最大连接数(全连接队列)。默认值通常是128,可以增加到更高的值来应对高并发连接。

    sysctl -w net.core.somaxconn=1024

    2. net.ipv4.tcp_max_syn_backlog

    这个参数定义了在SYN_RECV状态下最多可以有多少个连接请求(半连接队列)。默认值通常是256,可以增加到更高的值以支持更多的未完成连接。

    sysctl -w net.ipv4.tcp_max_syn_backlog=2048

    3. net.ipv4.tcp_fin_timeout

    这个参数设置TCP连接在FIN_WAIT_2状态下的超时时间。默认值通常是60秒,可以减小该值以更快地回收资源。

    sysctl -w net.ipv4.tcp_fin_timeout=30

    4. net.ipv4.tcp_tw_reuse

    这个参数允许TIME_WAIT套接字被重新用于新的TCP连接。启用此参数可以更快地回收TIME_WAIT状态的套接字。

    sysctl -w net.ipv4.tcp_tw_reuse=1

    5. net.ipv4.tcp_tw_recycle

    这个参数允许快速回收TIME_WAIT状态的套接字。注意:由于此参数可能会导致NAT环境下的连接问题,不推荐在公网服务器上使用。

    sysctl -w net.ipv4.tcp_tw_recycle=0

    6. net.ipv4.ip_local_port_range

    这个参数定义了可用于TCP/UDP连接的本地端口范围。默认范围通常是32768到60999,可以扩展这个范围以支持更多的并发连接。

    sysctl -w net.ipv4.ip_local_port_range="1024 65535"

    7. net.ipv4.tcp_syncookies

    这个参数可以防止SYN flood攻击。启用此参数可以在SYN队列满时启用syncookies机制。

    sysctl -w net.ipv4.tcp_syncookies=1

    8. net.core.netdev_max_backlog

    这个参数定义了每个网络接口接收队列的最大长度。默认值通常是1000,可以增加到更高的值以处理更多的网络流量。

    sysctl -w net.core.netdev_max_backlog=5000

    应用配置

    将以上配置写入 /etc/sysctl.conf 文件中,以便在系统重启时自动应用:

    net.core.somaxconn=1024
    net.ipv4.tcp_max_syn_backlog=2048
    net.ipv4.tcp_fin_timeout=30
    net.ipv4.tcp_tw_reuse=1
    net.ipv4.tcp_tw_recycle=0
    net.ipv4.ip_local_port_range=1024 65535
    net.ipv4.tcp_syncookies=1
    net.core.netdev_max_backlog=5000

    然后,通过以下命令立即生效:

    sysctl -p

    结论

    以上提供的数值只是初始参考,实际生产环境中需要根据具体负载情况进行监控和调整。不断进行性能测试和监控,以找到最适合的配置。


    这些内核参数能显著影响TCP高负载服务的性能,因为它们直接控制了操作系统处理网络连接的方式。让我们详细探讨每个参数如何影响TCP服务器的性能:

    1. net.core.somaxconn

    • 作用:定义了系统中每个监听套接字可排队的最大连接数(全连接队列)。
    • 影响:当服务器处理完三次握手后,连接会被放入全连接队列。如果这个队列太小,高并发时新的连接请求可能会被拒绝,导致客户端无法连接。

    2. net.ipv4.tcp_max_syn_backlog

    • 作用:定义了SYN_RECV状态下最多可以有多少个连接请求(半连接队列)。
    • 影响:当大量客户端同时发起连接请求时,如果半连接队列太小,新的SYN请求会被丢弃,导致客户端无法建立连接。适当增大这个值可以处理更多的并发SYN请求。

    3. net.ipv4.tcp_fin_timeout

    • 作用:设置TCP连接在FIN_WAIT_2状态下的超时时间。
    • 影响:这个参数控制连接关闭后资源的释放时间。较短的超时时间可以更快地回收资源,减少资源占用,适用于高并发短连接的场景。

    4. net.ipv4.tcp_tw_reuse

    • 作用:允许TIME_WAIT状态的套接字被重新用于新的连接。
    • 影响:启用此参数可以更快地回收TIME_WAIT状态的套接字,减少TIME_WAIT状态的连接数量,适用于服务器需要频繁建立和关闭连接的情况。

    5. net.ipv4.tcp_tw_recycle

    • 作用:允许快速回收TIME_WAIT状态的套接字。
    • 影响:可以加速TIME_WAIT连接的回收,但在NAT环境下可能导致连接问题,因此不推荐在公网服务器上使用。

    6. net.ipv4.ip_local_port_range

    • 作用:定义可用于TCP/UDP连接的本地端口范围。
    • 影响:增大本地端口范围可以支持更多的并发连接,防止端口耗尽的问题,适用于高并发的网络服务。

    7. net.ipv4.tcp_syncookies

    • 作用:防止SYN flood攻击,通过启用syncookies机制在SYN队列满时仍能处理连接请求。
    • 影响:可以在SYN flood攻击下保护服务器,使其仍能响应合法连接请求,提升服务器的抗攻击能力。

    8. net.core.netdev_max_backlog

    • 作用:定义每个网络接口接收队列的最大长度。
    • 影响:增大这个值可以缓解网络拥塞,防止网络接口因过多的数据包而丢包,适用于高流量的网络环境。

    总结

    这些内核参数影响TCP高负载服务性能的主要原因在于它们控制了系统如何处理和管理网络连接及其资源。在高并发场景下,适当地调整这些参数可以:

    • 提高连接请求的处理能力。
    • 优化资源回收,使系统更高效。
    • 增强抗攻击能力,确保服务稳定性。

    通过合理配置这些参数,服务器可以更好地应对大量并发连接,提高整体性能和稳定性。


    这些内核参数对不同的服务器配置会产生不同的影响。服务器的硬件配置、网络环境和负载情况都会影响这些参数的最佳值。以下是一些关键因素和它们与内核参数的关系:

    1. 服务器硬件配置

    CPU和内存

    • 高性能CPU和大内存:对于拥有高性能CPU和大内存的服务器,可以设置更高的参数值,因为这些服务器能够处理更多的并发连接和更高的网络流量。
    • 低性能CPU和少内存:对于资源有限的服务器,设置过高的参数值可能导致资源耗尽和系统不稳定。因此,需要找到一个平衡点,避免过度配置。

    网络接口

    • 高速网络接口(如10Gbps或更高):这些接口可以处理大量的网络流量,因此需要较高的net.core.netdev_max_backlog值以防止丢包。
    • 低速网络接口(如1Gbps或更低):较低的网络接口可能不需要非常高的net.core.netdev_max_backlog值,因为接口本身的流量处理能力有限。

    2. 网络环境

    网络拓扑

    • 内网环境:在内网环境中,可以利用net.ipv4.tcp_tw_recycle加速TIME_WAIT状态的回收,因为内网中通常没有复杂的NAT问题。
    • 公网环境:在公网环境中,不推荐使用net.ipv4.tcp_tw_recycle,因为它可能导致NAT环境下的连接问题。

    网络负载

    • 高并发连接:需要适当提高net.core.somaxconnnet.ipv4.tcp_max_syn_backlog以处理更多的并发连接请求。
    • 低并发连接:这些参数的默认值可能已经足够,不需要进行过多调整。

    3. 应用负载

    高并发短连接(如HTTP服务器)

    • 优化方向:需要快速回收连接资源,因此可以减小net.ipv4.tcp_fin_timeout和启用net.ipv4.tcp_tw_reuse
    • 注意事项:确保不会因过多的TIME_WAIT状态导致端口耗尽。

    长连接(如数据库服务器)

    • 优化方向:更关注连接的稳定性,可能不需要频繁调整tcp_fin_timeouttcp_tw_reuse
    • 注意事项:确保连接队列足够长以处理突发的连接请求。

    具体示例

    高性能Web服务器(多核CPU、大内存、10Gbps网络接口)

    sysctl -w net.core.somaxconn=4096
    sysctl -w net.ipv4.tcp_max_syn_backlog=8192
    sysctl -w net.ipv4.tcp_fin_timeout=15
    sysctl -w net.ipv4.tcp_tw_reuse=1
    sysctl -w net.ipv4.tcp_tw_recycle=0
    sysctl -w net.ipv4.ip_local_port_range="1024 65535"
    sysctl -w net.ipv4.tcp_syncookies=1
    sysctl -w net.core.netdev_max_backlog=10000

    资源有限的内部应用服务器(双核CPU、4GB内存、1Gbps网络接口)

    sysctl -w net.core.somaxconn=1024
    sysctl -w net.ipv4.tcp_max_syn_backlog=2048
    sysctl -w net.ipv4.tcp_fin_timeout=30
    sysctl -w net.ipv4.tcp_tw_reuse=1
    sysctl -w net.ipv4.tcp_tw_recycle=0
    sysctl -w net.ipv4.ip_local_port_range="32768 60999"
    sysctl -w net.ipv4.tcp_syncookies=1
    sysctl -w net.core.netdev_max_backlog=2000

    结论

    不同的服务器配置和使用场景需要不同的内核参数调整策略。高性能服务器可以承受更高的参数值,以充分利用其硬件资源和网络能力,而资源有限的服务器需要更谨慎地调整参数,以避免资源耗尽和系统不稳定。通过性能测试和监控,可以找到最适合特定环境和负载的参数配置。


    避免系统TCP半连接或全连接队列满的情况,通常需要从以下几个方面入手:调整系统参数、优化应用程序、增强硬件资源等。以下是一些具体的方法和建议:

    调整系统参数

    1. 增大全连接队列大小 (net.core.somaxconn)
       sysctl -w net.core.somaxconn=4096

    该参数定义了全连接队列的最大长度。通过增大这个值,可以允许更多的已完成三次握手的连接排队等待应用程序处理。

    1. 增大半连接队列大小 (net.ipv4.tcp_max_syn_backlog)
       sysctl -w net.ipv4.tcp_max_syn_backlog=8192

    该参数定义了半连接队列的最大长度。通过增大这个值,可以处理更多的SYN请求,避免在高并发情况下丢失连接请求。

    1. 启用Syn Cookies (net.ipv4.tcp_syncookies)
       sysctl -w net.ipv4.tcp_syncookies=1

    Syn Cookies 是一种防止SYN Flood攻击的方法。当半连接队列满时,启用Syn Cookies可以确保服务器仍然能够处理新的SYN请求,避免拒绝合法的连接请求。

    优化应用程序

    1. 提高应用程序处理连接的速度: 确保应用程序能够快速处理新连接,减少连接在队列中的等待时间。这可以通过优化代码、使用更高效的算法和数据结构来实现。
    2. 使用异步I/O或多线程/多进程模型: 采用异步I/O(如epollkqueue)或多线程/多进程模型来处理高并发连接,可以显著提高应用程序的处理能力。
    3. 合理设置连接超时: 确保应用程序设置了合理的连接超时,以便及时关闭空闲或无效的连接,释放资源。

    增强硬件资源

    1. 升级服务器硬件: 提高服务器的CPU、内存和网络接口的性能,可以增强服务器处理并发连接的能力。
    2. 使用负载均衡: 通过负载均衡器(如Nginx、HAProxy)将流量分发到多个后端服务器,减轻单个服务器的压力。

    网络配置和架构优化

    1. 分布式架构: 使用分布式系统架构,将负载分散到多个节点上,减少单点压力。
    2. 优化网络拓扑: 确保网络拓扑结构合理,减少网络瓶颈,提升整体网络性能。

    持续监控和调整

    1. 监控系统和应用性能: 使用监控工具(如Prometheus、Grafana)监控系统的队列长度、连接数、CPU、内存等指标,及时发现和解决性能瓶颈。
    2. 定期调整参数: 根据监控数据和实际负载情况,定期调整系统参数,以适应变化的业务需求。

    示例配置

    以下是一个示例的sysctl配置文件,用于调整TCP参数以应对高并发连接:

    # 增大全连接队列大小
    net.core.somaxconn=4096
    
    # 增大半连接队列大小
    net.ipv4.tcp_max_syn_backlog=8192
    
    # 启用Syn Cookies
    net.ipv4.tcp_syncookies=1
    
    # 减少TIME_WAIT状态连接的生命周期
    net.ipv4.tcp_fin_timeout=15
    
    # 允许TIME_WAIT状态的套接字被快速重用
    net.ipv4.tcp_tw_reuse=1
    
    # 设置本地端口范围
    net.ipv4.ip_local_port_range="1024 65535"
    
    # 增大网络接口接收队列大小
    net.core.netdev_max_backlog=5000

    将以上配置添加到/etc/sysctl.conf文件中,并通过以下命令使其生效:

    sysctl -p

    通过以上方法,可以有效避免系统TCP半连接或全连接队列满的情况,提高服务器在高并发场景下的性能和稳定性。


    关于65536端口以及TCP连接的相关问题

    根据《百万并发连接、65536和Linux TCP/IP 性能优化》,以下是一些关键要点:

    端口号的范围和使用

    • 在TCP协议中,端口号占用两个字节,即16位,所以总共有2^16=65536个端口号。
    • 系统通常保留0-1023端口作为知名服务端口,因此实际可用的端口数为65536 – 1024 = 64512个。
    • 作为客户端,同一时刻对同一个服务器同一个端口可以创建最多64512个TCP连接。

    服务器的连接限制

    • 服务器没有“65536”端口数量的限制。服务器可以处理多少客户端连接,取决于服务器的CPU、内存等硬件资源。
    • 一个TCP连接的唯一性由以下四元组决定:ServerIP, ServerPort, ClientIP, ClientPort。因此,服务器可以与多个客户端建立大量的并发连接。

    套接字和端口的误解

    • 接受(accept)之后产生的已连接套接字不会占用新的端口。新生成的套接字文件描述符(socket fd)用于区分客户端连接,其中包含客户端的IP和端口信息。

    百万并发连接的系统配置

    为了支持大量的并发连接,可以对系统进行以下优化配置:

    文件描述符数量

    sysctl -w fs.file-max=10485760 # 系统允许的文件描述符数量设置为1000万
    ulimit -n 1048576 # 单个进程的最大文件描述符数设置为100万
    echo '* soft nofile 1048576' >> /etc/security/limits.conf
    echo '* hard nofile 1048576' >> /etc/security/limits.conf

    TCP读写缓冲区大小

    sysctl -w net.ipv4.tcp_rmem=1024 # 每个TCP连接的读取缓冲区设置为1k
    sysctl -w net.ipv4.tcp_wmem=1024 # 每个TCP连接的写入缓冲区设置为1k

    本地端口范围

    sysctl -w net.ipv4.ip_local_port_range='1024 65535'

    TIME_WAIT连接的处理

    sysctl -w net.ipv4.tcp_tw_recycle=1  # 快速回收TIME_WAIT的连接
    sysctl -w net.ipv4.tcp_tw_reuse=1    # 允许将TIME-WAIT sockets重新用于新的TCP连接
    sysctl -w net.ipv4.tcp_max_tw_buckets=10000 # 系统同时保持TIME_WAIT套接字的最大数量

    其他重要参数

    sysctl -w net.core.netdev_max_backlog=400000 # 网络设备接收数据包的队列最大数目
    sysctl -w net.core.somaxconn=100000 # socket监听的backlog上限
    sysctl -w net.ipv4.tcp_max_syn_backlog=8192 # SYN队列长度
    sysctl -w net.ipv4.tcp_syncookies=1 # 开启SYN Cookies
    sysctl -w net.ipv4.tcp_timestamps=1 # 开启TCP时间戳
    sysctl -w net.ipv4.tcp_fin_timeout=10 # FIN-WAIT-2状态的保持时间
    sysctl -w net.ipv4.tcp_keepalive_time=1800 # keepalive消息的发送频度
    sysctl -w net.ipv4.tcp_keepalive_probes=3 # keepalive探测包的发送次数
    sysctl -w net.ipv4.tcp_keepalive_intvl=15 # keepalive探测包的发送间隔

    通过以上配置,可以显著提升Linux系统的TCP/IP性能,支持大规模的并发连接。

  • WordPress的对象缓存

    在当今这个数字化时代,网站的速度和性能对用户体验和商业成功至关重要。如果你的网站加载缓慢,用户可能会失去耐心,导致高跳出率和转化率的降低。幸运的是,WordPress提供了一种有效的解决方案来提升网站性能——对象缓存(Object Caching)。

    什么是对象缓存?

    对象缓存是一种服务器端的缓存机制,它通过将数据库查询结果存储在快速访问的内存中,从而加快数据的检索速度。当你的网站收到一个请求,服务器会检查是否已经缓存了该请求的结果。如果是,服务器将直接使用缓存的数据,而不是重新查询数据库。这种方式减少了服务器的负载,尤其在流量高峰期,可以显著提高网站的响应速度和用户体验。

    WordPress的对象缓存

    WordPress拥有一个内置的对象缓存系统,名为WP_Object_Cache。这个系统可以自动将数据库中的数据存储在PHP内存中,避免了重复查询数据库的需要。默认情况下,WordPress的对象缓存是临时的,它只为单个页面加载存储数据,请求结束后,缓存数据会被丢弃。

    为了解决这个问题,可以使用持久性缓存工具,如Redis或Memcached。这些工具可以跨多个页面加载缓存对象,进一步提升性能。

    如何使用WordPress的对象缓存

    要在你的WordPress网站上启用对象缓存,通常需要通过FTP客户端连接到你的网站后台,或者使用你托管账户仪表板中的文件管理器。然后,导航到网站的根目录并找到wp-config.php文件。在该文件中,确保ENABLE_CACHE的值被设置为“true”。

    此外,如果你的网站流量较大,可能需要考虑使用持久性缓存工具。这些工具可以在整个服务器上存储缓存数据,从而减少数据库的查询次数,提高网站加载速度。

    为什么对象缓存对网站至关重要

    对象缓存不仅可以提高网站的速度,还能减轻服务器的负担。在高流量时段,服务器不必每次都查询数据库,这可以显著减少服务器的负载,提高响应速度。此外,使用对象缓存还可以改善用户体验,因为用户可以更快地加载网页。

    结论

    对象缓存是提升WordPress网站性能的有效手段。无论是通过内置的WP_Object_Cache还是通过安装额外的持久性缓存工具,对象缓存都能帮助你的网站更快、更高效地运行。如果你的网站正遭受加载速度慢和用户满意度低的问题,那么现在是时候考虑启用对象缓存了。

    记住,优化网站性能是一个持续的过程,而对象缓存只是其中的一部分。通过不断学习和应用最佳实践,你可以确保你的网站始终保持快速和用户友好。

  • Web 图形技术人才:稀缺的“魔法师”

    在网页上实现炫酷的 3D 效果、流畅的动画和逼真的游戏画面,这背后离不开 Web 图形技术人才的辛勤付出。然而,这些掌握着 WebGL 和 WebGPU 等技术的开发者,却如同稀缺的“魔法师”,数量有限,难以满足日益增长的市场需求。

    Web 图形技术的“高门槛”

    想要成为一名 Web 图形技术人才,并非易事。除了需要掌握基本的 Web 开发技能,还需要具备一定的图形学基础,例如线性代数、矩阵变换、渲染管线等知识。此外,WebGL 和 WebGPU 的 API 复杂,调试工具也不够成熟,学习曲线较为陡峭。

    市场需求与人才缺口

    尽管 Web 图形技术的应用场景不断扩展,例如游戏、数据可视化、3D 建模等,但相比于其他 Web 开发领域,市场需求仍然相对较小。同时,由于开发成本较高,一些企业更倾向于选择 Canvas 或 SVG 等替代方案。

    学习资源与社区支持

    与其他 Web 开发技术相比,WebGL 和 WebGPU 的学习资源相对匮乏,开发者社区规模也较小,这给初学者带来了更大的挑战。

    未来展望:人才需求将逐渐增加

    随着 Web 技术的不断发展,Web 图形应用将会越来越普及,对 Web 图形技术人才的需求也会逐渐增加。未来,掌握 WebGL 和 WebGPU 等技术的开发者将会拥有更广阔的职业发展空间。

    如何成为 Web 图形技术人才?

    • 打好基础: 学习计算机图形学基础知识,例如线性代数、矩阵变换、渲染管线等。
    • 掌握 API: 学习 WebGL 或 WebGPU 的 API,并进行实践练习。
    • 利用学习资源: 寻找相关的学习资料,例如书籍、教程、在线课程等。
    • 参与社区: 加入开发者社区,与其他开发者交流经验,获取技术支持。

    结语

    Web 图形技术人才虽然稀缺,但其重要性不容忽视。随着 Web 图形应用的不断发展,这些“魔法师”将会在未来扮演更加重要的角色。

  • WebGL 和 WebGPU 人才稀缺的原因:技术难度与市场需求

    WebGL 和 WebGPU 作为浏览器端的图形渲染技术,能够实现复杂的 3D 图形和动画效果,但精通这些技术的人才却相对较少。这主要归因于以下几个因素:

    技术难度:

    • 图形学基础: WebGL 和 WebGPU 需要开发者具备一定的图形学基础,例如线性代数、矩阵变换、渲染管线等知识。
    • API 复杂: WebGL 和 WebGPU 的 API 相对复杂,需要开发者掌握大量的函数和参数,以及底层的图形渲染原理。
    • 调试困难: 浏览器端的图形调试工具不如桌面端成熟,调试 WebGL 和 WebGPU 程序相对困难。

    市场需求:

    • 应用场景有限: 目前 WebGL 和 WebGPU 的主要应用场景集中在游戏、数据可视化、3D 建模等领域,市场需求相对较小。
    • 替代方案: 对于一些简单的图形渲染需求,可以使用 Canvas 或 SVG 等技术实现,无需使用 WebGL 或 WebGPU。
    • 开发成本: 使用 WebGL 或 WebGPU 开发图形应用的成本相对较高,需要投入更多的时间和精力。

    其他因素:

    • 学习资源: 相比于其他 Web 开发技术,WebGL 和 WebGPU 的学习资源相对较少,学习曲线较为陡峭。
    • 社区支持: WebGL 和 WebGPU 的开发者社区规模较小,获取技术支持和交流经验的机会相对较少。

    总结:

    WebGL 和 WebGPU 人才稀缺的原因是多方面的,包括技术难度、市场需求、学习资源和社区支持等因素。随着 Web 图形应用的不断发展,对 WebGL 和 WebGPU 人才的需求也会逐渐增加,但短期内人才缺口仍然存在。