博客

  • 新iPad Pro暴力测试:史上最薄苹果产品容易弯折吗?

    近日,维纳斯在《挨踢1024》栏目发布了一则关于新iPad Pro的暴力测试。作为史上最薄的苹果产品,这款iPad Pro的耐用性备受关注。测试结果显示,新iPad Pro在一定程度上确实比较容易弯折。测试视频中,通过施加外力,iPad Pro出现了明显的弯曲现象,显示出其在结构上的脆弱性。

    在评论区,用户们纷纷表达了自己的看法。有些人认为,作为高端电子产品,iPad Pro的设计应该在美观与耐用之间找到平衡;而另一些人则表示,尽管它容易弯折,但作为日常使用的工具,只要不故意施加极端压力,完全可以满足需求。

    总体来看,这次测试为消费者提供了一个参考,让大家在购买时可以更全面地了解产品的特点和潜在问题。如果你也对新iPad Pro的耐用性有疑问,不妨观看这则暴力测试视频,进一步了解产品性能。

    更多详细内容请访问原文链接:新iPad Pro暴力测试


    了解更多:

    1. 抽屉新热榜
    2. 最佳第6人
  • ICMP隧道详解

    ICMP隧道是一种利用ICMP协议进行通信的隧道技术。它可以在不开放端口的情况下进行通信,因此具有一定的隐蔽性。下面将从技术原理、实现方式和检测防御等方面详细解释ICMP隧道的相关知识。

    技术原理

    ICMP隧道的实现原理是替换ICMP数据包中的Data部分。在ICMP协议中,Data部分用于传输数据。通过修改Data部分的内容,可以实现在ICMP数据包中传输自定义的数据。具体原理如下:

    1. ICMP协议结构:ICMP数据包的结构包括协议头部和Data部分。协议头部占据了0~31个字节,剩下的部分就是Data部分。
    2. Data部分的默认内容:不同操作系统的ping命令在发送ICMP数据包时,Data部分的内容是不同的。例如,Windows系统默认传输32字节的数据,内容是abcdefghijklmnopqrstuvwabcdefghi;Linux系统默认传输48字节的数据,内容是!”#$%&'()+,-./01234567。
    3. 替换Data部分:ICMP隧道的关键就是替换Data部分的内容。对于Windows系统,只需要替换Data部分并重新计算校验和即可。对于Linux系统,替换Data部分后还需要满足其他规律,以防止链路上的设备对错误包进行抛弃处理。

    实现方式

    实现ICMP隧道需要编写自己的ICMP发送和应答程序,以替代系统本身的ping程序。具体实现方式如下:

    1. ICMP隧道服务器端:服务器端是必须的,用于接收和处理ICMP隧道的请求和应答。可以通过关闭系统的ping应答程序,并启动自己的ICMP应答程序来实现。服务器端需要指定一个独立的公网IP和一个代理端的公网IP,其中代理端的公网IP可以通过在服务器端上使用tcpdump icmp命令获取。
    2. ICMP隧道代理端:代理端是ICMP隧道的客户端,用于与服务器端进行通信。代理端需要指定服务器端的公网IP和代理端自身的公网IP。在与服务器端建立连接后,代理端可以通过修改ICMP数据包的Data部分来传输自定义的数据。

    检测防御

    由于ICMP隧道具有一定的隐蔽性,因此对于网络安全而言,检测和防御ICMP隧道是非常重要的。常见的检测和防御方法包括:

    1. 流量分析:通过对网络流量进行分析,检测出异常的ICMP数据包,如数据包大小、频率等。
    2. IDS/IPS系统:使用入侵检测系统(IDS)或入侵防御系统(IPS)来监测和阻止ICMP隧道的传输。
    3. 防火墙配置:在防火墙中配置规则,限制ICMP数据包的传输,或者对ICMP数据包进行深度检测和过滤。
    4. 网络监控:通过实时监控网络流量和日志,及时发现和应对ICMP隧道的使用。

    综上所述,ICMP隧道是一种利用ICMP协议进行通信的隧道技术,具有一定的隐蔽性。在实际应用中,需要注意检测和防御ICMP隧道的使用,以保障网络安全。


    Learn more:

    1. 小白必看!ICMP隐蔽隧道从入门到精通 – 知乎
    2. 内网转发及隐蔽隧道 | 网络层隧道技术之ICMP隧道(pingTunnel/IcmpTunnel)-腾讯云开发者社区-腾讯云
    3. ICMP隧道通信原理与通信特征 – FreeBuf网络安全行业门户

    ICMP隧道技术是一种利用ICMP协议实现数据传输的隐蔽技术。它在网络通信中具备高度隐蔽性,能够绕过防火墙和入侵检测系统。让我们从技术原理、实现方式和检测防御三个方面详细探讨ICMP隧道技术。

    技术原理

    ICMP(Internet Control Message Protocol)是TCP/IP协议簇中的一个重要子协议,主要用于在IP主机和路由器之间传递控制消息,诸如网络是否连通、主机是否可达、路由是否可用等信息。ICMP隧道正是利用ICMP协议的这一特性,通过在ICMP报文中嵌入数据来实现隐蔽的数据传输。

    ICMP隧道的技术核心在于:

    1. ICMP报文结构:ICMP报文包含类型字段和代码字段,用于标识报文的类型。常见的ICMP类型包括ICMP_ECHO(请求)和ICMP_ECHOREPLY(应答)。
    2. 数据嵌入:ICMP报文可以携带数据,通常利用ICMP_ECHO和ICMP_ECHOREPLY报文的可选数据字段嵌入需要传输的数据。
    3. 隐蔽性:由于ICMP报文通常由系统内核处理,不占用特定端口,并且很多防火墙默认允许ICMP报文通过,因此ICMP隧道具有较高的隐蔽性。

    实现方式

    实现ICMP隧道通常需要编写客户端和服务器端程序,分别在受控主机和攻击者主机上运行。以下是常见的实现工具及其工作原理:

    1. icmpsh:这是一个简单且便携的ICMP隧道工具。受控端(客户端)使用C语言实现,运行在目标Windows机器上,而主控端(服务端)可以运行在任何平台上,支持C、Perl和Python等多种实现版本。
      • 建立隧道:受控端接收ICMP_ECHO数据包,解出其中隐藏的命令并执行,然后将执行结果封装在ICMP_ECHOREPLY数据包中发送给主控端。
      • 数据包分析:通过抓包可以看到传输的命令和响应数据。
    2. icmptunnel:通过创建虚拟网卡,将所有流量都路由到虚拟网卡,并封装在ICMP回显数据包中传输。
      • 建立隧道:客户端主机的所有用户流量都通过虚拟网卡tun0,icmptunnel在此接口上侦听IP数据包并封装在ICMP回显包中。
      • 数据包分析:抓包分析可以看到通过ICMP隧道传输的所有流量。
    3. ptunnel:支持大多数操作系统,包括Windows(需要WinPcap)。
      • 建立隧道:使用命令行工具建立ICMP隧道,类似于其他ICMP隧道工具,通过封装和解封装ICMP数据包实现数据传输。

    检测防御

    尽管ICMP隧道具有隐蔽性,但通过合理的检测和防御措施仍然可以有效防范其威胁。

    1. 流量分析:通过监控网络流量,分析ICMP报文的频率、大小和内容,识别异常的ICMP通信行为。
    2. IDS/IPS系统:部署入侵检测系统(IDS)和入侵防御系统(IPS),设置规则检测和阻止异常ICMP流量。
    3. 防火墙配置:在防火墙中配置策略,限制ICMP报文的传输,或对ICMP报文进行深度检测和内容过滤。
    4. 网络监控:实时监控网络流量和日志,及时发现和响应ICMP隧道的使用。

    总的来说,ICMP隧道技术是一种有效的隐蔽通信手段,但通过合理的检测和防御措施,可以有效降低其带来的安全风险。网络管理员需保持警惕,持续更新防御策略,以应对不断变化的安全威胁。


    Learn more:

    1. 小白必看!ICMP隐蔽隧道从入门到精通 – 知乎
    2. 内网转发及隐蔽隧道 | 网络层隧道技术之ICMP隧道(pingTunnel/IcmpTunnel)
      1. ICMP隧道通信原理与通信特征 – FreeBuf网络安全行业门户







    进一步阅读与参考

    对于那些对ICMP隧道技术有更深入兴趣的读者,可以参考以下资源:

    1. 《小白必看!ICMP隐蔽隧道从入门到精通》:这篇文章详细介绍了ICMP隧道的基本概念、实现方法和常见工具,适合新手入门学习。
    1. 《内网转发及隐蔽隧道 | 网络层隧道技术之ICMP隧道(pingTunnel/IcmpTunnel)-腾讯云开发者社区-腾讯云》:该文章深入探讨了ICMP隧道的网络层实现,适合有一定网络基础的读者进行学习。
    1. 《ICMP隧道通信原理与通信特征 – FreeBuf网络安全行业门户》:这篇文章从专业角度分析了ICMP隧道的通信原理与特征,并提供了防御和检测方案,适合网络安全从业者参考。

    通过阅读这些资源,读者可以更全面地了解ICMP隧道技术,并在实际工作中应用所学知识,提升网络安全水平。

    结语

    ICMP隧道技术虽然具有隐蔽性强、绕过防火墙等优点,但这也使它成为网络攻击者常用的手段之一。作为网络安全从业者,我们需要深刻理解其原理与实现方式,才能在实际工作中有效地检测和防御这种隐蔽通信技术。希望通过本文的介绍,读者能对ICMP隧道有一个全面的认识,并能在日常工作中更好地保障网络安全。

    如果您有任何疑问或需要进一步讨论,欢迎在评论区留言,我们将尽力解答您的问题。


    Learn more:

    1. 小白必看!ICMP隐蔽隧道从入门到精通 – 知乎
    2. 内网转发及隐蔽隧道 | 网络层隧道技术之ICMP隧道(pingTunnel/IcmpTunnel)-腾讯云开发者社区-腾讯云
    3. ICMP隧道通信原理与通信特征 – FreeBuf网络安全行业门户
  • 提升 BiglyBT 对 IPFS 友好的处理方式:更好地处理作为 Webseeds 使用的 IPFS URL

    在当今数字内容共享的时代,文件的分布和传输方式正在快速演变。BitTorrent 和 IPFS(InterPlanetary File System)是两个强大的工具,分别在文件共享和分布式存储领域占据重要地位。然而,这两个工具之间的互操作性尚有提升空间。最近,有用户在 GitHub 上提出了一项建议,旨在改进 BiglyBT 客户端对 IPFS URL 作为 Webseeds 的处理方式,从而使其更加 IPFS 友好。这一提议不仅有助于提升文件传输效率,还能进一步推动去中心化网络的发展。

    现状与问题

    当前,当 BiglyBT 遇到一个公共 IPFS 网关 URL 作为 Webseed 时,它会尝试连接到该网关。然而,通过公共网关下载托管在 IPFS 上的文件,特别是大文件,效率往往不高。用户 hollownights 提出,BiglyBT 应该在检测到公共 IPFS 网关 URL 作为 Webseed 时,自动将其重写为由本地主机提供的路径格式 URL,或者如果检测到使用“ipfs:”协议的原生 IPFS URL,则将其重写为子域网关 URL。

    具体而言,URL 的重写方式如下:

    • 公共网关 URL:https://gateway-host.tld/ipfs/{cid} → http://localhost:8080/ipfs/{cid}
    • 原生 IPFS URL:ipfs://{cidv1} → http://{cidv1}.ipfs.localhost:8080

    重写后,BiglyBT 将发起请求并等待 HTTP 206(或200)响应。如果收到响应,则继续连接;如果未收到响应,则放弃本地主机 URL,回退到公共网关 URL 或直接丢弃原生 URL。

    提议的改进

    hollownights 还提出,这种行为可以通过与 IPFS 软件进行通信(通过命令行或 API)进一步优化,但目前以保持简单为目标。此更改结合自动将下载的文件/文件夹导入本地 IPFS 节点的选项(#2823),将显著提高去中心化 Web 协议(IPFS)与去中心化“文件协议”(BitTorrent)之间的互操作性。

    此外,parg 对此提出了一些疑问:谁会使用这些 IPFS Webseeds?如果这些 Webseeds 被添加到公开发布的种子文件中,那么大多数用户不会运行本地 IPFS 节点。如果这些 Webseeds 仅限于 IPFS 社区,为什么还要使用种子文件?

    hollownights 解释道,这种方法不仅仅是为了增加种子,还可以帮助像互联网档案馆这样的网站更好地将 Web 协议和 BitTorrent 结合起来。他进一步指出,如果 BitTorrent 客户端能够与本地 IPFS 节点通信,将更容易在 IPFS 网络中填充文件和节点,解决(或至少减轻)Web 问题。

    实际应用

    虽然 parg 认为公众大规模安装和维护 IPFS 节点的可能性不大,但 hollownights 强调这项改进主要面向已经托管种子盒和 IPFS 节点的用户。这些用户通常会发布大量文件,并希望在不同网络之间分发这些文件,同时节省带宽。对于网站而言,这意味着可以使用 IPFS 分发网站上的文件,同时通过种子文件利用用户的带宽。如果用户托管 IPFS 节点,那么这种方式将更加高效;如果没有,一切将如常进行。

    通过这些改进,BiglyBT 将更好地支持 IPFS,从而推动去中心化网络的发展。这不仅有助于提高文件传输效率,还能让更多用户受益于去中心化技术的优势。

    https://github.com/BiglySoftware/BiglyBT/issues/2822
  • 巴尔的摩大桥事故:21名船员的“无期徒刑”

    七周前,巴尔的摩的弗朗西斯·斯科特·基大桥在一场意外中被撞断,这一消息至今仍在许多人的记忆中挥之不去。尽管事故已经发生了这么久,桥体的清理工作却进展缓慢,仅有部分残骸得到清理。直到5月13日,卡在船头的部分桥体才终于被爆破拆除。

    这起事故的起因可以追溯到“达利号”货船及其上的21名船员。时隔七周,这些船员仍然困在原地,不被允许离开。讽刺的是,这个地点距离最近的港口只有2海里(约3.7公里)。对于这些船员来说,这简直像是一场“无期徒刑”。

    事故的全貌

    事故的详细过程在之前的报道中已经提及,这里简要回顾一下:达利号原计划从巴尔的摩出发,经过27天的航程,将货物运至斯里兰卡。然而,这艘船在刚刚出发后不久便遭遇了事故。

    5月14日,美国运输安全委员会(NTSB)发布了一份24页的初步调查报告,揭示了事故的部分内幕。《纽约时报》梳理了其中的重点,指出达利号在出事前一天至少经历了两次电力故障,导致船员在出发前大约十个小时对电力系统进行了调整。虽然调整的具体影响尚不明确,但事故的直接原因是船舶的断路器跳闸,致使其丧失了推进和转向能力。

    达利号配备了四台柴油发动机驱动的发电机。NTSB的调查显示,事故当天使用的柴油并无质量问题,问题出在前一天船员在其中一台发动机上安装废气清洁系统时误关了排气风门,导致发动机熄火,发电机也随之关闭。后来船员用另一台发电机短暂恢复了供电,但燃料压力不足,导致这台发电机也跳闸。在恢复供电过程中,船员将断路器换成了备用的,这可能为后来的事故埋下了隐患。

    事故过程

    3月26日凌晨1点左右,达利号驶离巴尔的摩港,沿繁忙的航道前进。所有系统表面上都能正常工作,负责该船的高级驾驶员将控制权交给了一名学徒,自己则在一旁待命。然而,在接近大桥时,备用断路器突然跳闸引发停电,推进和转向系统随之失灵。高级驾驶员赶紧接过控制权,并下令船舵向左舷急转并抛锚。然而,另两个断路器也跳闸了,导致第二次停电。最终,达利号无可避免地与大桥相撞。

    事故的后果

    这起事故不仅导致六名建筑工人遇难,还对巴尔的摩港和弗朗西斯·斯科特·基大桥的商业运输造成了严重影响。据多家保险公司估计,此次事故造成的保险损失可能高达10至40亿美元,甚至被伦敦劳合社主席认为可能成为有史以来最大的一次海上保险损失。

    现在,律师们已经摩拳擦掌准备开始法律战斗。《华尔街日报》估计,整个诉讼可能会持续十年之久。

    谁来承担责任?

    调查人员收集了船上的数据,并与船员进行了交谈,所有船员的酒精测试结果均为阴性。然而,巴尔的摩市政府坚持认为事故由船员责任造成,已经起诉了达利号的拥有者和管理者,指责两家公司提供了“无能的船员”。

    此外,由于事故影响巨大,FBI也展开了刑事调查。由于签证限制、缺乏“上岸通行证”,再加上NTSB和FBI的联合调查,21名船员中有20名印度籍,1名斯里兰卡籍,他们都无法下船。FBI还没收了他们的所有通讯设备,使他们与外界几乎隔绝。

    船员的困境

    由于原定航程只需一个月,船员们的食物早已吃光。现在他们靠美国海岸警卫队提供的补给维持基本生活。船上的卫生条件也变得越来越糟糕,船员们无法获得足够的医疗用品和必要的维护工具。可以想象,这些船员每天都生活在焦虑和不确定中,既要应对长时间的拘留,又要面对可能的法律后果。

    船员的心声

    尽管他们的处境艰难,但这些船员依然保持了基本的职业操守。船员之一,阿尼尔·库马尔,在接受电话采访时表示:“我们每天都在等待,等待可以离开这艘船,等待可以回家。我们希望事情能尽快得到解决,这种等待的煎熬实在难以忍受。”

    未来的走向

    目前,达利号的船东和管理公司正积极配合调查,并为船员争取早日回国的权利。律师们也在为船员们争取法律上的公正待遇,试图证明事故是由于设备故障和操作失误所致,而非船员的个人责任。

    从法律角度来看,这起事故涉及多个方面的责任认定,包括设备制造商、船东、管理公司以及船员本身。无论最终结果如何,这起事故无疑将对未来的海上运输和安全标准产生深远影响。

    结语

    巴尔的摩大桥事故不仅是一次海上运输的惨剧,更是对人类耐力和职业道德的考验。21名船员在如此艰难的情况下仍然坚守岗位,等待最终的调查结果,这种精神值得我们尊敬和关注。

    然而,事故背后的深层次问题,如船舶设备的维护、船员的培训和管理、以及全球航运业的法律和监管机制,都需要进一步探讨和改进。希望这起事故能为未来的海上运输安全提供有益的教训,避免类似悲剧再次发生。


    希望通过详细描述巴尔的摩大桥事故的全过程,不仅让读者了解事件的来龙去脉,还能引发对海上运输安全和船员权益的关注。

  • 外包式儿童陪伴师:高薪背后的隐忧

    月入四万的“外包父母”,正掏空孩子的童年-虎嗅网 (huxiu.com)


    近些年,随着生活节奏的加快和工作压力的增大,越来越多的家长选择将育儿工作外包给所谓的“儿童成长陪伴师”。这种现象尤其在北上广深等大城市中愈发普遍。然而,这种外包育儿的模式真的能解决家长们的育儿难题吗?还是说它反而在无意间掏空了孩子们的童年?

    外包式儿童陪伴师:高薪背后的隐忧

    “儿童成长陪伴师”这个听起来颇为高大上的职业,其实就是代替家长陪伴孩子,监督他们的学习和生活。从帮助孩子完成作业到接送孩子参加各种培训班,这些陪伴师几乎包办了家长的所有职责。根据虎嗅网的报道,这些陪伴师的月薪可以高达四万元,市场需求也异常火爆。

    然而,问题也随之而来。首先,这个行业并没有被官方认定,自然缺乏相关的行业规范,服务质量参差不齐。陪伴师的资质也良莠不齐,有些只是换了个马甲的补课老师,实际提供的服务并不值那么高的费用。

    家长角色的缺位

    家长们选择外包陪伴,很大程度上是因为工作忙碌,无法抽出时间陪伴孩子。然而,真正的亲子关系需要的是家长的亲身参与和情感支持,而不是靠外人来完成。长期依赖外包,只会导致亲子关系的疏离。当孩子遇到青春期叛逆、厌学等问题时,家长和孩子之间缺乏沟通和了解,最终只能再次依赖外部的“专家”来解决问题。

    赵菊英式教育:短期见效的陷阱

    “网红”家庭教育专家赵菊英,以其极端的教育方式吸引了不少家长的关注。她通过砸烂孩子的玩具,来逼迫孩子形成所谓的“梦想”。这种“羞辱惩罚式”教育不仅无法真正解决问题,反而容易引发孩子的极端反抗和对家长的仇恨。

    赵菊英的“成功”并非个例,而是反映了家长们在教育上的焦虑和急功近利的心态。他们希望通过短期的努力和金钱的投入,迅速把孩子培养成“学霸”,但这种方法忽视了孩子作为独立个体的需求和感受。

    真正的亲子关系:不可外包的责任

    家庭教育的核心在于情感交流和价值观的传递,这是任何外聘的专家和陪伴师都无法替代的。家长们与其花钱买陪伴,不如多花时间学习如何真正成为一个好父母。我们不是孩子一出生就自动会当爹妈了,需要不断学习和成长。

    孩子的情感需求是随着年龄增长而不断上升的,家长们需要在孩子成长的过程中,给予足够的关注和支持。如果家长只是通过花钱来解决问题,最终只会导致亲子关系的疏离,甚至可能对孩子的心理健康造成长期的负面影响。

    结语

    短期内,外包育儿看似能够解决家长们的时间和精力问题,但却忽视了亲子关系中的核心:爱与陪伴。真正的亲子关系需要家长的亲身参与和无条件的接纳与理解。希望每一位家长都能意识到,育儿不是一场可以外包的任务,而是一段需要用心经营的旅程。

    让我们一同反思,在忙碌的生活中,如何找到平衡点,给予孩子真正需要的陪伴和支持。

    祝愿每一位家长和孩子都能在爱与理解中共同成长。

    (原文来自微信公众号:格致君,作者:格致君的后花园,更多精彩内容请点击虎嗅网。)

  • 《被互联网辜负的人: 互联网的士绅化如何制造了数字不正义》

    内容简介

    想象你生活的城市地图上忽然多出来一块空地,规划者邀请有特定背景的人加入,包括你,一起在这块空地上建设议事大厅、居住区、运动场、电影院等设施。大家一起添砖加瓦,几个月间空地变成一个富有生活气息的社区,吸引了越来越多的人申请加入。几年后,随着涌入人群一波接一波而来,规划者提议社区建设新空间,迎合新人群、新需求。你和其他初代居民反对,认为这会破坏社区氛围,但规划者不再理会你们的想法。你发现自己陷入了两难:留下,无法参与决定社区的未来;搬走,带不走你在社区里熟识的邻里、你给社区贡献的东西。

    互联网平台有很多工具来吸引、利用和剥削用户,但贡献内容、人气和数据的用户对平台如何运作没有约束力。这是真实发生在互联网近二十年里的事,互联网像遭遇士绅化的老城区,变得更加便捷、光鲜但缺乏多样性,有明确优先服务的人群,极端重视利润而轻视社群,急着把最初搭建社区但已不再有利用价值的人群边缘化甚至扫地出门。本书以美国为例,记录这个过程怎样发生,它的实质危害如何,以及网民可以如何行动。

    各方赞誉

    《被互联网辜负的人》巧妙地拆除了科技巨头的浪漫化概念,帮助读者理解互联网是一个日益孤立、商品化、被监视和失所的地方。这本易读易懂的书绝对是我所有课程的必读书目。

    ——克莱门西亚·罗德里格斯,美国天普大学媒介研究教授

    《被互联网辜负的人》令人信服地证明了士绅化及其在失所、孤立和商业化方面的后果已经从城市经济领域转移到了互联网上。这本书揭示了数字世界是如何从DIY反主流文化的空间转变为企业寡头的游乐场的。

    ——保罗·杰尔包多,伦敦国王学院数字文化中心主任

    杰西·林格尔继承了简·雅各布斯、玛格丽特·米德和丽贝卡·索尔尼特的传统,是美国令人敬畏的新批评声音。

    ——希瓦·维迪亚那桑,《谷歌化的反思》作者

    ed2k://|file|0E01DA59E7D18DFD1A876A5FE3AB5DDA.pdf|55375063|2C0EDB65F55EF50956F5701453E9DBD6|h=M3HLY3XNYYB46G55JLNLCL2BOICUWNPH|/

    ActivityPub 协议支持下的分布式联邦化社区才是最符合大部分人利益的选择。

  • 有关美国可能禁止TikTok的最新进展

    最新进展显示,美国政府对于禁止TikTok的立场仍然坚定。以下是关于美国可能禁止TikTok的最新进展的概述:

    1. 美国众议院通过了一项法案:众议院通过了一项法案,要求中国所有者字节跳动公司(ByteDance)出售TikTok,否则该应用将被禁止在美国使用[1]。该法案得到了两党的支持,因为共和党人和民主党人都担心中国的影响力。
    2. 法案要求出售TikTok:该法案规定,在六个月内必须将TikTok出售给一个让美国政府满意的买家。出售的结果需保证字节跳动不再对TikTok或其向用户推荐内容的算法拥有任何控制权。如果字节跳动不能或拒绝出售TikTok,应用程序商店和网站托管公司在美国分发或更新这款应用程序将变成非法。美国司法部有权对任何与TikTok合作或提供其应用程序下载的公司进行惩罚[1]
    3. TikTok承诺挑战法律:TikTok表示将在法庭上挑战这项法律,并强调其对用户权利的捍卫。公司首席执行官周受资发布视频表示,他们有信心继续为用户的权利而战,并呼吁人们分享TikTok如何改善他们的生活[2]
    4. 法律可能面临挑战:这项法案可能会面临法律挑战,并可能遭到北京的抵制。TikTok可能会依靠第一修正案来辩护,认为强制出售可能侵犯其用户的言论自由权。预计TikTok的案件将在法庭上得到审理[2]
    5. 不确定的出售情况:目前还不清楚谁有财力收购TikTok,因为它的价格会很高。如果被迫出售,目前还不清楚字节跳动是否会卖掉全球业务还是仅卖掉美国业务[1]

    尽管美国政府对禁止TikTok持坚定立场,但是目前仍有许多不确定因素,包括法律挑战和出售情况。因此,TikTok在美国是否会消失仍然不确定。


    Learn more:

    1. 关于众议院通过的针对TikTok法案,你应该知道的 – 纽约时报中文网
    2. 拜登签署针对TikTok法案,接下来会发生什么 – 纽约时报中文网
    3. TikTok禁令:“不卖就禁”局面下它会很快从美国消失吗 – BBC News 中文

    目前,除了美国和印度,还有一些国家也在考虑或已经采取了禁止TikTok的行动。以下是一些国家的情况:

    1. 澳大利亚:澳大利亚政府曾表示,他们在考虑禁止TikTok,担心该应用可能会对国家安全构成威胁[2]
    2. 日本:日本政府也在审查TikTok的安全性,并考虑采取相应的措施。他们担心该应用可能会泄露用户的个人信息[2]
    3. 新加坡:新加坡政府曾表示,他们正在审查TikTok的安全性,并将根据审查结果决定是否采取禁止措施[2]
    4. 印度尼西亚:印度尼西亚政府曾在2020年暂时禁止TikTok,理由是担心该应用可能会传播淫秽和不良内容[2]
    5. 巴基斯坦:巴基斯坦政府曾在2020年禁止TikTok,理由是担心该应用可能会传播淫秽和不道德的内容[2]

    需要注意的是,以上国家的禁止行动可能是临时性的,具体情况可能会随着时间的推移而发生变化。此外,由于TikTok在全球范围内拥有大量用户,禁止措施可能会受到用户和利益相关方的反对和抵制。


    Learn more:

    1. TikTok禁令:“不卖就禁”局面下它会很快从美国消失吗 – BBC News 中文
    2. 为何多个国家试图封禁TikTok – 纽约时报中文网
    3. 為何多個國家試圖封禁TikTok – 紐約時報中文網

    除了安全性问题,还有其他原因导致国家考虑禁止TikTok。以下是一些可能的原因:

    1. 数据隐私和安全:许多国家担心TikTok可能会收集和共享用户的个人数据,这可能对用户的隐私和网络安全构成威胁。这些国家担心用户数据可能被滥用或用于不当目的。
    2. 内容审查和传播控制:一些国家担心TikTok可能被用于传播不良或有害内容,包括政治宣传、虚假信息和仇恨言论。这些国家可能认为TikTok无法有效监管和控制其平台上的内容,因此考虑禁止该应用程序。
    3. 国家安全和外部干涉:一些国家担心TikTok可能被用于外部势力的干涉和操纵,以影响国内政治和社会稳定。这些国家可能认为TikTok的所有权和管理结构使其容易受到外部势力的影响,因此采取禁止措施以保护国家安全。
    4. 经济竞争和垄断担忧:一些国家担心TikTok的垄断地位和市场影响力可能对本国的科技和媒体产业造成不利影响。这些国家可能认为TikTok的垄断地位可能导致市场不公平竞争和经济不平衡,因此考虑限制或禁止该应用程序。

    需要注意的是,不同国家对于禁止TikTok的原因可能有所不同,这些原因可能是单独的或综合的。此外,一些国家可能会采取限制措施而不是完全禁止TikTok。


    Learn more:

    1. 全球近二十个国家和地区封禁TikTok,美国出手将是最重一击
    2. 为何多个国家试图封禁TikTok – 纽约时报中文网
    3. 美国限期“不卖就禁” 还有那些国家禁用TikTok? – DW – 2024年4月28日

    除了禁止,一些国家采取了其他限制措施来应对TikTok的问题。以下是一些国家采取的限制措施:

    • 数据安全和隐私保护措施:
    • 美国:美国参议院通过了《TikTok剥离法案》,要求中国母公司字节跳动将TikTok在美业务出售给美国公司,否则将面临在美国被禁的命运[1]
    • 印度:印度在2020年禁止了TikTok等59款中国应用程序,并在2020年以保护民众数据安全为由发出永久禁令[1]
    • 道德和心理健康问题:
    • 欧盟:欧盟当局认为TikTok容易使人成瘾,尤其是年轻用户,因此暂停了应用程序TikTok Lite的打赏功能,该功能用实体商品的优惠券来奖励观看视频的用户[1]
    • 阿富汗:塔利班重新夺取政权约一年后,于2022年在阿富汗禁用了TikTok,其主要考量是道德因素[1]
    • 伊朗:伊朗的毛拉政权封锁了几乎所有主要社交媒体平台,包括TikTok[1]
    • 吉尔吉斯斯坦、乌兹别克斯坦、尼泊尔和索马里:这些国家也出于类似原因禁止使用TikTok[1]
    • 政府机构限制使用TikTok:
    • 欧盟:欧盟委员会、欧洲理事会和欧洲议会禁止员工在工作设备上使用TikTok,担心中国政府可能获得设备上的数据[1]
    • 美国和加拿大:联邦政府雇员被禁止在工作设备上安装TikTok,多数州也出台了类似规定[1]
    • 澳大利亚、新西兰、英国、荷兰、挪威、丹麦和拉脱维亚等国家政府指示其员工从工作手机中删除TikTok[1]
    • 德国考虑监管:
    • 德国:德国联邦政府并无原则性的规定禁止在工作手机上使用TikTok,但政府设备上既未预装该App,也无法下载。德国执政党正在考虑对TikTok进行监管,或禁止在联邦公务员的手机上使用TikTok[1]

    这些限制措施的目的是保护数据安全、隐私、道德和心理健康等方面的考虑。


    Learn more:

    1. 美国限期“不卖就禁” 还有那些国家禁用TikTok? – DW – 2024年4月28日
    2. 为何多个国家试图封禁TikTok – 纽约时报中文网
    3. 哪些国家禁止Tiktok?原因何在? – DW – 2023年5月22日

  • 马斯克列出征服火星时间表:5年内无人登陆,10年内载人登陆,20年内建造城市,30年内会有文明

    Reblog via 抽屉新热榜

    马斯克列出征服火星时间表:5年内无人登陆,10年内载人登陆,20年内建造城市,30年内会有文明
    dig.chouti.com/link/42431666

  • Libgen中文新书速递:《機器學習最強入門:基礎數學/機率/統計邁向AI真實數據專題實作-王者歸來

    《機器學習最強入門:基礎數學/機率/統計邁向AI真實數據專題實作-王者歸來》
    作者:洪錦魁/洪锦魁
    深智數位股份有限公司 2023
    下載:libgen.is/book/index.php?md5=D

  • 深入浅出:WordPress插件开发基础

    欢迎来到WordPress插件开发的世界!WordPress插件不仅能为您的网站添加各种功能,还能让您在广大的WordPress社区中分享您的创意和技术成果。本文将带您逐步了解WordPress插件的开发基础,从头文件要求到钩子的使用,帮助您快速入门并掌握插件开发的核心要素。

    WordPress插件入门

    一个WordPress插件其实就是一个带有特定头注释的PHP文件。头注释包含了插件的元数据,例如插件名称和作者等。我们强烈建议为插件创建一个专门的目录,以保持文件整齐有序,便于后续维护。

    创建一个简单的插件

    1. 切换到WordPress站点的插件目录
       cd wp-content/plugins
    1. 创建一个新目录
       mkdir plugin-name
       cd plugin-name
    1. 创建一个PHP文件
       vi plugin-name.php

    注意:你可以使用任意你喜欢的文本编辑器来创建和编辑PHP文件。

    1. 添加插件头注释
       <?php
       /*
       Plugin Name: YOUR PLUGIN NAME
       */

    保存文件后,您可以在WordPress后台的插件列表中看到您创建的插件。

    钩子:Action和Filter

    WordPress钩子是插件开发的核心。通过钩子,您可以在特定的时机介入WordPress的代码执行流程,而无需编辑任何核心文件。

    • Action钩子:允许您添加或修改WordPress的功能。
    • Filter钩子:允许您修改用户提交的或展示给用户的内容。

    使用基础钩子

    在创建插件时,需要使用以下三个基础钩子:

    • register_activation_hook()

    插件激活时运行,用于设置插件的初始状态,如添加默认设置等。

      register_activation_hook(__FILE__, 'my_plugin_activate');
      function my_plugin_activate() {
          // 初始化代码
      }
    • register_deactivation_hook()

    插件禁用时运行,用于清理插件数据。

      register_deactivation_hook(__FILE__, 'my_plugin_deactivate');
      function my_plugin_deactivate() {
          // 清理代码
      }
    • register_uninstall_hook()

    插件卸载时运行,用于彻底删除插件数据。

      register_uninstall_hook(__FILE__, 'my_plugin_uninstall');
      function my_plugin_uninstall() {
          // 卸载代码
      }

    添加自定义钩子

    您可以使用do_action()函数添加自定义钩子,其他开发者可以通过这些钩子扩展或修改您的插件。

    do_action('my_custom_hook');

    移除挂载到钩子上的函数

    使用remove_action()可以移除挂载到某个钩子上的函数。

    remove_action('my_custom_hook', 'my_function');

    WordPress API的使用

    WordPress提供了丰富的API,大大简化了插件开发的过程。例如:

    • 选项API:用于将数据保存到数据库中。
    • HTTP API:用于发送HTTP请求。

    插件的发布

    在分享插件之前,请选择适当的许可证。推荐使用GNU通用公共许可证(GPLv2+),以确保与WordPress核心许可证兼容。

    插件头文件要求

    头文件至少需要包含插件名称,并可选择性地包含以下部分:

    • 插件名称:(必需)插件的名称。
    • 插件URI:插件主页的URL。
    • 描述:简短描述,不超过140个字符。
    • 版本:例如1.0或1.0.3。
    • 作者:插件作者的名字。
    • 作者URI:作者的个人网址。

    结语

    通过本文的介绍,您应该已经对WordPress插件开发有了基本的了解。插件开发不仅能提升您网站的功能性,还能为广大WordPress用户提供便利。快来动手开发属于您的第一个插件吧!

    如需更详细的教程和示例,欢迎访问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() {
            .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() {     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,
            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 的支持及其在实际应用中的普及,无用户名登录有望成为一种常见的身份验证方法。然而,在实施这一特性时,需要注意认证器的存储限制和兼容性问题,以确保用户体验的平滑过渡。

  • CBOR (Concise Binary Object Representation)

    CBOR,全称是简明二进制对象表示(Concise Binary Object Representation),是一种编码方式,常用于物联网(IoT)领域。它的设计目标是提供一种体积更小、更高效的二进制格式,类似于 JSON,但更适合资源受限的环境,如物联网设备。

    CBOR 的特点

    • 紧凑性:CBOR 的编码格式比 JSON 更紧凑,减少了数据传输的体积和存储空间。
    • 高效性:由于其二进制格式,解析和生成 CBOR 数据通常比处理文本格式的 JSON 更高效。
    • 自描述性:CBOR 编码的数据包含类型信息,解析时无需额外的模式(schema)。
    • 广泛支持:大部分编程语言都有相应的 CBOR 编码和解码库,可以方便地处理 CBOR 数据。

    CBOR 与 JSON 的比较

    特性CBORJSON
    格式二进制文本
    数据体积较小较大
    解析效率较高较低
    自描述性
    适用场景物联网、嵌入式系统、网络协议等Web 服务、配置文件等

    示例

    以下是一个简单的 JSON 对象及其对应的 CBOR 编码表示:

    JSON 示例:

    {
      "name": "Alice",
      "age": 30,
      "is_student": false
    }

    CBOR 编码表示:

    A3                                      # map(3)
      64                                    # text(4)
        6E616D65                            # "name"
      65                                    # text(5)
        416C696365                          # "Alice"
      63                                    # text(3)
        616765                              # "age"
      18 1E                                 # unsigned(30)
      6A                                    # text(10)
        69735F73747564656E74                # "is_student"
      F4                                    # false

    CBOR 库

    大部分编程语言都有相应的 CBOR 库,可以方便地进行编码和解码操作。以下是一些常见语言的 CBOR 库:

    • Python: cbor2, cbor
    • JavaScript: cbor
    • Go: github.com/fxamacker/cbor
    • Java: com.fasterxml.jackson.dataformat.cbor
    • C/C++: libcbor

    使用示例

    Python 示例:

    import cbor2
    
    # 编码 JSON 对象为 CBOR
    data = {
        "name": "Alice",
        "age": 30,
        "is_student": False
    }
    encoded = cbor2.dumps(data)
    print(encoded)  # 输出 CBOR 二进制数据
    
    # 解码 CBOR 为 JSON 对象
    decoded = cbor2.loads(encoded)
    print(decoded)  # 输出 {'name': 'Alice', 'age': 30, 'is_student': False}

    结论

    CBOR 提供了一种紧凑、高效的二进制编码格式,适用于资源受限的环境,如物联网设备。通过使用现有的 CBOR 库,可以轻松地在各种编程语言中进行 CBOR 数据的编码和解码操作,在提高数据传输效率的同时,保持了对各种复杂数据结构的支持。

  • 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


  • AI搜索:通向未来的关键一步

    引言:AI搜索的崛起

    近年来,AI搜索已经逐渐成为科技领域的一大热点。从Perplexity的新一轮融资,到ChatGPT将其首页变为搜索框,再到国内秘塔AI搜索和360AI搜索的崛起,这一切都预示着AI搜索正在成为新的行业共识。此外,不少企业也纷纷表示要加入这一领域的竞争,显示出AI搜索的巨大市场潜力和吸引力。

    搜索的市场格局

    搜索技术的发展经历了从狭义的搜索引擎到广义的内容发现的转变。狭义的搜索,如百度搜索和浏览器地址栏,已进入平台期,而广义的搜索,包括内容平台内的搜索功能,正处于上升阶段。随着优质内容的分散,用户的搜索需求也日益增长,推动了AI搜索技术的发展。

    AI搜索的本质

    AI搜索的核心优势在于其能够提供超越传统搜索的内容理解和用户体验。AI搜索不仅仅是关于提升搜索结果的相关性,更关键的是通过深度学习和自然语言处理技术,理解用户的真实意图,并提供更准确、个性化的搜索结果。

    用户使用搜索的真实目的

    用户使用搜索工具的最终目的,往往不仅仅是为了找到一个网址或一个答案,而是为了解决实际问题或获取具体的资源。例如,用户可能需要找到特定的信息进行学习研究,或者寻找特定的视频内容进行观看。AI搜索通过更好的理解用户需求,能够提供更符合用户期待的搜索体验。

    AI搜索的切入点和未来方向

    AI搜索需要找到与传统搜索不同的切入点,这通常意味着在特定的垂直领域或新的使用场景下,发挥AI的独特优势。例如,可以在学术研究或医疗信息查询等领域,通过AI搜索提供更专业、更深入的搜索服务。

    AI搜索与内容平台的关系

    成功的AI搜索引擎将是那些能够与内容平台紧密结合,共同构建强大内容生态系统的引擎。例如,通过与内容创作者和平台合作,AI搜索可以更有效地聚合和推荐内容,从而为用户提供更加丰富和精准的搜索结果。

    结论:AI搜索的战略意义

    AI搜索不仅是技术的革新,更是对用户搜索体验的全面革命。随着技术的不断进步和市场的逐渐成熟,AI搜索将成为连接用户需求与信息世界的关键桥梁。对于企业来说,投入AI搜索技术,开发更智能、更个性化的搜索解决方案,将是抓住未来市场机遇的关键。

    通过对AI搜索的深入理解和应用,我们可以预见一个更加智能和连接的信息时代的到来。

  • AI搜索全解析

    AI搜索,这个听起来颇具科技感的词汇,最近在科技圈里掀起了不小的波澜。从Perplexity获得新融资,到ChatGPT将首页改为搜索框,再到国内AI搜索领域的新星——秘塔AI搜索和360AI搜索的崛起,AI搜索似乎正逐渐成为新的行业共识。在这样的背景下,许多公司也开始摩拳擦掌,准备在AI搜索领域大展拳脚。

    AI搜索的市场格局

    首先,让我们来梳理一下搜索市场的格局。传统搜索,无论是百度的主页还是浏览器的地址栏,其市场已经进入一个相对稳定的平台期。随着优质内容逐渐被各大App如抖音、小红书、知乎等分割,传统搜索的体验虽然成熟,但面临着内容质量下降的挑战。

    然而,广义上的搜索,包括App内的搜索条以及对模型内部知识的搜索,其实正在上升期。用户数和搜索频次都在增加,显示出搜索需求的持续增长。

    AI搜索的本质

    AI搜索的核心在于“智能”,而不仅仅是“搜索”。这意味着,AI搜索需要基于大型语言模型(LLM)和其他工具构建的架构,提供更为精准和个性化的搜索结果。AI搜索的目标是更好地理解用户的Query(查询),并提供端到端的解决方案。

    用户迁移的切入点

    要让用户从传统搜索迁移到AI搜索,需要提供显著的新体验。这不仅仅是在搜索结果上做出微小改进,而是要找到新的使用场景和垂直领域,如学术、医疗、法律等,为用户提供真正有价值的新体验。

    AI搜索与内容平台的关系

    AI搜索与内容平台之间存在密切的依赖关系。长期来看,拥有高质量内容的社区在搜索领域更有可能胜出。例如,百度通过构建知道、百科等内容产品,试图控制内容并构建竞争壁垒。而在移动互联网时代,用户的搜索行为已经逐渐转移到各个App中,这对传统搜索引擎构成了挑战。

    AI搜索的具体应用

    一些AI搜索产品已经开始探索具体的应用场景,如:

    • 思维导图:帮助用户条理化地收集资料,提高研究效率。
    • 学术搜索和播客搜索:通过提高输入内容的质量,提升搜索结果的质量,特别适合学术研究和深度内容消费。
    • 一键生成PPT:利用AI搜索结果直接生成演示文稿,提高工作效率。
    • 资讯、视频、图片搜索:满足用户对传统搜索引擎功能的期待。

    搜索市场的机遇与挑战

    尽管AI搜索来势汹汹,但传统搜索依然有其稳固的市场和价值。搜索引擎依然是互联网流量的重要入口,具有巨大的商业价值。同时,随着技术的发展,新的搜索玩家有机会通过创新的场景和体验来挑战现有的市场格局。

    从AI搜索到AGI

    AI搜索是通往更高级的通用人工智能(AGI)的必经之路。通过更好地理解用户的Query,AI搜索将逐步攻克更多复杂场景,满足用户从信息搜索到内容创作的全方位需求。

    在这个过程中,AI搜索不仅需要技术上的突破,更需要对用户需求深刻的洞察和创新的产品思维。随着AI技术的不断进步,我们有理由相信,AI搜索将为用户带来更加丰富和智能的搜索体验。

  • Akka Streams 概念概述

    Akka Streams 是基于 Reactive Streams 规范的流处理库,它提供了一种声明式的 API 来处理和传递数据流。Akka Streams 的核心概念包括:

    1. Source:数据的起点,可以从中产生元素。
    2. Flow:处理数据的步骤,可以对数据进行转换、过滤等操作。
    3. Sink:数据的终点,接收从 SourceFlow 中传递过来的数据。
    4. Materialization:流的实际执行过程,这个过程会产生一个运行时值(如 Future)。

    通过组合 SourceFlowSink,可以构建出复杂的数据流处理逻辑。

    基本示例

    我们通过一个简单的例子来说明 Akka Streams 的基本概念。

    import akka.actor.ActorSystem
    import akka.stream.scaladsl.{Flow, Sink, Source}
    import akka.stream.{ActorMaterializer, Materializer}
    import scala.concurrent.Future
    import scala.util.{Failure, Success}
    
    object AkkaStreamsExample extends App {
      implicit val system: ActorSystem = ActorSystem("example-system")
      implicit val materializer: Materializer = Materializer(system)
      import system.dispatcher  // 用于处理 Future 的回调
    
      // 创建一个 Source,从1到10的整数序列
      val source: Source[Int, NotUsed] = Source(1 to 10)
    
      // 创建一个 Flow,对每个元素乘以2
      val flow: Flow[Int, Int, NotUsed] = Flow[Int].map(_ * 2)
    
      // 创建一个 Sink,打印每个接收到的元素
      val sink: Sink[Int, Future[Done]] = Sink.foreach[Int](println)
    
      // 将 Source、Flow 和 Sink 连接起来,形成一个流
      val runnableGraph: RunnableGraph[Future[Done]] = source.via(flow).toMat(sink)(Keep.right)
    
      // 运行流
      val result: Future[Done] = runnableGraph.run()
    
      // 处理流完成后的结果
      result.onComplete {
        case Success(_) =>
          println("Stream completed successfully")
          system.terminate()
        case Failure(e) =>
          println(s"Stream failed with e")       system.terminate()   } }</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">详细解释</h3> <!-- /wp:heading -->  <!-- wp:list {"ordered":true} --> <ol><!-- wp:list-item --> <li><strong>Source</strong>: <code>val source: Source[Int, NotUsed] = Source(1 to 10)</code> <code>Source</code> 是数据流的起点,这里我们创建了一个从 1 到 10 的整数序列作为数据源。</li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>Flow</strong>: <code>val flow: Flow[Int, Int, NotUsed] = Flow[Int].map(_ * 2)</code> <code>Flow</code> 是数据处理的步骤,这里我们创建了一个 <code>Flow</code>,它将每个输入元素乘以 2。</li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>Sink</strong>: <code>val sink: Sink[Int, Future[Done]] = Sink.foreach[Int](println)</code> <code>Sink</code> 是数据流的终点,这里我们创建了一个打印每个接收到的元素的 <code>Sink</code>。</li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>RunnableGraph</strong>: <code>val runnableGraph: RunnableGraph[Future[Done]] = source.via(flow).toMat(sink)(Keep.right)</code> 我们使用 <code>source.via(flow).toMat(sink)(Keep.right)</code> 将 <code>Source</code>、<code>Flow</code> 和 <code>Sink</code> 连接起来,形成一个完整的流。<code>toMat</code> 方法用于指定如何处理流的 materialized value,这里我们选择保留 <code>Sink</code> 的 materialized value,即 <code>Future[Done]</code>。</li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>运行流</strong>: <code>val result: Future[Done] = runnableGraph.run()</code> 使用 <code>run()</code> 方法来启动流的执行。这个方法会返回一个 <code>Future</code>,表示流的完成状态。</li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>处理流完成后的结果</strong>:<code>scala result.onComplete { case Success(_) => println("Stream completed successfully") system.terminate() case Failure(e) => println(s"Stream failed withe") system.terminate() }
    最后,我们监听 result 的完成情况,打印结果并终止 ActorSystem
  • 复杂示例

    下面是一个更复杂的示例,展示如何处理更复杂的数据流。

    import akka.actor.ActorSystem
    import akka.stream.scaladsl.{Flow, Sink, Source}
    import akka.stream.{ActorMaterializer, Materializer}
    import scala.concurrent.Future
    import scala.util.{Failure, Success}
    
    object ComplexAkkaStreamsExample extends App {
      implicit val system: ActorSystem = ActorSystem("complex-example-system")
      implicit val materializer: Materializer = Materializer(system)
      import system.dispatcher  // 用于处理 Future 的回调
    
      // 创建一个 Source,从1到100的整数序列
      val source: Source[Int, NotUsed] = Source(1 to 100)
    
      // 创建一个 Flow,过滤掉偶数
      val filterFlow: Flow[Int, Int, NotUsed] = Flow[Int].filter(_ % 2 != 0)
    
      // 创建一个 Flow,对每个元素进行平方
      val squareFlow: Flow[Int, Int, NotUsed] = Flow[Int].map(x => x * x)
    
      // 创建一个 Flow,将每个元素转换为字符串
      val stringFlow: Flow[Int, String, NotUsed] = Flow[Int].map(_.toString)
    
      // 创建一个 Sink,将每个接收到的元素打印出来
      val sink: Sink[String, Future[Done]] = Sink.foreach[String](println)
    
      // 将 Source、Flow 和 Sink 连接起来,形成一个流
      val runnableGraph: RunnableGraph[Future[Done]] = source
        .via(filterFlow)  // 过滤掉偶数
        .via(squareFlow)  // 对每个元素进行平方
        .via(stringFlow)  // 将每个元素转换为字符串
        .toMat(sink)(Keep.right)  // 连接到 Sink 并保持其 materialized value
    
      // 运行流
      val result: Future[Done] = runnableGraph.run()
    
      // 处理流完成后的结果
      result.onComplete {
        case Success(_) =>
          println("Stream completed successfully")
          system.terminate()
        case Failure(e) =>
          println(s"Stream failed with e")       system.terminate()   } }</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">详细解释</h3> <!-- /wp:heading -->  <!-- wp:list {"ordered":true} --> <ol><!-- wp:list-item --> <li><strong>创建 Source</strong>: <code>val source: Source[Int, NotUsed] = Source(1 to 100)</code> 我们创建了一个从 1 到 100 的整数序列作为数据源。</li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>创建 Flow</strong>:<ul><li><strong>过滤偶数</strong>:</li></ul><code>val filterFlow: Flow[Int, Int, NotUsed] = Flow[Int].filter(_ % 2 != 0)</code> 这个 <code>Flow</code> 只允许奇数通过。<ul><li><strong>平方元素</strong>:</li></ul><code>val squareFlow: Flow[Int, Int, NotUsed] = Flow[Int].map(x => x * x)</code> 这个 <code>Flow</code> 对每个输入元素进行平方运算。<ul><li><strong>转换为字符串</strong>:</li></ul><code>val stringFlow: Flow[Int, String, NotUsed] = Flow[Int].map(_.toString)</code> 这个 <code>Flow</code> 将每个整数转换为字符串。</li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>创建 Sink</strong>: <code>val sink: Sink[String, Future[Done]] = Sink.foreach[String](println)</code> 这个 <code>Sink</code> 打印每个接收到的字符串。</li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>连接 Source、Flow 和 Sink</strong>: <code>val runnableGraph: RunnableGraph[Future[Done]] = source .via(filterFlow) .via(squareFlow) .via(stringFlow) .toMat(sink)(Keep.right)</code> 我们使用 <code>via</code> 方法将 <code>Source</code> 和多个 <code>Flow</code> 连接起来,最后通过 <code>toMat</code> 方法连接到 <code>Sink</code> 并保持其 materialized value。</li> <!-- /wp:list-item -->  <!-- wp:list-item --> <li><strong>运行流并处理结果</strong>: <code>val result: Future[Done] = runnableGraph.run() result.onComplete { case Success(_) => println("Stream completed successfully") system.terminate() case Failure(e) => println(s"Stream failed withe") system.terminate() } 我们启动流的执行,并监听其完成状态,打印结果并终止 ActorSystem
    
    
    
    

    总结

    通过以上示例,我们可以看到 Akka Streams 提供了一种灵活且强大的方式来处理数据流。你可以使用 Source 作为数据的起点,使用 Flow 来处理数据,并使用 Sink 作为数据的终点。通过组合这些组件,你可以构建出复杂的数据流处理逻辑。同时,Akka Streams 还提供了多种工具和方法来监控和管理流的生命周期,确保流的正确运行和资源的有效管理。

  • akka的 RunnableGraph

    RunnableGraph 是 Akka Streams 的一个重要概念。Akka Streams 是一个基于反应式流(Reactive Streams)规范的库,用于处理异步流数据和背压(backpressure)管理。RunnableGraph 是一个封装了流的拓扑结构的不可变描述,它可以在流运行时物化为具体的值。

    Akka Streams 概述

    在 Akka Streams 中,流的基本构建块包括:

    • Source:可以从中获取数据的流的起点。
    • Flow:处理数据的流的中间部分。
    • Sink:接收数据的流的终点。

    什么是 RunnableGraph?

    RunnableGraph 是一个特殊类型的流拓扑,它已经完全连接,不需要任何额外的操作来形成一个完整的流。它从 Source 开始,通过 Flow 处理数据,最后将数据发送到 Sink

    RunnableGraph 可以通过调用 .run() 方法来启动流的处理。这个方法需要一个 Materializer,它定义了流将如何被实际执行。

    示例

    以下是一个简单的 Akka Streams 示例,展示了如何创建和运行一个 RunnableGraph

    import akka.actor.ActorSystem
    import akka.stream.ActorMaterializer
    import akka.stream.scaladsl.{RunnableGraph, Sink, Source}
    
    object Main extends App {
      implicit val system: ActorSystem = ActorSystem("example-system")
      implicit val materializer: ActorMaterializer = ActorMaterializer()
    
      // 创建一个 Source,从1到10的整数
      val source = Source(1 to 10)
    
      // 创建一个 Sink,将元素打印到控制台
      val sink = Sink.foreach[Int](println)
    
      // 将 Source 和 Sink 连接起来形成一个 RunnableGraph
      val runnableGraph: RunnableGraph[Unit] = source.to(sink)
    
      // 运行这个流
      runnableGraph.run()
    }

    在这个例子中:

    1. SourceSource(1 to 10) 生成从 1 到 10 的整数序列。
    2. SinkSink.foreach[Int](println) 打印每个接收到的整数。
    3. RunnableGraphsource.to(sink)Source 连接到 Sink,形成一个 RunnableGraph
    4. 运行流runnableGraph.run() 启动流的处理。

    物化值(Materialized Value)

    RunnableGraph 运行时会生成一个物化值(Materialized Value),它表示流运行时产生的结果。物化值可以是各种类型的对象,具体取决于流的定义。例如,流可以产生一个计算结果、一个 Future、或者其他一些值。

    val sumSink = Sink.fold[Int, Int](0)(_ + _)
    val sumRunnableGraph: RunnableGraph[Future[Int]] = source.toMat(sumSink)(Keep.right)
    
    val sumFuture: Future[Int] = sumRunnableGraph.run()

    在这个例子中,sumSink 是一个计算和的 Sink,sumRunnableGraph 运行后会生成一个 Future[Int],表示流处理完成后计算的和。

    总结

    RunnableGraph 是 Akka Streams 中用于表示完整流的拓扑结构的不可变描述。它结合了 SourceFlowSink,并通过调用 .run() 方法启动流的处理。RunnableGraph 提供了灵活的流处理能力,同时确保了流的定义与执行之间的分离。


    在 Akka Streams 中,内存泄漏问题可能源于多个因素,尤其是当你频繁创建和运行大量的 RunnableGraph 实例时。以下是一些可能导致内存泄漏的原因以及相应的解决方案:

    1. 不当的流生命周期管理

    如果你频繁创建和运行 RunnableGraph 而没有适当处理其生命周期,这可能会导致内存泄漏。确保在运行流后正确处理流的终止和资源释放。

    解决方案:确保流在完成或失败后正确关闭,释放资源。

    你是对的,不应该简单地关闭整个 ActorSystem,因为这可能会影响到系统中其他使用该 ActorSystem 的部分。相反,应该确保每个 RunnableGraph 在完成后正确处理其资源。以下是一些具体的建议和示例代码,以更好地管理流的生命周期。

    1. 使用 watchTermination 监控流的终止

    watchTermination 可以用来监控流的终止,并在流完成时执行一些清理操作。

    import akka.actor.ActorSystem
    import akka.stream.ActorMaterializer
    import akka.stream.scaladsl.{RunnableGraph, Sink, Source}
    import scala.concurrent.Future
    import scala.util.{Failure, Success}
    
    object Main extends App {
      implicit val system: ActorSystem = ActorSystem("example-system")
      implicit val materializer: ActorMaterializer = ActorMaterializer()
    
      val source = Source(1 to 10)
      val sink = Sink.foreach[Int](println)
    
      val runnableGraph: RunnableGraph[Future[Unit]] = source.toMat(sink)(Keep.right).watchTermination() { (mat, done) =>
        done.onComplete {
          case Success(_) => println("Stream completed successfully")
          case Failure(e) => println(s"Stream failed with e")     }(system.dispatcher)  // 使用系统的调度器     mat   }    runnableGraph.run() }</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">2. 使用 <code>Materializer</code> 正确处理流</h3> <!-- /wp:heading -->  <!-- wp:paragraph --> 确保 <code>Materializer</code> 在流完成后不再占用资源。对于长时间运行的应用程序,通常会重用一个 <code>Materializer</code> 实例。 <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>import akka.stream.Materializer  object Main extends App {   implicit val system: ActorSystem = ActorSystem("example-system")   implicit val materializer: Materializer = Materializer(system)    val source = Source(1 to 10)   val sink = Sink.foreach[Int](println)    val runnableGraph = source.toMat(sink)(Keep.right)    val result: Future[Unit] = runnableGraph.run()    result.onComplete {     case Success(_) =>       println("Stream completed successfully")     case Failure(e) =>       println(s"Stream failed withe")
      }(system.dispatcher)
    }

    3. 流的清理工作

    如果流中涉及外部资源(如数据库连接、文件句柄等),确保在流完成后正确关闭这些资源。

    import akka.NotUsed
    import akka.stream.scaladsl.{Flow, Sink, Source}
    
    val source: Source[Int, NotUsed] = Source(1 to 10)
    val resourceFlow: Flow[Int, Int, NotUsed] = Flow[Int].map { i =>
      // 模拟资源使用
      i
    }.watchTermination() { (_, done) =>
      done.onComplete {
        case _ =>
          // 在流完成后释放资源
          println("Releasing resources")
      }(system.dispatcher)
    }
    
    val sink: Sink[Int, Future[Done]] = Sink.foreach[Int](println)
    
    val runnableGraph = source.via(resourceFlow).toMat(sink)(Keep.right)
    runnableGraph.run()

    4. 使用 RestartSource 自动重启流

    在某些情况下,使用 RestartSource 或类似的工具可以帮助在流失败时自动重启,而不会导致资源泄漏。

    import akka.stream.RestartSettings
    import akka.stream.scaladsl.{RestartSource, Sink, Source}
    import scala.concurrent.duration._
    
    val restartSource = RestartSource.withBackoff(
      RestartSettings(
        minBackoff = 1.second,
        maxBackoff = 30.seconds,
        randomFactor = 0.2
      )
    ) { () =>
      Source(1 to 10)
    }
    
    val sink = Sink.foreach[Int](println)
    
    val runnableGraph = restartSource.toMat(sink)(Keep.right)
    runnableGraph.run()

    5. 监控和管理流

    使用 Akka 提供的监控工具(如 StreamRefsStreamSupervisor)来监控和管理流的状态,确保流正确运行和终止。

    import akka.stream.scaladsl.{Sink, Source}
    import akka.stream.testkit.scaladsl.StreamTestKit
    
    val source = Source(1 to 10)
    val sink = Sink.foreach[Int](println)
    
    val probe = StreamTestKit.probe[Int]
    val runnableGraph = source.toMat(sink)(Keep.right)
    
    // 监控流的完成情况
    val result = runnableGraph.run()
    result.onComplete {
      case Success(_) => println("Stream completed successfully")
      case Failure(e) => println(s"Stream failed with e") }(system.dispatcher)</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">总结</h3> <!-- /wp:heading -->  <!-- wp:paragraph --> 正确管理 Akka Streams 的流生命周期非常重要,不应通过简单关闭 <code>ActorSystem</code> 来 <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code></code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">2. 背压处理问题</h3> <!-- /wp:heading -->  <!-- wp:paragraph --> 如果流内的某些组件处理数据的速度不一致,可能会导致内存积压,尤其是在没有适当的背压处理机制时。 <!-- /wp:paragraph -->  <!-- wp:paragraph --> <strong>解决方案</strong>:确保流中的各个组件正确处理背压,避免数据在流中无限积压。 <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>val source = Source(1 to 1000000) val slowSink = Sink.foreach[Int] { i =>   Thread.sleep(10)  // 模拟慢速的处理   println(i) }  val runnableGraph: RunnableGraph[Future[Done]] = source.toMat(slowSink)(Keep.right) runnableGraph.run()</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">3. 大量未完成的Future</h3> <!-- /wp:heading -->  <!-- wp:paragraph --> 在处理流时,如果大量未完成的 <code>Future</code> 长时间存在,可能会消耗大量内存。 <!-- /wp:paragraph -->  <!-- wp:paragraph --> <strong>解决方案</strong>:合理管理 <code>Future</code> 的生命周期,避免长时间存在大量未完成的 <code>Future</code>。 <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>import akka.stream.scaladsl.Flow  val flow = Flow[Int].mapAsync(4) { i =>   Future {     Thread.sleep(100)  // 模拟异步操作     i   } } val runnableGraph: RunnableGraph[Future[Done]] = source.via(flow).toMat(slowSink)(Keep.right) runnableGraph.run()</code></pre> <!-- /wp:code -->  <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">4. 未释放的物化值</h3> <!-- /wp:heading -->  <!-- wp:paragraph --> 如果你创建的 <code>RunnableGraph</code> 产生了大量物化值(如 <code>Future</code>、<code>Promise</code> 等),且未及时释放,这些物化值会占用内存。 <!-- /wp:paragraph -->  <!-- wp:paragraph --> <strong>解决方案</strong>:在流完成后,及时处理和释放物化值。 <!-- /wp:paragraph -->  <!-- wp:code --> <pre class="wp-block-code"><code>val result: Future[Done] = runnableGraph.run()  result.onComplete {   case Success(_) =>     println("Stream completed successfully")     // 处理完成后的操作   case Failure(e) =>     println(s"Stream failed withe")
        // 处理失败后的操作
    }

    5. 过度并行化

    过度并行化处理可能会导致内存消耗过大,因为每个并行处理单元都会占用一定的内存。

    解决方案:调整并行化的级别,找到性能和内存使用之间的平衡点。

    val parallelism = 4  // 根据实际情况调整并行度
    val flow = Flow[Int].mapAsync(parallelism) { i =>
      Future {
        // 处理逻辑
        i
      }
    }

    6. 数据缓存

    在流中使用缓存操作(如 buffer)时,如果缓存大小过大或没有适当的限制,可能会导致内存泄漏。

    解决方案:合理设置缓存大小和策略,避免过度缓存。

    val bufferedFlow = Flow[Int].buffer(1000, OverflowStrategy.backpressure)
    val runnableGraphWithBuffer: RunnableGraph[Future[Done]] = source.via(bufferedFlow).toMat(slowSink)(Keep.right)
    runnableGraphWithBuffer.run()

    7. ActorSystem 和 Materializer 管理

    在 Akka Streams 中,ActorSystemMaterializer 是流执行的基础设施。如果你频繁创建这些资源而不正确终止它们,可能会导致内存泄漏。因此,建议重用这些资源,并在应用程序终止时正确关闭它们。

    解决方案:重用 ActorSystemMaterializer,并在应用程序结束时终止它们。

    import akka.actor.ActorSystem
    import akka.stream.ActorMaterializer
    
    object Main extends App {
      implicit val system: ActorSystem = ActorSystem("example-system")
      implicit val materializer: ActorMaterializer = ActorMaterializer()
    
      // 重用同一个 ActorSystem 和 Materializer
      for (_ <- 1 to 100) {
        val source = Source(1 to 10)
        val sink = Sink.foreach[Int](println)
        val runnableGraph: RunnableGraph[Future[Unit]] = source.toMat(sink)(Keep.right)
        runnableGraph.run()
      }
    
      // 在应用程序结束时终止 ActorSystem
      system.terminate()
    }

    8. 避免循环创建流

    循环创建和运行流可能导致大量未释放的流实例,积累内存使用。

    解决方案:如果必须循环创建流,确保每个流在完成后正确关闭,并尽量简化流的创建和运行。

    val source = Source(1 to 10)
    val sink = Sink.foreach[Int](println)
    
    def runStream(): Unit = {
      val runnableGraph: RunnableGraph[Future[Unit]] = source.toMat(sink)(Keep.right)
      val result: Future[Unit] = runnableGraph.run()
    
      result.onComplete {
        case Success(_) =>
          println("Stream completed successfully")
        case Failure(e) =>
          println(s"Stream failed with $e")
      }
    }
    
    // 定期运行流,但不在短时间内频繁创建
    system.scheduler.scheduleWithFixedDelay(
      initialDelay = Duration.Zero,
      delay = Duration(1, TimeUnit.SECONDS)
    )(() => runStream())

    9. 监控和调试内存使用

    使用工具监控和调试内存使用情况,找出内存泄漏的根本原因。例如,可以使用 Java 的内存分析工具(如 VisualVM 或 YourKit)来分析内存使用情况和泄漏点。

    解决方案:定期监控内存使用情况,及时发现和解决内存泄漏问题。

    10. 优化流的设计

    重新审视流的设计,确保流处理过程高效且不会导致内存泄漏。例如,避免在流中使用大型数据结构,合理划分流的处理逻辑。

    解决方案:优化流的设计和实现,确保流处理过程高效、内存使用合理。

    // 示例:优化流处理逻辑
    val optimizedFlow = Flow[Int].map { i =>
      // 简化处理逻辑,避免大型数据结构
      i * 2
    }
    val runnableGraph: RunnableGraph[Future[Done]] = source.via(optimizedFlow).toMat(sink)(Keep.right)
    runnableGraph.run()

    总结

    内存泄漏通常源于资源管理不当、流处理不当或设计问题。通过合理管理 ActorSystemMaterializer、正确处理流的生命周期、确保背压处理、优化流设计等方法,可以有效避免内存泄漏问题。同时,使用监控工具定期检查内存使用情况,有助于及时发现和解决潜在问题。