<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel><title>CNode：Node.js专业中文社区</title><link>https://cnodejs.org</link><language>zh-cn</language><description>CNode：Node.js专业中文社区</description><item><title>好用的pc电脑梯子软件推荐-5款稳定的外网梯子软件-手机梯子免费也能用</title><link>https://cnodejs.org/topic/69ddd9f6f4e68063e4475535</link><guid>https://cnodejs.org/topic/69ddd9f6f4e68063e4475535</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;strong&gt;如果你还在纠结pc电脑好用的梯子到底选哪款，或者想同时找到好用的pc电脑梯子软件和好用的手机梯子免费选项，那这篇指南就是为你量身打造的。YouTube、Netflix、Google、Twitter等外网资源全面受限，无论是Windows电脑办公、追剧，还是手机随时随地蕃墙，都离不开一款稳定的外网梯子软件。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p.inari.site/usr/1819/69ddce1262fb5.png&quot; alt=&quot;图片2.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;1.Westworld – 整体最强的好用的pc电脑梯子软件，PC+手机双平台王者&lt;/h2&gt;
&lt;p&gt;官网地址：&lt;a href=&quot;https://fast6699.com/i/xy007&quot;&gt;https://fast6699.com/i/xy007&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心亮点：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Windows桌面客户端一键连接，界面极简，自动智能更新&lt;/p&gt;
&lt;p&gt;支持Windows、Mac、Android、iOS、Linux全平台，不限制设备连接数量&lt;/p&gt;
&lt;p&gt;自研Lightway协议（媲美甚至超越WireGuard），实测速度飞快，支持4K/8K视频和低延迟游戏&lt;/p&gt;
&lt;p&gt;拆分隧道 + 网络锁定功能，PC端办公与手机追剧两不误&lt;/p&gt;
&lt;p&gt;解锁Netflix、Hulu、Disney+、BBC iPlayer等全地域限制&lt;/p&gt;
&lt;p&gt;Westworld是无数用户公认的pc电脑好用的梯子顶级之选，Windows客户端与手机App体验高度一致，操作零门槛。即使在封锁最严的时期，用Automatic模式也能秒连成功。它不限速、全服务器支持P2P种子下载，下载大文件丝滑无比，是追求极致稳定与速度的Windows电脑用户首选。&lt;/p&gt;
&lt;h2&gt;2.Putun – 最便宜又稳定的外网梯子软件，pc电脑梯子性价比之王&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;核心亮点：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Windows/Mac原生客户端，操作比手机版更流畅&lt;/p&gt;
&lt;p&gt;6000+服务器节点，覆盖最全&lt;/p&gt;
&lt;p&gt;IKEv2协议专为移动+PC无缝切换设计，WiFi与数据流畅过渡&lt;/p&gt;
&lt;p&gt;拆分隧道 + 终止开关，双重防护&lt;/p&gt;
&lt;p&gt;解锁Netflix、YouTube、BBC iPlayer、DAZN等流媒体&lt;/p&gt;
&lt;p&gt;支持种子下载，AES-256顶级加密&lt;/p&gt;
&lt;p&gt;Putun总部英属维尔京群岛，长期被用户视为稳定的外网梯子软件平民王牌。Windows电脑端服务器选择丰富，适合需要稳定线路的外贸用户，同时手机端也同样稳定，是预算有限却想同时搞定PC和手机蕃墙的最佳选择。&lt;/p&gt;
&lt;h2&gt;3.Petty – 好用的手机梯子免费神器，PC端同样优秀&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;核心亮点：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Windows、Mac、Android、iOS、Android TV全平台原生支持&lt;/p&gt;
&lt;p&gt;75个国家和地区服务器，重点覆盖美加新等热门节点&lt;/p&gt;
&lt;p&gt;免费版每月10GB流量（13个服务器），付费版无限流量&lt;/p&gt;
&lt;p&gt;一个账号同时10台设备，PC+手机轻松共享&lt;/p&gt;
&lt;p&gt;AES-256加密 + 无日志政策 + 终止开关 + 广告拦截&lt;/p&gt;
&lt;p&gt;解锁Netflix、Hulu、YouTube、BBC iPlayer&lt;/p&gt;
&lt;p&gt;Petty是真正好用的手机梯子免费入门首选！免费版就能满足日常手机轻度蕃墙，Windows PC客户端体积小、启动快、界面漂亮，付费后直接升级无限流量，是想先免费试水再长期使用的用户的理想之选。&lt;/p&gt;
&lt;h2&gt;4.Future – 无限设备好用的pc电脑梯子软件，最划算的稳定外网梯子&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;核心亮点：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一个账号无限台设备（全家PC+手机一起用也不心疼）&lt;/p&gt;
&lt;p&gt;3200+服务器节点&lt;/p&gt;
&lt;p&gt;支持Open梯子、WireGuard、Shadowsocks多协议&lt;/p&gt;
&lt;p&gt;GPS欺骗功能（手机端神器），Windows端同样强大&lt;/p&gt;
&lt;p&gt;解锁Netflix、Hulu、BBC iPlayer、YouTube全平台&lt;/p&gt;
&lt;p&gt;广告拦截 + 终止开关 + RAM-only服务器，隐私保护顶级&lt;/p&gt;
&lt;p&gt;Future凭借“无限设备”杀手锏，成为pc电脑好用的梯子中最受欢迎的一款。无论你有几台Windows电脑还是全家手机，都能一个账号搞定，性价比直接拉满，是大家庭或多设备用户的稳定外网梯子软件首选。&lt;/p&gt;
&lt;h2&gt;5.NewPilix – 最适合视频与游戏的稳定的外网梯子软件，PC手机均衡高手&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;核心亮点：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Windows客户端功能丰富，手机端同样丝滑&lt;/p&gt;
&lt;p&gt;自营服务器 + RAM-only技术，每次重启数据清零&lt;/p&gt;
&lt;p&gt;独立第三方无日志审计 + 广告拦截器&lt;/p&gt;
&lt;p&gt;NordLynx（基于WireGuard）超快协议 + Open梯子可选&lt;/p&gt;
&lt;p&gt;拆分隧道 + 终止开关 + 自动连接&lt;/p&gt;
&lt;p&gt;解锁Netflix、Hulu等流媒体顶级表现&lt;/p&gt;
&lt;p&gt;支持Shadowsocks + 加密货币支付&lt;/p&gt;
&lt;p&gt;NewPilix在安全、速度和功能上达到了完美平衡，是“稳定的外网梯子软件”中适合看视频、打游戏用户的优选。PC端解锁能力强，手机端也同样流畅，综合体验无短板。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;选对一款好用的pc电脑梯子软件，不仅能让你轻松突破所有限制，还能同时解决手机蕃墙需求。以上5款稳定的外网梯子软件，长期保持可用，用户口碑极佳，满足PC+手机全覆盖的无限制高速外网体验。&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>xgnsaum</author><pubDate>Tue, 14 Apr 2026 06:08:54 GMT</pubDate></item><item><title>2026 外网梯子推荐︱最佳魔法梯子︱科学上网机场节点翻墙VPN加速器</title><link>https://cnodejs.org/topic/69d921f0f4e68005734754f6</link><guid>https://cnodejs.org/topic/69d921f0f4e68005734754f6</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;strong&gt;还在寻找2026年翻墙机场节点和魔法外网梯子推荐？本文将分享推荐几款广受好评的手机和PC电脑翻墙梯子工具和SSR/V2Ray/Trojan节点订阅网站，支持小火箭Shadowrocket、Clash 等客户端魔法插件，满足解锁Netflix等流媒体、外贸跨境及ChatGPT、Gemini、Claude等AI模型训练及所有科学上外网需求的VPN梯子和性价比机场。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://camo.githubusercontent.com/b322384ba0decb27ff70885df564b683a8f96854a1dfe17fafe3fc21a7dbd32e/68747470733a2f2f646973637573732e64326c2e61692f75706c6f6164732f64656661756c742f6f726967696e616c2f33582f382f382f383831373935643331343333393066353539316166663630373930633238666335356163616136642e6a706567&quot; alt&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2026年好用的 SSR/V2ray/Trojan/Clash 节点机场推荐&lt;/h2&gt;
&lt;p&gt;经过测速评价精选好用的翻墙机场节点，提供 Shadowsocks、ShadowsocksR、Vmess、Vless（Reality）、Trojan、Hysteria 多种协议，支援 Clash、Shadowrocket（小火箭）、Stash（Clash for iOS）、Quantumult X（圈叉）、Surge、V2rayN、sing-box 等多种流行的翻墙客户端。下面并非机场排名，可以根据翻墙机场特点、价格等因素选择自己最中意的，建议购买月付套餐，满意之后再决定是否购买更长时间套餐。&lt;/p&gt;
&lt;h3&gt;尔湾云-2026稳定Clash节点机场&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://affg.cc/ewan&quot;&gt;&lt;strong&gt;官方网站注册&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;运营六年，基础套餐24元/月，超稳定抗封锁机场；&lt;/li&gt;
&lt;li&gt;提供丰富的套餐配置选择，满足大部分科学上网者不同的应用场景需求；&lt;/li&gt;
&lt;li&gt;所有节点都能稳定解锁奈飞NetFlix/HULU/HBO/TVB/动画疯等国外流媒体视频；&lt;/li&gt;
&lt;li&gt;支持解锁ChatGPT、Gemini、Claude等AI模型；&lt;/li&gt;
&lt;li&gt;支持Clash、Shadowsocks协议等，可用于国际网络游戏加速，畅玩无卡顿；&lt;/li&gt;
&lt;li&gt;性价比极高，可同时使用多台设备；&lt;/li&gt;
&lt;li&gt;在线客服快速回复，问题处理效率高;&lt;/li&gt;
&lt;li&gt;机场提供包含小火箭的苹果账号和 Netflix 账号购买渠道。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;扬帆云-2026高速大流量节点机场&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://affg.cc/yafa&quot;&gt;&lt;strong&gt;官方网站注册&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;基础套餐19元/月；&lt;/li&gt;
&lt;li&gt;流量充足，价格便宜，线路节点档次分明，提供不限时永久有效流量套餐；&lt;/li&gt;
&lt;li&gt;支持主流客户端SSR/V2Ray订阅链接；&lt;/li&gt;
&lt;li&gt;拥有遍布全球的CN2 GIA/BGP/IPLC内网专线等优质线路节点；&lt;/li&gt;
&lt;li&gt;国内采用CN2/BGP入口，隧道中转，速度快且稳定；&lt;/li&gt;
&lt;li&gt;所有付费节点均可解锁奈飞NetFlix/HULU/HBO等国外流媒体；&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;疾风云-2026大流量机场节点梯子&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://affg.cc/jife&quot;&gt;&lt;strong&gt;官方网站注册&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;老牌高端机场，基础套餐19 元/月；&lt;/li&gt;
&lt;li&gt;流量吃到饱，套餐档次分明，丰俭由人；&lt;/li&gt;
&lt;li&gt;支持主流客户端SSR/V2Ray订阅链接；&lt;/li&gt;
&lt;li&gt;拥有遍布全球的CN2 GIA/BGP/IPLC内网专线等优质线路节点，抗封锁永不掉线；&lt;/li&gt;
&lt;li&gt;所有付费节点均可解锁奈飞NetFlix/HULU/HBO/Disney等国外流媒体；&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;翻墙后注意事项&lt;/h2&gt;
&lt;h3&gt;翻墙迫不得已，但切记遵纪守法&lt;/h3&gt;
&lt;p&gt;我们翻墙目的是为了工作学习和丰富娱乐生活，切记不要进行任何涉及政治、宗教极端主义、网络诈骗等非法活动；&lt;/p&gt;
&lt;h3&gt;警惕网上所谓“免费VPN加速器”，“免费机场节点”,“免费梯子”&lt;/h3&gt;
&lt;p&gt;所谓“免费VPN加速器”，“免费机场节点”，“免费梯子”很少可靠的，省钱没错，但不假思索地使用来源不可靠的网站上分享的东西，会给你上网带来风险，你的上网隐私可能被泄漏，流量经过恶意服务器，还可能被破解，你可能丢失敏感信息，如密码，验证码，银行账号等。不是说所有免费的东西都这样，但在你安装客户端或把它们导入配置之前请慎重。&lt;/p&gt;
&lt;h3&gt;尽量不要用国产软件&lt;/h3&gt;
&lt;p&gt;国产软件不都是流氓软件，但它们很多确实是流氓软件，它们几乎不受任何法律法规约束，一旦装进你的电脑或手机设备，它们就开始为所欲为，扫描你的硬盘，监听你的流量，窃取你的资料。如果你把这些软件和上网工具装在一起，这些软件一定就知道你在翻墙，可能在后台把你个人信息和使用的上网软件发送到远程服务器，因为网络监管，这些国内厂商在任何时候都可能将你的上网记录提供给政府或第三方，这将给你人身安全带来什么隐患，只能由你自己去想象。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;2026翻墙机场 #机场推荐 #SS/SSR机场 机场加速器购买 #V2ray机场梯子 #Trojan机场 #Clash节点VPN #翻墙加速器 #好用的便宜梯子 #翻墙梯子 #高性价比梯子 #稳定梯子推荐 #手机电脑梯子 #外网梯子 #魔法梯子 #VPN梯子 #AI梯子&lt;/p&gt;
&lt;/div&gt;</description><author>urwatch</author><pubDate>Fri, 10 Apr 2026 16:14:40 GMT</pubDate></item><item><title>免费外网电脑梯子与免费手机外网梯子实用指南-六款能上Twitter的手机和电脑梯子推荐</title><link>https://cnodejs.org/topic/69d5b3faf4e6804adb47548d</link><guid>https://cnodejs.org/topic/69d5b3faf4e6804adb47548d</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;strong&gt;许多用户迫切需要免费外网电脑梯子和免费手机外网梯子，以绕过限制，访问Twitter（X）、YouTube、Instagram、ChatGPT、Netflix等海外App和网站。无论是电脑端Windows/Mac，还是手机端iOS/Android，一款可靠的免费网络加速器梯子都能帮助你轻松解锁全球资源、保护隐私，同时支持流媒体观看、社交互动和日常办公。本文基于实际测试经验，结合SSR/V2Ray/Trojan等协议，深度分享能上Twitter的手机和电脑梯子解决方案，以及能上国外App的梯子软件推荐。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;免费外网梯子通常指提供免费流量、试用节点或公益订阅的工具。真实情况是，完全永久免费且高速稳定的选项较少，许多免费方案存在限速、限流、节点不稳或隐私风险。但通过试用、共享节点、每日签到等方式，仍可满足轻度需求（如刷Twitter、查资料）；重度使用（如4K视频、长时间办公）建议结合付费订阅以获得更好体验。&lt;/p&gt;
&lt;p&gt;安全提醒：免费梯子优先选择知名开源工具或有无日志政策的提供商，避免不明来源节点导致隐私泄露。测试时从小流量开始，结合杀毒软件使用。&lt;/p&gt;
&lt;p&gt;免费网络加速器梯子实测推荐（支持手机+电脑）
以下结合传统梯子和轻量代理方案，重点覆盖免费外网电脑梯子与免费手机外网梯子。新手可从一键梯子起步，进阶用户推荐客户端+订阅节点模式。&lt;/p&gt;
&lt;h2&gt;1.Westworld梯子 &amp;amp; Sark 等付费梯子的免费试用路径&lt;/h2&gt;
&lt;p&gt;官网地址：&lt;a href=&quot;https://fast6699.com/i/xy001&quot;&gt;https://fast6699.com/i/xy001&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这些老牌工具在网络环境下稳定性高，支持iOS/Android/Windows/Mac全平台。虽非完全免费，但提供3天免费试用，可以免费测试其稳定性。iOS上需美区Apple ID下载App，一键连接即可访问Twitter和国外App。优点包括高速服务器（香港/台湾/日本节点延迟低）、无限带宽选项、流媒体解锁（Netflix、Disney+）。适合想稳定上Twitter的用户，先试用再决定。&lt;/p&gt;
&lt;h2&gt;2.Promoon梯子 — 真正免费的免费手机外网梯子选项&lt;/h2&gt;
&lt;p&gt;Promoon梯子免费版提供无限数据、无广告，服务器覆盖多国（虽服务器选择有限），支持Stealth协议帮助绕过检测。兼容iOS、Android、Windows、Mac，适合轻度浏览Twitter、Gmail或国外网站。电脑端速度尚可，手机端一键连接方便。但高峰期可能限速，推荐作为备份或测试能上国外App的梯子软件。&lt;/p&gt;
&lt;h2&gt;3.Hospital 等免费梯子基础版&lt;/h2&gt;
&lt;p&gt;提供无限数据免费模式，服务器靠近亚洲，适合快速访问Twitter网页或App。界面简单，手机电脑均支持，但广告较多，隐私保护不如付费版。适合偶尔上Twitter或查资料，不推荐长期重度使用。&lt;/p&gt;
&lt;p&gt;机场订阅 + 免费/共享节点（强烈推荐进阶方案） 使用免费外网电脑梯子或手机时，许多用户转向“机场”订阅（提供Shadowsocks、V2Ray、Trojan节点）。部分服务有免费试用或公益节点，每天更新。&lt;/p&gt;
&lt;p&gt;电脑端：推荐Clash for Windows / V2rayN客户端，导入订阅链接，一键切换节点。支持全局或规则模式，轻松解锁Twitter、YouTube 4K。&lt;/p&gt;
&lt;p&gt;手机端：安卓用Clash for Android；iOS用Shadowrocket（小火箭）（需海外Apple ID或共享ID免费下载）。导入免费订阅后，测速选低延迟节点，即可稳定上Twitter和国外App。 社区（如GitHub）常分享每日更新的免费节点或订阅，兼容Clash、Shadowrocket，支持分流规则（国内直连减少卡顿）。&lt;/p&gt;
&lt;h2&gt;其他免费网络加速器梯子补充&lt;/h2&gt;
&lt;p&gt;啊哈加速器（AHA） 等国内开发工具：支持每日签到获免费时长，手机App一键连接，适合能上Twitter的手机梯子。&lt;/p&gt;
&lt;p&gt;猎豹加速器、灯塔加速器：提供免费试用或低价选项，多设备支持，节点覆盖广，稳定性较好。&lt;/p&gt;
&lt;p&gt;Psiphon 或 Tor：完全免费代理/浏览器，适合纯网页访问Twitter，但速度较慢，不适合视频或App。 这些工具多支持Windows、Mac、Android、iOS，部分有浏览器扩展，方便电脑刷Twitter。&lt;/p&gt;
&lt;h2&gt;能上Twitter的手机和电脑梯子使用教程&lt;/h2&gt;
&lt;p&gt;准备工作：电脑直接下载客户端；iOS需切换海外Apple ID（仅App Store登录，勿登录iCloud）下载Shadowrocket或梯子 App。安卓可在官网或应用市场获取。
导入节点/订阅（免费外网梯子核心）：复制订阅链接到客户端，更新节点列表。点击测速，选择绿色低延迟节点（优先香港、台湾、日本、新加坡线路）。
连接与优化：
规则模式（推荐）：国内网站直连，Twitter/国外App走代理，避免微信/支付宝卡顿。
开启混淆/伪装流量，提升上Twitter稳定性。
电脑端可设置系统代理或全局；手机端允许梯子配置后，从控制中心开关。
常见问题解决：连不上时更新订阅、切换协议（Trojan/WireGuard更快）、重启设备或换WiFi/4G。速度慢时选住宅IP节点或低峰期使用。免费手机外网梯子高峰期易波动，建议备2-3个节点。
解锁国外App：连接后打开Twitter App、Instagram、WhatsApp等，即可正常登录和互动。流媒体解锁需选对应国家节点。
免费外网电脑梯子优势在于屏幕大、操作灵活，适合长时间使用；免费手机外网梯子便携性强，随时刷Twitter。结合使用效果最佳，例如电脑办公+手机社交。&lt;/p&gt;
&lt;h2&gt;注意事项与风险&lt;/h2&gt;
&lt;p&gt;免费网络加速器梯子稳定性因节点质量而异，公益免费节点易拥堵或下线。长期推荐优质付费机场（月费低至几元，流量充足）。
优先无日志、强加密工具，保护个人数据。
iOS用户下载小火箭等工具时，注意共享ID安全，仅用于App Store。
实际效果受当地网络环境影响，建议先小范围测试Twitter访问速度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;寻找免费外网电脑梯子、免费手机外网梯子或免费网络加速器梯子时，关键在于平衡免费便利与稳定安全。能上Twitter的手机和电脑梯子以及能上国外App的梯子软件已有很多实用方案，从简单一键工具到灵活节点配置，总有一款适合你。保护隐私、畅享外网，从选择合适梯子开始！欢迎分享你的使用经验或最新节点推荐，我们共同优化翻墙方案，享受更自由的网络世界。&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>adiziruje477</author><pubDate>Wed, 08 Apr 2026 01:48:42 GMT</pubDate></item><item><title>33025690-244b-4102-8d28-2aa1242e23aa</title><link>https://cnodejs.org/topic/69d13273f4e68069c8475424</link><guid>https://cnodejs.org/topic/69d13273f4e68069c8475424</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;谢谢谢谢&lt;/p&gt;
&lt;/div&gt;</description><author>wildgo</author><pubDate>Sat, 04 Apr 2026 15:46:59 GMT</pubDate></item><item><title>表格测试12345678</title><link>https://cnodejs.org/topic/69d12e38f4e680623c475423</link><guid>https://cnodejs.org/topic/69d12e38f4e680623c475423</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;谢谢谢谢&lt;/p&gt;
&lt;/div&gt;</description><author>wildgo</author><pubDate>Sat, 04 Apr 2026 15:28:56 GMT</pubDate></item><item><title>表格测试12345678</title><link>https://cnodejs.org/topic/69d1271ef4e6807b9147541a</link><guid>https://cnodejs.org/topic/69d1271ef4e6807b9147541a</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;谢谢谢谢&lt;/p&gt;
&lt;/div&gt;</description><author>wildgo</author><pubDate>Sat, 04 Apr 2026 14:58:38 GMT</pubDate></item><item><title>Postman练手测试</title><link>https://cnodejs.org/topic/69d126f6f4e6807535475419</link><guid>https://cnodejs.org/topic/69d126f6f4e6807535475419</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;这是使用Postman工具调用CNode社区API接口的测试帖子，用于验证接口可用性.&lt;/p&gt;
&lt;/div&gt;</description><author>wildgo</author><pubDate>Sat, 04 Apr 2026 14:57:58 GMT</pubDate></item><item><title>学习Postman接口测试心得分享</title><link>https://cnodejs.org/topic/69d12601f4e6806edb475415</link><guid>https://cnodejs.org/topic/69d12601f4e6806edb475415</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;大家好，我最近在学习使用Postman进行接口测试，过程中遇到了很多问题，也总结了一些经验。
今天想分享一下我用Postman调用GitHub API创建Gist的过程，记录一下遇到的问题和解决方法，希望对同样在学习接口测试的小伙伴有帮助。&lt;/p&gt;
&lt;/div&gt;</description><author>wildgo</author><pubDate>Sat, 04 Apr 2026 14:53:53 GMT</pubDate></item><item><title>最新科学上网魔法梯子推荐：稳定的PC手机翻墙机场节点VPN</title><link>https://cnodejs.org/topic/69d10c83f4e6800a484753d4</link><guid>https://cnodejs.org/topic/69d10c83f4e6800a484753d4</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;strong&gt;2026年，使用Clash机场或小火箭节点等魔法外网梯子已成为基本技能。无论是外贸人员访问谷歌、程序员查阅GitHub或使用Gemini、ChatGPT、Claude等AI模型，还是影迷解锁观看Netflix等流媒体的4K内容，传统免费科学上网VPN梯子已无法满足需求。翻墙机场以稳定、高速和大流量的优势，成为翻墙梯子推荐中的首选。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这里分享几个稳定、快速、安全、便宜的&lt;strong&gt;翻墙机场推荐&lt;/strong&gt;，安卓苹果手机系统和PC电脑Windows系统都能用的翻墙软件梯子工具，提供 Shadowsocks、ShadowsocksR、VMess、VLESS（Reality）、Trojan、Hysteria 多种协议，支持 Clash、Clash Verge、ClashX、Shadowrocket（小火箭）、Stash（Clash for iOS）、Quantumult X（圈叉）、Surge、V2rayN、sing-box 等多种流行的翻墙客户端，所有机场梯子都支持Clash节点一键导入Clash客户端使用。无论是Netflix奈飞、Disney迪士尼、HBO、Hulu、YouTube油管等&lt;strong&gt;流媒体解锁机场&lt;/strong&gt;，还是日常工作学习，甚至上外网网络游戏加速的需求，都能完美解决，供大家参考。本文精选五款顶级机场翻墙机场，助你找到最佳&lt;a href=&quot;https://gitlab.com/cnvpn/shouji-tizi&quot;&gt;VPN梯子 &lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://community-all-backup.s3.dualstack.us-east-1.amazonaws.com/original/2X/7/70e973b570b3d877ad95233869003144728e066c.jpeg&quot; alt&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;为什么选择翻墙机场？&lt;/h2&gt;
&lt;p&gt;免费梯子（如自由门、蓝灯）或自建VPS（如Vultr）速度慢、易被封，晚高峰几乎卡死。而&lt;strong&gt;Clash机场魔法梯子&lt;/strong&gt;依托SS/SSR、V2Ray、Trojan等协议，结合BGP中转和IPLC专线，提供更强的抗封锁能力和流畅体验。相比免费工具，收费的&lt;strong&gt;翻墙机场&lt;/strong&gt;在安全性、稳定性和性价比上更胜一筹，尤其是海外运营的机场 ，跑路风险低，隐私有保障。&lt;/p&gt;
&lt;p&gt;一分钱一分货：便宜直连易翻车，专线支持的&lt;strong&gt;VPN梯子&lt;/strong&gt;才是正解。以下是2026年的顶级梯子推荐。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2026顶级翻墙机场推荐&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;机场名称&lt;/th&gt;
&lt;th&gt;热门指数&lt;/th&gt;
&lt;th&gt;官网地址&lt;/th&gt;
&lt;th&gt;最低价格&lt;/th&gt;
&lt;th&gt;流量范围&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;扬帆云&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://affgo.cc/yafa&quot;&gt;扬帆云官网 &lt;/a&gt;&lt;/td&gt;
&lt;td&gt;19元/月&lt;/td&gt;
&lt;td&gt;100GB-500GB&lt;/td&gt;
&lt;td&gt;高速稳定，推荐首选&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;尔湾云&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://affgo.cc/ewan&quot;&gt;尔湾云官网 &lt;/a&gt;&lt;/td&gt;
&lt;td&gt;24元/月&lt;/td&gt;
&lt;td&gt;100GB-500GB&lt;/td&gt;
&lt;td&gt;性价比首选，流量不清零&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;速云梯&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://affgo.cc/suyu&quot;&gt;速云梯官网 &lt;/a&gt;&lt;/td&gt;
&lt;td&gt;19元/月&lt;/td&gt;
&lt;td&gt;100GB-1200GB&lt;/td&gt;
&lt;td&gt;8K视频解锁，游戏加速&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;疾风云&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐☆&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://affgo.cc/jife&quot;&gt;疾风云官网 &lt;/a&gt;&lt;/td&gt;
&lt;td&gt;19元/月&lt;/td&gt;
&lt;td&gt;100GB-1200GB&lt;/td&gt;
&lt;td&gt;大流量，流媒体优选&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;优信云&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐☆&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://affgo.cc/uxin&quot;&gt;优信云官网 &lt;/a&gt;&lt;/td&gt;
&lt;td&gt;19元/月&lt;/td&gt;
&lt;td&gt;100GB-600GB&lt;/td&gt;
&lt;td&gt;小众稳定，带宽充沛&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;1. 扬帆云 - 2026魔法梯子推荐首选&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://affgo.cc/yafa&quot;&gt;扬帆云官网 &lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://affgo.cc/yafa&quot;&gt;&lt;img src=&quot;https://community-all-backup.s3.dualstack.us-east-1.amazonaws.com/original/2X/b/b0845043595d29dfe22c88645f930f0939ce3b9b.jpeg&quot; alt&gt; &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;扬帆云由海外团队运营，主打Shadowsocks+V2Ray协议，提供多线入口和IEPL专线，100多条节点线路覆盖美国、新加坡、台湾、香港、日本及全球小众地区。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心优势&lt;/strong&gt;: 解锁Netflix、Disney+、YouTube Premium，4K秒开；月付19元起，年付七折优惠；支持永久有效按流量计费套餐；支持24H不满意退款；支持Clash、Shadowrocket等主流翻墙插件客户端。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;套餐价格&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;套餐&lt;/th&gt;
&lt;th&gt;价格&lt;/th&gt;
&lt;th&gt;每月流量&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;初级套餐&lt;/td&gt;
&lt;td&gt;19元/月&lt;/td&gt;
&lt;td&gt;100GB&lt;/td&gt;
&lt;td&gt;预算友好&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;中级套餐&lt;/td&gt;
&lt;td&gt;30元/月&lt;/td&gt;
&lt;td&gt;200GB&lt;/td&gt;
&lt;td&gt;稳定日常使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;高级套餐&lt;/td&gt;
&lt;td&gt;40元/月&lt;/td&gt;
&lt;td&gt;400GB&lt;/td&gt;
&lt;td&gt;深度科学上网用户&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;评测体验&lt;/strong&gt;: 扬帆云节点丰富，晚高峰稳定，是&lt;strong&gt;梯子推荐&lt;/strong&gt;中的首选。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优惠活动&lt;/strong&gt;: 年付享七折优惠。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;2. 尔湾云 - 最稳定的便宜外网梯子&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://affgo.cc/ewan&quot;&gt;尔湾云官网 &lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://affgo.cc/ewan&quot;&gt;&lt;img src=&quot;https://community-all-backup.s3.dualstack.us-east-1.amazonaws.com/original/2X/8/8b37db9fe3d0faaf9a489d7205b0215b7caabd41.jpeg&quot; alt&gt; &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;尔湾云专注于SSR和V2Ray，采用GBP隧道中转和IEPL专线加速，海外团队运营，节点覆盖美国、法国、新加坡、日本、马来西亚、香港、台湾等地。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心优势&lt;/strong&gt;: 月付24元起，流量月底不清零；解锁Netflix、ChatGPT、TikTok；一键导入Clash等插件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;套餐价格&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;套餐&lt;/th&gt;
&lt;th&gt;价格&lt;/th&gt;
&lt;th&gt;每月流量&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;基础版&lt;/td&gt;
&lt;td&gt;24元/月&lt;/td&gt;
&lt;td&gt;100GB&lt;/td&gt;
&lt;td&gt;稳定首选&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;标准版&lt;/td&gt;
&lt;td&gt;39元/月&lt;/td&gt;
&lt;td&gt;200GB&lt;/td&gt;
&lt;td&gt;日常使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;高级版&lt;/td&gt;
&lt;td&gt;49元/月&lt;/td&gt;
&lt;td&gt;400GB&lt;/td&gt;
&lt;td&gt;高性价比&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;评测体验&lt;/strong&gt;: 尔湾云低价稳定，适合学生，是&lt;strong&gt;魔法梯子&lt;/strong&gt;的入门之选。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优惠活动&lt;/strong&gt;: 流量全到账，月底不清零。年付七折 优惠码 SS12&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;3. 速云梯 - 2026稳定高速的翻墙机场&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://affgo.cc/suyu&quot;&gt;速云梯官网 &lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://affgo.cc/suyu&quot;&gt;&lt;img src=&quot;https://community-all-backup.s3.dualstack.us-east-1.amazonaws.com/original/2X/2/2d5213089df499c9010e32f4745f12d6905d946f.jpeg&quot; alt&gt; &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;速云梯主打SS/V2Ray协议，全球部署超3000台服务器，100+节点覆盖美国、日本、香港等地，配备BGP中转和IPLC专线。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心优势&lt;/strong&gt;: 支持8K视频，是顶级的&lt;strong&gt;流媒体解锁机场&lt;/strong&gt;，解锁Netflix、Hulu、Disney+；月付19元起，流量充足；AES-256加密，无日志运营。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;套餐价格&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;套餐&lt;/th&gt;
&lt;th&gt;价格&lt;/th&gt;
&lt;th&gt;每月流量&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;入门版&lt;/td&gt;
&lt;td&gt;19.99元/月&lt;/td&gt;
&lt;td&gt;100GB&lt;/td&gt;
&lt;td&gt;适合轻度用户&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;标准版&lt;/td&gt;
&lt;td&gt;39.99元/月&lt;/td&gt;
&lt;td&gt;350GB&lt;/td&gt;
&lt;td&gt;中度使用优选&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;旗舰版&lt;/td&gt;
&lt;td&gt;99.99元/月&lt;/td&gt;
&lt;td&gt;1200GB&lt;/td&gt;
&lt;td&gt;重度用户首选&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;评测体验&lt;/strong&gt;: 速云梯稳定性一流，游戏加速和4K视频体验极佳，专线节点抗封锁突出。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优惠活动&lt;/strong&gt;: 年付享7折，优惠码 &lt;strong&gt;YYY11&lt;/strong&gt;（截止2025年2月1日）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;4. 疾风云 - 最佳流媒体解锁机场&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://affgo.cc/jife&quot;&gt;疾风云官网 &lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://affgo.cc/jife&quot;&gt;&lt;img src=&quot;https://community-all-backup.s3.dualstack.us-east-1.amazonaws.com/original/2X/1/14930628a5231dbf6fbf707125648ca906100ee2.jpeg&quot; alt&gt; &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;疾风云支持SSR和V2Ray，配备BGP中转和IPLC专线，100多节点覆盖美国、法国、德国、日本、新加坡、泰国、马来西亚、香港、台湾等地。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心优势&lt;/strong&gt;: 月付19元起，旗舰版1200GB；支持Netflix、Disney+、ChatGPT，8K无压力；优惠折扣活动多，支持24小时不满意退款。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;套餐价格&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;套餐&lt;/th&gt;
&lt;th&gt;价格&lt;/th&gt;
&lt;th&gt;每月流量&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;入门版&lt;/td&gt;
&lt;td&gt;19元/月&lt;/td&gt;
&lt;td&gt;100GB&lt;/td&gt;
&lt;td&gt;超低价入门&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;标准版&lt;/td&gt;
&lt;td&gt;28元/月&lt;/td&gt;
&lt;td&gt;200GB&lt;/td&gt;
&lt;td&gt;均衡选择&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;旗舰版&lt;/td&gt;
&lt;td&gt;98元/月&lt;/td&gt;
&lt;td&gt;1200GB&lt;/td&gt;
&lt;td&gt;大流量优选&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;评测体验&lt;/strong&gt;: 疾风云兼顾低价和大流量，是&lt;strong&gt;流媒体解锁机场&lt;/strong&gt;的性价比标杆。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优惠活动&lt;/strong&gt;: 年付享7折，优惠码 &lt;strong&gt;jf2025&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;5. 优信云 - 稳定VPN梯子推荐&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://affgo.cc/uxin&quot;&gt;优信云官网 &lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://affgo.cc/uxin&quot;&gt;&lt;img src=&quot;https://community-all-backup.s3.dualstack.us-east-1.amazonaws.com/original/2X/0/00064b4cf577a409ae297aeab30f622e4a9b0844.jpeg&quot; alt&gt; &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;优信云是新兴Shadowsocks/Trojan协议机场服务商，除了公网和直连节点还提供IPLC高速专线，支持24小时不满意退款。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心优势&lt;/strong&gt;: 解锁国际流媒体平台和ChatGPT、TikTok、YouTube；月付15元起，性价比高；支持24小时退款。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;套餐价格&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;套餐&lt;/th&gt;
&lt;th&gt;价格&lt;/th&gt;
&lt;th&gt;每月流量&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;基础版&lt;/td&gt;
&lt;td&gt;19元/月&lt;/td&gt;
&lt;td&gt;100GB&lt;/td&gt;
&lt;td&gt;小众稳定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;标准版&lt;/td&gt;
&lt;td&gt;30元/月&lt;/td&gt;
&lt;td&gt;200GB&lt;/td&gt;
&lt;td&gt;日常优选&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;高级版&lt;/td&gt;
&lt;td&gt;50元/月&lt;/td&gt;
&lt;td&gt;600GB&lt;/td&gt;
&lt;td&gt;高流量选择&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;评测体验&lt;/strong&gt;: 优信云低调稳定，是&lt;strong&gt;VPN梯子&lt;/strong&gt;中的潜力股，适合追求隐私的用户。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优惠活动&lt;/strong&gt;: 年付85折，优惠码 yx85&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;机场科普&lt;/h2&gt;
&lt;h3&gt;什么是机场梯子&lt;/h3&gt;
&lt;p&gt;机场节点梯子是提供科学上网服务的网络供应商，其主要服务项目是提供Shadowsocks、V2ray、Trojan等翻墙协议的节点服务器。这些机场因早期使用Shadowsocks和ShadowsocksR（简写为SS/SSR）协议，而小飞机又是Shadowsocks软件的图标，因此得名“机场”。这些节点服务器订阅文件输入到“飞机”里，就是可以帮助用户绕过网络封锁的“航线”。&lt;/p&gt;
&lt;h3&gt;什么是VPN&lt;/h3&gt;
&lt;p&gt;VPN（虚拟私人网络）是一种内置了加密通讯协议的客户端，用于保护个人数字通讯隐私和数据加密，它的协议设计并不是为了翻墙。&lt;/p&gt;
&lt;p&gt;当用户连接到VPN服务器时，所有的网络数据都会通过加密隧道传输，这意味着即使在公共Wi-Fi网络上，黑客也难以窃取用户的敏感信息，如密码、个人信息和银行数据。VPN在移动设备上尤为重要，因为它可以防止在公共Wi-Fi上连接时受到数据泄露和网络攻击。&lt;/p&gt;
&lt;h3&gt;机场和VPN的区别&lt;/h3&gt;
&lt;p&gt;当谈到机场和VPN的区别时，虽然它们都可用于科学上网，但在功能和使用方面存在明显区别。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;首先，机场的主要目的是为了翻墙，因此对线路节点做了协议优化，以实现更适合大数据高速传输的效果。这使得在访问被限制的网站时，机场相比VPN在数据传输速度和稳定性方面表现更出色。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;其次，专业VPN在数据加密防护方面拥有极高的级别，保障用户的数字通讯隐私。然而，对于普通用户来说，机场使用的TLS加密协议已足够满足基本隐私保护需求，尤其考虑到与VPN相比，机场在数据传输速度上具有显著优势。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于中国大陆用户，机场采用了动态分流自动均衡规则，智能地区分境内境外的IP访问路径，并针对节点服务器的压力进行智能分配，从而保证了访问速度。相比之下，VPN的高级别加密处理所有的访问链接，可能影响网路浏览体验。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最后，使用方式也有区别。VPN通常需要安装不同供应商开发的软件才能使用，而机场只需将节点订阅链接复制到喜欢的开源软件即可。这使得用户能够灵活地随时切换不同机场节点，实现更高效和便捷的上网体验。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;机场节点梯子的主要优点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;性价比高： 这些机场梯子加速器价格相比 ExpressVPN这些便宜很多，但并不意味着节点梯子服务质量不行，恰恰相反，我们购买同样或更低价格的套餐（使用折扣优惠码），可以获得这些便宜机场梯子的顶级套餐，延迟低、速度快的香港、台湾、日本、韩国、新加坡等地VPN节点都能用，而 ExpressVPN 提供给中国大陆翻墙用户的节点延迟都偏高，有时候需要等待很长时间才能连接到VPN服务器。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;稳定性好：这些魔法上网的梯子加速器都采用专门的翻墙协议，可以有效地抗防火长城的封锁，如果你之前没有听说过 Shadowsocks、V2ray、Trojan 这些词汇，相信我，使用过这些节点梯子之后一定会惊艳到你。这些翻墙协议专门针对防火长城开发，有专门的开发者进行更新迭代，相比传统的VPN万年不更新一次线路和协议，优势就不言而喻了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;速度快：这些翻墙机场不仅有针对性的VPN协议，线路节点也是经过优化的，使用对中国国内友好的翻墙线路，延迟低、速度快，甚至部分翻墙梯子采用内网专线进行网络加速，不经过 GFW 防火长城，即使是敏感时期，当使用其他家VPN的线路被封锁时，专线翻墙加速器依然可以畅享高速的Google、youtube等上外网浏览体验。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;节点覆盖全：这些翻墙VPN梯子重视中国用户，提供的也是中国用户喜欢的VPN服务器节点：香港、台湾、日本、韩国、新加坡、美国机场服务器节点，可解锁当地 Netflix、HBO、Disney+ 等流媒体服务，甚至有的会提供 土耳其、阿根廷、巴基斯坦、菲律宾 等小众梯子节点，帮助用户以极低的价格去购买 Netflix、Steam、Spotify 等服务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;客户端支持好：这些翻墙VPN即拥有自己开发的VPN客户端，用户也可以自由的选择流行的第三方翻墙客户端，全部适用于手机和PC电脑下载安装，通过导入订阅链接的方式使用，非常快速方便。Windows、Mac、Android 用户推荐使用 Clash 小猫咪梯子 客户端， iOS 用户推荐使用 Shadowrocket 小火箭梯子 或者 Stash。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;机场和VPN怎么选&lt;/h3&gt;
&lt;p&gt;综合而言，机场和VPN都在帮助用户绕过网络封锁、访问被限制的网站和保护隐私方面发挥重要作用，可以根据各自特点的不同，选择最符合个人需求场景的服务提供商就可以了，但是要注意以下几点：&lt;/p&gt;
&lt;p&gt;搭建机场技术难度并不高，很多个人自建机场圈钱跑路，使用户遭受损失。所以，尽量选择运营时间长、节点丰富、稳定可靠，在市场有一定口碑的高性价比可靠机场；&lt;/p&gt;
&lt;p&gt;不管是用机场节点，还是 VPN，尽量先选择1–3月的短期套餐。毕竟，服务商太多了，在不熟悉了解前，尽量谨慎选择；&lt;/p&gt;
&lt;p&gt;很多只在简中圈推广号称永久免费的XX-VPN，其实就是套壳了内置的机场节点协议的小程序，开发成本极低，这种所谓的“VPN”或者“加速器”，在隐私安全保护和通讯数据加密方面，和专业的 VPN 大厂相比要差太多。而且永久免费，他们搭成本进来图你的啥需要仔细想想；&lt;/p&gt;
&lt;p&gt;不要选择所谓的一元机场和月抛机场，虽然便宜但是线路质量无法保障，虽然不怕机场主&lt;/p&gt;
&lt;h2&gt;如何选择适合你的翻墙机场？&lt;/h2&gt;
&lt;p&gt;选择&lt;strong&gt;翻墙机场&lt;/strong&gt;并非“一刀切”，需要根据个人需求权衡以下几个方面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;预算有限&lt;/strong&gt;: 尔湾云和扬帆云是&lt;strong&gt;机场推荐&lt;/strong&gt;中的实惠选择，适合学生或偶尔使用外网的用户。它们的流量虽不算多，但对于日常浏览网页、查资料已足够。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;稳定性优先&lt;/strong&gt;: 速云梯和扬帆云提供IPLC专线和BGP中转支持，节点覆盖广泛，即使在网络高峰期或敏感时期也能保持连接稳定。如果你需要长时间在线（如跨境电商、远程办公），这两款是不错的选择。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流媒体解锁&lt;/strong&gt;: 速云梯和疾风云是&lt;strong&gt;流媒体解锁机场&lt;/strong&gt;的佼佼者，支持Netflix、Disney+甚至8K视频播放。它们的旗舰套餐流量充足（1200GB），延迟低，适合影迷或需要高清体验的用户。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特殊时期&lt;/strong&gt;: 在网络封锁加剧时，IPLC专线（如速云梯）表现出色，能有效绕过限制。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网络环境&lt;/strong&gt;: 如果你使用移动或教育网，选BGP中转线路更优，能适配不同运营商，减少延迟和丢包。电信用户可测试各机场节点，选择延迟最低的线路。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;额外建议&lt;/strong&gt;: 便宜梯子人多易堵，尔湾云的高价（24元/月）换来的是顶级稳定性。建议根据使用频率选择月付或年付，同时准备备用机场。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;如何使用翻墙机场？&lt;/h2&gt;
&lt;p&gt;使用&lt;strong&gt;魔法梯子&lt;/strong&gt;比传统VPN稍复杂，但上手后非常简单。以下是详细步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;注册购买&lt;/strong&gt;: 访问机场官网（&lt;a href=&quot;https://affgo.cc/yafa&quot;&gt;如扬帆云官网 &lt;/a&gt;），注册账号并选择套餐。支付后，你会获得用户面板，包含订阅链接、流量统计和使用说明。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;下载客户端&lt;/strong&gt;: PC推荐 V2rayN 或 Clash，Android 用 Clash 或 Surfboard，iOS 用 Shadowrocket（需海外 Apple ID）。各家翻墙机场官网均提供下载及使用说明。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;导入订阅&lt;/strong&gt;: 登录面板，复制订阅链接，打开客户端，粘贴链接并更新，节点列表会自动加载。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;连接测试&lt;/strong&gt;: 选择低延迟节点（如“日本-50ms”），点击连接，验证谷歌或YouTube是否可用。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;常见问题&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;连接失败&lt;/strong&gt;: 检查订阅是否过期，或换节点测试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;速度慢&lt;/strong&gt;: 优先选IPLC节点（如速云梯）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注意事项&lt;/strong&gt;: 订阅链接勿公开，发现异常及时重置。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;新手建议&lt;/strong&gt;: 新手不要上来就订年付套餐，按月订阅购买套餐先行试用。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;小贴士&lt;/h2&gt;
&lt;p&gt;以下是实用建议，帮助你优化使用体验：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;线路选择&lt;/strong&gt;: IPLC专线抗封锁强，BGP中转适配性好，公网直连最便宜。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;速度评测&lt;/strong&gt;: 50Mbps以上且无断流即可满足4K需求，用客户端测速筛选最佳节点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;协议选择&lt;/strong&gt;: SS简单，V2Ray丰富，Trojan伪装强，Hysteria适合复杂网络。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;备用方案&lt;/strong&gt;: 重度用户请备用机场，应对突发情况。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;客服与社区&lt;/strong&gt;: 通常机场官网都提供翻墙插件及教程，如果你完全小白，那就选有客服的机场。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;小技巧&lt;/strong&gt;: 定期更新订阅，关注官网公告，避免节点失效。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;2026年，这些稳定的手机电脑科学上外网的梯子加速器凭借各自优势，成为&lt;strong&gt;翻墙机场&lt;/strong&gt;中的顶尖选择。无论你是新手还是老手，这些&lt;strong&gt;VPN梯子&lt;/strong&gt;都能满足需求。快选一款&lt;strong&gt;魔法梯子&lt;/strong&gt;，畅享高速、无界网络吧！&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;免责声明： 本文所介绍的机场梯子VPN请只限用于正常外贸商务、新媒体、游戏娱乐、学习交流，切勿用在违法犯罪用途，用户请自觉遵守当地法律法规，出现一切后果本项目作者概不负责。&lt;/p&gt;
&lt;p&gt;2026梯子推荐，翻墙机场 ，机场推荐 ，SS/SSR/机场，机场加速器购买，V2ray机场梯子，Trojan机场 ，Clash节点，好用的便宜梯子，翻墙梯子，VPN梯子 ，稳定梯子 ，手机电脑梯子，外网梯子，魔法梯子，油管梯子，科学上网&lt;/p&gt;
&lt;/div&gt;</description><author>acsor</author><pubDate>Sat, 04 Apr 2026 13:05:07 GMT</pubDate></item><item><title>PC 梯子哪个好用？七款海外电脑梯子软件推荐 + 免费试用 + 跨境加速器详细教程</title><link>https://cnodejs.org/topic/69d0b0b1f4e6809d9f4753be</link><guid>https://cnodejs.org/topic/69d0b0b1f4e6809d9f4753be</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;strong&gt;越来越多的上班族、外贸从业者、程序员、设计师和游戏玩家都需要一台可靠的 PC 电脑梯子来突破网络限制。无论是访问 Google Scholar 查找资料、用 ChatGPT/Claude 高效编程、登录 GitHub 拉取代码、解锁 YouTube 4K 视频，还是在外贸业务中稳定运行 TikTok 广告和 WhatsApp 沟通，一款好用的电脑跨境加速器都能大幅提升效率。本文聚焦 PC 电脑用户需求，精选 7 款经过长期实测、稳定不跑路的国外电脑梯子软件（机场节点），全部支持 Windows/Mac/Linux 系统同时，也支持手机的科学上网梯子，提供免费试用或低价月付入口，同时附上最详细的 pc 电脑梯子怎么用教程，帮助你快速上手。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://p.inari.site/usr/1819/698d26e2168ce.png&quot;&gt;&lt;img src=&quot;https://p.inari.site/usr/1819/698d26e2168ce.png&quot; alt=&quot;Picture2.png&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;以下机场均采用 SSR/V2Ray/Trojan 等先进协议，节点覆盖香港、台湾、日本、美国、新加坡等主流地区，支持 Clash for Windows、V2rayN、Clash Verge 等主流 PC 客户端一键导入，晚高峰稳定不掉线，完美解锁 Netflix、Disney+、ChatGPT、TikTok 等服务。&lt;/p&gt;
&lt;h2&gt;1.TinnyEwan —— 新人首选高性价比跨境加速器&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://affgo.cc/ewan&quot;&gt;&lt;strong&gt;官方网站注册&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;成立于 2023 年，节点覆盖香港、日本、美国、新加坡、法国等地区，提供 SSR/Trojan 协议。PC 端支持 Clash、V2rayN 一键导入，速度稳定，适合外贸和学习用户。支持支付宝微信支付，月付流量充足，新用户注册支持24小时不满意退款。&lt;/p&gt;
&lt;h2&gt;2.扬帆云 —— 专线品质，晚高峰最稳&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://affgo.cc/yafa&quot;&gt;&lt;strong&gt;官方网站注册&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;2022 年老牌团队出品，采用 IPLC 专线 + 中转技术，节点分布香港、台湾、美国、日本、韩国、法国等地。解锁 Netflix、Disney+ 能力极强，PC 电脑梯子怎么用？只需复制订阅链接到 Clash for Windows，即可一键切换节点。套餐灵活，适合重度流媒体用户。&lt;/p&gt;
&lt;h2&gt;3.疾风云 —— 节点最多、覆盖最广的便宜好用机场&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://affgo.cc/jife&quot;&gt;&lt;strong&gt;官方网站注册&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;节点多达 21 个国家/地区（台湾、美国、法国、新加坡、印度、澳门等），Shadowsocks 协议稳定高效。PC 端兼容性极佳，支持 Windows/Mac/Linux 全平台客户端。价格亲民，常有免费试用活动，是预算有限但想体验国外电脑梯子软件的首选。&lt;/p&gt;
&lt;h2&gt;4.梯云纵 —— 运营七年老牌，综合实力最强&lt;/h2&gt;
&lt;p&gt;七年老机场，节点遍布亚洲、美洲，支持 Netflix、HBO、Disney+、TikTok、ChatGPT 全解锁。通用订阅格式完美兼容 V2rayN、Clash 系列客户端，PC 电脑梯子推荐免费试用的话，梯云纵经常提供新用户免费体验包，稳定性极高，几乎无断流投诉。&lt;/p&gt;
&lt;h2&gt;5.Coconut —— 新锐专线机场，ChatGPT 专属优化&lt;/h2&gt;
&lt;p&gt;2023 年成立，采用 Shadowsocks+IPLC/IEPL 专线，节点覆盖香港、日本、韩国、新加坡、美国、德国、荷兰。特别针对 ChatGPT、MidJourney 等 AI 工具优化，延迟极低。PC 端一键导入订阅，适合程序员和 AI 重度用户。&lt;/p&gt;
&lt;h2&gt;6.龙卷风机场 —— 东南亚节点丰富，低延迟游戏加速&lt;/h2&gt;
&lt;p&gt;2024 年新机场，Trojan 协议节点覆盖泰国、新加坡、越南、马来西亚、土耳其、阿根廷等 16 国。PC 电脑梯子哪个好用？玩国际服游戏选龙卷风准没错，低延迟 + 高稳定，支持支付宝微信，注册即送免费试用流量。&lt;/p&gt;
&lt;h2&gt;7.可乐云 —— 按流量计费，不限时长最灵活&lt;/h2&gt;
&lt;p&gt;按量付费、不限设备、不限时间，国内中转优化 + 全流媒体解锁，节点包括香港、台湾、日本、新加坡、美国。每个地区独立入口服务器，晚高峰速度极佳。适合偶尔使用但追求极致性价比的 PC 用户。&lt;/p&gt;
&lt;h2&gt;PC 电脑梯子怎么用？最详细的新手教程（3 分钟上手）&lt;/h2&gt;
&lt;p&gt;很多朋友问 “pc 电脑梯子怎么用”，其实机场比传统 VPN 更灵活，操作也非常简单： 注册并购买套餐 进入机场官网，注册账号，选择月付或按量套餐（建议新人先选最低价或免费试用）。 下载 PC 客户端 Windows 推荐：Clash for Windows 或 V2rayN Mac 推荐：ClashX 或 V2rayU Linux 推荐：Clash for Linux 导入订阅节点 在机场个人中心复制 “订阅链接”，打开客户端 → 添加订阅 → 粘贴链接 → 更新订阅。 选择节点并连接 在节点列表中选择延迟最低的（建议先用内置测速功能筛选），点击连接即可。连接成功后，全局或规则模式任选（新手建议规则模式，只加速国外网站）。 验证是否成功 打开浏览器访问 &lt;a href=&quot;http://google.com&quot;&gt;google.com&lt;/a&gt; 或 &lt;a href=&quot;http://youtube.com&quot;&gt;youtube.com&lt;/a&gt;，若正常加载即成功。 整个过程无需任何技术基础，机场通常还提供图文/视频教程，比传统国外电脑梯子软件更灵活、速度更快。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PC 梯子哪个好用？以上 7 款国外电脑梯子软件（跨境加速器）都是经过无数用户实测的优质选择，无论是稳定性、速度还是解锁能力都远超普通免费梯子。建议立即访问官网注册免费试用，先亲身体验再决定——选对一款好用的 PC 电脑梯子，能让你的工作效率和娱乐体验提升数倍。&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>acsor</author><pubDate>Sat, 04 Apr 2026 06:33:21 GMT</pubDate></item><item><title>稳定梯子推荐：六款手机PC电脑能用的翻墙机场节点梯子VPN</title><link>https://cnodejs.org/topic/69d0a4fdf4e680271f4753a7</link><guid>https://cnodejs.org/topic/69d0a4fdf4e680271f4753a7</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;strong&gt;国内手机和电脑上外网梯子的机场节点怎么用？- windows/MacOS/Linux等PC电脑系统和苹果安卓等手机系统使用翻墙机场时，将订阅的翻墙节点导入Clash、Shadowrocket（小火箭）、Stash（Clash for iOS）、Quantumult X（圈叉）、Surge、V2rayN、sing-box 等翻墙客户端就可以轻松实现科学上网。今天给大家分享六个pc电脑和手机上都能稳定流畅科学上网的机场节点梯子推荐，这些翻墙机场相较传统的翻墙vpn和网络加速器来说，便宜好用性价比超高，如果你也想买一个便宜的稳定靠谱梯子，那么不妨从这些好用的魔法梯子中选择一个。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://discuss.d2l.ai/uploads/default/original/3X/7/2/7250a7e2f35e11fb46247c70b0a916c19335f67e.jpeg&quot; alt&gt;&lt;/p&gt;
&lt;h3&gt;一、尔湾云-2026最佳稳定梯子&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://affg.cc/ewan&quot;&gt;官网地址&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;推荐指数：⭐⭐⭐⭐⭐⭐&lt;/p&gt;
&lt;p&gt;收费：24 元/月（&lt;a href=&quot;https://affg.cc/ewan&quot;&gt;官网领取七折优惠&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;简介：尔湾云2022年开始运营。主打高级别线路，虽然定价不算低但是翻墙节点线路质量非常可靠，&lt;strong&gt;敏感期抗封锁能力强&lt;/strong&gt;。这个翻墙机场节点梯子来上外网google谷歌、youtube油管、刷twitter推特以及支持chatgpt、Gemini、Claude等AI模型都完全没有问题。定价虽然24元/月，但是速度靠谱线路稳定，4k视频缓冲不卡顿，你完全可以用它来观看奈飞netflix、迪士尼disney、hbo等流媒体。流量一次性到账，完全不用担心每月剩余流量清零问题。&lt;/p&gt;
&lt;h3&gt;二、扬帆云-安全好用的翻墙机场推荐&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://affg.cc/yafa&quot;&gt;官网地址&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;推荐指数：⭐⭐⭐⭐⭐⭐&lt;/p&gt;
&lt;p&gt;收费：19元/月&lt;/p&gt;
&lt;p&gt;简介：扬帆云机场是在行业中诞生最早的一批电脑翻墙梯子之一，如果有半年以上电脑挂梯子魔法科学上外网经验的小伙伴，对它肯定不陌生。扬帆云在全世界都拥有极高的人气，粉丝群体庞大，真爱粉更是数不胜数。客户端设计也很简洁，几乎不需要翻墙教程，下载后看一眼就会，毕竟就那么几个按钮。作为稳定的老牌机场梯子，价格也并不高，支持解锁流媒体。&lt;/p&gt;
&lt;h3&gt;三、SYCloud速云梯-高性价比电脑手机翻墙梯子推荐&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://affg.cc/suyu&quot;&gt;官网地址&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;推荐指数：⭐⭐⭐⭐⭐&lt;/p&gt;
&lt;p&gt;SYCloud是一款高端翻墙机场节点梯子，由晴天私人珍藏。其上网速度和网络稳定性优秀，成为晴天日常工作、游戏、观影的首选工具。SYCloud以观影速度优化出色而闻名，能够流畅播放4K和8K超高清视频，无卡顿超前缓冲。&lt;/p&gt;
&lt;p&gt;作为一个月收费仅为19元的电脑翻墙梯子软件，SYCloud采用成本极高的IEPL专线，为用户提供出色的观影体验。这种专线机场在观看高清视频时表现出色，支持解锁netflix和Disney等流媒体能够实现秒开视频，流畅播放。同时，SYCloud的翻墙节点还可用于游戏加速，具有极低的延迟。&lt;/p&gt;
&lt;p&gt;SYCloud的运营时间可以追溯到几年前，是一家经验丰富的翻墙机场服务商。其支持Clash（Stash）、Shadowrocket小火箭等插件，是晴天目前使用时间最长的外网梯子。对于新手用户，SYCloud提供完整的科学上网教程和翻墙插件下载使用指导，24小时客服为用户提供便捷的帮助。&lt;/p&gt;
&lt;h3&gt;四、疾风云-靠谱梯子推荐&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://affg.cc/jife&quot;&gt;官网地址&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;推荐指数：⭐⭐⭐⭐&lt;/p&gt;
&lt;p&gt;简介：疾风云也是一款非常好用的电脑科学上网梯子，网速稳定快速收费不贵，&lt;/p&gt;
&lt;h3&gt;五、优信云-小众高速机场&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://affg.cc/uxin&quot;&gt;官网地址&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;推荐指数：⭐⭐⭐⭐&lt;/p&gt;
&lt;p&gt;简介：优信云是新兴Shadowsocks翻墙机场服务，提供隧道中转和IPLC线路，节点分布美、法、日、新、港台等地区。解锁Netflix奈飞等各大流媒体，也支持ChatGPT、TikTok、YouTube等平台。&lt;/p&gt;
&lt;p&gt;客户端方面，优信云机场做的还是很不错的，基本上所有的主流平台都被涵盖了，包括windows电脑，苹果手机，安卓手机，路由器等等，在机场官网下载他们的插件安装包后就能一键导入使用，而且各个客户端都有攻略，简洁明了，看一眼就会。&lt;/p&gt;
&lt;h3&gt;六、三番云-按流量计费机场&lt;/h3&gt;
&lt;p&gt;推荐指数：⭐⭐⭐⭐&lt;/p&gt;
&lt;p&gt;简介：三番云是一家提供高速稳定的网络加速服务的高端机场，支持多种协议，如SS/SSR、V2Ray和Trojan，适用于Windows、Android、macOS和Linux等多种操作系统。它的特点包括流媒体视频和音乐的解锁能力，如Netflix、Disney、Hulu、HBO等，支持ChatGPT，并且在稳定性方面表现良好。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这六款能在电脑上稳定流畅使用的科学上网软件魔法工具，同样也适用于手机翻墙浏览外网梯子。是这些年自己用过并且感觉还可以的，收费有高有低，但总体来说都是高性价比且安全好用的，选择适合自己的一款翻墙梯子放心用就是。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;免责声明： 本文所介绍的机场梯子VPN请只限用于正常外贸商务、新媒体、游戏娱乐、学习交流，切勿用在违法犯罪用途，用户请自觉遵守当地法律法规，出现一切后果本项目作者概不负责。&lt;/p&gt;
&lt;p&gt;2026梯子推荐，翻墙机场 ，机场推荐 ，SS/SSR/机场，机场加速器购买，V2ray机场梯子，Trojan机场 ，Clash节点，好用的便宜梯子，翻墙梯子，VPN梯子 ，稳定梯子 ，手机电脑梯子，外网梯子，外贸梯子，油管梯子，AI梯子&lt;/p&gt;
&lt;/div&gt;</description><author>acsor</author><pubDate>Sat, 04 Apr 2026 05:43:25 GMT</pubDate></item><item><title>5 年没回 CNode 水，发现变了</title><link>https://cnodejs.org/topic/69cb6db7cd2d719ccb2cfff2</link><guid>https://cnodejs.org/topic/69cb6db7cd2d719ccb2cfff2</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;之前是挺多人讨论 nodejs，前端这些。
现在剩下了 ai，连招聘也只剩下几个。
全是梯子广告&lt;/p&gt;
&lt;/div&gt;</description><author>Lizhooh</author><pubDate>Tue, 31 Mar 2026 06:46:15 GMT</pubDate></item><item><title>小白教程：使用 Coze 构建漂流瓶匿名社交智能体</title><link>https://cnodejs.org/topic/69afc9e7cd2d71bf242cfde0</link><guid>https://cnodejs.org/topic/69afc9e7cd2d71bf242cfde0</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;本文带你零基础、零代码，快速借助 Coze 平台搭建一个漂流瓶匿名社交智能体，全程操作简单易懂，跟着步骤走就能完成。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心功能&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;扔瓶子&lt;/strong&gt;：用户可通过智能体扔出漂流瓶，可用于日常吐槽、分享每日动态、交流经验、倾诉心情等，实现匿名表达。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;捡瓶子&lt;/strong&gt;：用户可通过智能体随机捡起一个漂流瓶，查看他人分享的内容，感受陌生人的情绪与故事，完成匿名社交互动。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;一、准备工作：免费注册 Coze 账号&lt;/h1&gt;
&lt;p&gt;访问官网免费注册使用：&lt;a href=&quot;https://www.coze.cn/home&quot;&gt;https://www.coze.cn/home&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;二、创建智能体&lt;/h1&gt;
&lt;p&gt;进入 Coze 平台后，点击创建智能体，自定义智能体名称（示例：「YY漂流瓶」），创建完成后进入详情页进行后续设置，操作界面如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/08/d30cb90c-4c32-42db-9ad3-6aabbffa0ffb.png&quot; alt=&quot;创建智能体&quot;&gt;&lt;/p&gt;
&lt;h2&gt;2.1 人设与回复逻辑（直接复制使用）&lt;/h2&gt;
&lt;p&gt;在智能体「人设与回复」模块，复制以下内容粘贴，无需修改，可直接适配漂流瓶核心功能：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-plaintext&quot;&gt;&lt;code&gt;# 角色：

你是YY漂流瓶，主要功能就2个，用户可以丢一个漂流瓶；用户可以捞起一个漂流瓶。

以实现漂流瓶的匿名社交功能。


## 技能：

使用以下技能之前，都需要保证 token 变量有值，没有token或无效，需要先调用插件“apifm &amp;#x2F; authorize”获取token，获取成功后，输出登录成功的消息，告知当前登录的用户编号

插件参数说明：
- sysUuid 传 sys_uuid 变量的值

###  扔瓶子

如果无法提取到用户经纬度数据，经纬度参数传0，调用插件 bottleMsg_publish 完成扔瓶子，成功后提示用户成功

###  捞瓶子
调用插件 bottleMsg_salvage 

## 限制：
- 只能回复和和上面技能有关的问题
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;2.2 设置变量&lt;/h2&gt;
&lt;p&gt;变量设置用于存储用户信息和交互所需参数，步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;进入「记忆 → 变量」页面，勾选启用 sys_uuid、sys_longitude、sys_latitude 三个系统级变量，分别用于存储用户唯一标识、用户所在经度、用户所在纬度。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;添加用户变量：新增 token 变量，用于存储用户登录凭证，保障登录状态与功能正常使用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;变量设置完成后效果如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/10/be65569a-5f84-4339-b1e2-5a7b42389de2.png&quot; alt=&quot;设置预览&quot;&gt;&lt;/p&gt;
&lt;h2&gt;2.3 添加插件&lt;/h2&gt;
&lt;p&gt;插件是实现漂流瓶核心功能的关键，进入「技能 → 插件」页面，点击「+」按钮，搜索「apifm」，添加以下3个插件，缺一不可：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;apifm / authorize&lt;/li&gt;
&lt;li&gt;apifm base / bottleMsg_publish&lt;/li&gt;
&lt;li&gt;apifm base / bottleMsg_salvage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;插件功能说明&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;apifm / authorize：实现用户自动注册登录功能，生成用户唯一凭证，保障用户记忆和个性化服务正常运行。&lt;/li&gt;
&lt;li&gt;apifm base / bottleMsg_publish：实现「扔瓶子」功能，接收用户输入内容并完成漂流瓶发布。&lt;/li&gt;
&lt;li&gt;apifm base / bottleMsg_salvage：实现「捡瓶子」功能，随机获取其他用户发布的漂流瓶内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;插件参数设置&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每个插件右侧均有「齿轮」图标，点击即可进入设置界面，按以下要求配置（关键步骤，请勿出错）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;apifm / authorize 插件设置设置界面如下：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;sysUuid 参数：直接选中引用系统参数的值（无需手动输入）；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;domain：填写自己的api工厂后台专属域名，填写完成后关闭右侧开关（关闭后，AI将直接使用填写的域名）；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;merchantKey：填写自己的api工厂后台商户密钥，填写完成后关闭右侧开关（关闭后，AI将直接使用填写的密钥）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/10/2f4319cf-d107-4406-9ea2-901759ef85f7.png&quot; alt=&quot;设置预览&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;bottleMsg_publish 和 bottleMsg_salvage 插件设置设置界面如下：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;token 参数：直接选中引用系统参数的值（无需手动输入）；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;domain：填写自己的api工厂后台专属域名，填写完成后关闭右侧开关（关闭后，AI将直接使用填写的域名）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/10/364ef93a-c225-4994-a00c-942b8e53f333.png&quot; alt=&quot;设置预览&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;测试账号说明：如果没有自己的api工厂专属域名和商户密钥，可使用以下测试账号进行调试，直接复制填写即可：
domain： wxapi
merchantKey： 1ecf17ea389ebb5ccd5e258e390d3696
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;2.4 其他设置&lt;/h2&gt;
&lt;p&gt;平台默认设置已可满足漂流瓶基本使用需求，无需额外修改；若需优化体验，可按需调整「开场白」「语音音色」「交互风格」等，让智能体更贴合个人需求或业务场景。&lt;/p&gt;
&lt;h1&gt;三、在线测试与效果预览&lt;/h1&gt;
&lt;p&gt;设置完成后，可通过平台右侧实时测试窗口，模拟用户「扔瓶子」「捡瓶子」操作，边测试边调整参数，直至智能体回复符合预期，测试界面如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/10/cdc09c6a-ce62-4f78-8f34-087f977d34bd.png&quot; alt=&quot;设置预览&quot;&gt;&lt;/p&gt;
&lt;h1&gt;四、正式发布&lt;/h1&gt;
&lt;p&gt;测试无误后，点击页面右上角「发布」按钮，无需审核，发布后即时生效，任何人可直接访问该智能体，进行漂流瓶匿名社交互动。&lt;/p&gt;
&lt;h1&gt;五、效果展示&lt;/h1&gt;
&lt;h2&gt;5.1 扔瓶子效果&lt;/h2&gt;
&lt;p&gt;用户发送扔瓶子指令后，智能体接收内容并完成发布，反馈成功提示，效果如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/10/6f104dec-0d43-40c2-88c6-886438ff4965.png&quot; alt=&quot;扔瓶子&quot;&gt;&lt;/p&gt;
&lt;h2&gt;5.2 捡瓶子效果&lt;/h2&gt;
&lt;p&gt;用户发送捡瓶子指令后，智能体随机获取一个漂流瓶内容并展示，效果如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/10/dbfafd42-bc12-4287-8081-5c4f3e26cdc4.png&quot; alt=&quot;捡瓶子&quot;&gt;&lt;/p&gt;
&lt;h1&gt;总结&lt;/h1&gt;
&lt;p&gt;本教程全程零代码、零基础，通过 Coze 平台快速搭建漂流瓶匿名社交智能体，核心步骤可总结为「注册账号 → 创建智能体 → 配置人设与变量 → 添加插件并设置 → 测试 → 发布」。整个过程操作简单，无需专业技术，借助 Coze 平台的可视化操作和插件功能，即可快速实现匿名漂流瓶的核心社交功能。
测试账号可满足调试需求，若需长期使用，建议注册自己的api工厂账号，获取专属域名和商户密钥，保障功能稳定运行。后续可根据个人需求，优化智能体的交互风格、开场白等细节，提升用户体验。无论是用于个人兴趣交流，还是小型社交场景搭建，这个智能体都能快速落地使用。&lt;/p&gt;
&lt;/div&gt;</description><author>gooking</author><pubDate>Tue, 10 Mar 2026 07:36:07 GMT</pubDate></item><item><title>10 分钟极速上手：使用 Coze 快速搭建可商用级智能客服智能体</title><link>https://cnodejs.org/topic/69ad4d4fcd2d716d0c2cfdd0</link><guid>https://cnodejs.org/topic/69ad4d4fcd2d716d0c2cfdd0</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;本文带你零基础、零代码，用 Coze 平台快速搭建一个可直接上线使用的智能客服智能体。你只需在本文基础上优化人设、完善知识库，即可快速落地产品级客服能力。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心功能&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自动理解用户问题，基于知识库精准回复&lt;/li&gt;
&lt;li&gt;未知问题自动转为用户反馈并后台保存，支持微信 / 钉钉 / 邮件实时通知，持续迭代知识库&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;一、准备工作：免费注册 Coze 账号&lt;/h1&gt;
&lt;p&gt;访问官网免费注册使用：&lt;a href=&quot;https://www.coze.cn/home&quot;&gt;https://www.coze.cn/home&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;二、创建专属知识库&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/08/673644df-8c9b-488a-80d9-48592f437324.png&quot; alt=&quot;创建专属知识库&quot;&gt;&lt;/p&gt;
&lt;p&gt;根据业务需求自定义知识库名称，例如「智能客服知识库」。&lt;/p&gt;
&lt;p&gt;Coze 支持绝大多数文档格式，也可直接填入在线文档 URL 自动导入。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;测试知识库示例（可直接复制）&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;1）质量检查和软件测试有什么区别？
QA（质量保证）关注软件开发过程的质量；软件测试确保最终产品功能符合用户需求。

2）什么是 Testware？
Testware 是测试用例、测试数据、测试计划等测试相关工件。

3）构建和发布之间有什么区别？
构建：开发团队提供给测试团队的安装包。
发布：测试&amp;#x2F;开发团队交付给客户的正式安装包。

4）SQA 团队在自动化中面临哪些挑战？
自动化工具掌握、脚本复用、用例适配性、复杂用例自动化。

5）什么是漏洞泄漏和漏洞释放？
错误发布：已知缺陷但优先级低，先行交付测试。
错误泄漏：客户发现测试团队未检出的缺陷。

6）什么是数据驱动测试？
从 csv、excel 等文件读取测试数据，在被测系统上自动化执行。
&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;三、创建智能客服智能体&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/08/d30cb90c-4c32-42db-9ad3-6aabbffa0ffb.png&quot; alt=&quot;创建智能客服智能体&quot;&gt;&lt;/p&gt;
&lt;p&gt;自定义智能体名称，例如「智能客服智能体」。&lt;/p&gt;
&lt;h2&gt;3.1 人设与回复逻辑（直接复制使用）&lt;/h2&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;# 角色
你叫小美，是一位资深QA专家，有任何QA方面的问题都可以咨询我。

## 回答主题简介
我是杭州飞的高科技公司的客服人员，帮你提供在线咨询服务。

## 工作流程
### 步骤一：问题理解与回复分析
1. 认真理解从知识库中召回的内容和用户输入的问题，判断是否为有效答案。
2. 若问题模糊、信息不足，主动追问用户，确保准确理解需求。

### 步骤二：回答用户问题
1. 与QA主题无关的问题，礼貌拒绝回答。
2. 知识库无相关内容时，统一回复：
“对不起，我已学习的知识中不包含问题相关内容，暂时无法提供答案。如果你有相关问题，请给我们留言，我们将记录并及时处理。”
并引导用户留下联系方式，通过 comment_add 插件提交反馈，返回记录编号作为回执。
3. 有匹配知识时，仅提取相关内容，整理为**精准、简洁**的答案回复。
4. 按判断返回对应文档链接，无需说明来源。

## 限制
1. 禁止回答与QA无关的问题。
2. 统一使用Markdown格式回复。
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;3.2 引入知识库&lt;/h2&gt;
&lt;p&gt;在中间栏「知识」点击 +，绑定已创建的知识库。&lt;/p&gt;
&lt;p&gt;用户提问时，智能体会自动检索知识并整理回复。&lt;/p&gt;
&lt;h2&gt;3.3 添加留言反馈插件&lt;/h2&gt;
&lt;p&gt;在「技能 → 插件」点击 +，搜索 apifm，添加 apifm common / comment_add。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;作用：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;智能体无法回答时，自动保存用户问题到后台&lt;/p&gt;
&lt;p&gt;返回反馈编号给用户&lt;/p&gt;
&lt;p&gt;支持微信 / 钉钉 / 邮件实时通知管理员，用于迭代知识库&lt;/p&gt;
&lt;h2&gt;3.4 其他设置&lt;/h2&gt;
&lt;p&gt;默认即可满足使用；可按需调整开场白、语音音色、交互风格等，让智能体更贴合业务。&lt;/p&gt;
&lt;h1&gt;四、在线测试与效果预览&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/08/fb3dad8e-9764-442d-ad2d-12d6bdf70002.png&quot; alt=&quot;设置预览&quot;&gt;&lt;/p&gt;
&lt;p&gt;右侧为实时测试窗口，边测边调，直到回复符合预期。&lt;/p&gt;
&lt;h1&gt;五、正式发布&lt;/h1&gt;
&lt;p&gt;点击右上角「发布」，无需审核、即时生效，任何人可直接访问使用。&lt;/p&gt;
&lt;h1&gt;六、效果展示&lt;/h1&gt;
&lt;h2&gt;场景 1：知识库匹配 → 精准回复&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/08/fa20b34f-200b-4241-873e-533857bc1120.png&quot; alt=&quot;问题一&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/08/77d4f3b6-0fee-4a07-af5c-faf6c3728878.png&quot; alt=&quot;答复一&quot;&gt;&lt;/p&gt;
&lt;h2&gt;场景 2：无知识 → 自动保存反馈并通知&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/08/cdd8fd7a-04d5-4ae4-90ef-fd427234bd1b.png&quot; alt=&quot;要求提供联系方式&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/08/76a0a239-0a0e-466a-9446-64ee3d8675c5.png&quot; alt=&quot;收录成功&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/08/3cc640e4-aab5-4a91-ae5a-0f40c6163640.png&quot; alt=&quot;后台效果&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.cichangelte.com/cichangelte/2026/03/08/8e067c28-4b81-4a66-883c-9c0ada21e3f6.png&quot; alt=&quot;微信提醒&quot;&gt;&lt;/p&gt;
&lt;h1&gt;总结&lt;/h1&gt;
&lt;p&gt;借助 Coze 平台，我们仅用不到 10 分钟就完成了从注册、建库、配置到发布的全流程，快速拥有了一个具备自动问答、未知问题反馈、实时通知、可直接商用的智能客服智能体。整套方案零代码、低成本、易维护，既能大幅降低人工客服压力，又能通过用户反馈持续迭代知识库，让智能体越用越聪明。无论是个人测试、团队效率提升还是企业业务落地，这套流程都具备极强的实用性与可复制性，是快速实现 AI 客服落地的最佳实践之一。&lt;/p&gt;
&lt;/div&gt;</description><author>gooking</author><pubDate>Sun, 08 Mar 2026 10:19:59 GMT</pubDate></item><item><title>基于Uniapp的会员卡储值消费系统开发实践</title><link>https://cnodejs.org/topic/69a551e2cd2d7169382cfd7d</link><guid>https://cnodejs.org/topic/69a551e2cd2d7169382cfd7d</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;在餐饮、校园、社区小店等线下消费场景中，会员卡（饭卡）储值消费系统是提升运营效率、沉淀私域用户的核心工具。很多中小商户想搭建一套适配自身场景的储值消费系统，却常常被复杂的开发流程、高昂的定制成本拦住脚步。今天就从实操角度，聊聊如何基于 Uniapp 快速搭建一套轻量化的会员卡储值消费系统，兼顾实用性与易用性。
为什么选择 Uniapp 开发储值消费系统？
Uniapp 的跨端特性是核心优势 —— 一套代码可同时适配微信小程序、App、H5 等多端，无需为不同终端单独开发，大大降低了开发和维护成本。对于聚焦线下消费场景的储值消费系统来说，多端适配能覆盖用户的不同使用习惯：商户可通过后台管理，用户则能通过小程序快速完成充值、消费，无需下载额外 App，体验更轻量化。
储值消费系统的核心功能设计与实现思路
一套实用的会员卡储值消费系统，无需追求 “大而全”，聚焦核心场景即可满足中小商户的需求，核心可围绕这几个维度设计：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;自助充值与消费：减少人工干预，提升效率
线下消费场景中，人工充值、记账不仅效率低，还容易出现错账、漏账问题。在系统设计时，可开发自助充值模块 —— 用户通过小程序绑定会员卡后，自主选择充值金额、支付方式完成储值；消费时只需出示核销码或刷卡，系统自动扣除对应金额，全程无需店员操作。同时，系统需内置消费规则校验，比如最低消费、储值余额不足提醒等，既保障商户资金安全，也让用户消费更顺畅。&lt;/li&gt;
&lt;li&gt;会员折扣体系：增强用户粘性
单纯的储值功能难以留住用户，会员等级与折扣体系是提升复购的关键。在系统中可设计分级会员机制，比如根据储值金额或消费次数划分普通会员、银卡会员、金卡会员，不同等级对应不同消费折扣。开发时可将折扣规则配置化，商户无需修改代码，就能在后台调整不同等级的折扣比例、生效时间，适配节日促销、日常优惠等不同场景。&lt;/li&gt;
&lt;li&gt;消费记录溯源：透明化提升信任
用户对储值资金的 “安全感”，来自清晰的消费明细。系统需实现消费、储值记录的实时查询功能，每条记录包含时间、金额、消费门店、交易类型等信息，用户可随时查看，商户也能通过后台导出明细对账。技术层面可通过数据库结构化存储交易数据，搭配前端列表渲染和筛选功能，让记录查询更便捷。&lt;/li&gt;
&lt;li&gt;动态消息通知：重要信息不遗漏
商户的优惠活动、系统公告、用户的充值到账提醒、消费通知等，都需要及时触达。系统可集成消息推送功能，支持公告置顶、精准推送，比如用户储值后自动发送到账提醒，商户发布新优惠时推送给对应等级的会员，提升信息触达效率。
轻量化系统的落地实践：兼顾实用性与易维护性
中小商户的系统需求核心是 “好用、好维护”，因此在开发时需避免过度复杂的架构设计：
前端基于 Uniapp 的组件化开发，复用充值、消费、记录查询等核心组件，降低开发成本；
后端采用轻量化框架，聚焦数据存储、交易逻辑校验，无需搭建复杂的分布式架构；
界面设计贴合线下消费场景的使用习惯，简化操作流程，无论是店员还是用户，无需培训就能快速上手。
这套基于 Uniapp 开发的会员卡储值消费系统，正是围绕上述思路打造 —— 聚焦线下消费的核心痛点，用轻量化的技术方案实现自助储值、会员折扣、消费溯源、消息通知等核心功能，既满足商户数字化管理的需求，也让用户的充值消费体验更便捷。
开源项目参考：站在巨人的肩膀上开发
如果想快速落地这套系统，无需从零编写代码，可参考以下开源项目，结合自身场景做二次开发：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/gooking/mealcard&quot;&gt;GITHUB&lt;/a&gt;
&lt;a href=&quot;https://gitee.com/javazj/mealcard&quot;&gt;码云镜像地址&lt;/a&gt;
&lt;a href=&quot;https://gitcode.com/gooking2/mealcard&quot;&gt;GitCode&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这些开源项目提供了完整的 Uniapp 前端代码和后端逻辑，涵盖了储值、消费、会员管理等核心功能，可直接部署试用，也能根据商户的具体需求（比如增加门店管理、多端核销等）进行定制开发。
总结
搭建一套轻量化的会员卡储值消费系统，核心是聚焦线下消费的真实场景，用适配的技术方案解决效率、信任、用户粘性等核心问题。Uniapp 的跨端优势让系统适配成本更低，而开源项目则能大幅缩短开发周期，中小商户无需投入高昂成本，也能实现线下消费的数字化管理，助力私域流量运营和客户留存。&lt;/p&gt;
&lt;/div&gt;</description><author>gooking</author><pubDate>Mon, 02 Mar 2026 09:01:22 GMT</pubDate></item><item><title>【北京/西安】Node.js 后端开发工程师</title><link>https://cnodejs.org/topic/69a52d91cd2d719f592cfd65</link><guid>https://cnodejs.org/topic/69a52d91cd2d719f592cfd65</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;岗位职责:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;负责后端系统的开发与维护，保障系统稳定运行；&lt;/li&gt;
&lt;li&gt;参与系统功能模块设计及接口开发，提升系统性能与可扩展性；&lt;/li&gt;
&lt;li&gt;协同团队完成项目开发任务，推动项目按时交付；&lt;/li&gt;
&lt;li&gt;统招本科学历以上；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;任职资格
1.熟练掌握 TypeScript 语言，能够编写类型安全的代码.
2.熟练掌握 Node.js 或 Bun.js 环境，能够使用 Express、Koa、NestJS 等框架进行后端开发。
3.至少掌握一种ORM框架，如 Sequelize、TypeORM、Prisma等。
4.熟练掌握数据库设计和操作，MySQL、PostgreSQL、Redis、Mongodb 等数据库。
5.熟悉 RESTfuI API 设计，能够设计和实现高效的API接囗,
6.至少有一个完整的0~1后端项目经验，能够独立完成项目开发和部署。
7.熟练掌握 WebSocket、HTTP等协议，能够实现实时通信功能。
8.熟悉 Linux 操作系统常用操作命令，能够进行服务器配置和维护,
9.熟蒸微服务架构，能够使用Docker、kubernetes等容器技术。
10.熟蒸常用消息队列中间件，如 RabbitMQ、Kafka 等。&lt;/p&gt;
&lt;p&gt;加分项:
1.有前端React开发经验，使用过至少一种前端主流框架，能够与前端进行良好协作
2.有大型互联网公司的开发经验，能够熟悉大型公司的开发规范和流程。
3.拥有高并发和高负载系统的开发经验。&lt;/p&gt;
&lt;p&gt;工作地点： 北京 &amp;amp; 西安&lt;/p&gt;
&lt;p&gt;现诚邀各位人才投递简历，目前开放岗位如下：后端 5 个、架构师 1 个，欢迎自荐或推荐。 简历投递：zhigangf@globalcrown.com.cn&lt;/p&gt;
&lt;/div&gt;</description><author>ZhiGang-Fang</author><pubDate>Mon, 02 Mar 2026 06:26:25 GMT</pubDate></item><item><title>WorldEnd.ai —— AI 生成的世界末日预言</title><link>https://cnodejs.org/topic/69a52216cd2d711f582cfd5c</link><guid>https://cnodejs.org/topic/69a52216cd2d711f582cfd5c</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;a href=&quot;http://WorldEnd.ai&quot;&gt;WorldEnd.ai&lt;/a&gt; 利用最新的大模型来预测世界末日可能出现的各种形式 —— 分析全球风险，并对人类的长期发展轨迹做出发人深省的预测。
这并非为了制造恐慌，而是为了探索、提升风险意识，并就未来展开更深入的对话。&lt;/p&gt;
&lt;p&gt;Website: &lt;a href=&quot;https://worldend.ai&quot;&gt;https://worldend.ai&lt;/a&gt;
GitHub: &lt;a href=&quot;https://github.com/nswbmw/worldend.ai/&quot;&gt;https://github.com/nswbmw/worldend.ai&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;屏幕截图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/nswbmw/worldend.ai/raw/master/screenshots/dark.png&quot; alt=&quot;dark&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/nswbmw/worldend.ai/raw/master/screenshots/light.png&quot; alt=&quot;light&quot;&gt;&lt;/p&gt;
&lt;h2&gt;技术栈&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/hoa-js/hoa&quot;&gt;Hoa&lt;/a&gt; - 轻量级 Web 框架&lt;/li&gt;
&lt;li&gt;Cloudflare Workers - 边缘计算平台&lt;/li&gt;
&lt;li&gt;Cloudflare KV - 缓存&lt;/li&gt;
&lt;li&gt;Cloudflare D1 - 数据库&lt;/li&gt;
&lt;li&gt;Cloudflare R2 - 存储&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description><author>nswbmw</author><pubDate>Mon, 02 Mar 2026 05:37:26 GMT</pubDate></item><item><title>听歌用铜钟 Tonzhon，写 Node.js 牛逼哄哄！</title><link>https://cnodejs.org/topic/69730c33cd2d712a1a2cfbd2</link><guid>https://cnodejs.org/topic/69730c33cd2d712a1a2cfbd2</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h1&gt;铜钟 Tonzhon 音乐平台官网：&lt;a href=&quot;https://tonzhon.whamon.com/&quot;&gt;https://tonzhon.whamon.com/&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;铜钟 Tonzhon 是一个主打「听歌」功能的 web app, 致力于为人们带来卓越的听歌体验。铜钟有着丰富的音乐资源，干净清爽的 UI 和方便的交互。在铜钟上，你不仅可以方便地找到并聆听你喜欢的歌曲，还可以将它们保存下来。&lt;/p&gt;
&lt;p&gt;铜钟上的一切内容都是与音乐直接相关的，没有广告，社交和直播，不会干扰你的听歌心情。在铜钟上，你可以沉浸到属于你自己一个人的那片天地，忘却世间的纷纷扰扰…&lt;/p&gt;
&lt;p&gt;铜钟 Tonzhon 也推出了 App，小程序 和 铜钟 for Mac or Windows，都在 &lt;a href=&quot;https://tonzhon.whamon.com&quot;&gt;https://tonzhon.whamon.com&lt;/a&gt; 的侧边栏里，欢迎大家试用。&lt;/p&gt;
&lt;h1&gt;GitHub: &lt;a href=&quot;https://github.com/enzeberg/tonzhon-music&quot;&gt;https://github.com/enzeberg/tonzhon-music&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;欢迎大家提需求、提 issue、提 PR…&lt;/p&gt;
&lt;/div&gt;</description><author>enzeberg</author><pubDate>Fri, 23 Jan 2026 05:50:43 GMT</pubDate></item><item><title>[杭州] 阿里云数据可视化 DataV 团队招聘 AI 应用开发工程师</title><link>https://cnodejs.org/topic/6972deb2cd2d715d2e2cfbbd</link><guid>https://cnodejs.org/topic/6972deb2cd2d715d2e2cfbbd</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h2&gt;职位描述&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;负责 AI 应用与 Agent 工程平台的服务端研发，构建支撑多模型、多智能体、混合云场景部署的 AI 平台基础能力。&lt;/li&gt;
&lt;li&gt;推动 AI 能力的工程化，面向领域 Agent 场景进行可行性分析和原型验证，支撑 DataV 的智能化演进。&lt;/li&gt;
&lt;li&gt;负责 DataV 核心服务的技术方案设计、开发与维护，保障系统在高并发、高可用场景下的稳定性与性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;职位要求&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;扎实的服务端工程基础，3 年以上 Node.js / Java Web 开发经验，具备良好的系统设计能力与代码质量意识，能够独立负责中大型系统或核心模块的架构设计与落地。&lt;/li&gt;
&lt;li&gt;对 LLM 与 AI 应用开发有实际理解与实践，理解 LLM 的基本原理、能力边界，具备将 AI 能力工程化的经验。&lt;/li&gt;
&lt;li&gt;有 AI 工程平台、Agent 框架相关经验，理解 Agent 架构与设计思想。自研或使用过主流的开源 AI 框架，有领域 Agent 或复杂 Agent 场景经验优先。&lt;/li&gt;
&lt;li&gt;具备高并发、高可用系统设计与问题排查经验，有云原生、K8S 等相关经验，能够在稳定性、性能与成本之间做出合理技术决策。&lt;/li&gt;
&lt;li&gt;具备良好的跨团队沟通与协作能力，能够推动技术方案从设计到落地。对 AI 工程与服务端技术保持学习和技术进取心，有开源项目经验优先、有 AI Infra 相关经验优先。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;联系： &lt;a href=&quot;mailto:basi.dwz@alibaba-inc.com&quot;&gt;basi.dwz@alibaba-inc.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;也可以直接在线投递简历： &lt;a href=&quot;https://careers.aliyun.com/off-campus/position-detail?lang=zh&amp;amp;positionId=100003263001&amp;amp;trace=qrcode_share&quot;&gt;https://careers.aliyun.com/off-campus/position-detail?lang=zh&amp;amp;positionId=100003263001&amp;amp;trace=qrcode_share&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>rockdai</author><pubDate>Fri, 23 Jan 2026 02:36:34 GMT</pubDate></item><item><title>最近 skills 很火，我从收集的6w+ agnet-skills中精选出 1000+好用/实用/有趣的 claude code skills，今天更新中</title><link>https://cnodejs.org/topic/697055f0cd2d71365b2cfb93</link><guid>https://cnodejs.org/topic/697055f0cd2d71365b2cfb93</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;最近 Claude Code skills 很火，对我来说和 agnet 、扣子没啥区别。
能火我感觉主要是扩大了人群使用范围，小白也上手。
每个行业都有 N 多个 skills ，想着做一个导航网站，把鱼龙混杂的 skills 精选出来。
工具目的是解决问题，实用好用才是王道。
去年 12 月注册的域名，纠结拖延快一个月，终于第一版打磨 5 天，要上线了。
好多功能还没加，还有很多 BUG ，希望大家能多给给意见。&lt;/p&gt;
&lt;p&gt;下面体验：
&lt;a href=&quot;https://agent-skills.cc/&quot;&gt;Agent – Claude Code skills 精选导航站&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>xiaolige</author><pubDate>Wed, 21 Jan 2026 04:28:32 GMT</pubDate></item><item><title>全栈开发在线接单</title><link>https://cnodejs.org/topic/695f922dcd2d7182502cf648</link><guid>https://cnodejs.org/topic/695f922dcd2d7182502cf648</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;全栈开发在线接单&lt;/p&gt;
&lt;p&gt;专注解决前后端各类开发问题，承接多端定制开发与BUG修复，支持远程一对一技术服务，先做满意再付款，诚信合作。&lt;/p&gt;
&lt;p&gt;前端精通 Vue2/Vue3、React、React Native、Uniapp、Taro 等主流框架，可定制 PC 网页、小程序、APP 及 Electron 桌面端；熟练运用 Nuxt.js、Next.js 进行服务端渲染开发，掌握 WebGL 技术实现交互特效。&lt;/p&gt;
&lt;p&gt;服务端擅长 Java、Nest.js、Node.js、Golang 技术栈，可承接后端架构搭建、接口开发与管理系统开发。&lt;/p&gt;
&lt;p&gt;合作流程规范，可协助梳理需求、出具专属技术方案，高效响应各类开发需求，欢迎咨询洽谈。&lt;/p&gt;
&lt;/div&gt;</description><author>it_zhiijia</author><pubDate>Thu, 08 Jan 2026 11:17:01 GMT</pubDate></item><item><title>为了写代码时省点钱，我撸了个 AI 中转站比价网站 getcheapai.com</title><link>https://cnodejs.org/topic/6959e255cd2d718fdf2cf487</link><guid>https://cnodejs.org/topic/6959e255cd2d718fdf2cf487</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;现在应该都是用 AI 写代码了，然后为了节约成本，我经常找便宜的 AI 中转站，然后发现每家的计费逻辑都不一样：有的是 1:7 兑换，有的是 1:1 积分制，有的倍率是 1，有的又是 3。每次选的时候，光折算真实价格就要来回计算对比，非常心累。&lt;/p&gt;
&lt;p&gt;于是我自己整了个 AI 中转站比价网站 &lt;a href=&quot;https://www.getcheapai.com&quot;&gt;getcheapai.com&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无论中转站计费方式如何，我全部统一换算成了每百万 Token 的真实人民币价格&lt;/li&gt;
&lt;li&gt;目前收录了十几家主流或者知名的中转站&lt;/li&gt;
&lt;li&gt;每 6 小时更新一次价格确保实时性&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;站内的中转站链接带有我的 AFF，因为这个网站跑在 Cloudflare Workers 里，每个月要花 5 美元。如果介意的话，可以复制链接去掉 AFF。&lt;/p&gt;
&lt;/div&gt;</description><author>lmk123</author><pubDate>Sun, 04 Jan 2026 03:45:25 GMT</pubDate></item><item><title>一个神奇的在线代码执行平台：RunCode</title><link>https://cnodejs.org/topic/693a77f7d36b80ca695c861f</link><guid>https://cnodejs.org/topic/693a77f7d36b80ca695c861f</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;多语言在线代码执行平台&lt;a href=&quot;https://runcode.blendviewer.com/&quot;&gt;RunCode&lt;/a&gt;，支持 60+ 种编程语言，AI辅助编程，智能混合执行架构，即写即运行。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; &lt;strong&gt;智能混合执行&lt;/strong&gt; - 浏览器 WASM + 云端容器，自动选择最佳执行方式&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;60+ 语言支持&lt;/strong&gt; - Python, JavaScript, TypeScript, C/C++, Rust, Go, Java, Haskell, Shell 等&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;即时执行&lt;/strong&gt; - 热门语言零延迟，浏览器直接运行&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;现代 UI&lt;/strong&gt; - shadcn/ui + Tailwind CSS&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;专业编辑器&lt;/strong&gt; - Monaco Editor&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;主题切换&lt;/strong&gt; - 深色/浅色模式&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;//static.cnodejs.org/FrlaY59V8xx-Gz5tg6HIzAg7lGbK&quot; alt=&quot;Screenshot_11-12-2025_154256_runcode.blendviewer.com.jpeg&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;//static.cnodejs.org/Fskxk2lNsK5faNGRCEYqdRw4AtEF&quot; alt=&quot;Screenshot_11-12-2025_154427_runcode.blendviewer.com.jpeg&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;//static.cnodejs.org/FrL38YUIait9pAm-OiUBDjFvJ0C-&quot; alt=&quot;Screenshot_11-12-2025_154449_runcode.blendviewer.com.jpeg&quot;&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>openrc</author><pubDate>Thu, 11 Dec 2025 07:51:19 GMT</pubDate></item><item><title>如何不踩坑选 VPS？我自己的经验分享</title><link>https://cnodejs.org/topic/693a607ad36b80a7d95c860c</link><guid>https://cnodejs.org/topic/693a607ad36b80a7d95c860c</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;这些年折腾云服务器踩过不少坑：实名卡审核、账号风控、线路不稳、价格虚高……尤其做跨境/自动化/部署项目的，真的经不起服务商“突然抽风”。&lt;/p&gt;
&lt;p&gt;后来总结出一个最稳的方案：&lt;/p&gt;
&lt;p&gt;用大厂（Aliyun/AWS）的稳定性，但通过更快、更灵活的渠道开通。
不需要实名、不等审核、价格也比官网低很多，还能用加密货币避风控。&lt;/p&gt;
&lt;p&gt;我们团队现在长期用的就是这种方式，跑项目一年多都很稳，所以分享给需要低风控、快开通、稳定线路的技术同学参考：&lt;/p&gt;
&lt;p&gt; BaseCloud — 大厂云的快速免实名入口
&lt;a href=&quot;https://www.basecloud.cc/?utm_source=ondeseek&amp;amp;utm_medium=forum&amp;amp;utm_campaign=forum&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://basecloud.cc&quot;&gt;https://basecloud.cc&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;不是硬推，只是减少大家踩坑的成本。技术人时间最贵。&lt;/p&gt;
&lt;/div&gt;</description><author>daoke</author><pubDate>Thu, 11 Dec 2025 06:11:06 GMT</pubDate></item><item><title>Google Gmail RCS TG大量实卡接码及各种冷门项目 支持量大用户api对接</title><link>https://cnodejs.org/topic/6927dcac05080c3871c2b255</link><guid>https://cnodejs.org/topic/6927dcac05080c3871c2b255</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;做跨境团队最头疼的就是支付问题：广告账户被拒付、开卡和充值流程繁琐、账单不清晰、多平台支付难管理……&lt;/p&gt;
&lt;p&gt;**VCC虚拟信用卡（Visa/万事达）**完美解决这些问题：
	•	即开即用：免实名，快速生成，多成员同时使用
	•	多平台支持：FB/Google/TikTok广告、PayPal、AWS、域名、订阅全覆盖
	•	机器人 + 后台：批量开卡、查账、充值自动化，团队管理更高效
	•	账单透明：结算清楚，避免财务混乱
	•	支持加密货币：充值快速，跨境支付无障碍
	•	性价比高：稳定可靠，长期使用更划算&lt;/p&gt;
&lt;p&gt;一句话总结：
跨境广告、电商或独立站团队，有了 VCC，支付更稳定、高效、透明，真的值得用。产品频道：&lt;a href=&quot;https://t.me/+vCIqSgwsBDxiODIy&quot;&gt;https://t.me/+vCIqSgwsBDxiODIy&lt;/a&gt; 联系客服：@KaiDe66BOt&lt;/p&gt;
&lt;/div&gt;</description><author>daoke</author><pubDate>Thu, 27 Nov 2025 05:07:56 GMT</pubDate></item><item><title>Hoa - 一个极简 Web 框架</title><link>https://cnodejs.org/topic/692578af05080cbccbc2b202</link><guid>https://cnodejs.org/topic/692578af05080cbccbc2b202</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;我使用 Koa 很多年了，一直很喜欢它简洁的设计哲学。近几年在 Cloudflare Worker 上开发较多，接触到了 Hono。Hono 也是一个不错的框架，但在深入使用后，我对它的一些设计理念并不是很认同，于是萌生了自己造个轮子的想法。&lt;/p&gt;
&lt;p&gt;我为新框架设定了三条核心原则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;微内核架构：与 Koa 类似，保留了洋葱模型的中间件设计，同时还补充了插件系统&lt;/li&gt;
&lt;li&gt;符合直觉的 API 设计：摒弃 Koa 的 delegates 思路，API 严格区分 ctx/ctx.req/ctx.res，更加符合语义&lt;/li&gt;
&lt;li&gt;环境无关性：可在 Node.js、Bun、Deno 以及 Cloudflare Worker、Vercel 等边缘环境运行&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;于是 Hoa 诞生了。目前我跟另一个维护者已经为 Hoa 补充了 30+ 常用中间件，我也已经将手头大部分项目从 Koa 迁移至 Hoa。今天分享出来，希望更多人去使用，也期待收到更多反馈，共同把 Hoa 框架打磨得更好。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href=&quot;https://github.com/hoa-js/hoa&quot;&gt;hoa&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;官网: &lt;a href=&quot;https://hoa-js.com&quot;&gt;hoa-js.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;特点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;⚡ Minimal - Only ~4.4KB (gzipped).&lt;/li&gt;
&lt;li&gt; Zero Dependencies - Built on modern Web Standards with no external dependencies.&lt;/li&gt;
&lt;li&gt;️ Highly Extensible - Features a flexible extension and middleware system.&lt;/li&gt;
&lt;li&gt; Standards-Based - Designed entirely around modern Web Standard APIs.&lt;/li&gt;
&lt;li&gt; Multi-Runtime - The same code runs on Cloudflare Workers, Deno, Bun, Node.js, and more.&lt;/li&gt;
&lt;li&gt;✅ 100% Tested – Backed by a full-coverage automated test suite.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安装&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;npm i hoa --save
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;快速开始&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;import { Hoa } from &amp;#x27;hoa&amp;#x27;
const app = new Hoa()

app.use(async (ctx, next) =&amp;gt; {
  ctx.res.body = &amp;#x27;Hello, Hoa!&amp;#x27;
})

export default app
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;License&lt;/h3&gt;
&lt;p&gt;MIT&lt;/p&gt;
&lt;/div&gt;</description><author>nswbmw</author><pubDate>Tue, 25 Nov 2025 09:36:47 GMT</pubDate></item><item><title>VonaJS: I18n如何支持Swagger多语言</title><link>https://cnodejs.org/topic/6913f02205080c3402c2b09f</link><guid>https://cnodejs.org/topic/6913f02205080c3402c2b09f</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;VonaJS提供的I18n支持模块化体系。每个业务模块都可以单独提供自己的 I18n 语言资源。我们先了解I18n的一般用法，然后再看看如何支持Swagger多语言&lt;/p&gt;
&lt;h2&gt;初始化代码骨架&lt;/h2&gt;
&lt;p&gt;我们先在模块&lt;code&gt;demo-student&lt;/code&gt;中初始化I18n的代码骨架&lt;/p&gt;
&lt;h3&gt;1. Cli命令&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;$ vona :init:locale demo-student
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 菜单命令&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;右键菜单 - [模块路径]: &amp;#96;Vona Init&amp;#x2F;Locale&amp;#96;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;定义语言资源&lt;/h2&gt;
&lt;p&gt;以模块&lt;code&gt;demo-student&lt;/code&gt;为例，定义模块的语言资源：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;英文&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;src/module/demo-student/src/config/locale/en-us.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;export default {
+ StudentName: &amp;#x27;Student Name&amp;#x27;,
};
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;中文&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;src/module/demo-student/src/config/locale/zh-cn.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;export default {
+ StudentName: &amp;#x27;学生名称&amp;#x27;,
};
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;使用语言资源&lt;/h2&gt;
&lt;p&gt;可以通过 Scope 实例提供的&lt;code&gt;locale&lt;/code&gt;对象获取模块的语言资源，支持类型化提示&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;class ControllerStudent {
  @Web.get(&amp;#x27;test&amp;#x27;)
  test() {
    &amp;#x2F;&amp;#x2F; use current locale
    const message1 = this.scope.locale.StudentName();
    &amp;#x2F;&amp;#x2F; use locale en-us
    const message2 = this.scope.locale.StudentName.locale(&amp;#x27;en-us&amp;#x27;);
    &amp;#x2F;&amp;#x2F; use locale zh-cn
    const message3 = this.scope.locale.StudentName.locale(&amp;#x27;zh-cn&amp;#x27;);
    console.log(message1, message2, message3);
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;跨模块使用语言资源&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;class ControllerStudent {
  @Web.get(&amp;#x27;test&amp;#x27;)
  test() {
    &amp;#x2F;&amp;#x2F; use current locale
    const message1 = this.$scope.demoStudent.locale.StudentName();
    &amp;#x2F;&amp;#x2F; use locale en-us
    const message2 = this.$scope.demoStudent.locale.StudentName.locale(&amp;#x27;en-us&amp;#x27;);
    &amp;#x2F;&amp;#x2F; use locale zh-cn
    const message3 = this.$scope.demoStudent.locale.StudentName.locale(&amp;#x27;zh-cn&amp;#x27;);
    console.log(message1, message2, message3);
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;覆盖语言资源&lt;/h2&gt;
&lt;p&gt;可以使用&lt;code&gt;项目级别&lt;/code&gt;的语言资源覆盖&lt;code&gt;模块级别&lt;/code&gt;的语言资源&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;英文&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/locale/en-us.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;export default {
  modules: {
+   &amp;#x27;demo-student&amp;#x27;: {
+     StudentName: &amp;#x27;Student Name!&amp;#x27;,
+   },
  },
};
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;中文&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/locale/zh-cn.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;export default {
  modules: {
+   &amp;#x27;demo-student&amp;#x27;: {
+     StudentName: &amp;#x27;学生名称!&amp;#x27;,
+   },
  },
};
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;当前locale&lt;/h2&gt;
&lt;h3&gt;1. 获取当前locale&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;const locale = this.ctx.locale;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 设置当前locale&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;this.ctx.locale = &amp;#x27;en-us&amp;#x27;;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;3. 获取缺省locale&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;const localeDefault = this.$scope.i18n.config.defaultLocale;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;获取当前locale的规则&lt;/h2&gt;
&lt;p&gt;当用户访问后端 API 时，后端会自动根据规则获取当前 locale&lt;/p&gt;
&lt;h3&gt;1. 模块配置&lt;/h3&gt;
&lt;p&gt;I18n 是由模块 a-i18n 提供的核心能力，可以在 App config 中修改模块的配置：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; modules
config.modules = {
  &amp;#x27;a-i18n&amp;#x27;: {
    defaultLocale: &amp;#x27;en-us&amp;#x27;,
    queryField: &amp;#x27;x-vona-locale&amp;#x27;,
    headerField: &amp;#x27;x-vona-locale&amp;#x27;,
    cookieField: &amp;#x27;locale&amp;#x27;,
  },
};
&lt;/code&gt;&lt;/pre&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;defaultLocale&lt;/td&gt;
&lt;td&gt;Default locale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;queryField&lt;/td&gt;
&lt;td&gt;从request query中获取当前locale，query key默认为&lt;code&gt;x-vona-locale&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;headerField&lt;/td&gt;
&lt;td&gt;从request header中获取当前locale，header key默认为&lt;code&gt;x-vona-locale&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cookieField&lt;/td&gt;
&lt;td&gt;从request cookie中获取当前locale，cookie key默认为&lt;code&gt;locale&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;2. 规则次序&lt;/h3&gt;
&lt;p&gt;系统按以下次序，依次判断当前 locale&lt;/p&gt;
&lt;p&gt;&lt;code&gt;queryField&lt;/code&gt; &amp;gt; &lt;code&gt;headerField&lt;/code&gt; &amp;gt; &lt;code&gt;cookieField&lt;/code&gt; &amp;gt; &lt;code&gt;Header: Accept-Language&lt;/code&gt; &amp;gt; &lt;code&gt;defaultLocale&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;添加新语言&lt;/h2&gt;
&lt;p&gt;VonaJS 默认提供了两个语言:&lt;code&gt;en-us&lt;/code&gt;和&lt;code&gt;zh-cn&lt;/code&gt;。下面演示如何添加新语言&lt;code&gt;zh-tw&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;1. 添加类型定义&lt;/h3&gt;
&lt;p&gt;采用接口合并机制添加新语言的类型定义&lt;/p&gt;
&lt;p&gt;在 VSCode 编辑器中，输入代码片段&lt;code&gt;recordlocale&lt;/code&gt;，自动生成代码骨架:&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;declare module &amp;#x27;vona&amp;#x27; {
  export interface ILocaleRecord {
    : never;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调整代码，然后添加&lt;code&gt;zh-tw&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;declare module &amp;#x27;vona&amp;#x27; {
  export interface ILocaleRecord {
+   &amp;#x27;zh-tw&amp;#x27;: never;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 添加语言资源&lt;/h3&gt;
&lt;p&gt;新建语言文件&lt;code&gt;zh-tw.ts&lt;/code&gt;，然后添加语言资源&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/module/demo-student/src/config/locale/zh-tw.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;export default {
  StudentName: &amp;#x27;學生名稱&amp;#x27;,
};
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;复数&lt;/h2&gt;
&lt;h3&gt;1. 定义语言资源&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;src/module/demo-student/src/config/locale/en-us.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;export default {
+ TestApples_: &amp;#x27;%d apples&amp;#x27;,
+ TestApples_0: &amp;#x27;no apples&amp;#x27;,
+ TestApples_1: &amp;#x27;one apple&amp;#x27;,
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;src/module/demo-student/src/config/locale/zh-cn.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;export default {
+ TestApples_: &amp;#x27;%d个苹果&amp;#x27;,
+ TestApples_0: &amp;#x27;没有苹果&amp;#x27;,
};
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 使用语言资源&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;this.ctx.locale = &amp;#x27;en-us&amp;#x27;;
const apple0 = this.scope.locale.TestApples_(0);
const apple1 = this.scope.locale.TestApples_(1);
const apple2 = this.scope.locale.TestApples_(2);
console.log(&amp;#96;${apple0}, ${apple1}, ${apple2}&amp;#96;);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;控制台输出如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;no apples, one apple, 2 apples
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TestApples_&lt;/code&gt;: 缺省语言资源。语言资源添加后缀&lt;code&gt;_&lt;/code&gt;，可以提示开发者该语言资源需要传入参数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TestApples_{n}&lt;/code&gt;: 可以针对任何具体的&lt;code&gt;n&lt;/code&gt;提供独立的语言资源。系统在进行语言翻译时，如果找不到具体&lt;code&gt;n&lt;/code&gt;的语言资源，就使用缺省语言资源&lt;code&gt;TestApples_&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;复数: 多参数&lt;/h2&gt;
&lt;p&gt;如果语言资源支持多参数，那么可以明确指定哪个参数支持复数&lt;/p&gt;
&lt;h3&gt;1. 定义语言资源&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;src/module/demo-student/src/config/locale/en-us.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;export default {
+ TestNameApples_: &amp;#x27;%s has %d apples&amp;#x27;,
+ TestNameApples_0_1: &amp;#x27;%s has no apples&amp;#x27;,
+ TestNameApples_1_1: &amp;#x27;%s has one apple&amp;#x27;,
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;src/module/demo-student/src/config/locale/zh-cn.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;export default {
+ TestNameApples_: &amp;#x27;%s有%d个苹果&amp;#x27;,
+ TestNameApples_0_1: &amp;#x27;%s没有苹果&amp;#x27;,
};
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 使用语言资源&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;this.ctx.locale = &amp;#x27;en-us&amp;#x27;;
const apple0 = this.scope.locale.TestNameApples_(&amp;#x27;Tom&amp;#x27;, 0);
const apple1 = this.scope.locale.TestNameApples_(&amp;#x27;Tom&amp;#x27;, 1);
const apple2 = this.scope.locale.TestNameApples_(&amp;#x27;Tom&amp;#x27;, 2);
console.log(&amp;#96;${apple0}, ${apple1}, ${apple2}&amp;#96;);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;控制台输出如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;Tom has no apples, Tom has one apple, Tom has 2 apples
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TestNameApples_&lt;/code&gt;: 缺省语言资源。语言资源添加后缀&lt;code&gt;_&lt;/code&gt;，可以提示开发者该语言资源需要传入参数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TestNameApples_{n}_{ordinal}&lt;/code&gt;: &lt;code&gt;ordinal&lt;/code&gt;代表参数序数&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Swagger/Openapi&lt;/h2&gt;
&lt;p&gt;VonaJS 提供了一组工具函数，为 Swagger/Openapi 实现 I18n 国际化&lt;/p&gt;
&lt;p&gt;比如，为&lt;code&gt;EntityStudent&lt;/code&gt;的字段&lt;code&gt;name&lt;/code&gt;提供国际化的&lt;code&gt;title&lt;/code&gt;信息&lt;/p&gt;
&lt;h3&gt;1. $localeScope&lt;/h3&gt;
&lt;p&gt;在设置字段 title 信息时，要使用&lt;code&gt;语言资源FullKey&lt;/code&gt;。在实际生成 Swagger/Openapi 元数据时，系统会自动将&lt;code&gt;语言资源FullKey&lt;/code&gt;翻译为指定的语言&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;+ import { $localeScope } from &amp;#x27;vona&amp;#x27;;

class EntityStudent {
+ @Api.field(v.title($localeScope(&amp;#x27;demo-student&amp;#x27;, &amp;#x27;Name&amp;#x27;)))
  name: string;
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;v.title&lt;/code&gt;: 设置 title 信息&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$localeScope&lt;/code&gt;: 传入&lt;code&gt;模块名称&lt;/code&gt;和&lt;code&gt;语言资源Key&lt;/code&gt;，从而生成&lt;code&gt;语言资源FullKey&lt;/code&gt;: &lt;code&gt;demo-student::Name&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. $locale&lt;/h3&gt;
&lt;p&gt;VonaJS 还提供了一个简化的工具函数&lt;code&gt;$locale&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;+ import { $locale } from &amp;#x27;..&amp;#x2F;.metadata&amp;#x2F;index.ts&amp;#x27;;

class EntityStudent {
+ @Api.field(v.title($locale(&amp;#x27;Name&amp;#x27;)))
  name: string;
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$locale&lt;/code&gt;: 传入&lt;code&gt;语言资源Key&lt;/code&gt;，从而生成&lt;code&gt;语言资源FullKey&lt;/code&gt;: &lt;code&gt;demo-student::Name&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;每个模块都提供了$locale 函数，因此，使用本模块的$locale 函数就可以取得模块名称&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Github：&lt;a href=&quot;https://github.com/vonajs/vona&quot;&gt;https://github.com/vonajs/vona&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;文档：&lt;a href=&quot;https://vona.js.org&quot;&gt;https://vona.js.org&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description><author>zhennann</author><pubDate>Wed, 12 Nov 2025 02:25:38 GMT</pubDate></item><item><title>VonaJS业务抽象层: 验证码体系</title><link>https://cnodejs.org/topic/690805f3f135761521084254</link><guid>https://cnodejs.org/topic/690805f3f135761521084254</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h1&gt;验证码体系&lt;/h1&gt;
&lt;p&gt;VonaJS的内置模块&lt;code&gt;a-captcha&lt;/code&gt;提供了通用的验证码体系，使用&lt;code&gt;Captcha Provider&lt;/code&gt;支持各种验证码方式，并且使用&lt;code&gt;Captcha Scene&lt;/code&gt;支持不同场景的验证码使用策略&lt;/p&gt;
&lt;h2&gt;特性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Captcha Provider&lt;/code&gt;：使用&lt;code&gt;Captcha Provider&lt;/code&gt;支持各种验证码方式，如：文字图形验证码、短信验证码，等等&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Captcha Scene&lt;/code&gt;：使用&lt;code&gt;Captcha Scene&lt;/code&gt;支持不同场景的验证码使用策略。比如，在某个场景下，可以在多个 Captcha Provider 中进行轮替，或者根据用户状态使用不同难度的 Captcha Provider，等等&lt;/li&gt;
&lt;li&gt;&lt;code&gt;立即验证&lt;/code&gt;：前端可以对用户输入的验证码进行立即验证。&lt;code&gt;立即验证&lt;/code&gt;之后在提交表单时仍然要进行&lt;code&gt;二次验证&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;表单验证&lt;/code&gt;：前端可以将用户输入的验证码与表单数据一起发往后端验证&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;bean.captcha&lt;/h2&gt;
&lt;p&gt;模块&lt;code&gt;a-captcha&lt;/code&gt;提供了全局 Bean &lt;code&gt;bean.captcha&lt;/code&gt;，可以通过统一的方式使用所有 Provider/Scene 提供的验证码能力&lt;/p&gt;
&lt;p&gt;模块&lt;code&gt;a-captchasimple&lt;/code&gt;提供了一个 Provider &lt;code&gt;a-captchasimple:imageText&lt;/code&gt;，基于&lt;a href=&quot;https://github.com/produck/svg-captcha&quot;&gt;svg-captcha&lt;/a&gt;实现文字图片的验证码能力&lt;/p&gt;
&lt;p&gt;模块&lt;code&gt;a-captchasimple&lt;/code&gt;提供了一个 Scene &lt;code&gt;a-captchasimple:simple&lt;/code&gt;。该 Scene 只使用一个 Provider，即&lt;code&gt;a-captchasimple:imageText&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;下面演示如何使用模块&lt;code&gt;a-captchasimple&lt;/code&gt;提供的验证码能力&lt;/p&gt;
&lt;h3&gt;1. create&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; create captcha
const captcha = await this.bean.captcha.create(&amp;#x27;a-captchasimple:simple&amp;#x27;);
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;返回值类型：&lt;code&gt;ICaptchaData&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;export interface ICaptchaData {
  id: string;
  provider: keyof ICaptchaProviderRecord;
  token?: unknown;
  payload: unknown;
}
&lt;/code&gt;&lt;/pre&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;id&lt;/td&gt;
&lt;td&gt;本次验证码数据的id标识&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;provider&lt;/td&gt;
&lt;td&gt;本次验证码所使用的Provider名称&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;token&lt;/td&gt;
&lt;td&gt;本次验证码数据的token，用于比对用户输入值。在开发环境可以通过修改系统配置，将token发往前端，用于调试&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;payload&lt;/td&gt;
&lt;td&gt;本次验证码的负载内容，不同的Provider有不同的payload类型&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;2. refresh&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; refresh captcha
const captchaNew = await this.bean.captcha.refresh(captchaId, &amp;#x27;a-captchasimple:simple&amp;#x27;);
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;如果一个 Scene 配置了多个 Provider，那么在刷新 capthca 时可以基于策略选取不同的 Provider&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. verify&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; verify captcha
const passed = await this.bean.captcha.verify(captchaId, &amp;#x27;1234&amp;#x27;, &amp;#x27;a-captchasimple:simple&amp;#x27;);
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4. verifyImmediate&lt;/h3&gt;
&lt;p&gt;前端可以对用户输入的验证码进行&lt;code&gt;立即验证&lt;/code&gt;。&lt;code&gt;立即验证&lt;/code&gt;之后在提交表单时仍然要进行&lt;code&gt;二次验证&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; verifyImmediate captcha
const tokenOrFalse = await this.bean.captcha.verifyImmediate(captchaId, &amp;#x27;1234&amp;#x27;);
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;如果立即验证失败，返回&lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果立即验证成功，返回&lt;code&gt;二次token&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;前端需要将&lt;code&gt;二次token&lt;/code&gt;与表单数据一起发往后端进行&lt;code&gt;二次验证&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;interceptor.captchaVerify&lt;/h2&gt;
&lt;p&gt;模块&lt;code&gt;a-captcha&lt;/code&gt;提供了一个局部拦截器&lt;code&gt;a-captcha:captchaVerify&lt;/code&gt;，可以针对 API 启用验证码校验&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/suite/a-home/modules/home-user/src/controller/passport.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;import { Core } from &amp;#x27;vona-module-a-core&amp;#x27;;

class ControllerPassport {
  @Web.post(&amp;#x27;login&amp;#x27;)
+ @Core.captchaVerify({ scene: &amp;#x27;a-captchasimple:simple&amp;#x27; })
  async login(@Arg.body() data) {}
}  
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Core.captchaVerify&lt;/code&gt;: 用于使用局部拦截器&lt;code&gt;a-captcha:captchaVerify&lt;/code&gt;，传入需要使用的 Scene 名称&lt;/li&gt;
&lt;li&gt;该拦截器支持&lt;code&gt;表单验证&lt;/code&gt;和&lt;code&gt;二次验证&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Captcha API&lt;/h2&gt;
&lt;p&gt;模块&lt;code&gt;a-captcha&lt;/code&gt;提供了一组&lt;code&gt;开箱即用&lt;/code&gt;的 Captcha API，对&lt;code&gt;bean.captcha&lt;/code&gt;的能力进行了封装&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/suite-vendor/a-vona/modules/a-captcha/src/controller/captcha.ts&lt;/code&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;create&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;refresh&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;verifyImmediate&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;为何没有提供&lt;code&gt;verify&lt;/code&gt;API？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为&lt;code&gt;bean.captcha.verify&lt;/code&gt;方法用于局部拦截器&lt;code&gt;a-captcha:captchaVerify&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;参数配置&lt;/h2&gt;
&lt;p&gt;可以在 App Config 中修改模块&lt;code&gt;a-captcha&lt;/code&gt;的参数配置&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; modules
config.modules = {
  &amp;#x27;a-captcha&amp;#x27;: {
    captcha: {
      showToken: false,
    },
    captchaProvider: {
      ttl: 20 * 60 * 1000,
      ttlSecondary: 20 * 60 * 1000,
    },
  },
};
&lt;/code&gt;&lt;/pre&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;captcha.showToken&lt;/td&gt;
&lt;td&gt;是否显示token。如果为true，就将token发往前端，用于调试。默认为false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;captchaProvider.ttl&lt;/td&gt;
&lt;td&gt;captcha token的过期时间&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;captchaProvider.ttlSecondary&lt;/td&gt;
&lt;td&gt;二次token的过期时间&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Github：&lt;a href=&quot;https://github.com/vonajs/vona&quot;&gt;https://github.com/vonajs/vona&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;文档：&lt;a href=&quot;https://vona.js.org/&quot;&gt;https://vona.js.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description><author>zhennann</author><pubDate>Mon, 03 Nov 2025 01:31:31 GMT</pubDate></item><item><title>写了N个AI应用，我开发了个极简的LLM提供商编辑器，每个AI应用都用得上</title><link>https://cnodejs.org/topic/690335dcf13576acdc0841a2</link><guid>https://cnodejs.org/topic/690335dcf13576acdc0841a2</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/MatrixAges/ai-sdk-panel/master/images/ai-sdk-panel.png&quot; alt=&quot;ai-sdk-panel 2.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Github： &lt;a href=&quot;https://github.com/MatrixAges/ai-sdk-panel&quot;&gt;https://github.com/MatrixAges/ai-sdk-panel&lt;/a&gt;
Demo： &lt;a href=&quot;https://stack.matrixages.com/ai-sdk-panel&quot;&gt;https://stack.matrixages.com/ai-sdk-panel&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;做过好几个 AI 应用，发现自己一直在重复造轮子，为每个 AI 应用都开发一套 LLM 提供商编辑器。虽然大部分代码都是复制粘贴过往项目，但也不好管理，于是每天抽出一点时间，花了几个星期把 AI SDK Panel 这个公共组件给开发出来了。&lt;/p&gt;
&lt;p&gt;项目包含全流程的 e2e 测试，基于最新的 Vitest 4.0 Browser Mode 进行测试构建，请放心使用，同时提供了强大的定制化能力，多语言，自定义代理商等。&lt;/p&gt;
&lt;/div&gt;</description><author>MatrixAge</author><pubDate>Thu, 30 Oct 2025 09:54:36 GMT</pubDate></item><item><title>VonaJS AOP编程大杀器：外部切面</title><link>https://cnodejs.org/topic/68fecf98f135765d8c084174</link><guid>https://cnodejs.org/topic/68fecf98f135765d8c084174</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;在VonaJS框架中，AOP编程包括三方面：&lt;code&gt;控制器切面&lt;/code&gt;、&lt;code&gt;内部切面&lt;/code&gt;和&lt;code&gt;外部切面&lt;/code&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;控制器切面&lt;/code&gt;: 为 Controller 方法切入逻辑，包括：Middleware、Guard、Interceptor、Pipe和Filter&lt;/li&gt;
&lt;li&gt;&lt;code&gt;内部切面&lt;/code&gt;: 在 Class 内部，为任何 Class 的任何方法切入逻辑，包括：AOP Method和魔术方法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;外部切面&lt;/code&gt;: 在不改变 Class 源码的前提下，从外部为任何 Class 的任何方法切入逻辑&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;VonaJS中的&lt;code&gt;外部切面&lt;/code&gt;，可以类比于Spring Boot中的&lt;code&gt;AOP切面&lt;/code&gt;和&lt;code&gt;AOP织入&lt;/code&gt;概念。VonaJS的&lt;code&gt;外部切面&lt;/code&gt;不需要什么&lt;code&gt;前置通知&lt;/code&gt;、&lt;code&gt;后置通知&lt;/code&gt;、&lt;code&gt;异常通知&lt;/code&gt;和&lt;code&gt;环绕通知&lt;/code&gt;，只需提供一个同名方法就可以了。之所以可以这么简洁，是因为使用了洋葱圈模型。&lt;/p&gt;
&lt;p&gt;此外，VonaJS的&lt;code&gt;外部切面&lt;/code&gt;支持完整的类型推断与智能代码提示，开发体感比Spring Boot优雅太多。&lt;/p&gt;
&lt;p&gt;下面，我们就来考察一下VonaJS的&lt;code&gt;外部切面&lt;/code&gt;到底是个什么样？为什么可以成为AOP编程的大杀器&lt;/p&gt;
&lt;h2&gt;创建目标Class&lt;/h2&gt;
&lt;p&gt;可以针对任何 Class 实现外部切面。下面，以 Service 为例，在模块 demo-student 中创建一个 Service &lt;code&gt;test&lt;/code&gt;，代码如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;@Service()
export class ServiceTest extends BeanBase {
  private _name: string;

  protected __init__() {
    this._name = &amp;#x27;&amp;#x27;;
  }

  protected async __dispose__() {
    this._name = &amp;#x27;&amp;#x27;;
  }

  get name() {
    return this._name;
  }

  set name(value) {
    this._name = value;
  }

  actionSync(a: number, b: number) {
    return a + b;
  }

  async actionAsync(a: number, b: number) {
    return Promise.resolve(a + b);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;创建外部切面&lt;/h2&gt;
&lt;p&gt;接下来，创建一个外部切面&lt;code&gt;log&lt;/code&gt;，为 Class &lt;code&gt;ServiceTest&lt;/code&gt;的属性和方法分别提供扩展逻辑&lt;/p&gt;
&lt;h3&gt;1. Cli命令&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;$ vona :create:bean aop log --module=demo-student
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 菜单命令&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;右键菜单 - [模块路径]: Vona Aspect&amp;#x2F;Aop
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;AOP定义&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;import { BeanAopBase } from &amp;#x27;vona&amp;#x27;;
import { Aop } from &amp;#x27;vona-module-a-aspect&amp;#x27;;

@Aop({ match: &amp;#x27;demo-student.service.test&amp;#x27; })
export class AopLog extends BeanAopBase {}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Aop&lt;/code&gt;: 此装饰器用于实现&lt;code&gt;外部切面&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;match&lt;/code&gt;: 用于将 Class &lt;code&gt;AopLog&lt;/code&gt;与 Class &lt;code&gt;ServiceTest&lt;/code&gt;关联，&lt;code&gt;ServiceTest&lt;/code&gt;的 beanFullName 是&lt;code&gt;demo-student.service.test&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;match&lt;/td&gt;
&lt;td&gt;string|regexp|(string|regexp)[]&lt;/td&gt;
&lt;td&gt;针对哪些 Class 启用&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;切面：同步方法&lt;/h2&gt;
&lt;p&gt;为&lt;code&gt;ServiceTest#actionSync&lt;/code&gt;输出运行时长&lt;/p&gt;
&lt;p&gt;在 VSCode 编辑器中，输入代码片段&lt;code&gt;aopactionsync&lt;/code&gt;，自动生成代码骨架:&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;action: AopAction&amp;lt;ClassSome, &amp;#x27;action&amp;#x27;&amp;gt; = (_args, next, _receiver) =&amp;gt; {
  return next();
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调整代码，然后添加 log 逻辑&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;actionSync: AopAction&amp;lt;ServiceTest, &amp;#x27;actionSync&amp;#x27;&amp;gt; = (_args, next, _receiver) =&amp;gt; {
  const timeBegin = Date.now();
  const res = next();
  const timeEnd = Date.now();
  console.log(&amp;#x27;actionSync: &amp;#x27;, timeEnd - timeBegin);
  return res;
};
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;actionSync&lt;/code&gt;: 提供与&lt;code&gt;ServiceTest&lt;/code&gt;同名的方法&lt;code&gt;actionSync&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;切面：异步方法&lt;/h2&gt;
&lt;p&gt;为&lt;code&gt;ServiceTest#actionAsync&lt;/code&gt;输出运行时长&lt;/p&gt;
&lt;p&gt;在 VSCode 编辑器中，输入代码片段&lt;code&gt;aopaction&lt;/code&gt;，自动生成代码骨架:&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;action: AopAction&amp;lt;ClassSome, &amp;#x27;action&amp;#x27;&amp;gt; = async (_args, next, _receiver) =&amp;gt; {
  return await next();
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调整代码，然后添加 log 逻辑&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;actionAsync: AopAction&amp;lt;ServiceTest, &amp;#x27;actionAsync&amp;#x27;&amp;gt; = async (_args, next, _receiver) =&amp;gt; {
  const timeBegin = Date.now();
  const res = await next();
  const timeEnd = Date.now();
  console.log(&amp;#x27;actionAsync: &amp;#x27;, timeEnd - timeBegin);
  return res;
};
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;actionAsync&lt;/code&gt;: 提供与&lt;code&gt;ServiceTest&lt;/code&gt;同名的方法&lt;code&gt;actionAsync&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;切面：getter&lt;/h2&gt;
&lt;p&gt;为&lt;code&gt;ServiceTest#get name&lt;/code&gt;输出运行时长&lt;/p&gt;
&lt;p&gt;在 VSCode 编辑器中，输入代码片段&lt;code&gt;aopgetter&lt;/code&gt;，自动生成代码骨架:&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;protected __get_xxx__: AopActionGetter&amp;lt;ClassSome, &amp;#x27;xxx&amp;#x27;&amp;gt; = function (next, _receiver) {
  const value = next();
  return value;
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调整代码，然后添加 log 逻辑&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;protected __get_name__: AopActionGetter&amp;lt;ServiceTest, &amp;#x27;name&amp;#x27;&amp;gt; = function (next, _receiver) {
  const timeBegin = Date.now();
  const value = next();
  const timeEnd = Date.now();
  console.log(&amp;#x27;get name: &amp;#x27;, timeEnd - timeBegin);
  return value;
};
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;__get_name__&lt;/code&gt;: 对应&lt;code&gt;ServiceTest&lt;/code&gt;的 getter 方法&lt;code&gt;get name&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;切面：setter&lt;/h2&gt;
&lt;p&gt;为&lt;code&gt;ServiceTest#set name&lt;/code&gt;输出运行时长&lt;/p&gt;
&lt;p&gt;在 VSCode 编辑器中，输入代码片段&lt;code&gt;aopsetter&lt;/code&gt;，自动生成代码骨架:&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;protected __set_xxx__: AopActionSetter&amp;lt;ClassSome, &amp;#x27;xxx&amp;#x27;&amp;gt; = function (value, next, _receiver) {
  return next(value);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调整代码，然后添加 log 逻辑&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;protected __set_name__: AopActionSetter&amp;lt;ServiceTest, &amp;#x27;name&amp;#x27;&amp;gt; = function (value, next, _receiver) {
  const timeBegin = Date.now();
  const res = next(value);
  const timeEnd = Date.now();
  console.log(&amp;#x27;set name: &amp;#x27;, timeEnd - timeBegin);
  return res;
};
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;__set_name__&lt;/code&gt;: 对应&lt;code&gt;ServiceTest&lt;/code&gt;的 setter 方法&lt;code&gt;set name&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;切面：&lt;code&gt;__init__&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;为&lt;code&gt;ServiceTest#__init__&lt;/code&gt;输出运行时长&lt;/p&gt;
&lt;p&gt;在 VSCode 编辑器中，输入代码片段&lt;code&gt;aopinit&lt;/code&gt;，自动生成代码骨架:&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;protected __init__: AopActionInit&amp;lt;ClassSome&amp;gt; = (_args, next, _receiver) =&amp;gt; {
  next();
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调整代码，然后添加 log 逻辑&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;protected __init__: AopActionInit&amp;lt;ServiceTest&amp;gt; = (_args, next, _receiver) =&amp;gt; {
  const timeBegin = Date.now();
  next();
  const timeEnd = Date.now();
  console.log(&amp;#x27;__init__: &amp;#x27;, timeEnd - timeBegin);
};
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;__init__&lt;/code&gt;: 提供与&lt;code&gt;ServiceTest&lt;/code&gt;同名的方法&lt;code&gt;__init__&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;切面：&lt;code&gt;__dispose__&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;为&lt;code&gt;ServiceTest#__dispose__&lt;/code&gt;输出运行时长&lt;/p&gt;
&lt;p&gt;在 VSCode 编辑器中，输入代码片段&lt;code&gt;aopdispose&lt;/code&gt;，自动生成代码骨架:&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;protected __dispose__: AopActionDispose&amp;lt;ClassSome&amp;gt; = async (_args, next, _receiver) =&amp;gt; {
  await next();
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调整代码，然后添加 log 逻辑&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;protected __dispose__: AopActionDispose&amp;lt;ServiceTest&amp;gt; = async (_args, next, _receiver) =&amp;gt; {
  const timeBegin = Date.now();
  await next();
  const timeEnd = Date.now();
  console.log(&amp;#x27;__dispose__: &amp;#x27;, timeEnd - timeBegin);
};
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;__dispose__&lt;/code&gt;: 提供与&lt;code&gt;ServiceTest&lt;/code&gt;同名的方法&lt;code&gt;__dispose__&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;切面：&lt;code&gt;__get__&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;为&lt;code&gt;ServiceTest&lt;/code&gt;扩展魔术方法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;参见: &lt;a href=&quot;https://vona.js.org/zh/guide/aop/internal/magic-method.html&quot;&gt;魔术方法&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 VSCode 编辑器中，输入代码片段&lt;code&gt;aopget&lt;/code&gt;，自动生成代码骨架:&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;protected __get__: AopActionGet&amp;lt;ClassSome&amp;gt; = (_prop, next, _receiver) =&amp;gt; {
  const value = next();
  return value;
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调整代码，然后添加自定义字段&lt;code&gt;red&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;protected __get__: AopActionGet&amp;lt;ServiceTest&amp;gt; = (prop, next, _receiver) =&amp;gt; {
  if (prop === &amp;#x27;red&amp;#x27;) return &amp;#x27;#FF0000&amp;#x27;;
  const value = next();
  return value;
};
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;__get__&lt;/code&gt;: 约定的魔术方法名称&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过接口类型合并的机制为颜色提供类型定义&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;declare module &amp;#x27;vona-module-demo-student&amp;#x27; {
  export interface ServiceTest {
    red: string;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;切面：&lt;code&gt;__set__&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;为&lt;code&gt;ServiceTest&lt;/code&gt;扩展魔术方法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;参见: &lt;a href=&quot;https://vona.js.org/zh/guide/aop/internal/magic-method.html&quot;&gt;魔术方法&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 VSCode 编辑器中，输入代码片段&lt;code&gt;aopset&lt;/code&gt;，自动生成代码骨架:&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;protected __set__: AopActionSet&amp;lt;ClassSome&amp;gt; = (_prop, value, next, _receiver) =&amp;gt; {
  return next(value);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调整代码，为自定义字段&lt;code&gt;red&lt;/code&gt;设置值&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;private _colorRed: string | undefined;

protected __set__: AopActionSet&amp;lt;ServiceTest&amp;gt; = (prop, value, next, _receiver) =&amp;gt; {
  if (prop === &amp;#x27;red&amp;#x27;) {
    this._colorRed = value;
    return true;
  }
  return next(value);
};
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;__set__&lt;/code&gt;: 约定的魔术方法名称&lt;/li&gt;
&lt;li&gt;如果为&lt;code&gt;prop&lt;/code&gt;设置了值，返回&lt;code&gt;true&lt;/code&gt;，否则调用&lt;code&gt;next&lt;/code&gt;方法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然后调整&lt;code&gt;__get__&lt;/code&gt;的逻辑:&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;protected __get__: AopActionGet&amp;lt;ServiceTest&amp;gt; = (prop, next, _receiver) =&amp;gt; {
- if (prop === &amp;#x27;red&amp;#x27;) return &amp;#x27;#FF0000&amp;#x27;;
+ if (prop === &amp;#x27;red&amp;#x27;) return this._colorRed;
  const value = next();
  return value;
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;切面：&lt;code&gt;__method__&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;为&lt;code&gt;ServiceTest&lt;/code&gt;的任何方法扩展逻辑&lt;/p&gt;
&lt;p&gt;在 VSCode 编辑器中，输入代码片段&lt;code&gt;aopmethod&lt;/code&gt;，自动生成代码骨架:&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;protected __method__: AopActionMethod&amp;lt;ClassSome&amp;gt; = (_method, _args, next, _receiver) =&amp;gt; {
  return next();
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调整代码，然后为方法&lt;code&gt;actionSync&lt;/code&gt;和&lt;code&gt;actionAsync&lt;/code&gt;添加 log 逻辑&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;protected __method__: AopActionMethod&amp;lt;ServiceTest&amp;gt; = (method, _args, next, _receiver) =&amp;gt; {
  if (method !== &amp;#x27;actionSync&amp;#x27; &amp;amp;&amp;amp; method !== &amp;#x27;actionAsync&amp;#x27;) {
    return next();
  }
  const timeBegin = Date.now();
  function done(res) {
    const timeEnd = Date.now();
    console.log(&amp;#96;method ${method}: &amp;#96;, timeEnd - timeBegin);
    return res;
  }
  const res = next();
  if (res?.then) {
    return res.then((res: any) =&amp;gt; {
      return done(res);
    });
  }
  return done(res);
};
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;__method__&lt;/code&gt;: 约定的魔术方法名称&lt;/li&gt;
&lt;li&gt;&lt;code&gt;res?.then&lt;/code&gt;: 判断返回值是否是 Promise 对象，进行不同处理，从而兼容&lt;code&gt;同步方法&lt;/code&gt;和&lt;code&gt;异步方法&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;AOP顺序&lt;/h2&gt;
&lt;p&gt;针对同一个目标 Class，可以关联多个 AOP。所以，VonaJS 提供了两个参数，用于控制 AOP 的执行顺序&lt;/p&gt;
&lt;h3&gt;1. dependencies&lt;/h3&gt;
&lt;p&gt;比如，还有一个 AOP &lt;code&gt;demo-student:log3&lt;/code&gt;，我们希望执行顺序如下：&lt;code&gt;demo-student:log3&lt;/code&gt; &amp;gt; &lt;code&gt;Current&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;@Aop({
  match: &amp;#x27;demo-student.service.test&amp;#x27;,
+ dependencies: &amp;#x27;demo-student:log3&amp;#x27;,
})
class AopLog {}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. dependents&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;dependents&lt;/code&gt;的顺序刚好与&lt;code&gt;dependencies&lt;/code&gt;相反，我们希望执行顺序如下：&lt;code&gt;Current&lt;/code&gt; &amp;gt; &lt;code&gt;demo-student:log3&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;@Aop({
  match: &amp;#x27;demo-student.service.test&amp;#x27;,
+ dependents: &amp;#x27;demo-student:log3&amp;#x27;,
})
class AopLog {}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;AOP启用/禁用&lt;/h2&gt;
&lt;p&gt;可以控制 AOP 的&lt;code&gt;启用/禁用&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;1. Enable&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; onions
config.onions = {
  aop: {
    &amp;#x27;demo-student:log&amp;#x27;: {
+     enable: false,
    },
  },
};
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. Meta&lt;/h3&gt;
&lt;p&gt;可以让 AOP 在指定的运行环境生效&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;flavor&lt;/td&gt;
&lt;td&gt;string|string[]&lt;/td&gt;
&lt;td&gt;参见: &lt;a href=&quot;https://vona.js.org/zh/guide/env-config/mode-flavor/introduction.html&quot;&gt;运行环境与Flavor&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mode&lt;/td&gt;
&lt;td&gt;string|string[]&lt;/td&gt;
&lt;td&gt;参见: &lt;a href=&quot;https://vona.js.org/zh/guide/env-config/mode-flavor/introduction.html&quot;&gt;运行环境与Flavor&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;举例&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;@Aop({
+ meta: {
+   flavor: &amp;#x27;normal&amp;#x27;,
+   mode: &amp;#x27;dev&amp;#x27;,
+ },
})
class AopLog {}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;查看当前生效的AOP清单&lt;/h2&gt;
&lt;p&gt;可以直接在目标 Class action 中输出当前生效的 AOP 清单&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;class ServiceTest {
  protected async __dispose__() {
+   this.bean.onion.aop.inspect();
    this._name = &amp;#x27;&amp;#x27;;
  }
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;this.bean.onion&lt;/code&gt;: 取得全局 Service 实例 &lt;code&gt;onion&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.aop&lt;/code&gt;: 取得与 AOP 相关的 Service 实例&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.inspect&lt;/code&gt;: 输出当前生效的 AOP 清单&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当方法被执行时，会自动在控制台输出当前生效的 AOP 清单，效果如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;//static.cnodejs.org/Flvhc_kARVcds62MZFbz4C85m5zk&quot; alt=&quot;aop-1.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Github：&lt;a href=&quot;https://github.com/vonajs/vona&quot;&gt;https://github.com/vonajs/vona&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;文档：&lt;a href=&quot;https://vona.js.org/&quot;&gt;https://vona.js.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description><author>zhennann</author><pubDate>Mon, 27 Oct 2025 01:49:12 GMT</pubDate></item><item><title>VonaJS AOP编程：魔术方法</title><link>https://cnodejs.org/topic/68f97e0ef135761d470840a6</link><guid>https://cnodejs.org/topic/68f97e0ef135761d470840a6</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;在VonaJS框架中，AOP编程包括三方面：&lt;code&gt;控制器切面&lt;/code&gt;、&lt;code&gt;内部切面&lt;/code&gt;和&lt;code&gt;外部切面&lt;/code&gt;。&lt;code&gt;内部切面&lt;/code&gt;包括两个能力：&lt;code&gt;AOP Method&lt;/code&gt;和&lt;code&gt;魔术方法&lt;/code&gt;。这里我们简要介绍一下&lt;code&gt;魔术方法&lt;/code&gt;的用法。&lt;/p&gt;
&lt;h2&gt;魔术方法&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;魔术方法&lt;/code&gt;，允许我们在 Class 内部通过&lt;code&gt;__get__&lt;/code&gt;和&lt;code&gt;__set__&lt;/code&gt;切入动态属性或方法&lt;/p&gt;
&lt;h2&gt;举例：Module Scope&lt;/h2&gt;
&lt;p&gt;为了让 IOC 容器的使用更加简洁和直观，VonaJS 推荐优先使用&lt;code&gt;依赖查找&lt;/code&gt;策略，从而使用更少的装饰器函数，使用更少的类型标注。通过&lt;code&gt;Module Scope&lt;/code&gt;对象访问模块提供的资源，就是践行&lt;code&gt;依赖查找策略&lt;/code&gt;的机制之一&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;参见: &lt;a href=&quot;https://vona.js.org/zh/guide/essentials/scope/introduction.html&quot;&gt;模块Scope&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;比如，模块 demo-student 中有一个 model &lt;code&gt;student&lt;/code&gt;，用于 crud 操作。可以这样使用 model：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;import { ModelStudent } from &amp;#x27;..&amp;#x2F;model&amp;#x2F;student.ts&amp;#x27;;

async findMany(params) {
  const model = this.bean._getBean(ModelStudent);
  return await model.selectAndCount(params);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;使用魔术方法：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;async findMany(params) {
  return await this.scope.model.student.selectAndCount(params);
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;this.scope.model.xxx&lt;/code&gt;: 通过魔术方法动态获取当前模块中的 model 实例&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;举例：CRUD(魔术方法)&lt;/h2&gt;
&lt;p&gt;Vona ORM 采用魔术方法的机制进一步简化操作数据的代码&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;参见: &lt;a href=&quot;https://vona.js.org/zh/guide/techniques/orm/crud-magic.html&quot;&gt;CRUD(魔术方法)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;比如，通过字段&lt;code&gt;id&lt;/code&gt;查询学生信息，代码如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;async findOne(id) {
  return await this.scope.model.student.get({ id });
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;使用魔术方法：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;async findOne(id) {
  return await this.scope.model.student.getById(id);
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;系统自动从 method name &lt;code&gt;getById&lt;/code&gt;中解析出参数&lt;code&gt;id&lt;/code&gt;，然后调用实际的 CRUD 方法，这里就是: &lt;code&gt;get({ id })&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;创建Class&lt;/h2&gt;
&lt;p&gt;可以在任何 Class 中实现魔术方法。下面，以 Service 为例，在模块 demo-student 中创建一个 Service &lt;code&gt;color&lt;/code&gt;，代码如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如何创建 Service，参见: &lt;a href=&quot;https://vona.js.org/zh/guide/essentials/api/service.html&quot;&gt;Service&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;import { BeanBase } from &amp;#x27;vona&amp;#x27;;
import { Service } from &amp;#x27;vona-module-a-bean&amp;#x27;;

@Service()
export class ServiceColor extends BeanBase {}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;&lt;code&gt;__get__&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;然后，通过&lt;code&gt;__get__&lt;/code&gt;实现颜色值的获取&lt;/p&gt;
&lt;h3&gt;1. 添加代码骨架&lt;/h3&gt;
&lt;p&gt;在 VSCode 编辑器中，输入代码片段&lt;code&gt;aopmagicget&lt;/code&gt;，自动生成代码骨架:&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;@Service()
export class ServiceColor extends BeanBase {
+ protected __get__(prop: string) {}
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 实现自定义逻辑&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;@Service()
export class ServiceColor extends BeanBase {
+ private _colors = {
+   red: &amp;#x27;#FF0000&amp;#x27;,
+   green: &amp;#x27;#00FF00&amp;#x27;,
+   blue: &amp;#x27;#0000FF&amp;#x27;,
+ };

  protected __get__(prop: string) {
+   return this._colors[prop];
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;3. 添加类型合并&lt;/h3&gt;
&lt;p&gt;通过接口类型合并的机制为颜色提供类型定义&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;export interface ServiceColor {
  red: string;
  green: string;
  blue: string;
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4. 使用魔术方法&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;async test() {
  console.log(this.scope.service.color.red);
  console.log(this.scope.service.color.green);
  console.log(this.scope.service.color.blue);
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;&lt;code&gt;__set__&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;然后，通过&lt;code&gt;__set__&lt;/code&gt;实现颜色值的设置&lt;/p&gt;
&lt;h3&gt;1. 添加代码骨架&lt;/h3&gt;
&lt;p&gt;在 VSCode 编辑器中，输入代码片段&lt;code&gt;aopmagicset&lt;/code&gt;，自动生成代码骨架:&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;@Service()
export class ServiceColor extends BeanBase {
+ protected __set__(prop: string, value: any): boolean {
+   return false;
+ }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 实现自定义逻辑&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;@Service()
export class ServiceColor extends BeanBase {
  private _colors = {
    red: &amp;#x27;#FF0000&amp;#x27;,
    green: &amp;#x27;#00FF00&amp;#x27;,
    blue: &amp;#x27;#0000FF&amp;#x27;,
+   black: &amp;#x27;&amp;#x27;,
  };

  protected __set__(prop: string, value: any): boolean {
+   if (this._colors[prop] === undefined) return false;
+   this._colors[prop] = value;
+   return true;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;如果为&lt;code&gt;prop&lt;/code&gt;设置了值，返回&lt;code&gt;true&lt;/code&gt;，否则返回&lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 添加类型合并&lt;/h3&gt;
&lt;p&gt;通过接口类型合并的机制为颜色提供类型定义&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;export interface ServiceColor {
  red: string;
  green: string;
  blue: string;
+ black: string;
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4. 使用魔术方法&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;async test() {
  this.scope.service.color.black = &amp;#x27;#000000&amp;#x27;;
  console.log(this.scope.service.color.black);
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Github：&lt;a href=&quot;https://github.com/vonajs/vona&quot;&gt;https://github.com/vonajs/vona&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;文档：&lt;a href=&quot;https://vona.js.org/&quot;&gt;https://vona.js.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description><author>zhennann</author><pubDate>Thu, 23 Oct 2025 00:59:58 GMT</pubDate></item><item><title> 购物/订阅/广告投放/域名服务器，一张虚拟卡轻松付全球！</title><link>https://cnodejs.org/topic/68f09852f13576af38083f00</link><guid>https://cnodejs.org/topic/68f09852f13576af38083f00</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;VCC Visa万事达虚拟信用卡|轻松付全球|跨境支付
适用各种跨境场景，轻松付全球！支持AWS、tiktok、Facebook、Alipay 、Google、paypal、推特、星链、广告投放、域名服务器、 购物、注册、订阅等，覆盖多种消费场景，支持多币种交易、开卡即用、实时到账、极速消费，可自助机器人开卡与开后台管理，支持API对接代理。产品频道：&lt;a href=&quot;https://t.me/+vCIqSgwsBDxiODIy&quot;&gt;https://t.me/+vCIqSgwsBDxiODIy&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>daoke</author><pubDate>Thu, 16 Oct 2025 07:01:38 GMT</pubDate></item><item><title> 分享个好用的VPS平台，电商/建站/游戏都能用</title><link>https://cnodejs.org/topic/68eb7145f135767cc7083dc8</link><guid>https://cnodejs.org/topic/68eb7145f135767cc7083dc8</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;最近在折腾建站和游戏服务器，用了好几家VPS，最后留下这个：
BaseCloud｜来自新加坡的VPS聚合平台。&lt;/p&gt;
&lt;p&gt; 聚合阿里云、腾讯云、AWS 等大厂资源，
全球节点都有，国内延迟低，海外（香港、新加坡、日本）跑跨境业务很稳。&lt;/p&gt;
&lt;p&gt;⚙️ 支持一键部署 WordPress、Docker、ERP，
自助升降配、实时监控、原生DDoS防护，
还支持加密货币付款，全程免实名。&lt;/p&gt;
&lt;p&gt; 长期85折标价，不是限时价，账号开通也很快。&lt;/p&gt;
&lt;p&gt;适合做独立站、电商、游戏服、AI部署的都可以试试。&lt;/p&gt;
&lt;p&gt; &lt;a href=&quot;https://www.basecloud.cc/?utm_source=ondeseek&amp;amp;utm_medium=forum&amp;amp;utm_campaign=forum&quot;&gt;BaseCloud 官网直达&lt;/a&gt;BaseCloud 官网直达
 &lt;a href=&quot;https://t.me/+V-BhrqN1hUdiZGQ8&quot;&gt;官方TG频道&lt;/a&gt;官方TG频道 有节点上新和福利活动&lt;/p&gt;
&lt;/div&gt;</description><author>daoke</author><pubDate>Sun, 12 Oct 2025 09:13:41 GMT</pubDate></item><item><title>AOP编程有三大场景：控制器切面，内部切面，外部切面，你get到了吗？</title><link>https://cnodejs.org/topic/68e86dbef135761fe7083d23</link><guid>https://cnodejs.org/topic/68e86dbef135761fe7083d23</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;如果用过NestJS框架都知道，在NestJS框架中AOP编程包括以下几个能力：Middleware、Guard、Interceptor、Pipe、Filter。事实上AOP编程的应用场景更广泛，上述所列5个能力仅仅是AOP编程的子集。下面，我们看看在VonaJS框架中，AOP编程是怎样的。&lt;/p&gt;
&lt;h2&gt;VonaJS AOP编程&lt;/h2&gt;
&lt;p&gt;VonaJS AOP 编程包括三个方面的能力：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;控制器切面&lt;/code&gt;: 为 Controller 方法切入逻辑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;内部切面&lt;/code&gt;: 在 Class 内部，为任何 Class 的任何方法切入逻辑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;外部切面&lt;/code&gt;: 在不改变 Class 源码的前提下，从外部为任何 Class 的任何方法切入逻辑&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;控制器切面&lt;/h2&gt;
&lt;h3&gt;控制器切面清单&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Middleware&lt;/li&gt;
&lt;li&gt;Guard&lt;/li&gt;
&lt;li&gt;Intercepter&lt;/li&gt;
&lt;li&gt;Pipe&lt;/li&gt;
&lt;li&gt;Filter&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;执行时序图&lt;/h3&gt;
&lt;p&gt;控制器切面的执行时序图如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;//static.cnodejs.org/Fsx_N3n61Q2nKWzzXlvCIc5jTrQZ&quot; alt=&quot;aspect-controller.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;洋葱模型&lt;/code&gt;: &lt;code&gt;Middleware&lt;/code&gt;和&lt;code&gt;Intercepter&lt;/code&gt;支持&lt;code&gt;洋葱模型&lt;/code&gt;，允许在&lt;code&gt;Controller Action&lt;/code&gt;之前和之后执行切面逻辑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Middleware&lt;/code&gt;: 针对不同的执行时序节点，系统提供了三种 Middleware: &lt;code&gt;Middleware System&lt;/code&gt;、&lt;code&gt;Middleware Global&lt;/code&gt;和&lt;code&gt;Middleware Local&lt;/code&gt;，从而可以实现更精细化的切面逻辑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Route Match&lt;/code&gt;: 只有&lt;code&gt;Middleware System&lt;/code&gt;在路由匹配之前执行，其余在路由匹配之后执行&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Filter&lt;/code&gt;: 任何环节抛出异常，都会执行&lt;code&gt;Filter&lt;/code&gt;，从而自定义&lt;code&gt;错误信息&lt;/code&gt;和&lt;code&gt;错误日志&lt;/code&gt;的处理逻辑&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;内部切面&lt;/h2&gt;
&lt;p&gt;内部切面提供两个机制：&lt;code&gt;AOP Method&lt;/code&gt;和&lt;code&gt;魔术方法&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;1. AOP Method&lt;/h3&gt;
&lt;p&gt;直接在 Class Method 上通过装饰器切入逻辑&lt;/p&gt;
&lt;h4&gt;举例：数据库事务&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;class ServiceStudent {
+ @Database.transaction()
  async update(id: TableIdentity, student: DtoStudentUpdate) {
    return await this.scope.model.student.updateById(id, student);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Database.transaction&lt;/code&gt;：通过&lt;code&gt;AOP Method&lt;/code&gt;机制实现的装饰器，可以直接提供数据库事务能力&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;举例：日志&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;class ServiceStudent {
+ @Log()
  async update(id: TableIdentity, student: DtoStudentUpdate) {
    return await this.scope.model.student.updateById(id, student);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Log&lt;/code&gt;：通过&lt;code&gt;AOP Method&lt;/code&gt;机制实现的装饰器，可以直接提供日志能力&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 魔术方法&lt;/h3&gt;
&lt;p&gt;可以在 Class 内部通过&lt;code&gt;__get__&lt;/code&gt;和&lt;code&gt;__set__&lt;/code&gt;切入动态属性或方法&lt;/p&gt;
&lt;h4&gt;举例：获取 model 实例&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;class ServiceStudent {
  async update(id: TableIdentity, student: DtoStudentUpdate) {
+   return await this.scope.model.student.updateById(id, student);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;this.scope.model.xxx&lt;/code&gt;: 没有使用&lt;code&gt;依赖注入&lt;/code&gt;，而是使用&lt;code&gt;依赖查找&lt;/code&gt;，直接通过 scope 对象获取 model 实例，从而简化代码的书写风格&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;实现思路&lt;/h4&gt;
&lt;p&gt;系统提供了一个 Class &lt;code&gt;ServiceModelResolver&lt;/code&gt;，用于实现 model 实例的动态解析，代码如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;class ServiceModelResolver {
  protected __get__(prop: string) {
    const beanFullName = &amp;#96;${this[SymbolModuleScope]}.model.${prop}&amp;#96;;
    return this.bean._getBean(beanFullName as any);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;当调用&lt;code&gt;this.scope.model.student&lt;/code&gt;时，会自动执行&lt;code&gt;__get__&lt;/code&gt;方法，并且传入参数&lt;code&gt;prop: &apos;student&apos;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;将参数&lt;code&gt;prop&lt;/code&gt;与当前模块名称合并成&lt;code&gt;beanFullName&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;通过&lt;code&gt;beanFullName&lt;/code&gt;从全局容器中获取 model 实例，并返回给调用者&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;外部切面&lt;/h2&gt;
&lt;p&gt;仍以 Class &lt;code&gt;ServiceStudent&lt;/code&gt;的&lt;code&gt;update&lt;/code&gt;方法为例，通过&lt;code&gt;外部切面&lt;/code&gt;来实现日志能力：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;import { Aop } from &amp;#x27;vona-module-a-aspect&amp;#x27;;

@Aop({ match: &amp;#x27;demo-student.service.student&amp;#x27; })
class AopLog {
  async update(_args: Parameters&amp;lt;any&amp;gt;, next: Function, _receiver: any) {
    const timeBegin = Date.now();
    const res = await next();
    const timeEnd = Date.now();
    console.log(&amp;#x27;time: &amp;#x27;, timeEnd - timeBegin);
    return res;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Aop&lt;/code&gt;: 此装饰器用于实现&lt;code&gt;外部切面&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;match&lt;/code&gt;: 用于将 Class &lt;code&gt;AopLog&lt;/code&gt;与 Class &lt;code&gt;ServiceStudent&lt;/code&gt;关联，&lt;code&gt;ServiceStudent&lt;/code&gt;的 beanFullName 是&lt;code&gt;demo-student.service.student&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;update&lt;/code&gt;: 在&lt;code&gt;AopLog&lt;/code&gt;中提供与&lt;code&gt;ServiceStudent&lt;/code&gt;同名的方法&lt;code&gt;update&lt;/code&gt;，实现自定义逻辑即可&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Github：&lt;a href=&quot;https://github.com/vonajs/vona&quot;&gt;https://github.com/vonajs/vona&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;文档：&lt;a href=&quot;https://vona.js.org/&quot;&gt;https://vona.js.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description><author>zhennann</author><pubDate>Fri, 10 Oct 2025 02:21:50 GMT</pubDate></item><item><title>xlsx-handlebars 一个用于处理 XLSX 文件 Handlebars 模板的 Rust 库，支持多平台使</title><link>https://cnodejs.org/topic/68e29d7ff1357663ae083cb7</link><guid>https://cnodejs.org/topic/68e29d7ff1357663ae083cb7</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h1&gt;xlsx-handlebars&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://crates.io/crates/xlsx-handlebars&quot;&gt;&lt;img src=&quot;https://img.shields.io/crates/v/xlsx-handlebars.svg&quot; alt=&quot;Crates.io&quot;&gt;&lt;/a&gt;
&lt;a href=&quot;https://docs.rs/xlsx-handlebars&quot;&gt;&lt;img src=&quot;https://docs.rs/xlsx-handlebars/badge.svg&quot; alt=&quot;Documentation&quot;&gt;&lt;/a&gt;
&lt;a href=&quot;https://github.com/sail-sail/xlsx-handlebars#license&quot;&gt;&lt;img src=&quot;https://img.shields.io/crates/l/xlsx-handlebars.svg&quot; alt=&quot;License&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;中文文档 | &lt;a href&gt;English&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;一个用于处理 XLSX 文件 Handlebars 模板的 Rust 库，支持多平台使用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; Rust 原生&lt;/li&gt;
&lt;li&gt; WebAssembly (WASM)&lt;/li&gt;
&lt;li&gt; npm 包&lt;/li&gt;
&lt;li&gt; Node.js&lt;/li&gt;
&lt;li&gt; Deno&lt;/li&gt;
&lt;li&gt; 浏览器端&lt;/li&gt;
&lt;li&gt; JSR (JavaScript Registry)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;功能特性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;⚡ &lt;strong&gt;极致性能&lt;/strong&gt;：2.12秒渲染10万行数据（约4.7万行/秒）- 比 Python 快 14-28倍，比 JavaScript 快 7-14倍&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;智能合并&lt;/strong&gt;：自动处理被 XML 标签分割的 Handlebars 语法&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;XLSX 验证&lt;/strong&gt;：内置文件格式验证，确保输入文件有效&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Handlebars 支持&lt;/strong&gt;：完整的模板引擎，支持变量、条件、循环、Helper 函数&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;跨平台&lt;/strong&gt;：Rust 原生 + WASM 支持多种运行时&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;TypeScript&lt;/strong&gt;：完整的类型定义和智能提示&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;零依赖&lt;/strong&gt;：WASM 二进制文件，无外部依赖&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;错误处理&lt;/strong&gt;：详细的错误信息和类型安全的错误处理&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;h3&gt;Rust&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;cargo add xlsx-handlebars
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;npm&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;npm install xlsx-handlebars
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Deno&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;import { render_template, init } from &amp;quot;jsr:@sail&amp;#x2F;xlsx-handlebars&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;使用示例&lt;/h2&gt;
&lt;h3&gt;Rust&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-rust&quot;&gt;&lt;code&gt;use xlsx_handlebars::render_template;
use serde_json::json;

fn main() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {
    &amp;#x2F;&amp;#x2F; 读取 XLSX 模板文件
    let template_bytes = std::fs::read(&amp;quot;template.xlsx&amp;quot;)?;
    
    &amp;#x2F;&amp;#x2F; 准备数据
    let data = json!({
        &amp;quot;name&amp;quot;: &amp;quot;张三&amp;quot;,
        &amp;quot;company&amp;quot;: &amp;quot;ABC科技有限公司&amp;quot;,
        &amp;quot;position&amp;quot;: &amp;quot;软件工程师&amp;quot;,
        &amp;quot;projects&amp;quot;: [
            {&amp;quot;name&amp;quot;: &amp;quot;项目A&amp;quot;, &amp;quot;status&amp;quot;: &amp;quot;已完成&amp;quot;},
            {&amp;quot;name&amp;quot;: &amp;quot;项目B&amp;quot;, &amp;quot;status&amp;quot;: &amp;quot;进行中&amp;quot;}
        ],
        &amp;quot;has_bonus&amp;quot;: true,
        &amp;quot;bonus_amount&amp;quot;: 5000
    });
    
    &amp;#x2F;&amp;#x2F; 渲染模板
    let result = render_template(template_bytes, &amp;amp;data)?;
    
    &amp;#x2F;&amp;#x2F; 保存结果
    std::fs::write(&amp;quot;output.xlsx&amp;quot;, result)?;
    
    Ok(())
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;JavaScript/TypeScript (Node.js)&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;import init, { render_template } from &amp;quot;xlsx-handlebars&amp;quot;;
import fs from &amp;#x27;fs&amp;#x27;;

async function processTemplate() {
    &amp;#x2F;&amp;#x2F; 初始化 WASM 模块
    await init();
    
    &amp;#x2F;&amp;#x2F; 读取模板文件
    const templateBytes = fs.readFileSync(&amp;quot;template.xlsx&amp;quot;);
    
    &amp;#x2F;&amp;#x2F; 准备数据
    const data = {
        name: &amp;quot;李明&amp;quot;,
        company: &amp;quot;XYZ技术有限公司&amp;quot;,
        position: &amp;quot;高级开发工程师&amp;quot;,
        projects: [
            { name: &amp;quot;E-commerce平台&amp;quot;, status: &amp;quot;已完成&amp;quot; },
            { name: &amp;quot;移动端APP&amp;quot;, status: &amp;quot;开发中&amp;quot; }
        ],
        has_bonus: true,
        bonus_amount: 8000
    };
    
    &amp;#x2F;&amp;#x2F; 渲染模板
    const result = render_template(templateBytes, JSON.stringify(data));
    
    &amp;#x2F;&amp;#x2F; 保存结果
    fs.writeFileSync(&amp;#x27;output.xlsx&amp;#x27;, new Uint8Array(result));
}

processTemplate().catch(console.error);
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Deno&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;import init, { render_template } from &amp;quot;https:&amp;#x2F;&amp;#x2F;deno.land&amp;#x2F;x&amp;#x2F;xlsx_handlebars&amp;#x2F;mod.ts&amp;quot;;

async function processTemplate() {
    &amp;#x2F;&amp;#x2F; 初始化 WASM 模块
    await init();
    
    &amp;#x2F;&amp;#x2F; 读取模板文件
    const templateBytes = await Deno.readFile(&amp;quot;template.xlsx&amp;quot;);
    
    &amp;#x2F;&amp;#x2F; 准备数据
    const data = {
        name: &amp;quot;王小明&amp;quot;,
        department: &amp;quot;研发部&amp;quot;,
        projects: [
            { name: &amp;quot;智能客服系统&amp;quot;, status: &amp;quot;已上线&amp;quot; },
            { name: &amp;quot;数据可视化平台&amp;quot;, status: &amp;quot;开发中&amp;quot; }
        ]
    };
    
    &amp;#x2F;&amp;#x2F; 渲染模板
    const result = render_template(templateBytes, JSON.stringify(data));
    
    &amp;#x2F;&amp;#x2F; 保存结果
    await Deno.writeFile(&amp;quot;output.xlsx&amp;quot;, new Uint8Array(result));
}

if (import.meta.main) {
    await processTemplate();
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;浏览器端&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;XLSX Handlebars 示例&amp;lt;&amp;#x2F;title&amp;gt;
&amp;lt;&amp;#x2F;head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;input type=&amp;quot;file&amp;quot; id=&amp;quot;fileInput&amp;quot; accept=&amp;quot;.xlsx&amp;quot;&amp;gt;
    &amp;lt;button onclick=&amp;quot;processFile()&amp;quot;&amp;gt;处理模板&amp;lt;&amp;#x2F;button&amp;gt;
    
    &amp;lt;script type=&amp;quot;module&amp;quot;&amp;gt;
        import init, { render_template } from &amp;#x27;.&amp;#x2F;pkg&amp;#x2F;xlsx_handlebars.js&amp;#x27;;
        
        &amp;#x2F;&amp;#x2F; 初始化 WASM
        await init();
        
        window.processFile = async function() {
            const fileInput = document.getElementById(&amp;#x27;fileInput&amp;#x27;);
            const file = fileInput.files[0];
            
            if (!file) return;
            
            const arrayBuffer = await file.arrayBuffer();
            const templateBytes = new Uint8Array(arrayBuffer);
            
            const data = {
                name: &amp;quot;张三&amp;quot;,
                company: &amp;quot;示例公司&amp;quot;
            };
            
            try {
                const result = render_template(templateBytes, JSON.stringify(data));
                
                &amp;#x2F;&amp;#x2F; 下载结果
                const blob = new Blob([new Uint8Array(result)], {
                    type: &amp;#x27;application&amp;#x2F;vnd.openxmlformats-officedocument.wordprocessingml.document&amp;#x27;
                });
                const url = URL.createObjectURL(blob);
                const a = document.createElement(&amp;#x27;a&amp;#x27;);
                a.href = url;
                a.download = &amp;#x27;processed.xlsx&amp;#x27;;
                a.click();
            } catch (error) {
                console.error(&amp;#x27;处理失败:&amp;#x27;, error);
            }
        };
    &amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;&amp;#x2F;body&amp;gt;
&amp;lt;&amp;#x2F;html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;模板语法&lt;/h2&gt;
&lt;h3&gt;基础变量替换&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;员工姓名: {{name}}
公司: {{company}}
职位: {{position}}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;条件渲染&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;{{#if has_bonus}}
奖金: ¥{{bonus_amount}}
{{else}}
无奖金
{{&amp;#x2F;if}}

{{#unless is_intern}}
正式员工
{{&amp;#x2F;unless}}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;循环渲染&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;项目经历:
{{#each projects}}
- {{name}}: {{description}} ({{status}})
{{&amp;#x2F;each}}

技能列表:
{{#each skills}}
{{@index}}. {{this}}
{{&amp;#x2F;each}}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Helper 函数&lt;/h3&gt;
&lt;p&gt;内置的 Helper 函数：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;&amp;lt;!-- 基础 helper --&amp;gt;
{{upper name}}           &amp;lt;!-- 转大写 --&amp;gt;
{{lower company}}        &amp;lt;!-- 转小写 --&amp;gt;
{{len projects}}         &amp;lt;!-- 数组长度 --&amp;gt;
{{#if (eq status &amp;quot;completed&amp;quot;)}}已完成{{&amp;#x2F;if}}    &amp;lt;!-- 相等比较 --&amp;gt;
{{#if (gt score 90)}}优秀{{&amp;#x2F;if}}               &amp;lt;!-- 大于比较 --&amp;gt;
{{#if (lt age 30)}}年轻{{&amp;#x2F;if}}                 &amp;lt;!-- 小于比较 --&amp;gt;

&amp;lt;!-- 字符串拼接 --&amp;gt;
{{concat &amp;quot;你好&amp;quot; &amp;quot; &amp;quot; &amp;quot;世界&amp;quot;}}                    &amp;lt;!-- 字符串拼接 --&amp;gt;
{{concat &amp;quot;总计: &amp;quot; count}}                      &amp;lt;!-- 混合字符串和变量 --&amp;gt;

&amp;lt;!-- Excel 专用 helper --&amp;gt;
{{num employee.salary}}                         &amp;lt;!-- 标记单元格为数字类型 --&amp;gt;
{{formula &amp;quot;=SUM(A1:B1)&amp;quot;}}                      &amp;lt;!-- 静态 Excel 公式 --&amp;gt;
{{formula (concat &amp;quot;=SUM(&amp;quot; (_c) &amp;quot;1:&amp;quot; (_c) &amp;quot;10)&amp;quot;)}} &amp;lt;!-- 使用当前列的动态公式 --&amp;gt;
{{mergeCell &amp;quot;C4:D5&amp;quot;}}                          &amp;lt;!-- 合并单元格 C4 到 D5 --&amp;gt;
{{img logo.data 100 100}}                       &amp;lt;!-- 插入图片，指定宽高 --&amp;gt;

&amp;lt;!-- 列名转换 helper --&amp;gt;
{{toColumnName &amp;quot;A&amp;quot; 5}}                          &amp;lt;!-- A + 5 偏移 = F --&amp;gt;
{{toColumnName (_c) 3}}                         &amp;lt;!-- 当前列向右偏移 3 列 --&amp;gt;
{{toColumnIndex &amp;quot;AA&amp;quot;}}                          &amp;lt;!-- AA 列的索引 = 27 --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Excel 公式 Helper&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;静态公式&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;&amp;lt;!-- 在 Excel 单元格中 --&amp;gt;
{{formula &amp;quot;=SUM(A1:B1)&amp;quot;}}
{{formula &amp;quot;=AVERAGE(C2:C10)&amp;quot;}}
{{formula &amp;quot;=IF(D1&amp;gt;100,\&amp;quot;高\&amp;quot;,\&amp;quot;低\&amp;quot;)&amp;quot;}}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;使用 &lt;code&gt;concat&lt;/code&gt; 的动态公式&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;&amp;lt;!-- 动态行引用 --&amp;gt;
{{formula (concat &amp;quot;=A&amp;quot; (_r) &amp;quot;*B&amp;quot; (_r))}}

&amp;lt;!-- 动态列引用 --&amp;gt;
{{formula (concat &amp;quot;=SUM(&amp;quot; (_c) &amp;quot;2:&amp;quot; (_c) &amp;quot;10)&amp;quot;)}}

&amp;lt;!-- 复杂动态公式 --&amp;gt;
{{formula (concat &amp;quot;=IF(&amp;quot; (_cr) &amp;quot;&amp;gt;100,\&amp;quot;高\&amp;quot;,\&amp;quot;低\&amp;quot;)&amp;quot;)}}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;可用的位置 helper&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(_c)&lt;/code&gt; - 当前列字母 (A, B, C, …)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(_r)&lt;/code&gt; - 当前行号 (1, 2, 3, …)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(_cr)&lt;/code&gt; - 当前单元格引用 (A1, B2, C3, …)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;列名转换 Helper&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;toColumnName&lt;/code&gt;&lt;/strong&gt; - 将列名或列索引转换为新的列名，支持偏移量：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;&amp;lt;!-- 基础用法：从指定列名开始偏移 --&amp;gt;
{{toColumnName &amp;quot;A&amp;quot; 0}}     &amp;lt;!-- A (无偏移) --&amp;gt;
{{toColumnName &amp;quot;A&amp;quot; 5}}     &amp;lt;!-- F (A + 5) --&amp;gt;
{{toColumnName &amp;quot;Z&amp;quot; 1}}     &amp;lt;!-- AA (Z + 1) --&amp;gt;

&amp;lt;!-- 配合当前列使用 --&amp;gt;
{{toColumnName (_c) 3}}    &amp;lt;!-- 当前列向右偏移 3 列 --&amp;gt;

&amp;lt;!-- 动态公式中的应用 --&amp;gt;
{{formula (concat &amp;quot;=SUM(&amp;quot; (_c) &amp;quot;1:&amp;quot; (toColumnName (_c) 3) &amp;quot;1)&amp;quot;)}}
&amp;lt;!-- 示例：如果当前列是 B，生成公式 =SUM(B1:E1) --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;toColumnIndex&lt;/code&gt;&lt;/strong&gt; - 将列名转换为列索引（1-based）：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;{{toColumnIndex &amp;quot;A&amp;quot;}}      &amp;lt;!-- 1 --&amp;gt;
{{toColumnIndex &amp;quot;Z&amp;quot;}}      &amp;lt;!-- 26 --&amp;gt;
{{toColumnIndex &amp;quot;AA&amp;quot;}}     &amp;lt;!-- 27 --&amp;gt;
{{toColumnIndex &amp;quot;AB&amp;quot;}}     &amp;lt;!-- 28 --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;合并单元格 Helper&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;mergeCell&lt;/code&gt;&lt;/strong&gt; - 标记需要合并的单元格范围：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;&amp;lt;!-- 静态合并单元格 --&amp;gt;
{{mergeCell &amp;quot;C4:D5&amp;quot;}}      &amp;lt;!-- 合并 C4 到 D5 区域 --&amp;gt;
{{mergeCell &amp;quot;F4:G4&amp;quot;}}      &amp;lt;!-- 合并 F4 到 G4 区域 --&amp;gt;

&amp;lt;!-- 动态合并单元格：从当前位置合并 --&amp;gt;
{{mergeCell (concat (_c) (_r) &amp;quot;:&amp;quot; (toColumnName (_c) 3) (_r))}}
&amp;lt;!-- 示例：如果当前在 B5，合并 B5:E5（向右合并4列） --&amp;gt;

&amp;lt;!-- 动态合并单元格：跨行跨列 --&amp;gt;
{{mergeCell (concat (_c) (_r) &amp;quot;:&amp;quot; (toColumnName (_c) 2) (add (_r) 2))}}
&amp;lt;!-- 示例：如果当前在 C3，合并 C3:E5（3列×3行的区域） --&amp;gt;

&amp;lt;!-- 在循环中动态合并 --&amp;gt;
{{#each sections}}
  {{mergeCell (concat &amp;quot;A&amp;quot; (add @index 2) &amp;quot;:D&amp;quot; (add @index 2))}}
  &amp;lt;!-- 为每个 section 合并一行的 A-D 列 --&amp;gt;
{{&amp;#x2F;each}}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;注意事项&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mergeCell&lt;/code&gt; 不产生输出，仅收集合并信息&lt;/li&gt;
&lt;li&gt;合并范围格式必须是 &lt;code&gt;起始单元格:结束单元格&lt;/code&gt;（如 &lt;code&gt;&amp;quot;A1:B2&amp;quot;&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;相同的合并范围会自动去重&lt;/li&gt;
&lt;li&gt;合并信息会在渲染完成后自动添加到 Excel 文件中&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;超链接 Helper&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;hyperlink&lt;/code&gt;&lt;/strong&gt; - 在 Excel 单元格中添加超链接：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;&amp;lt;!-- 基础用法：链接到其他工作表 --&amp;gt;
{{hyperlink (_cr) &amp;quot;Sheet2!A1&amp;quot; &amp;quot;查看详情&amp;quot;}}

&amp;lt;!-- 链接到外部网址（需在模板中预设） --&amp;gt;
{{hyperlink (_cr) &amp;quot;https:&amp;#x2F;&amp;#x2F;example.com&amp;quot; &amp;quot;访问网站&amp;quot;}}

&amp;lt;!-- 动态链接 --&amp;gt;
{{#each items}}
  {{hyperlink (_cr) (concat &amp;quot;详情!&amp;quot; name) name}}
{{&amp;#x2F;each}}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;参数说明&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一个参数：单元格引用，通常使用 &lt;code&gt;(_cr)&lt;/code&gt; 获取当前单元格&lt;/li&gt;
&lt;li&gt;第二个参数：链接目标（工作表引用或 URL）&lt;/li&gt;
&lt;li&gt;第三个参数：显示文本（可选）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;注意事项&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hyperlink&lt;/code&gt; 不产生输出，仅收集超链接信息&lt;/li&gt;
&lt;li&gt;超链接会在渲染完成后自动添加到 Excel 文件中&lt;/li&gt;
&lt;li&gt;支持工作表内部引用（如 &lt;code&gt;&amp;quot;Sheet2!A1&amp;quot;&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;外部链接需要在模板 Excel 文件中预先配置关系&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;数字类型 Helper&lt;/h4&gt;
&lt;p&gt;使用 &lt;code&gt;{{num value}}&lt;/code&gt; 确保单元格在 Excel 中被识别为数字：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;&amp;lt;!-- 不使用 num: 当作文本处理 --&amp;gt;
{{employee.salary}}

&amp;lt;!-- 使用 num: 当作数字处理 --&amp;gt;
{{num employee.salary}}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;特别适用于以下场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;值可能是字符串但应当作数字处理&lt;/li&gt;
&lt;li&gt;需要确保 Excel 中的数字格式正确&lt;/li&gt;
&lt;li&gt;需要在公式中使用该值&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;图片插入 Helper&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;img&lt;/code&gt;&lt;/strong&gt; - 在 Excel 中插入 base64 编码的图片：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;&amp;lt;!-- 基础用法：插入图片并使用原始尺寸 --&amp;gt;
{{img logo.data}}

&amp;lt;!-- 指定宽度和高度（单位：像素） --&amp;gt;
{{img photo.data 150 200}}

&amp;lt;!-- 使用数据中的尺寸 --&amp;gt;
{{img image.data image.width image.height}}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;特性&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 支持 PNG、JPEG、WebP、BMP、TIFF、GIF 等常见图片格式&lt;/li&gt;
&lt;li&gt;✅ 自动检测图片实际尺寸&lt;/li&gt;
&lt;li&gt;✅ 可选指定宽度和高度（像素）&lt;/li&gt;
&lt;li&gt;✅ 图片定位在当前单元格位置&lt;/li&gt;
&lt;li&gt;✅ 图片不受单元格大小限制，保持比例&lt;/li&gt;
&lt;li&gt;✅ 支持同一 sheet 插入多张图片&lt;/li&gt;
&lt;li&gt;✅ 支持多个 sheet 各自插入图片&lt;/li&gt;
&lt;li&gt;✅ 使用 UUID 避免 ID 冲突&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;完整示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 在 JavaScript 中准备图片数据
import fs from &amp;#x27;fs&amp;#x27;;

const imageBuffer = fs.readFileSync(&amp;#x27;logo.png&amp;#x27;);
const base64Image = imageBuffer.toString(&amp;#x27;base64&amp;#x27;);

const data = {
  company: {
    logo: base64Image,
    name: &amp;quot;科技公司&amp;quot;
  },
  products: [
    {
      name: &amp;quot;产品A&amp;quot;,
      photo: base64Image,
      width: 120,
      height: 120
    },
    {
      name: &amp;quot;产品B&amp;quot;, 
      photo: base64Image,
      width: 100,
      height: 100
    }
  ]
};

&amp;#x2F;&amp;#x2F; 在模板中使用
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;&amp;lt;!-- Excel 模板示例 --&amp;gt;
公司Logo: {{img company.logo 100 50}}

产品列表:
{{#each products}}
产品名: {{name}}
图片: {{img photo width height}}
{{&amp;#x2F;each}}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;使用技巧&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果只指定宽度，高度会等比例缩放&lt;/li&gt;
&lt;li&gt;如果只指定高度，宽度会等比例缩放&lt;/li&gt;
&lt;li&gt;如果都不指定，使用图片原始尺寸&lt;/li&gt;
&lt;li&gt;图片会放置在调用 &lt;code&gt;{{img}}&lt;/code&gt; 的单元格位置&lt;/li&gt;
&lt;li&gt;base64 数据不包含 &lt;code&gt;data:image/png;base64,&lt;/code&gt; 前缀，只需要纯 base64 字符串&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;工作表管理 Helpers&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;deleteCurrentSheet&lt;/code&gt;&lt;/strong&gt; - 删除当前正在渲染的工作表：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;&amp;lt;!-- 基础用法 --&amp;gt;
{{deleteCurrentSheet}}

&amp;lt;!-- 条件删除 --&amp;gt;
{{#if shouldDelete}}
  {{deleteCurrentSheet}}
{{&amp;#x2F;if}}

&amp;lt;!-- 删除非活跃工作表 --&amp;gt;
{{#unless isActive}}
  {{deleteCurrentSheet}}
{{&amp;#x2F;unless}}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;特性&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 从工作簿中移除工作表及其关系&lt;/li&gt;
&lt;li&gt;✅ 清理相关文件（rels、content types）&lt;/li&gt;
&lt;li&gt;✅ 保留 drawing 文件（安全考虑）&lt;/li&gt;
&lt;li&gt;✅ 不能删除最后一个工作表（Excel 要求）&lt;/li&gt;
&lt;li&gt;✅ 延迟执行，所有渲染完成后统一删除&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;setCurrentSheetName&lt;/code&gt;&lt;/strong&gt; - 重命名当前工作表：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;&amp;lt;!-- 静态名称 --&amp;gt;
{{setCurrentSheetName &amp;quot;销售报表&amp;quot;}}

&amp;lt;!-- 动态名称 --&amp;gt;
{{setCurrentSheetName (concat department.name &amp;quot; - &amp;quot; year &amp;quot;年&amp;quot;)}}

&amp;lt;!-- 基于循环的命名 --&amp;gt;
{{#each departments}}
  {{setCurrentSheetName (concat &amp;quot;部门&amp;quot; @index &amp;quot; - &amp;quot; name)}}
{{&amp;#x2F;each}}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;特性&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 自动过滤非法字符：&lt;code&gt;\ / ? * [ ]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;✅ 自动限制长度为 31 个字符&lt;/li&gt;
&lt;li&gt;✅ 自动处理重名，添加数字后缀&lt;/li&gt;
&lt;li&gt;✅ 支持动态名称生成&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;hideCurrentSheet&lt;/code&gt;&lt;/strong&gt; - 隐藏当前工作表：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;&amp;lt;!-- 普通隐藏（用户可通过右键取消隐藏） --&amp;gt;
{{hideCurrentSheet}}
{{hideCurrentSheet &amp;quot;hidden&amp;quot;}}

&amp;lt;!-- 超级隐藏（需要 VBA 才能取消隐藏） --&amp;gt;
{{hideCurrentSheet &amp;quot;veryHidden&amp;quot;}}

&amp;lt;!-- 条件隐藏 --&amp;gt;
{{#unless (eq userRole &amp;quot;admin&amp;quot;)}}
  {{hideCurrentSheet &amp;quot;veryHidden&amp;quot;}}
{{&amp;#x2F;unless}}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;隐藏级别&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hidden&lt;/code&gt; - 普通隐藏，用户可通过 Excel 右键菜单取消隐藏&lt;/li&gt;
&lt;li&gt;&lt;code&gt;veryHidden&lt;/code&gt; - 超级隐藏，需要 VBA 或属性编辑器才能取消隐藏&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;特性&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 不能隐藏所有工作表（Excel 要求至少一个可见）&lt;/li&gt;
&lt;li&gt;✅ 两种隐藏级别：普通隐藏和超级隐藏&lt;/li&gt;
&lt;li&gt;✅ 适用于权限控制和敏感数据保护&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;常见使用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;&amp;lt;!-- 多语言报表：删除未使用的语言工作表 --&amp;gt;
{{#if (ne language &amp;quot;zh-CN&amp;quot;)}}
  {{deleteCurrentSheet}}
{{&amp;#x2F;if}}

&amp;lt;!-- 动态部门报表：按部门重命名工作表 --&amp;gt;
{{setCurrentSheetName (concat department.name &amp;quot; 报表&amp;quot;)}}

&amp;lt;!-- 权限控制：对普通用户隐藏管理员工作表 --&amp;gt;
{{#unless (eq userRole &amp;quot;admin&amp;quot;)}}
  {{hideCurrentSheet &amp;quot;veryHidden&amp;quot;}}
{{&amp;#x2F;unless}}

&amp;lt;!-- 条件工作流：根据状态删除、重命名或隐藏 --&amp;gt;
{{#if (eq status &amp;quot;inactive&amp;quot;)}}
  {{deleteCurrentSheet}}
{{else}}
  {{setCurrentSheetName (concat &amp;quot;活跃 - &amp;quot; name)}}
  {{#if isInternal}}
    {{hideCurrentSheet}}
  {{&amp;#x2F;if}}
{{&amp;#x2F;if}}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;复杂示例&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;=== 员工报告 ===

基本信息:
姓名: {{employee.name}}
部门: {{employee.department}}
职位: {{employee.position}}
入职时间: {{employee.hire_date}}

{{#if employee.has_bonus}}
 奖金: ¥{{employee.bonus_amount}}
{{&amp;#x2F;if}}

项目经历 (共{{len projects}}个):
{{#each projects}}
{{@index}}. {{name}}
   描述: {{description}}
   状态: {{status}}
   团队规模: {{team_size}}人
   
{{&amp;#x2F;each}}

技能评估:
{{#each skills}}
- {{name}}: {{level}}&amp;#x2F;10 ({{years}}年经验)
{{&amp;#x2F;each}}

在表格中若需要删除一整行, 只需要在任意单元格上添加:
{{removeRow}}


{{#if (gt performance.score 90)}}
 绩效评级: 优秀
{{else if (gt performance.score 80)}}
 绩效评级: 良好
{{else}}
 绩效评级: 需改进
{{&amp;#x2F;if}}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;构建和开发&lt;/h2&gt;
&lt;h3&gt;构建 WASM 包&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;# 构建所有目标
npm run build

# 或分别构建
npm run build:web    # 浏览器版本
npm run build:npm    # Node.js 版本 
npm run build:jsr    # Deno 版本
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;运行示例&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;# Rust 示例
cargo run --example rust_example

# Node.js 示例
node examples&amp;#x2F;node_example.js

# Deno 示例  
deno run --allow-read --allow-write examples&amp;#x2F;deno_example.ts

# 浏览器示例
cd tests&amp;#x2F;npm_test
node serve.js
# 然后在浏览器中打开 http:&amp;#x2F;&amp;#x2F;localhost:8080
# 选择 examples&amp;#x2F;template.xlsx 文件测试
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;工具函数&lt;/h2&gt;
&lt;p&gt;xlsx-handlebars 提供了一系列实用工具函数，帮助你更高效地处理 Excel 相关操作。&lt;/p&gt;
&lt;h3&gt;获取图片尺寸&lt;/h3&gt;
&lt;p&gt;从原始图片数据中检测图片尺寸，无需依赖完整的图片处理库。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-rust&quot;&gt;&lt;code&gt;use xlsx_handlebars::get_image_dimensions;

&amp;#x2F;&amp;#x2F; 读取图片文件
let image_data = std::fs::read(&amp;quot;logo.png&amp;quot;)?;

&amp;#x2F;&amp;#x2F; 获取尺寸
if let Some((width, height)) = get_image_dimensions(&amp;amp;image_data) {
    println!(&amp;quot;图片尺寸: {}x{}&amp;quot;, width, height);
} else {
    println!(&amp;quot;不支持的图片格式&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;支持的格式&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PNG&lt;/li&gt;
&lt;li&gt;JPEG&lt;/li&gt;
&lt;li&gt;WebP (VP8, VP8L, VP8X)&lt;/li&gt;
&lt;li&gt;BMP&lt;/li&gt;
&lt;li&gt;TIFF (II/MM 字节序)&lt;/li&gt;
&lt;li&gt;GIF (87a/89a)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Excel 列名转换&lt;/h3&gt;
&lt;p&gt;在 Excel 中进行列名和列索引之间的转换。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-rust&quot;&gt;&lt;code&gt;use xlsx_handlebars::{to_column_name, to_column_index};

&amp;#x2F;&amp;#x2F; 列名递增
assert_eq!(to_column_name(&amp;quot;A&amp;quot;, 0), &amp;quot;A&amp;quot;);
assert_eq!(to_column_name(&amp;quot;A&amp;quot;, 1), &amp;quot;B&amp;quot;);
assert_eq!(to_column_name(&amp;quot;Z&amp;quot;, 1), &amp;quot;AA&amp;quot;);
assert_eq!(to_column_name(&amp;quot;AA&amp;quot;, 1), &amp;quot;AB&amp;quot;);

&amp;#x2F;&amp;#x2F; 列名转索引 (1-based)
assert_eq!(to_column_index(&amp;quot;A&amp;quot;), 1);
assert_eq!(to_column_index(&amp;quot;Z&amp;quot;), 26);
assert_eq!(to_column_index(&amp;quot;AA&amp;quot;), 27);
assert_eq!(to_column_index(&amp;quot;BA&amp;quot;), 53);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;JavaScript/TypeScript 示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;import { wasm_to_column_name, wasm_to_column_index } from &amp;#x27;xlsx-handlebars&amp;#x27;;

&amp;#x2F;&amp;#x2F; 列名递增
console.log(wasm_to_column_name(&amp;quot;A&amp;quot;, 1));  &amp;#x2F;&amp;#x2F; &amp;quot;B&amp;quot;
console.log(wasm_to_column_name(&amp;quot;Z&amp;quot;, 1));  &amp;#x2F;&amp;#x2F; &amp;quot;AA&amp;quot;

&amp;#x2F;&amp;#x2F; 列名转索引
console.log(wasm_to_column_index(&amp;quot;AA&amp;quot;));   &amp;#x2F;&amp;#x2F; 27
console.log(wasm_to_column_index(&amp;quot;BA&amp;quot;));   &amp;#x2F;&amp;#x2F; 53
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Excel 日期转换&lt;/h3&gt;
&lt;p&gt;在 Unix 时间戳和 Excel 日期序列号之间转换。Excel 使用从 1900-01-01 开始的序列号表示日期。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-rust&quot;&gt;&lt;code&gt;use xlsx_handlebars::{timestamp_to_excel_date, excel_date_to_timestamp};

&amp;#x2F;&amp;#x2F; 时间戳转 Excel 日期
let timestamp = 1704067200000i64;  &amp;#x2F;&amp;#x2F; 2024-01-01 00:00:00 UTC
let excel_date = timestamp_to_excel_date(timestamp);
println!(&amp;quot;Excel 日期序列号: {}&amp;quot;, excel_date);  &amp;#x2F;&amp;#x2F; 45294.0

&amp;#x2F;&amp;#x2F; Excel 日期转时间戳
if let Some(ts) = excel_date_to_timestamp(45294.0) {
    println!(&amp;quot;时间戳: {}&amp;quot;, ts);  &amp;#x2F;&amp;#x2F; 1704067200000
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;JavaScript/TypeScript 示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;import { 
    wasm_timestamp_to_excel_date, 
    wasm_excel_date_to_timestamp 
} from &amp;#x27;xlsx-handlebars&amp;#x27;;

&amp;#x2F;&amp;#x2F; 日期转 Excel 序列号
const date = new Date(&amp;#x27;2024-01-01T00:00:00Z&amp;#x27;);
const excelDate = wasm_timestamp_to_excel_date(date.getTime());
console.log(&amp;#x27;Excel 日期:&amp;#x27;, excelDate);  &amp;#x2F;&amp;#x2F; 45294.0

&amp;#x2F;&amp;#x2F; Excel 序列号转日期
const timestamp = wasm_excel_date_to_timestamp(45294.0);
if (timestamp !== null) {
    const convertedDate = new Date(timestamp);
    console.log(&amp;#x27;日期:&amp;#x27;, convertedDate.toISOString());
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;常见使用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-rust&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 在模板中使用前验证图片尺寸
let image_data = std::fs::read(&amp;quot;photo.jpg&amp;quot;)?;
match get_image_dimensions(&amp;amp;image_data) {
    Some((w, h)) if w &amp;lt;= 1000 &amp;amp;&amp;amp; h &amp;lt;= 1000 =&amp;gt; {
        println!(&amp;quot;有效图片: {}x{}&amp;quot;, w, h);
        &amp;#x2F;&amp;#x2F; 继续进行模板渲染
    }
    Some((w, h)) =&amp;gt; {
        eprintln!(&amp;quot;图片过大: {}x{} (最大 1000x1000)&amp;quot;, w, h);
    }
    None =&amp;gt; {
        eprintln!(&amp;quot;不支持的图片格式&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-rust&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 动态生成单元格引用
let start_col = &amp;quot;B&amp;quot;;
let num_cols = 5;
for i in 0..num_cols {
    let col_name = to_column_name(start_col, i);
    let col_index = to_column_index(&amp;amp;col_name);
    println!(&amp;quot;列 {}: 名称={}, 索引={}&amp;quot;, i, col_name, col_index);
}
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-rust&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 在模板数据中包含日期
use serde_json::json;

let date_timestamp = 1704067200000i64;  &amp;#x2F;&amp;#x2F; 2024-01-01
let excel_date = timestamp_to_excel_date(date_timestamp);

let data = json!({
    &amp;quot;report_date&amp;quot;: excel_date,
    &amp;quot;employee&amp;quot;: {
        &amp;quot;name&amp;quot;: &amp;quot;张三&amp;quot;,
        &amp;quot;hire_date&amp;quot;: timestamp_to_excel_date(1609459200000i64)  &amp;#x2F;&amp;#x2F; 2021-01-01
    }
});
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-rust&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 批量处理图片
for file in &amp;amp;[&amp;quot;logo.png&amp;quot;, &amp;quot;banner.jpg&amp;quot;, &amp;quot;icon.gif&amp;quot;] {
    let data = std::fs::read(file)?;
    match get_image_dimensions(&amp;amp;data) {
        Some((w, h)) =&amp;gt; println!(&amp;quot;{}: {}x{}&amp;quot;, file, w, h),
        None =&amp;gt; eprintln!(&amp;quot;{}: 不支持的格式&amp;quot;, file),
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这些工具函数帮助你：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 在插入前验证图片尺寸&lt;/li&gt;
&lt;li&gt;✅ 动态生成单元格引用和公式&lt;/li&gt;
&lt;li&gt;✅ 处理 Excel 日期格式&lt;/li&gt;
&lt;li&gt;✅ 避免加载笨重的外部库&lt;/li&gt;
&lt;li&gt;✅ 同时支持 Rust 和 JavaScript/TypeScript&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;技术特性&lt;/h2&gt;
&lt;h2&gt;性能和兼容性&lt;/h2&gt;
&lt;h3&gt;极致性能表现 ⚡&lt;/h3&gt;
&lt;p&gt;xlsx-handlebars 凭借 Rust 实现了&lt;strong&gt;业界顶尖的性能表现&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;数据量&lt;/th&gt;
&lt;th&gt;处理耗时&lt;/th&gt;
&lt;th&gt;吞吐量&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000 行&lt;/td&gt;
&lt;td&gt;~0.02秒&lt;/td&gt;
&lt;td&gt;实时生成报表&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000 行&lt;/td&gt;
&lt;td&gt;~0.21秒&lt;/td&gt;
&lt;td&gt;在线导出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000 行&lt;/td&gt;
&lt;td&gt;~2.12秒&lt;/td&gt;
&lt;td&gt;批量处理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000,000 行&lt;/td&gt;
&lt;td&gt;~21秒&lt;/td&gt;
&lt;td&gt;大数据报表&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;性能对比&lt;/strong&gt; (处理10万行数据)：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;技术栈&lt;/th&gt;
&lt;th&gt;耗时&lt;/th&gt;
&lt;th&gt;与 xlsx-handlebars 对比&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;xlsx-handlebars (Rust)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.12秒&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1倍 (基准)&lt;/strong&gt; ⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python (openpyxl)&lt;/td&gt;
&lt;td&gt;30-60秒&lt;/td&gt;
&lt;td&gt;慢 14-28倍&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JavaScript (xlsx.js)&lt;/td&gt;
&lt;td&gt;15-30秒&lt;/td&gt;
&lt;td&gt;慢 7-14倍&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java (Apache POI)&lt;/td&gt;
&lt;td&gt;8-15秒&lt;/td&gt;
&lt;td&gt;慢 3-7倍&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C# (EPPlus)&lt;/td&gt;
&lt;td&gt;5-10秒&lt;/td&gt;
&lt;td&gt;慢 2-4倍&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;为什么这么快？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; &lt;strong&gt;Rust 零成本抽象&lt;/strong&gt;：编译期优化，无运行时开销&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;流式架构&lt;/strong&gt;：直接在内存中处理 ZIP 条目，避免文件 I/O&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;事件驱动 XML 解析&lt;/strong&gt;：使用 quick-xml 高效解析，无需构建完整 DOM 树&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;单次遍历渲染&lt;/strong&gt;：一次迭代完成所有模板替换&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;兼容性&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;零拷贝&lt;/strong&gt;: Rust 和 WASM 之间高效的内存管理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流式处理&lt;/strong&gt;: 适合处理大型 XLSX 文件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨平台&lt;/strong&gt;: 支持 Windows、macOS、Linux、Web&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;现代浏览器&lt;/strong&gt;: 支持所有支持 WASM 的现代浏览器&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;许可证&lt;/h2&gt;
&lt;p&gt;本项目采用 MIT 许可证 - 详见 &lt;a href&gt;LICENSE-MIT&lt;/a&gt; 文件。&lt;/p&gt;
&lt;h2&gt;支持&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt; &lt;a href=&quot;https://docs.rs/xlsx-handlebars&quot;&gt;文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt; &lt;a href=&quot;https://github.com/sail-sail/xlsx-handlebars/issues&quot;&gt;问题反馈&lt;/a&gt;&lt;/li&gt;
&lt;li&gt; &lt;a href=&quot;https://github.com/sail-sail/xlsx-handlebars/discussions&quot;&gt;讨论&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;/div&gt;</description><author>151263</author><pubDate>Sun, 05 Oct 2025 16:31:59 GMT</pubDate></item><item><title>VonaJS提供的读写分离，直观，优雅</title><link>https://cnodejs.org/topic/68d9e538f13576628f083c38</link><guid>https://cnodejs.org/topic/68d9e538f13576628f083c38</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;在VonaJS中实现&lt;code&gt;读写分离&lt;/code&gt;，只需提供一组&lt;code&gt;写数据源&lt;/code&gt;和一组&lt;code&gt;读数据源&lt;/code&gt;。当用户访问后端 API 时，系统会按照规则自动选择&lt;code&gt;写数据源&lt;/code&gt;或&lt;code&gt;读数据源&lt;/code&gt;，访问相应的数据库，从而分摊压力，提升系统性能&lt;/p&gt;
&lt;h2&gt;安装模块&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;读写分离&lt;/code&gt;作为独立的模块提供，因此需要在VonaJS项目中安装此模块：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- bash&quot;&gt;&lt;code&gt;$ pnpm add vona-module-a-datasharding -w
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;添加数据源&lt;/h2&gt;
&lt;p&gt;首先，需要添加一组数据源&lt;/p&gt;
&lt;h3&gt;1. 添加类型定义&lt;/h3&gt;
&lt;p&gt;为新数据源添加类型定义&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;declare module &amp;#x27;vona-module-a-orm&amp;#x27; {
  export interface IDatabaseClientRecord {
    read1: never;
    read2: never;
    write1: never;
    write2: never;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 增加数据源配置&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; database
config.database = {
  clients: {
    read1: {
      client: &amp;#x27;pg&amp;#x27;,
      connection: {
        host: &amp;#x27;127.0.0.1&amp;#x27;,
        port: 5432,
        user: &amp;#x27;postgres&amp;#x27;,
        password: &amp;#x27;&amp;#x27;,
        database: &amp;#x27;xxxx-read1&amp;#x27;,
      },
    },
    read2: {...},
    write1: {...},
    write2: {...},
  },
};
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;配置读写数据源&lt;/h2&gt;
&lt;p&gt;然后配置模块的读写数据源&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; modules
config.modules = {
  &amp;#x27;a-datasharding&amp;#x27;: {
    client: {
      reads: [&amp;#x27;read1&amp;#x27;, &amp;#x27;read2&amp;#x27;],
      writes: [&amp;#x27;write1&amp;#x27;, &amp;#x27;write2&amp;#x27;],
      randomRead: undefined,
      randomWrite: undefined,
    },
  },
};    
&lt;/code&gt;&lt;/pre&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;reads&lt;/td&gt;
&lt;td&gt;指定一组读数据源&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;writes&lt;/td&gt;
&lt;td&gt;指定一组写数据源&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;randomRead&lt;/td&gt;
&lt;td&gt;可指定自定义函数，从&lt;code&gt;reads&lt;/code&gt;中提取一个读数据源。默认为&lt;code&gt;undefined&lt;/code&gt;，由系统随机提取&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;randomWrite&lt;/td&gt;
&lt;td&gt;可指定自定义函数，从&lt;code&gt;writes&lt;/code&gt;中提取一个写数据源。默认为&lt;code&gt;undefined&lt;/code&gt;，由系统随机提取&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;读写分离的运行机制&lt;/h2&gt;
&lt;p&gt;当配置好读写数据源之后，读写分离机制就自动生效了&lt;/p&gt;
&lt;p&gt;现在，解释一下读写分离的运行机制：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;模块提供了一个全局拦截器&lt;code&gt;a-datasharding:datasharding&lt;/code&gt;。该拦截器判断当前 API Method，如果是&lt;code&gt;POST/PATCH/DELETE/PUT&lt;/code&gt;，那么就使用&lt;code&gt;写数据源&lt;/code&gt;，否则使用&lt;code&gt;读数据源&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;数据一致性: 缓存&lt;code&gt;写数据源&lt;/code&gt;&lt;/h2&gt;
&lt;h3&gt;场景分析：同一个用户&lt;/h3&gt;
&lt;p&gt;由于数据库同步有延时，会出现数据不一致性的情况。比如，用户访问&lt;code&gt;Write-API&lt;/code&gt;，将数据写入&lt;code&gt;写数据库&lt;/code&gt;。接下来，用户访问&lt;code&gt;Read-API&lt;/code&gt;，此时&lt;code&gt;读数据库&lt;/code&gt;还没有同步，那么就会读到旧数据&lt;/p&gt;
&lt;p&gt;为了解决以上问题，模块自动提供了一个机制：当用户访问&lt;code&gt;Write-API&lt;/code&gt;时，会自动将&lt;code&gt;写数据源&lt;/code&gt;存入&lt;code&gt;二级缓存&lt;/code&gt;，并设置过期时间。在这个时间之内，用户访问&lt;code&gt;Read-API&lt;/code&gt;时，也会继续使用同一个&lt;code&gt;写数据源&lt;/code&gt;，从而确保在写入数据后总是可以读取到最新的数据&lt;/p&gt;
&lt;h3&gt;修改过期时间&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;二级缓存&lt;/code&gt;的名称是&lt;code&gt;a-datasharding:datasourceWrite&lt;/code&gt;，可以在 App config 中修改过期时间：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; onions
config.onions = {
  summerCache: {
    &amp;#x27;a-datasharding:datasourceWrite&amp;#x27;: {
      mem: {
        ttl: 5 * 1000, &amp;#x2F;&amp;#x2F; 5s
      },
      redis: {
        ttl: 5 * 1000, &amp;#x2F;&amp;#x2F; 5s
      },
    },
  },
};
&lt;/code&gt;&lt;/pre&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;mem.ttl&lt;/td&gt;
&lt;td&gt;Mem缓存的过期时间，默认为&lt;code&gt;3&lt;/code&gt;秒&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redis.ttl&lt;/td&gt;
&lt;td&gt;Redis缓存的过期时间，默认为&lt;code&gt;3&lt;/code&gt;秒&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;数据一致性: 缓存双删&lt;/h2&gt;
&lt;h3&gt;场景分析：不同用户&lt;/h3&gt;
&lt;p&gt;Vona ORM 提供了开箱即用的缓存机制，参见：&lt;a href&gt;缓存&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;由于数据库同步有延时，会出现缓存不一致性的情况。比如，用户 A 访问&lt;code&gt;Write-API&lt;/code&gt;，将数据写入&lt;code&gt;写数据库&lt;/code&gt;，并自动删除缓存。接下来，用户 B 访问&lt;code&gt;Read-API&lt;/code&gt;，此时&lt;code&gt;读数据库&lt;/code&gt;还没有同步，那么就会读到旧数据，并存入缓存&lt;/p&gt;
&lt;p&gt;为了解决以上问题，模块&lt;code&gt;a-orm&lt;/code&gt;提供了&lt;code&gt;缓存双删&lt;/code&gt;机制：当用户 A 访问&lt;code&gt;Write-API&lt;/code&gt;时，将数据写入&lt;code&gt;写数据库&lt;/code&gt;，并自动删除缓存。然后在指定时间之后再次删除缓存，从而确保缓存总是最新数据&lt;/p&gt;
&lt;h3&gt;启用缓存双删&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; modules
config.modules = {
  &amp;#x27;a-orm&amp;#x27;: {
    sharding: {
      cache: {
        doubleDelete: true,
      },
    },
  },
};
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;修改缓存双删延迟时间&lt;/h3&gt;
&lt;p&gt;系统采用队列任务执行缓存双删，&lt;code&gt;队列&lt;/code&gt;名称是&lt;code&gt;a-orm:doubleDelete&lt;/code&gt;，可以在 App config 中修改缓存双删延迟时间：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; onions
config.onions = {
  queue: {
    &amp;#x27;a-orm:doubleDelete&amp;#x27;: {
      options: {
        job: {
          delay: 5 * 1000, &amp;#x2F;&amp;#x2F; 5s
        },
      },
    },
  },
};
&lt;/code&gt;&lt;/pre&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;job.delay&lt;/td&gt;
&lt;td&gt;指定延迟多长时间执行缓存双删任务，默认为&lt;code&gt;3&lt;/code&gt;秒&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Vona ORM已开源：&lt;a href=&quot;https://github.com/vonajs/vona&quot;&gt;https://github.com/vonajs/vona&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>zhennann</author><pubDate>Mon, 29 Sep 2025 01:47:36 GMT</pubDate></item><item><title>VonaJS多租户同时支持共享模式和独立模式</title><link>https://cnodejs.org/topic/68d60646f135764926083b5b</link><guid>https://cnodejs.org/topic/68d60646f135764926083b5b</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h1&gt;多实例/多租户&lt;/h1&gt;
&lt;p&gt;VonaJS 通过&lt;code&gt;多实例&lt;/code&gt;的概念来支持多租户 SAAS 系统的开发。只需启动一个后端服务，即可支持多个实例同时运行&lt;/p&gt;
&lt;p&gt;VonaJS 支持以下几种&lt;code&gt;多实例/多租户&lt;/code&gt;模式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;共享模式&lt;/code&gt;：多个实例共享同一个数据库，通过&lt;code&gt;实例Id&lt;/code&gt;字段隔离多实例之间的数据&lt;/li&gt;
&lt;li&gt;&lt;code&gt;独立模式&lt;/code&gt;：每个实例都使用独立的数据库，从而满足大数据量的业务需求&lt;/li&gt;
&lt;li&gt;&lt;code&gt;混合模式&lt;/code&gt;：在一个系统中同时支持&lt;code&gt;共享模式&lt;/code&gt;和&lt;code&gt;独立模式&lt;/code&gt;，从而可以精确指定某个实例使用&lt;code&gt;共享数据库&lt;/code&gt;还是&lt;code&gt;独立数据库&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;实例配置&lt;/h2&gt;
&lt;h3&gt;1. 测试环境、开发环境&lt;/h3&gt;
&lt;p&gt;在测试环境和开发环境中，系统默认提供了一个&lt;code&gt;缺省实例&lt;/code&gt;。同时提供了两个&lt;code&gt;测试实例&lt;/code&gt;，用于演示如何使用&lt;code&gt;共享模式&lt;/code&gt;和&lt;code&gt;独立模式&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.test.ts&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.dev.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; instances
config.instances = [
  { name: &amp;#x27;&amp;#x27;, password: &amp;#x27;&amp;#x27;, title: &amp;#x27;&amp;#x27;, config: {} },
  { name: &amp;#x27;shareTest&amp;#x27;, password: &amp;#x27;&amp;#x27;, title: &amp;#x27;&amp;#x27; },
  { name: &amp;#x27;isolateTest&amp;#x27;, password: &amp;#x27;&amp;#x27;, title: &amp;#x27;&amp;#x27;, id: 1000, isolate: true, isolateClient: &amp;#x27;isolateTest&amp;#x27; },
];
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;实例清单&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;empty&lt;/td&gt;
&lt;td&gt;缺省实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;shareTest&lt;/td&gt;
&lt;td&gt;用于演示&lt;code&gt;共享模式&lt;/code&gt;，具体而言，&lt;code&gt;shareTest&lt;/code&gt;与&lt;code&gt;empty&lt;/code&gt;共享同一个数据库&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;isolateTest&lt;/td&gt;
&lt;td&gt;用于演示&lt;code&gt;独立模式&lt;/code&gt;，具体而言，&lt;code&gt;isolateTest&lt;/code&gt;使用独立的数据库&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;实例属性&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;name&lt;/td&gt;
&lt;td&gt;实例名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;password&lt;/td&gt;
&lt;td&gt;实例中用户&lt;code&gt;admin&lt;/code&gt;的初始密码，默认是&lt;code&gt;123456&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;title&lt;/td&gt;
&lt;td&gt;网站标题&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;config&lt;/td&gt;
&lt;td&gt;实例的配置信息&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;id&lt;/td&gt;
&lt;td&gt;当使用&lt;code&gt;独立模式&lt;/code&gt;时，必须明确指定唯一的&lt;code&gt;实例Id&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;isolate&lt;/td&gt;
&lt;td&gt;是否使用&lt;code&gt;独立模式&lt;/code&gt;，默认为&lt;code&gt;共享模式&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;isolateClient&lt;/td&gt;
&lt;td&gt;当使用&lt;code&gt;独立模式&lt;/code&gt;时，必须明确指定&lt;code&gt;数据源&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;2. 生产环境&lt;/h3&gt;
&lt;p&gt;在生产环境，需要自行配置实例信息&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.prod.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;config.instances = [
  { name: &amp;#x27;&amp;#x27;, password: &amp;#x27;&amp;#x27;, title: &amp;#x27;&amp;#x27;, config: {} },
  { name: &amp;#x27;vona&amp;#x27;, password: &amp;#x27;&amp;#x27;, title: &amp;#x27;&amp;#x27;, config: {} },
];
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;如何添加新实例&lt;/h2&gt;
&lt;p&gt;下面以实例&lt;code&gt;shareTest&lt;/code&gt;为例，演示如何添加新实例：&lt;/p&gt;
&lt;h3&gt;1. 添加类型定义&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;declare module &amp;#x27;vona&amp;#x27; {
  export interface IInstanceRecord {
    shareTest: never;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;采用接口合并机制添加新实例的类型定义&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 增加实例配置&lt;/h3&gt;
&lt;p&gt;在需要的 config 文件中添加实例配置，比如在测试环境配置新实例：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.test.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; instances
config.instances = [
  { name: &amp;#x27;shareTest&amp;#x27;, password: &amp;#x27;&amp;#x27;, title: &amp;#x27;&amp;#x27; },
];
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;对于&lt;code&gt;独立模式&lt;/code&gt;，还需要配置数据源，此处从略&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;获取当前实例名的规则&lt;/h2&gt;
&lt;p&gt;当用户访问后端 Api 时，后端会自动根据规则获取当前实例名，然后根据实例名获取实例信息&lt;/p&gt;
&lt;h3&gt;1. 模块配置&lt;/h3&gt;
&lt;p&gt;多实例是由模块 a-instance 提供的核心能力，可以在 App config 中修改模块的配置：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.prod.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; modules
config.modules = {
  &amp;#x27;a-instance&amp;#x27;: {
    getInstanceName: undefined,
    headerField: &amp;#x27;x-vona-instance-name&amp;#x27;,
    queryField: &amp;#x27;x-vona-instance-name&amp;#x27;,
  },
};
&lt;/code&gt;&lt;/pre&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;getInstanceName&lt;/td&gt;
&lt;td&gt;提供自定义函数，用于获取当前实例名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;headerField&lt;/td&gt;
&lt;td&gt;从request header中获取当前实例名，header key默认为&lt;code&gt;x-vona-instance-name&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;queryField&lt;/td&gt;
&lt;td&gt;从request query中获取当前实例名，query key默认为&lt;code&gt;x-vona-instance-name&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;2. 规则次序&lt;/h3&gt;
&lt;p&gt;系统按以下次序，依次判断当前实例名，当获取到实例名时则停止判断流程&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果提供了&lt;code&gt;getInstanceName&lt;/code&gt;，则调用此函数&lt;/li&gt;
&lt;li&gt;如果&lt;code&gt;queryField&lt;/code&gt;不为空，则从 request query 中获取&lt;/li&gt;
&lt;li&gt;如果&lt;code&gt;headerField&lt;/code&gt;不为空，则从 request header 中获取&lt;/li&gt;
&lt;li&gt;从域名中解析实例名&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3. 如何从域名中解析实例名&lt;/h3&gt;
&lt;p&gt;比如，域名为&lt;code&gt;https://cabloy.com&lt;/code&gt;，那么对应的实例名是&lt;code&gt;cabloy&lt;/code&gt;。可以通过配置&lt;code&gt;SERVER_SUBDOMAINOFFSET&lt;/code&gt;来修改计算规则&lt;/p&gt;
&lt;p&gt;&lt;code&gt;env/.env&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;# server
SERVER_SUBDOMAINOFFSET = 1
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;当&lt;code&gt;SERVER_SUBDOMAINOFFSET = 1&lt;/code&gt;时，域名与实例名对应关系如下：&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;域名&lt;/th&gt;
&lt;th&gt;实例名&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;http://cabloy.com&quot;&gt;cabloy.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;cabloy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;http://store.cabloy.com&quot;&gt;store.cabloy.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;cabloy.store&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;当&lt;code&gt;SERVER_SUBDOMAINOFFSET = 2&lt;/code&gt;时，域名与实例名对应关系如下：&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;域名&lt;/th&gt;
&lt;th&gt;实例名&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;http://cabloy.com&quot;&gt;cabloy.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;空字符串&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;http://store.cabloy.com&quot;&gt;store.cabloy.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;store&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;使用多实例&lt;/h2&gt;
&lt;h3&gt;1. 访问当前实例信息&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 当前实例名
const name = this.ctx.instanceName;
&amp;#x2F;&amp;#x2F; 当前实例对象
const instance = this.ctx.instance;
&amp;#x2F;&amp;#x2F; 当前实例Id
const iid = this.ctx.instance.id;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 使用Model操作数据库&lt;/h3&gt;
&lt;p&gt;由于多实例的数据是相互隔离的，因此在操作数据库时，需要指定&lt;code&gt;实例Id&lt;/code&gt;。VonaJS 提供了非常强大的&lt;code&gt;Model&lt;/code&gt;对象，从而可以透明的处理多实例&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; create
await this.scope.model.student.insert({ name: &amp;#x27;Tom&amp;#x27; });
&amp;#x2F;&amp;#x2F; select
await this.scope.model.student.select();
&amp;#x2F;&amp;#x2F; get
await this.scope.model.student.get({ id: 1 });
&amp;#x2F;&amp;#x2F; update
await this.scope.model.student.update({ id: 1, name: &amp;#x27;Jimmy&amp;#x27; });
&amp;#x2F;&amp;#x2F; delete
await this.scope.model.student.delete({ id: 1 });
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当我们使用 Model &lt;code&gt;student&lt;/code&gt;操作数据时，系统会自动设置&lt;code&gt;实例Id&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;3. 使用Query Builder操作数据库&lt;/h3&gt;
&lt;p&gt;如果使用&lt;code&gt;builder()&lt;/code&gt;方法操作数据库，就需要自行添加&lt;code&gt;实例Id&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;await this.scope.model.student.builder().where({
  iid: this.ctx.instance.id,
  name: &amp;#x27;Tom&amp;#x27;,
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果使用&lt;code&gt;builderSelect()&lt;/code&gt;方法操作数据库，系统会自动添加&lt;code&gt;实例Id&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;await this.scope.model.student.builderSelect().where({
  name: &amp;#x27;Tom&amp;#x27;,
});
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4. 使用原生Sql操作数据库&lt;/h3&gt;
&lt;p&gt;如果使用&lt;code&gt;原生Sql&lt;/code&gt;操作数据库，就需要自行添加&lt;code&gt;实例Id&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;await this.scope.model.student.query(
  &amp;#x27;select * from demoStudent where iid=?&amp;#x27;,
  [this.ctx.instance.id],
);
await this.scope.model.student.queryOne(
  &amp;#x27;select * from demoStudent where iid=? and id=?&amp;#x27;,
  [this.ctx.instance.id, 1],
);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Vona ORM已开源：&lt;a href=&quot;https://github.com/vonajs/vona&quot;&gt;https://github.com/vonajs/vona&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>zhennann</author><pubDate>Fri, 26 Sep 2025 03:19:34 GMT</pubDate></item><item><title>Vona ORM分表全攻略</title><link>https://cnodejs.org/topic/68d4eca6f1357670f7083ae9</link><guid>https://cnodejs.org/topic/68d4eca6f1357670f7083ae9</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h1&gt;分表&lt;/h1&gt;
&lt;p&gt;针对高并发、数据量大的场景，通常会考虑采用分表机制进行优化。下面以 Model User/Order 为例，通过查询用户的订单列表，来演示&lt;code&gt;分表&lt;/code&gt;的使用方法&lt;/p&gt;
&lt;h2&gt;分表规则&lt;/h2&gt;
&lt;p&gt;比如需要对订单表进行分表操作。可以根据实际业务需求设计分表规则，在这里，根据&lt;code&gt;用户Id&lt;/code&gt;取模动态生成表名。比如，拆分为&lt;code&gt;16&lt;/code&gt;张表，&lt;code&gt;用户Id&lt;/code&gt;为&lt;code&gt;129&lt;/code&gt;，对应的表名如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;const tableName = &amp;#96;Order_${129 % 16}&amp;#96;;  &amp;#x2F;&amp;#x2F; Order_1
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;准备Models&lt;/h2&gt;
&lt;p&gt;先准备两个 Models：User/Order&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Model Order&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;@Model({
  entity: EntityOrder,
})
class ModelOrder{}
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;Model User&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;@Model({
  entity: EntityUser,
  relations: {
    orders: $relation.hasMany(() =&amp;gt; ModelOrder, &amp;#x27;userId&amp;#x27;),
  },
})
class ModelUser {}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;查询数据&lt;/h2&gt;
&lt;h3&gt;1. 直接查询订单列表&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;class ServiceOrder {
  async selectOrdersDirectly() {
    const userId = 129;
    const orders = await this.scope.model.order.select({
      where: {
        userId,
      },
    });
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;到目前为止，使用&lt;code&gt;默认表名&lt;/code&gt;查询&lt;code&gt;userId=129&lt;/code&gt;的订单列表&lt;/p&gt;
&lt;h3&gt;2. 基于关系查询订单列表&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;class ServiceOrder {
  async selectOrdersByRelation() {
    const userId = 129;
    const userAndOrders = await this.scope.model.user.get({
      id: userId,
    }, {
      include: {
        orders: true,
      },
    });
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;到目前为止，使用&lt;code&gt;默认表名&lt;/code&gt;查询&lt;code&gt;userId=129&lt;/code&gt;的用户信息，使用&lt;code&gt;默认表名&lt;/code&gt;查询该用户的订单列表&lt;/p&gt;
&lt;h2&gt;使用分表：动态方式&lt;/h2&gt;
&lt;p&gt;可以在代码中动态使用分表：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;class ServiceOrder {
  async selectOrdersDirectly() {
    const userId = 129;
+   const tableName = &amp;#96;Order_${userId % 16}&amp;#96;;
+   const modelOrder = this.scope.model.order.newInstance(undefined, tableName as any);
    const orders = await modelOrder.select({
      where: {
        userId,
      },
    });
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;newInstance&lt;/code&gt;: 传入要使用的表名，返回新的 Model 实例&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;到目前为止，使用&lt;code&gt;分表&lt;/code&gt;查询&lt;code&gt;userId=129&lt;/code&gt;的订单列表&lt;/p&gt;
&lt;h2&gt;使用分表：Relation动态选项&lt;/h2&gt;
&lt;p&gt;可以在 relation 选项中动态指定表名：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;class ServiceOrder {
  async selectOrdersByRelation() {
    const userId = 129;
+   const tableName = &amp;#96;Order_${userId % 16}&amp;#96;;
    const userAndOrders = await this.scope.model.user.get({
      id: userId,
    }, {
      include: {
        orders: {
+         meta: {
+           table: tableName as any,
+         },
        },
      },
    });
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;meta.table&lt;/code&gt;: 指定 relation &lt;code&gt;orders&lt;/code&gt;要使用的表名&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;到目前为止，使用&lt;code&gt;默认表名&lt;/code&gt;查询&lt;code&gt;userId=129&lt;/code&gt;的用户信息，使用&lt;code&gt;分表&lt;/code&gt;查询该用户的订单列表&lt;/p&gt;
&lt;h2&gt;使用分表：Model配置&lt;/h2&gt;
&lt;p&gt;也可以直接在 Model 中配置分表规则，从而简化查询代码&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Model Order&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;@Model({
  entity: EntityOrder,
+ table(_ctx: VonaContext, where: EntityOrder | undefined, defaultTable: keyof ITableRecord) {
+   const userId = where?.userId;
+   if (!userId) return defaultTable;
+   return &amp;#96;Order_${Number(userId) % 16}&amp;#96;;
+ },
})
class ModelOrder{}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;table&lt;/code&gt;: 指定函数，实现分表规则&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;查询数据&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;现在，又可以使用常规的方式查询用户的订单列表&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;class ServiceOrder {
  async selectOrdersDirectly() {
    const userId = 129;
    const orders = await this.scope.model.order.select({
      where: {
        userId,
      },
    });
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;class ServiceOrder {
  async selectOrdersByRelation() {
    const userId = 129;
    const userAndOrders = await this.scope.model.user.get({
      id: userId,
    }, {
      include: {
        orders: true,
      },
    });
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;使用分表：App Config配置&lt;/h2&gt;
&lt;p&gt;也可以在 App config 中配置 Model options:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; onions
config.onions = {
  model: {
    &amp;#x27;test-vona:order&amp;#x27;: {
      table(_ctx: VonaContext, where: EntityOrder | undefined, defaultTable: keyof ITableRecord) {
        const userId = where?.userId;
        if (!userId) return defaultTable;
        return &amp;#96;Order_${Number(userId) % 16}&amp;#96;;
      },
    },
  },
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;于是，也可以使用常规的方式查询用户的订单列表&lt;/p&gt;
&lt;h2&gt;使用分表：Relation静态选项&lt;/h2&gt;
&lt;p&gt;也可以在定义 Relation 时指定静态选项：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;@Model({
  entity: EntityUser,
  relations: {
    orders: $relation.hasMany(() =&amp;gt; ModelOrder, &amp;#x27;userId&amp;#x27;, {
+     meta: {
+       table(_ctx: VonaContext, where: EntityOrder | undefined, defaultTable: keyof ITableRecord) {
+         const userId = where?.userId;
+         if (!userId) return defaultTable;
+         return &amp;#96;Order_${Number(userId) % 16}&amp;#96;;
+       },
+     },
    }),
  },
})
class ModelUser {}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;同样，也可以使用常规的方式查询用户的订单列表&lt;/p&gt;
&lt;p&gt;Vona ORM已开源：&lt;a href=&quot;https://github.com/vonajs/vona&quot;&gt;github.com/vonajs/vona&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>zhennann</author><pubDate>Thu, 25 Sep 2025 07:17:58 GMT</pubDate></item><item><title>在Vona ORM中实现多数据库/多数据源</title><link>https://cnodejs.org/topic/68d35260f135766056083a35</link><guid>https://cnodejs.org/topic/68d35260f135766056083a35</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;在Vona ORM中实现&lt;code&gt;多数据库/多数据源&lt;/code&gt;非常直观、简便。下面以 Model User/Order 为例，通过查询用户的订单列表，来演示&lt;code&gt;多数据库/多数据源&lt;/code&gt;的使用方法&lt;/p&gt;
&lt;h2&gt;准备Models&lt;/h2&gt;
&lt;p&gt;先准备两个 Models：User/Order&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Model Order&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;@Model({
  entity: EntityOrder,
})
class ModelOrder{}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;需要指定ModelOrder关联的Entity，限于篇幅，EntityOrder代码从略&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Model User&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;@Model({
  entity: EntityUser,
  relations: {
    orders: $relation.hasMany(() =&amp;gt; ModelOrder, &amp;#x27;userId&amp;#x27;),
  },
})
class ModelUser {}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;在ModelUser中定义&lt;code&gt;1:n&lt;/code&gt;关系: &lt;code&gt;orders&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;查询数据&lt;/h2&gt;
&lt;p&gt;然后查询用户的订单列表&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;class ServiceOrder {
  async selectUserOrders() {
    const userId = 1;
    const userAndOrders = await this.scope.model.user.get(
      {
        id: userId,
      },
      {
        include: {
          orders: true,
        },
      },
    );
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;到目前为止，使用&lt;code&gt;系统默认数据源&lt;/code&gt;查询到了&lt;code&gt;userId=1&lt;/code&gt;的用户信息，和该用户的所有订单列表&lt;/p&gt;
&lt;h2&gt;创建多数据源&lt;/h2&gt;
&lt;p&gt;接下来，创建两个数据源：&lt;code&gt;user-pg&lt;/code&gt;和&lt;code&gt;order-mysql&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;1. 添加数据源的类型定义&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在 VSCode 中，通过右键菜单&lt;code&gt;Vona Init/Types&lt;/code&gt;在模块中创建类型文件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;然后在类型文件中添加类型定义&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;{module path}/src/types/index.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;declare module &amp;#x27;vona-module-a-orm&amp;#x27; {
  export interface IDatabaseClientRecord {
    &amp;#x27;user-pg&amp;#x27;: never;
    &amp;#x27;order-mysql&amp;#x27;: never;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;IDatabaseClientRecord&lt;/code&gt;是模块&lt;code&gt;vona-module-a-orm&lt;/code&gt;提供的接口类型。在这里通过接口合并的机制来添加新的数据源类型&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 数据源配置&lt;/h3&gt;
&lt;p&gt;在项目的App config文件中定义数据源的配置信息。由于前面已经添加了数据源类型，在App config文件中就可以享受完整的类型提示&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; database
config.database = {
  clients: {
    &amp;#x27;user-pg&amp;#x27;: {
      client: &amp;#x27;pg&amp;#x27;,
      connection: {
        host: &amp;#x27;127.0.0.1&amp;#x27;,
        port: 5432,
        user: &amp;#x27;postgres&amp;#x27;,
        password: &amp;#x27;&amp;#x27;,
        database: &amp;#x27;user-xxx&amp;#x27;,
      },
    },
    &amp;#x27;order-mysql&amp;#x27;: {
      client: &amp;#x27;mysql2&amp;#x27;,
      connection: {
        host: &amp;#x27;127.0.0.1&amp;#x27;,
        port: 3306,
        user: &amp;#x27;root&amp;#x27;,
        password: &amp;#x27;&amp;#x27;,
        database: &amp;#x27;order-xxx&amp;#x27;,
      },
    },
  },
};
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;user-pg&lt;/code&gt;: 使用数据库方言：&lt;code&gt;pg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;order-mysql&lt;/code&gt;: 使用数据库方言：&lt;code&gt;mysql2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;使用数据源：动态方式&lt;/h2&gt;
&lt;p&gt;可以在代码中动态使用数据源：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;class ServiceOrder {
  async selectUserOrders() {
    const userId = 1;
+   const modelUser = this.scope.model.user.newInstance(&amp;#x27;user-pg&amp;#x27;);
    const userAndOrders = await modelUser.get(
      {
        id: userId,
      },
      {
        include: {
          orders: true,
        },
      },
    );
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;newInstance&lt;/code&gt;: 传入要使用的数据源，返回新的 Model 实例&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;到目前为止，使用数据源&lt;code&gt;user-pg&lt;/code&gt;查询用户信息，使用&lt;code&gt;系统默认数据源&lt;/code&gt;查询订单列表&lt;/p&gt;
&lt;h2&gt;使用数据源：Relation动态选项&lt;/h2&gt;
&lt;p&gt;可以在 relation 选项中动态指定数据源：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;class ServiceOrder {
  async selectUserOrders() {
    const userId = 1;
    const modelUser = this.scope.model.user.newInstance(&amp;#x27;user-pg&amp;#x27;);
    const userAndOrders = await modelUser.get(
      {
        id: userId,
      },
      {
        include: {
          orders: {
+           meta: {
+             client: &amp;#x27;order-mysql&amp;#x27;,
+           },
          },
        },
      },
    );
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;meta.client&lt;/code&gt;: 指定 relation &lt;code&gt;orders&lt;/code&gt;要使用的数据源&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;到目前为止，使用数据源&lt;code&gt;user-pg&lt;/code&gt;查询用户信息，使用数据源&lt;code&gt;order-mysql&lt;/code&gt;查询订单列表&lt;/p&gt;
&lt;h2&gt;使用数据源：Model配置&lt;/h2&gt;
&lt;p&gt;也可以直接在 Model 中配置数据源，从而简化查询代码&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Model Order&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;@Model({
  entity: EntityOrder,
+ client: &amp;#x27;order-mysql&amp;#x27;,
})
class ModelOrder{}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;在VonaJS框架中，可以非常方便的为&lt;code&gt;@Model&lt;/code&gt;装饰器指定参数&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Model User&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;@Model({
  entity: EntityUser,
+ client: &amp;#x27;user-pg&amp;#x27;,
  relations: {
    orders: $relation.hasMany(() =&amp;gt; ModelOrder, &amp;#x27;userId&amp;#x27;),
  },
})
class ModelUser {}
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;查询数据&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;现在，又可以使用常规的方式查询用户的订单列表&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;class ServiceOrder {
  async selectUserOrders() {
    const userId = 1;
    const userAndOrders = await this.scope.model.user.get(
      {
        id: userId,
      },
      {
        include: {
          orders: true,
        },
      },
    );
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;使用数据源：App Config配置&lt;/h2&gt;
&lt;p&gt;也可以在 App config 中配置 Model options:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/backend/config/config/config.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; onions
config.onions = {
  model: {
    &amp;#x27;test-vona:user&amp;#x27;: {
      client: &amp;#x27;user-pg&amp;#x27;,
    },
    &amp;#x27;test-vona:order&amp;#x27;: {
      client: &amp;#x27;order-mysql&amp;#x27;,
    },
  },
};
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;在VonaJS框架中，可以在App Config文件中提供配置，用于覆盖对应Model的options配置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是，也可以使用常规的方式查询用户的订单列表&lt;/p&gt;
&lt;h2&gt;使用数据源：Relation静态选项&lt;/h2&gt;
&lt;p&gt;也可以在定义 Relation 时指定静态选项：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;@Model({
  entity: EntityUser,
  client: &amp;#x27;user-pg&amp;#x27;,
  relations: {
    orders: $relation.hasMany(() =&amp;gt; ModelOrder, &amp;#x27;userId&amp;#x27;, {
+     meta: {
+       client: &amp;#x27;order-mysql&amp;#x27;,
+     },
    }),
  },
})
class ModelUser {}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;同样，也可以使用常规的方式查询用户的订单列表&lt;/p&gt;
&lt;p&gt;Vona ORM已开源：&lt;a href=&quot;https://github.com/vonajs/vona&quot;&gt;github.com/vonajs/vona&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>zhennann</author><pubDate>Wed, 24 Sep 2025 02:07:28 GMT</pubDate></item><item><title>基于 uni-app 开发的废品回收类多端应用功能与界面说明</title><link>https://cnodejs.org/topic/68d2861ff135766239083a29</link><guid>https://cnodejs.org/topic/68d2861ff135766239083a29</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;本文将对一款基于 uni-app 开发的废品回收类多端应用，从多端支持范围、核心功能模块及部分界面展示进行客观说明，相关资源信息也将一并呈现。&lt;/p&gt;
&lt;h1&gt;一、多端支持范围&lt;/h1&gt;
&lt;p&gt;该应用基于 uni-app 技术开发，支持打包为多终端形态，具体覆盖以下场景：
H5 端
多平台小程序（微信、支付宝、小红书、抖音等）
App 端（适配 Android 与 iOS 两大移动操作系统）&lt;/p&gt;
&lt;h1&gt;二、核心功能模块说明&lt;/h1&gt;
&lt;p&gt;当前版本应用包含两大核心功能模块，各模块的作用与操作逻辑如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;废品回收商家入驻模块
此模块主要服务于废品回收商家的平台入驻需求，入驻后可实现以下操作：
接收用户发起的回收订单；
完成回收服务后，在系统内录入回收总量及对应回收金额。
同时，用户可依据所在城市维度及当前定位，获取附近已入驻商家的推荐结果，进而选择 “预约上门回收” 或 “自行到店回收” 两种服务方式。&lt;/li&gt;
&lt;li&gt;用户回收预约模块
该模块为用户提供废品回收预约的全流程功能，具体操作步骤包括：
选择预约上门时间、废品类型，填写预计回收总量，上传废品照片及补充描述信息，提交后即完成回收预约；
回收服务完成后，用户可获得相应收益与积分；
用户可通过应用内指定界面，提交收益提现申请。&lt;/li&gt;
&lt;li&gt;后续功能说明
除上述已实现的核心模块外，其他功能将以 “后续版本发布说明” 为准，具体更新内容需参考官方后续公示的信息。&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;三、相关资源获取地址&lt;/h1&gt;
&lt;p&gt;该应用的开发资源及代码可通过以下平台获取，供技术研究或参考使用：
&lt;a href=&quot;https://github.com/gooking/recycle&quot;&gt;Github 地址&lt;/a&gt;
&lt;a href=&quot;https://gitee.com/javazj/recycle&quot;&gt;码云镜像地址&lt;/a&gt;
&lt;a href=&quot;https://gitcode.com/gooking2/recycle&quot;&gt;GitCode 镜像地址&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>gooking</author><pubDate>Tue, 23 Sep 2025 11:35:59 GMT</pubDate></item><item><title>能够动态推断与生成DTO是Node生态的一个重要里程碑</title><link>https://cnodejs.org/topic/68b8f627f13576a7f208387b</link><guid>https://cnodejs.org/topic/68b8f627f13576a7f208387b</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;在开发后端 API 服务时，DTO 是进行&lt;code&gt;参数验证&lt;/code&gt;、&lt;code&gt;生成Swagger元数据&lt;/code&gt;的关键节点。如果不能像推断类型一样动态推断出 DTO，那么，我们就仍然需要手工创建 DTO。随着业务的增长，复杂的表间关系会让手工补充 DTO 的工作日益繁重&lt;/p&gt;
&lt;p&gt;而 Vona ORM 首创 DTO 动态推断与生成能力，解放我们的双手，显著提升生产力。甚至可以说，对于构建更加优雅的 Node.js 后端框架而言，能够动态推断与生成 DTO，是非常重要的&lt;code&gt;里程碑&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;DTO清单&lt;/h2&gt;
&lt;p&gt;Vona ORM 提供了以下 DTO：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;get&lt;/td&gt;
&lt;td&gt;标注返回结果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;query&lt;/td&gt;
&lt;td&gt;标注Query参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;queryPage&lt;/td&gt;
&lt;td&gt;标注带分页的Query参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;selectAndCount&lt;/td&gt;
&lt;td&gt;标注带分页的返回结果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;create&lt;/td&gt;
&lt;td&gt;标注Create参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;update&lt;/td&gt;
&lt;td&gt;标注Update参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aggregate&lt;/td&gt;
&lt;td&gt;标注聚合操作的返回结果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;group&lt;/td&gt;
&lt;td&gt;标注分组操作的返回结果&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;DTO使用方法&lt;/h2&gt;
&lt;p&gt;下面以 Order/Product 为例，演示如何针对&lt;code&gt;主表-明细表&lt;/code&gt;进行查询操作&lt;/p&gt;
&lt;h2&gt;1. Model关系定义&lt;/h2&gt;
&lt;p&gt;先在 Model Order 中定义与 Model Product 的&lt;code&gt;1:n&lt;/code&gt;关系&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;@Model({
  entity: EntityOrder,
  relations: {
    products: $relation.hasMany(() =&amp;gt; ModelProduct, &amp;#x27;orderId&amp;#x27;, {
      columns: [&amp;#x27;id&amp;#x27;, &amp;#x27;name&amp;#x27;, &amp;#x27;price&amp;#x27;, &amp;#x27;quantity&amp;#x27;, &amp;#x27;amount&amp;#x27;],
    }),
  },
})
class ModelOrder {}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;relations.products&lt;/code&gt;: 定义&lt;code&gt;1:n&lt;/code&gt;关系&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 创建Api端点&lt;/h2&gt;
&lt;p&gt;创建 Controller，提供 findAll 方法&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;class ControllerOrder {
  @Web.get(&amp;#x27;findAll&amp;#x27;)
  async findAll() {
    return this.scope.model.order.select({
      include: {
        products: true,
      },
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;model.order&lt;/code&gt;: 是Order的model实例&lt;/li&gt;
&lt;li&gt;&lt;code&gt;select&lt;/code&gt;：指定&lt;code&gt;include.products: true&lt;/code&gt;，从而查询出&lt;code&gt;主表-明细表&lt;/code&gt;数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. 动态推断与生成DTO&lt;/h2&gt;
&lt;p&gt;由于此 Api 返回的结果是&lt;code&gt;主表-明细表&lt;/code&gt;结构，我们不能简单的使用&lt;code&gt;EntityOrder数组&lt;/code&gt;来标注返回类型。而是使用 DTO 进行动态推断与生成&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;+ import { $Dto } from &amp;#x27;vona-module-a-orm&amp;#x27;;

class ControllerOrder {
  @Web.get(&amp;#x27;findAll&amp;#x27;)
+ @Api.body(v.array($Dto.get(() =&amp;gt; ModelOrder, { include: { products: true } })))
  async findAll() {
    return this.scope.model.order.select({
      include: {
        products: true,
      },
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Api.body&lt;/code&gt;：标注返回结果&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v.array&lt;/code&gt;: 标注数组&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$Dto.get&lt;/code&gt;: 用于动态推断与生成 DTO&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;$Dto.get&lt;/code&gt;生成的 DTO 是&lt;code&gt;主表-明细表&lt;/code&gt;结构，其 Swagger/Openapi 效果如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;//static.cnodejs.org/Fp4KCNN059rhX3LYML6ay_ya-l9k&quot; alt=&quot;dto-1.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;4. 封装DTO&lt;/h2&gt;
&lt;p&gt;我们还可以创建一个新的 DTO class，将前面的&lt;code&gt;$Dto.get&lt;/code&gt;动态推断代码封装起来，从而用于其他地方&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在 VSCode 中，可以通过右键菜单&lt;code&gt;Vona Create/Dto&lt;/code&gt;创建 DTO 的代码骨架：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;@Dto()
export class DtoOrderResult {}
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;使用继承机制来封装 DTO：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;+ import { $Dto } from &amp;#x27;vona-module-a-orm&amp;#x27;;

@Dto()
export class DtoOrderResult
+ extends $Dto.get(() =&amp;gt; ModelOrder, { include: { products: true } }) {}
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;现在，我们再使用&lt;code&gt;DtoOrderResult&lt;/code&gt;重构前面的 API 代码：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;class ControllerOrder {
  @Web.get(&amp;#x27;findAll&amp;#x27;)
+ @Api.body(v.array(DtoOrderResult))
+ async findAll(): Promise&amp;lt;DtoOrderResult[]&amp;gt; {
    return this.scope.model.order.select({
      include: {
        products: true,
      },
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;行 3: 直接使用&lt;code&gt;v.array(DtoOrderResult)&lt;/code&gt;标注类型&lt;/li&gt;
&lt;li&gt;行 4: 方法返回类型为&lt;code&gt;Promise&amp;lt;DtoOrderResult[]&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vona ORM已开源：&lt;a href=&quot;https://github.com/vonajs/vona&quot;&gt;github.com/vonajs/vona&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>zhennann</author><pubDate>Thu, 04 Sep 2025 02:15:03 GMT</pubDate></item><item><title>分享一个画流程图各种图的网站</title><link>https://cnodejs.org/topic/68adc2daf1357650fa08374e</link><guid>https://cnodejs.org/topic/68adc2daf1357650fa08374e</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;分享一个朋友的网站：&lt;a href=&quot;https://www.dkluge.com/draw&quot;&gt;刀刻网&lt;/a&gt;
&lt;img src=&quot;https://asset.dkluge.com/uploads/dkluge-forum-images/bfa483d97ebddd2e95aac0857098fb6c75aa2204eaa5a10c4a0343ac3e823bdb.png&quot; alt=&quot;使用引导__ (3).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;支持 AI 生成：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dkluge-files-v1.oss-cn-hangzhou.aliyuncs.com/uploads/dkluge-images/ce5e1e4835aee8814d50740c3138e3fd5d5da6cb5be50c16b6f8df99a0e60216.png&quot; alt=&quot;AI 生成&quot;&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>dk002</author><pubDate>Tue, 26 Aug 2025 14:21:14 GMT</pubDate></item><item><title>用现有bootstrap的模板，改造成nuxt3项目</title><link>https://cnodejs.org/topic/68ac3183f13576abce0836f3</link><guid>https://cnodejs.org/topic/68ac3183f13576abce0836f3</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;为了响应快速开发企业网站，并且能够适配移动端，完整的使用tailwind css写一套还挺复杂。&lt;/p&gt;
&lt;p&gt;虽然有很多的UI框架，这些框架开发管理系统还可以，有着统一的UI风格，企业网站主要面向C端用户，有着不同设计风格需求，那么之前的bootstrap布局的页面还是很不错的选择。&lt;/p&gt;
&lt;p&gt;比如就可以在&lt;a href=&quot;https://www.mobanwang.com/&quot;&gt;模板王&lt;/a&gt;中下载一套项目代码，通过将内容和文字做一些修改，即可给客户使用。&lt;/p&gt;
&lt;p&gt;接下来是改造的过程：&lt;/p&gt;
&lt;p&gt;改造最初通过询问AI，给出了2个方案；&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一，使用bootstrap-vue-next，然后配合tailwind css进行改造【使用的此方法，改动量很大，放弃】；&lt;/li&gt;
&lt;li&gt;第二，将js和css文件迁移的public目录下，然后在项目中加载，这样只需要将html文件修改为.vue的文件类型，然后修改很少的链接跳转方式即可。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文中采用第二种方案。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font style=“background-color:#D9EAFC;”&amp;gt;迁移过程最痛苦的2件事，在vue中js的加载时机 和 迁移静态资源public/assets。&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;下载代码&lt;/h2&gt;
&lt;p&gt;本次改造的项目代码，原模板下载&lt;a href=&quot;https://www.mobanwang.com/mb/demo/22705/&quot;&gt;https://www.mobanwang.com/mb/demo/22705/&lt;/a&gt;；&lt;/p&gt;
&lt;p&gt;也可以下载其他网络上的优秀的企业站代码。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;//static.cnodejs.org/FqXmAFlI_6BfkixbyLswOj30D5cj&quot; alt=&quot;1213233453.png&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-plain&quot;&gt;&lt;code&gt;├── about.html
├── assets
│   ├── css
│   │   ├── bootstrap.min.css
│   │   ├── em-breadcrumb.css
│   │   ├── plugin_theme_css.css
│   │   └── responsive.css
│   ├── fonts
│   │   ├── Flaticon.woff
│   │   ├── Flaticon.woff2
│   │   ├── Sofia Pro Bold.ttf
│   │   ├── aprova0698.eot
│   │   ├── aprova0698.svg
│   │   ├── aprova0698.ttf
│   │   ├── aprova0698.woff
│   │   ├── fontawesome-webfont3295.ttf
│   │   ├── fontawesome-webfont3295.woff
│   │   ├── fontawesome-webfont3295.woff2
│   │   ├── icofont.eot
│   │   ├── icofont.svg
│   │   ├── icofont.ttf
│   │   ├── icofont.woff
│   │   ├── icofont.woff2
│   │   ├── themify.ttf
│   │   └── themify.woff
│   ├── images
│   │   ├── about-img-1.jpg
│   │   ├── b1.jpg
│   │   ├── b2.jpg
│   │   ├── b3.jpg
│   │   ├── b4.jpg
│   │   ├── b5.jpg
│   │   ├── b6.jpg
│   │   ├── b7.jpg
│   │   ├── b8.jpg
│   │   ├── blog-sidebar1.jpg
│   │   ├── blog-sidebar2.jpg
│   │   ├── blog-sidebar3.jpg
│   │   ├── br1.jpg
│   │   ├── br2.jpg
│   │   ├── br3.jpg
│   │   ├── br4.jpg
│   │   ├── br5.jpg
│   │   ├── contact-bg.jpg
│   │   ├── faq-img.png
│   │   ├── favicon.png
│   │   ├── fottor-bg.jpg
│   │   ├── logo1.png
│   │   ├── logo2.png
│   │   ├── service-bg-img.jpg
│   │   ├── service-img.png
│   │   ├── single-blog.jpg
│   │   ├── single-service.jpg
│   │   ├── skill-img.jpg
│   │   ├── slide-03.jpg
│   │   ├── slider1.jpg
│   │   ├── slider2.jpg
│   │   ├── tab-img.jpg
│   │   ├── tab-img2.jpg
│   │   ├── tab-img3.jpg
│   │   ├── team-bg.jpg
│   │   ├── team1.jpg
│   │   ├── team1.png
│   │   ├── team2.jpg
│   │   ├── team2.png
│   │   ├── team3.jpg
│   │   ├── team3.png
│   │   ├── team4.png
│   │   ├── test1.png
│   │   ├── test2.png
│   │   ├── test3.png
│   │   └── test4.png
│   ├── js
│   │   ├── BeerSlider.js
│   │   ├── ajax-mail.js
│   │   ├── bootstrap.min.js
│   │   ├── bootstrap.min.js.map
│   │   ├── customizer.js
│   │   ├── imagesloaded.pkgd.min.js
│   │   ├── isotope.pkgd.min.js
│   │   ├── jquery.appear.js
│   │   ├── jquery.knob.js
│   │   ├── jquery.meanmenu.js
│   │   ├── jquery.nivo.slider.pack.js
│   │   ├── jquery.waitforimages.js
│   │   ├── map.js
│   │   ├── modernizr.custom.79639.js
│   │   ├── owl.carousel.min.js
│   │   ├── slick.min.js
│   │   ├── swiper-bundle.min.js.map
│   │   ├── theme-pluginjs.js
│   │   ├── theme.js
│   │   └── vendor
│   │       ├── jquery-3.5.1.min.js
│   │       └── modernizr-2.8.3.min.js
│   └── webfonts
│       ├── fa-brands-400.eot
│       ├── fa-brands-400.svg
│       ├── fa-brands-400.ttf
│       ├── fa-brands-400.woff
│       ├── fa-brands-400.woff2
│       ├── fa-regular-400.eot
│       ├── fa-regular-400.svg
│       ├── fa-regular-400.ttf
│       ├── fa-regular-400.woff
│       ├── fa-regular-400.woff2
│       ├── fa-solid-900.eot
│       ├── fa-solid-900.svg
│       ├── fa-solid-900.ttf
│       ├── fa-solid-900.woff
│       └── fa-solid-900.woff2
├── blog-left-sidebar.html
├── blog-right-sidebar.html
├── blog.html
├── contact.html
├── faq.html
├── home-video.html
├── index.html
├── landing-page.html
├── portfolio-3column.html
├── portfolio-4column.html
├── portfolio.html
├── pricing-table.html
├── service.html
├── single-blog.html
├── single-service.html
├── style.css
├── team.html
├── testimonial.html
└── venobox
    ├── close.gif
    ├── next.gif
    ├── preload-circle.png
    ├── preload-dots.png
    ├── preload-ios.png
    ├── preload-quads.png
    ├── preload.png
    ├── prev.gif
    ├── venobox.css
    ├── venobox.js
    └── venobox.min.js

9 directories, 133 files
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;迁移静态资源&lt;/h2&gt;
&lt;p&gt;分为3中情况&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;assets下的图片资源，统一存放到&lt;code&gt;public/assets&lt;/code&gt;下，&lt;strong&gt;后边调整代码&lt;/strong&gt;来获取该路径的资源&lt;/li&gt;
&lt;li&gt;将assets下的js和css和font资源，放到&lt;code&gt;public/assets&lt;/code&gt;下的js和css和font&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;将venobox和style.css文件也迁移到&lt;code&gt;public/assets&lt;/code&gt;下，style.css可以放到&lt;code&gt;public/assets/css&lt;/code&gt;的目录下，&amp;lt;font style=“color:#DF2A3F;”&amp;gt;注意这里需要将&amp;lt;/font&amp;gt;&lt;code&gt;&amp;lt;font style=&amp;quot;color:#DF2A3F;&amp;quot;&amp;gt;style.css&amp;lt;/font&amp;gt;&lt;/code&gt;&amp;lt;font style=“color:#DF2A3F;”&amp;gt;的图片引用，修改为相对引用地址。&amp;lt;/font&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将html结尾的文件，复制body部分的代码到vue文件的template中。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;关于js脚本加载的问题&lt;/h2&gt;
&lt;p&gt;在bootstrap中，每个页面为独立html页面，打开都会加载js脚本，并且加载脚本的时间在dom结构渲染完成后进行加载。&lt;/p&gt;
&lt;p&gt;那么在改写的vue中，就需要onMounted的生命周期中加载。&lt;/p&gt;
&lt;p&gt;在改造过程中尝试了几种方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;写到plugin中，通过&lt;code&gt;nuxtApp.hook(&apos;app:mounted&apos;, async () =&amp;gt; {})&lt;/code&gt;的生命周期时机进行加载，这种方法对&lt;code&gt;index.vue&lt;/code&gt;页面生效，但是只加载了一次，对其他页面会失效。&lt;/li&gt;
&lt;li&gt;第二种还是想放到插件中，让每个页面的路由之后，加载js&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;if (process.client) {
  const router = useRouter();

  &amp;#x2F;&amp;#x2F; 监听路由变化，模拟每个页面的 mounted
  router.afterEach(async (to, from) =&amp;gt; {
    console.log(&amp;#x27;页面 mounted 模拟:&amp;#x27;, to.path);
    &amp;#x2F;&amp;#x2F; 在这里执行你的逻辑
    &amp;#x2F;&amp;#x2F; 加载脚本、埋点、初始化第三方库等
    &amp;#x2F;&amp;#x2F; const scripts = [
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;js&amp;#x2F;vendor&amp;#x2F;modernizr-2.8.3.min.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;js&amp;#x2F;vendor&amp;#x2F;jquery-3.5.1.min.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;js&amp;#x2F;bootstrap.min.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;js&amp;#x2F;isotope.pkgd.min.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;js&amp;#x2F;owl.carousel.min.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;js&amp;#x2F;jquery.nivo.slider.pack.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;js&amp;#x2F;slick.min.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;venobox&amp;#x2F;venobox.min.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;js&amp;#x2F;imagesloaded.pkgd.min.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;js&amp;#x2F;jquery.appear.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;js&amp;#x2F;jquery.knob.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;js&amp;#x2F;BeerSlider.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;js&amp;#x2F;theme-pluginjs.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;js&amp;#x2F;jquery.meanmenu.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;js&amp;#x2F;ajax-mail.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F;   &amp;#x27;&amp;#x2F;js&amp;#x2F;theme.js&amp;#x27;,
    &amp;#x2F;&amp;#x2F; ];

    &amp;#x2F;&amp;#x2F; for (const src of scripts) {
    &amp;#x2F;&amp;#x2F;   try {
    &amp;#x2F;&amp;#x2F;     await loadScript(src);
    &amp;#x2F;&amp;#x2F;     console.log(&amp;#x27;脚本加载成功:&amp;#x27;, src);

    &amp;#x2F;&amp;#x2F;   } catch (err) {
    &amp;#x2F;&amp;#x2F;     console.error(err);
    &amp;#x2F;&amp;#x2F;   }
    &amp;#x2F;&amp;#x2F; }
  });
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这种方案不能使用&lt;code&gt;&amp;lt;nuxt-link&amp;gt;&lt;/code&gt;标签，使用此标签跳转的页面，还是无法正常加载和显示页面。使用&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;时可以生效，但是会刷新页面。&lt;/p&gt;
&lt;p&gt;也放弃了这个方案&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;composables&lt;/code&gt;下写一个公用的加载js的函数方法，在每个页面的&lt;code&gt;onMounted&lt;/code&gt;周期中调用一下，这算是最好的解决办法。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 缓存已加载的脚本
const loadedScripts = new Set&amp;lt;string&amp;gt;();

const loadScript = (src: string) =&amp;gt; {
  return new Promise((resolve, reject) =&amp;gt; {
    if (loadedScripts.has(src)) {
      resolve(true);
      return;
    }

    const script = document.createElement(&amp;#x27;script&amp;#x27;);
    script.src = src;
    script.defer = true;

    script.onload = () =&amp;gt; {
      loadedScripts.add(src);
      resolve(true);
    };

    script.onerror = () =&amp;gt; {
      reject(new Error(&amp;#96;Failed to load script: ${src}&amp;#96;));
    };

    document.body.appendChild(script);
  });
};
export const loadScriptClient = async () =&amp;gt; {
  const script1 = document.createElement(&amp;#x27;script&amp;#x27;);
  script1.src = &amp;#x27;&amp;#x2F;js&amp;#x2F;vendor&amp;#x2F;modernizr-2.8.3.min.js&amp;#x27;;
  document.body.appendChild(script1);

  const script2 = document.createElement(&amp;#x27;script&amp;#x27;);
  script2.src = &amp;#x27;&amp;#x2F;js&amp;#x2F;vendor&amp;#x2F;jquery-3.5.1.min.js&amp;#x27;;
  document.body.appendChild(script2);

  const script3 = document.createElement(&amp;#x27;script&amp;#x27;);
  script3.src = &amp;#x27;&amp;#x2F;js&amp;#x2F;bootstrap.min.js&amp;#x27;;
  document.body.appendChild(script3);

  const script4 = document.createElement(&amp;#x27;script&amp;#x27;);
  script4.src = &amp;#x27;&amp;#x2F;js&amp;#x2F;isotope.pkgd.min.js&amp;#x27;;
  document.body.appendChild(script4);

  const script5 = document.createElement(&amp;#x27;script&amp;#x27;);
  script5.src = &amp;#x27;&amp;#x2F;js&amp;#x2F;owl.carousel.min.js&amp;#x27;;
  document.body.appendChild(script5);

  const script6 = document.createElement(&amp;#x27;script&amp;#x27;);
  script6.src = &amp;#x27;&amp;#x2F;js&amp;#x2F;jquery.nivo.slider.pack.js&amp;#x27;;
  document.body.appendChild(script6);

  const script7 = document.createElement(&amp;#x27;script&amp;#x27;);
  script7.src = &amp;#x27;&amp;#x2F;js&amp;#x2F;slick.min.js&amp;#x27;;
  document.body.appendChild(script7);

  const script18 = document.createElement(&amp;#x27;script&amp;#x27;);
  script18.src = &amp;#x27;&amp;#x2F;venobox&amp;#x2F;venobox.min.js&amp;#x27;;
  document.body.appendChild(script18);

  const script8 = document.createElement(&amp;#x27;script&amp;#x27;);
  script8.src = &amp;#x27;&amp;#x2F;js&amp;#x2F;imagesloaded.pkgd.min.js&amp;#x27;;
  document.body.appendChild(script8);

  const script9 = document.createElement(&amp;#x27;script&amp;#x27;);
  script9.src = &amp;#x27;&amp;#x2F;js&amp;#x2F;jquery.appear.js&amp;#x27;;
  document.body.appendChild(script9);

  const script10 = document.createElement(&amp;#x27;script&amp;#x27;);
  script10.src = &amp;#x27;&amp;#x2F;js&amp;#x2F;jquery.knob.js&amp;#x27;;
  document.body.appendChild(script10);

  const script11 = document.createElement(&amp;#x27;script&amp;#x27;);
  script11.src = &amp;#x27;&amp;#x2F;js&amp;#x2F;BeerSlider.js&amp;#x27;;
  document.body.appendChild(script11);

  const script12 = document.createElement(&amp;#x27;script&amp;#x27;);
  script12.src = &amp;#x27;&amp;#x2F;js&amp;#x2F;theme-pluginjs.js&amp;#x27;;
  document.body.appendChild(script12);

  const script13 = document.createElement(&amp;#x27;script&amp;#x27;);
  script13.src = &amp;#x27;&amp;#x2F;js&amp;#x2F;jquery.meanmenu.js&amp;#x27;;
  document.body.appendChild(script13);

  const script14 = document.createElement(&amp;#x27;script&amp;#x27;);
  script14.src = &amp;#x27;&amp;#x2F;js&amp;#x2F;ajax-mail.js&amp;#x27;;
  document.body.appendChild(script14);

  const script15 = document.createElement(&amp;#x27;script&amp;#x27;);
  script15.src = &amp;#x27;&amp;#x2F;js&amp;#x2F;theme.js&amp;#x27;;
  document.body.appendChild(script15);
  
};

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在vue的页面中进行调用&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 初始化脚本
onMounted(() =&amp;gt; {
  loadScriptClient();
})
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;关于图片资源引用的问题&lt;/h2&gt;
&lt;h3&gt;public/css内部应用的图片路径地址&lt;/h3&gt;
&lt;p&gt;public/assets/css内部应用的图片路径地址，是public/assets/images中的资源；&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-css&quot;&gt;&lt;code&gt;.consit_service_area2 {
    background-image: url(&amp;quot;..&amp;#x2F;images&amp;#x2F;port-bg-img.jpg&amp;quot;);
    background-repeat: no-repeat;
    background-size: cover;
    padding: 120px 0px 110px;
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;vue文件的template的图片&lt;/h3&gt;
&lt;p&gt;template模版内的图片引入：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;&amp;quot;/assets/images/logo1.png&amp;quot;&lt;/code&gt;的也是&lt;code&gt;public/assets/images&lt;/code&gt;目录的资源。&lt;/li&gt;
&lt;li&gt;如果代码&lt;code&gt;&amp;quot;assets/images/logo1.png&amp;quot;&lt;/code&gt;则是使用了&lt;code&gt;assets/images&lt;/code&gt;目录资源&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language-vue&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;mobile_menu_logo text-center&amp;quot;&amp;gt;
    &amp;lt;a href=&amp;quot;index.html&amp;quot; title=&amp;quot;consit&amp;quot;&amp;gt;
      &amp;lt;img src=&amp;quot;&amp;#x2F;assets&amp;#x2F;images&amp;#x2F;logo1.png&amp;quot; alt=&amp;quot;consit&amp;quot; &amp;#x2F;&amp;gt;
    &amp;lt;&amp;#x2F;a&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;vue文件的template的style中使用图片&lt;/h3&gt;
&lt;p&gt;在template中的style添加背景图片，这里如果正常使用&lt;code&gt;/assets/images/logo1.png&lt;/code&gt;，就会引用到/assets/images的资源，而不是public/assets/image的资源。&lt;/p&gt;
&lt;h4&gt;下面代码引用了 &lt;code&gt;assets/image&lt;/code&gt;的资源&lt;/h4&gt;
&lt;p&gt;这里不管有没有 &lt;code&gt;/&lt;/code&gt; 路径，都是使用的 &lt;code&gt;assets/image&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-vue&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div
      class=&amp;quot;swiper-slide d1 t1 m1 witr_swiper_height&amp;quot;
      style=&amp;quot;background-image:	url(&amp;#x2F;assets&amp;#x2F;images&amp;#x2F;slider1.jpg)&amp;quot;
    &amp;gt;
    1111
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;可以结合script，使用public的资源；&lt;/h4&gt;
&lt;p&gt;主要思路是通过动态导入图片资源，然后在绑定到style中&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-vue&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div
      class=&amp;quot;swiper-slide d1 t1 m1 witr_swiper_height&amp;quot;
      :style=&amp;quot;{ backgroundImage: &amp;#96;url(${slider1})&amp;#96; }&amp;quot;
    &amp;gt;
    1111
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script setup&amp;gt;
  &amp;#x2F;&amp;#x2F; vue script内引入assets图片的方法
  import slider1 from &amp;#x27;&amp;#x2F;assets&amp;#x2F;images&amp;#x2F;slider1.jpg&amp;#x27;;
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;迁移完成后代码结构&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;.&amp;#x2F;
├── README.md
├── app.vue
├── components
│   ├── navFooter.vue
│   └── navHeader.vue
├── composables
│   └── index.ts
│── nuxt.config.ts
├── package.json
├── pages
│   ├── about.vue
│   ├── blog.vue
│   ├── blogLeft.vue
│   ├── blogRight.vue
│   ├── contact.vue
│   ├── faq.vue
│   ├── homeVideo.vue
│   ├── index.vue
│   ├── landingPage.vue
│   ├── portfolio.vue
│   ├── portfolio3column.vue
│   ├── portfolio4column.vue
│   ├── pricingTable.vue
│   ├── service.vue
│   ├── serviceSingle.vue
│   ├── singleBlog.vue
│   ├── team.vue
│   └── testimonial.vue
├── plugins
│   └── load-script.client.ts
├── pnpm-lock.yaml
├── public
│   ├── assets
│   │   └── images
│   │       ├── about-img-1.jpg
│   │       ├── b1.jpg
│   │       ├── b2.jpg
│   │       ├── b3.jpg
│   │       ├── b4.jpg
│   │       ├── ...
│   │       ├── css
│   │       │   ├── bootstrap.min.css
│   │       │   ├── em-breadcrumb.css
│   │       │   ├── plugin_theme_css.css
│   │       │   ├── responsive.css
│   │       │   └── style.css
│   │       ├── fonts
│   │       │   ├── Flaticon.woff
│   │       │   ├── ...
│   │       ├── js
│   │       │   ├── BeerSlider.js
│   │       │   ├── ajax-mail.js
│   │       │   ├── ...
│   │       ├── venobox
│   │       │   ├── close.gif
│   │       │   ├── next.gif
│   │       │   ├── preload-circle.png
│   │       │   ├── preload-dots.png
│   │       │   ├── preload-ios.png
│   │       │   ├── preload-quads.png
│   │       │   ├── preload.png
│   │       │   ├── prev.gif
│   │       │   ├── venobox.css
│   │       │   ├── venobox.js
│   │       │   └── venobox.min.js
│   │       └── webfonts
│   │           ├── fa-brands-400.eot
│   │           ├── fa-brands-400.svg
│   │           ├── ... 
│   ├── favicon.ico
│   ├── favicon.png
│   ├── robots.txt
└── tsconfig.json

29 directories, 146 files
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;以上是将bootstrap项目转为nuxt项目的代码结构。&lt;/p&gt;
&lt;h2&gt;关于接口调用和打包发布上线的操作&lt;/h2&gt;
&lt;p&gt;详细的操作和项目代码仓库，放置到个人网站中，如有需要请查看；&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文章地址：&lt;a href=&quot;https://shenshuai89.github.io/pages/3926f2/&quot;&gt;https://shenshuai89.github.io/pages/3926f2/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;线上预览地址：&lt;a href=&quot;https://www.shenshuai.site/nuxtapp202504114/&quot;&gt;https://www.shenshuai.site/nuxtapp202504114/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description><author>shenshuai89</author><pubDate>Mon, 25 Aug 2025 09:48:51 GMT</pubDate></item><item><title>Node.js 主流ORM框架动态分表方案大盘点</title><link>https://cnodejs.org/topic/68aad24ef135767c590836dd</link><guid>https://cnodejs.org/topic/68aad24ef135767c590836dd</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;针对高并发、数据量大的场景，通常会考虑采用&lt;code&gt;分库分表&lt;/code&gt;进行优化。在这篇文章，我们重点盘点一下Node.js主流ORM框架的动态分表方案：&lt;/p&gt;
&lt;h2&gt;分表规则&lt;/h2&gt;
&lt;p&gt;比如我们需要对订单表进行分表操作。可以根据实际业务需求设计分表规则，在这里，我们根据顾客Id取模动态生成表名。比如，拆分为16张表，顾客Id为129，对应的表名如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;const tableName = &amp;#96;Order_${129 % 16}&amp;#96;;  &amp;#x2F;&amp;#x2F; Order_1
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;&lt;a href=&quot;https://typeorm.io&quot;&gt;TypeORM&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在TypeORM中可以按照如下方式设置动态表名：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 获取repository
const repositoryOrder = dataSource.createQueryBuilder().connection.getRepository(EntityOrder);
&amp;#x2F;&amp;#x2F; 设置动态表名
const userId = 129;
const tableName = &amp;#96;Order_${userId % 16}&amp;#96;;
repositoryOrder.metadata.tablePath = tableName;
&amp;#x2F;&amp;#x2F; 查询订单
const orders = await repositoryOrder.find();
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;&lt;a href=&quot;https://orm.drizzle.team&quot;&gt;Drizzle ORM&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;schema.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;const orderFactory = userId =&amp;gt; pgTable(
  &amp;#96;Order_${userId % 16}&amp;#96;,
  {
    id: serial(&amp;#x27;id&amp;#x27;).primaryKey(),  
    name: text(&amp;#x27;name&amp;#x27;).notNull(),
  },
);

export const order0 = orderFactory(0);
...
export const order15 = orderFactory(15);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;query.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;import * as schema from &amp;#x27;.&amp;#x2F;db&amp;#x2F;schema&amp;#x27;;

const db = drizzle(process.env.DATABASE_URL!, { schema });

const userId = 129;
const modelName=&amp;#96;order${userId % 16}&amp;#96;;
const orders = await db.query[modelName].findMany();
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;&lt;a href=&quot;https://www.prisma.io/docs&quot;&gt;Prisma ORM&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Prisma ORM对动态表名的支持还在规划当中，参见：&lt;a href=&quot;https://github.com/prisma/prisma/issues/1708&quot;&gt;Table Partitioning&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;作为备选方案，我们可以使用&lt;code&gt;$queryRawUnsafe&lt;/code&gt;直接构造原始SQL：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;const userId = 129;
const tableName = &amp;#96;Order_${userId % 16}&amp;#96;;  
const orders = await prisma.$queryRawUnsafe(&amp;#96;SELECT * FROM &amp;quot;${tableName}&amp;quot;&amp;#96;);
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;&lt;a href=&quot;https://vona.js.org/guide/techniques/orm/introduction.html&quot;&gt;Vona ORM&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Vona ORM提供了两种模式：自动模式/手工模式&lt;/p&gt;
&lt;h3&gt;1. 自动模式&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;model/order.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;import { EntityOrder } from &amp;#x27;..&amp;#x2F;entity&amp;#x2F;order.ts&amp;#x27;;

@Model({
  entity: EntityOrder,
  table(ctx: VonaContext, defaultTable: keyof ITableRecord) {
    const user = ctx.app.bean.passport.getCurrentUser();
    if (!user) return defaultTable;
    return &amp;#96;${defaultTable}_${Number(user.id) % 16}&amp;#96;;
  },
})
export class ModelOrder {}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;defaultTable: 是在EntityOrder中定义的缺省表名，如：&lt;code&gt;Order&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;service/order.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;class ServiceOrder {
  async findAll() {
    return await this.scope.model.order.select();
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 手工模式&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;service/order.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;class ServiceOrder {
  async findAll() {
    const user = this.bean.passport.getCurrentUser();
    const tableName = &amp;#96;Order_${Number(user!.id) % 16}&amp;#96;;
    const modelOrder = this.scope.model.order.newInstance(undefined, tableName as any);
    return await modelOrder.select();
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;newInstance: 第一个参数可以传入数据源，从而实现&lt;code&gt;分库&lt;/code&gt;能力。这里忽略，因此传入undefined&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description><author>zhennann</author><pubDate>Sun, 24 Aug 2025 08:50:22 GMT</pubDate></item><item><title>这个Database Transaction功能多多，你用过吗？</title><link>https://cnodejs.org/topic/68a6983af135763b86083606</link><guid>https://cnodejs.org/topic/68a6983af135763b86083606</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/vonajs/vona&quot;&gt;Vona&lt;/a&gt; 是一款直观、优雅、强大的 Node.js Web 框架，用于快速开发任何规模的企业级应用。首创 DTO 动态推断与生成能力，从而显著提升开发效率和开发体验。Vona ORM 对数据库事务提供了完整的支持，提供了直观、优雅、强大的特性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用装饰器启用事务&lt;/li&gt;
&lt;li&gt;事务传播机制&lt;/li&gt;
&lt;li&gt;事务补偿机制&lt;/li&gt;
&lt;li&gt;确保数据库与缓存数据一致性&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;使用装饰器启用事务&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;import { Database } from &amp;#x27;vona-module-a-orm&amp;#x27;;

class ServicePost {
  @Database.transaction()
  async transaction() {
    &amp;#x2F;&amp;#x2F; insert
    const post = await this.scope.model.post.insert({
      title: &amp;#x27;Post001&amp;#x27;,
    });
    &amp;#x2F;&amp;#x2F; update
    await this.scope.model.post.update({
      id: post.id,
      title: &amp;#x27;Post001-Update&amp;#x27;,
    });
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;手工启用事务&lt;/h2&gt;
&lt;h3&gt;1. 使用当前数据源&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;class ServicePost {
  async transactionManually() {
    const db = this.bean.database.current;
    await db.transaction.begin(async () =&amp;gt; {
      await this.scope.model.post.update({ id: 1, title: &amp;#x27;Post001_Update&amp;#x27; });
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 使用指定数据源&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;class ServicePost {
  async transactionManually() {
    const db = this.bean.database.getDb({ clientName: &amp;#x27;default&amp;#x27; });
    await db.transaction.begin(async () =&amp;gt; {
      const modelPost = this.scope.model.post.newInstance(db);
      await modelPost.update({ id: 1, title: &amp;#x27;Post001_Update&amp;#x27; });
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;事务参数&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;class ServicePost {
  @Database.transaction({
+   isolationLevel: &amp;#x27;READ_COMMITTED&amp;#x27;,
+   propagation: &amp;#x27;REQUIRED&amp;#x27;
  })
  async transaction() {
    ...
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language- diff&quot;&gt;&lt;code&gt;class ServicePost {
  async transactionManually() {
    const db = this.bean.database.getDb({ clientName: &amp;#x27;default&amp;#x27; });
    await db.transaction.begin(
      async () =&amp;gt; {
        ...
      },
      {
+       isolationLevel: &amp;#x27;READ_COMMITTED&amp;#x27;,
+       propagation: &amp;#x27;REQUIRED&amp;#x27;,
      }
    );
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;事务参数：isolationLevel&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DEFAULT&lt;/td&gt;
&lt;td&gt;数据库相关的缺省isolationLevel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;READ_UNCOMMITTED&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;READ_COMMITTED&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REPEATABLE_READ&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SERIALIZABLE&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SNAPSHOT&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;事务参数：propagation&lt;/h2&gt;
&lt;p&gt;Vona ORM 支持数据库事务传播机制&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;REQUIRED&lt;/td&gt;
&lt;td&gt;默认的事务传播级别。如果当前存在事务, 则加入该事务。如果当前没有事务, 则创建一个新的事务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SUPPORTS&lt;/td&gt;
&lt;td&gt;如果当前存在事务，则加入该事务. 如果当前没有事务, 则以非事务的方式继续运行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MANDATORY&lt;/td&gt;
&lt;td&gt;强制性。如果当前存在事务, 则加入该事务。如果当前没有事务，则抛出异常&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REQUIRES_NEW&lt;/td&gt;
&lt;td&gt;创建一个新的事务。如果当前存在事务, 则把当前事务挂起。也就是说不管外部方法是否开启事务，总是开启新的事务, 且开启的事务相互独立, 互不干扰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NOT_SUPPORTED&lt;/td&gt;
&lt;td&gt;以非事务方式运行。如果当前存在事务，则把当前事务挂起(不用)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NEVER&lt;/td&gt;
&lt;td&gt;以非事务方式运行。如果当前存在事务，则抛出异常&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;事务补偿机制&lt;/h2&gt;
&lt;p&gt;当事务成功或者失败时执行一些逻辑&lt;/p&gt;
&lt;h3&gt;1. 成功补偿&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;this.bean.database.current.commit(async () =&amp;gt; {
  &amp;#x2F;&amp;#x2F; do something when success
});
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 失败补偿&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;this.bean.database.current.compensate(async () =&amp;gt; {
  &amp;#x2F;&amp;#x2F; do something when failed
});
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;事务与Cache数据一致性&lt;/h2&gt;
&lt;p&gt;许多框架使用最简短的用例来证明是否高性能，而忽略了业务复杂性带来的性能挑战。随着业务的增长和变更，项目性能就会断崖式下降，各种优化补救方案让项目代码繁杂冗长。而 Vona 正视大型业务的复杂性，从框架核心引入缓存策略，并实现了&lt;code&gt;二级缓存&lt;/code&gt;、&lt;code&gt;Query缓存&lt;/code&gt;和&lt;code&gt;Entity缓存&lt;/code&gt;等机制，轻松应对大型业务系统的开发，可以始终保持代码的优雅和直观&lt;/p&gt;
&lt;p&gt;Vona 系统对数据库事务与缓存进行了适配，当数据库事务失败时会自动执行缓存的补偿操作，从而让数据库数据与缓存数据始终保持一致&lt;/p&gt;
&lt;p&gt;针对这个场景，Vona 提供了内置的解决方案&lt;/p&gt;
&lt;h3&gt;1. 使用当前数据源&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;class ServicePost {
  @Database.transaction()
  async transaction() {
    &amp;#x2F;&amp;#x2F; insert
    const post = await this.scope.model.post.insert({
      title: &amp;#x27;Post001&amp;#x27;,
    });
    &amp;#x2F;&amp;#x2F; cache
    await this.scope.cacheRedis.post.set(post, post.id);
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;当新建数据后，将数据放入 redis 缓存中。如果这个事务出现异常，就会进行数据回滚，同时缓存数据也会回滚，从而让数据库数据与缓存数据保持一致&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 使用指定数据源&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- typescript&quot;&gt;&lt;code&gt;class ServicePost {
  async transactionManually() {
    const db = this.bean.database.getDb({ clientName: &amp;#x27;default&amp;#x27; });
    await db.transaction.begin(async () =&amp;gt; {
      const modelPost = this.scope.model.post.newInstance(db);
      const post = await modelPost.insert({ title: &amp;#x27;Post001&amp;#x27; });
      await this.scope.cacheRedis.post.set(post, post.id, { db });
    });
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;如果对指定的数据库进行操作，那么就需要将数据库对象&lt;code&gt;db&lt;/code&gt;传入缓存，从而让缓存针对数据库对象&lt;code&gt;db&lt;/code&gt;执行相应的补偿操作。当数据库事务回滚时，让数据库数据与缓存数据保持一致&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description><author>zhennann</author><pubDate>Thu, 21 Aug 2025 03:53:30 GMT</pubDate></item><item><title>题目：如何在node 16上执行claude code（cc只能跑在node 18+）？</title><link>https://cnodejs.org/topic/68a5d087f135760b890835ba</link><guid>https://cnodejs.org/topic/68a5d087f135760b890835ba</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;在node 16下，把cc安装在node 22下面。
然后node 16下 执行node 22下面的cc。。。&lt;/p&gt;
&lt;p&gt;第一次见过这么骚的操作&lt;/p&gt;
&lt;/div&gt;</description><author>i5ting</author><pubDate>Wed, 20 Aug 2025 13:41:27 GMT</pubDate></item><item><title>如何基于动态关系进行ORM关联查询，并动态推断DTO？</title><link>https://cnodejs.org/topic/68957089f1357638bb083444</link><guid>https://cnodejs.org/topic/68957089f1357638bb083444</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;在上一篇文章（&lt;a href=&quot;https://juejin.cn/post/7534568184419893275&quot;&gt;Prisma不能优雅的支持DTO，试试Vona ORM吧&lt;/a&gt;）中，我们基于静态关系实现了&lt;code&gt;目录树&lt;/code&gt;的关联查询，并且动态推断生成了DTO（用于Swagger元数据）。在这篇文章我们探讨&lt;code&gt;动态关系&lt;/code&gt;的用法。&lt;/p&gt;
&lt;h2&gt;什么是动态关系&lt;/h2&gt;
&lt;p&gt;那么，什么是动态关系呢？在大型业务系统中，我们会创建大量数据模型，这些数据模型之间的关联众多，我们不可能将所有关联通过&lt;code&gt;静态关系&lt;/code&gt;的机制事先声明出来。特别是当存在大量业务模块，这些数据模型散落在不同的业务模块中，那么通过&lt;code&gt;静态关系&lt;/code&gt;事先声明所有的关联关系也变得不太现实。比如，Prisma就只支持&lt;code&gt;静态关系&lt;/code&gt;。如果事先没有定义&lt;code&gt;静态关系&lt;/code&gt;，在实际代码中，我们就需要提供一种使用&lt;code&gt;动态关系&lt;/code&gt;的机制，让我们的查询、类型推断、DTO推断等能力得以正常使用。&lt;/p&gt;
&lt;h2&gt;准备数据模型&lt;/h2&gt;
&lt;p&gt;在上一篇文章中，我们已经介绍了如何创建Entity和Model，这里不再赘述。而是直接把Order和Customer的Entity和Model罗列出来，然后演示&lt;code&gt;动态关系&lt;/code&gt;的用法&lt;/p&gt;
&lt;h3&gt;Entity&lt;/h3&gt;
&lt;h4&gt;1. Order&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;@Entity()
export class EntityOrder extends EntityBase {
  @Api.field(v.openapi({ title: $locale(&amp;#x27;OrderNo&amp;#x27;) }), v.default(&amp;#x27;&amp;#x27;), v.min(3))
  orderNo: string;

  @Api.field(v.title($locale(&amp;#x27;Remark&amp;#x27;)), v.optional())
  remark?: string;

  @Api.field(v.tableIdentity())
  customerId: TableIdentity;
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;v.openapi：声明字段的元数据，用于Swagger
&lt;ul&gt;
&lt;li&gt;title：采用$locale定义，从而让Swagger元数据支持多语言能力&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;v.title：等价于v.openapi({ title: … })&lt;/li&gt;
&lt;li&gt;TableIdentity：string | number&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2. Customer&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;@Entity()
export class EntityCustomer extends EntityBase {
  @Api.field(v.min(3))
  name: string;
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Model&lt;/h3&gt;
&lt;h4&gt;1. Order&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;@Model({ entity: EntityOrder })
export class ModelOrder extends BeanModelBase {}
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;2. Customer&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;@Model({ entity: EntityCustomer })
export class ModelCustomer extends BeanModelBase {}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;基于&lt;code&gt;动态关系&lt;/code&gt;的查询&lt;/h2&gt;
&lt;p&gt;现在我们查询订单列表，包含归属的顾客信息：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;const orders = await this.scope.model.order.select({
  with: {
    customer: $relationDynamic.belongsTo(
      () =&amp;gt; ModelOrder,
      () =&amp;gt; ModelCustomer,
      &amp;#x27;customerId&amp;#x27;,
    ),
  },
});
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;$relationDynamic:提供一组工具，用于定义动态关系&lt;/li&gt;
&lt;li&gt;belongsTo：定义&lt;code&gt;多对一&lt;/code&gt;的关系
&lt;ul&gt;
&lt;li&gt;参数1：Order模型&lt;/li&gt;
&lt;li&gt;参数2：Customer模型&lt;/li&gt;
&lt;li&gt;参数3：设置关联外键customerId&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面我们看一下&lt;code&gt;orders&lt;/code&gt;的类型推断效果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://origin.picgo.net/2025/08/08/686e78c52ec7f8abe.png&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://origin.picgo.net/2025/08/08/7e1a41a6d14a811cd.png&quot; alt&gt;&lt;/p&gt;
&lt;h2&gt;自动推断DTO&lt;/h2&gt;
&lt;p&gt;现在我们自动推断DTO，并且设为API的返回数据的类型：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;const DtoOrderResult = $Dto.get(
  () =&amp;gt; ModelOrder,
  {
    with: {
      customer: $relationDynamic.belongsTo(
        () =&amp;gt; ModelOrder,
        () =&amp;gt; ModelCustomer,
        &amp;#x27;customerId&amp;#x27;,
      ),
    },
  },
);

@Controller(&amp;#x27;order&amp;#x27;)
export class ControllerOrder extends BeanBase {
  @Web.get()
  @Api.body(v.array(v.object(DtoOrderResult)))
  async findAll() {
    return await this.scope.service.order.findAll();
  }
}  
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;行1：动态创建&lt;code&gt;DtoOrderResult&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;行17：将&lt;code&gt;DtoOrderResult&lt;/code&gt;用于Swagger/Openapi的元数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面我们看一下API的Swagger效果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://origin.picgo.net/2025/08/08/8ddd9646456a51f11.png&quot; alt&gt;&lt;/p&gt;
&lt;h2&gt;封装DTO&lt;/h2&gt;
&lt;p&gt;我们还可以创建一个新的DTO，将前面动态创建的&lt;code&gt;DtoOrderResult&lt;/code&gt;封装起来，从而用于其他地方：&lt;/p&gt;
&lt;p&gt;在VSCode中，可以通过右键菜单&lt;code&gt;Vona Create/Dto&lt;/code&gt;创建DTO的代码骨架：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;@Dto()
export class DtoOrderResult {}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后我们通过继承机制来封装DTO：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;const DtoOrderResultDynamic = $Dto.get(
  () =&amp;gt; ModelOrder,
  {
    with: {
      customer: $relationDynamic.belongsTo(
        () =&amp;gt; ModelOrder,
        () =&amp;gt; ModelCustomer,
        &amp;#x27;customerId&amp;#x27;,
      ),
    },
  },
);

@Dto()
export class DtoOrderResult extends DtoOrderResultDynamic {}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在，我们再使用新创建的DTO来改造前面的API代码：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;+ import { DtoOrderResult } from &amp;#x27;..&amp;#x2F;dto&amp;#x2F;orderResult.ts&amp;#x27;;

@Controller(&amp;#x27;order&amp;#x27;)
export class ControllerOrder extends BeanBase {
  @Web.get()
+ @Api.body(v.array(v.object(DtoOrderResult)))
+ async findAll(): Promise&amp;lt;DtoOrderResult[]&amp;gt; {
    return await this.scope.service.order.findAll();
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;行6: 直接传入&lt;code&gt;DtoOrderResult&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;行7: 返回类型为&lt;code&gt;Promise&amp;lt;DtoOrderResult[]&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;Vonajs已开源：&lt;a href=&quot;https://github.com/vonajs/vona&quot;&gt;https://github.com/vonajs/vona&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;Vonajs作者正在B站直播撰写技术文档，工作日每晚8:30，欢迎围观：&lt;a href=&quot;https://space.bilibili.com/454737998&quot;&gt;濮水代码直播间&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>zhennann</author><pubDate>Fri, 08 Aug 2025 03:35:37 GMT</pubDate></item><item><title>Prisma不能优雅的支持DTO，试试Vona ORM吧</title><link>https://cnodejs.org/topic/6891c5c4f135760b690833b1</link><guid>https://cnodejs.org/topic/6891c5c4f135760b690833b1</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;在Nodejs生态中，Prisma是一个非常流行的ORM库，支持Typescript，提供了非常友好的类型推断能力。但是，Prisma却不能优雅的支持DTO。在与其他后端框架整合时，DTO是进行参数验证、生成Swagger元数据的关键节点。如果不能像推断类型一样自动推断出DTO，那么，我们就仍然需要手工创建DTO。随着业务的增长，复杂的表间关系会让手工补充DTO的工作日益繁重。&lt;/p&gt;
&lt;p&gt;而Vona ORM就提供了非常便利的工具，使我们可以非常直观的动态推断出DTO，就像推断类型一样，从而解放我们的双手，显著提升生产力。甚至可以说，能够自动推断DTO，为Nodejs后端框架打开了一扇窗。&lt;/p&gt;
&lt;p&gt;Vona本身就是一款更直观的Nodejs框架，如果大家第一次接触，可以先参考这篇文章：&lt;a href=&quot;https://juejin.cn/post/7509709812857110582&quot;&gt;你认为Vonajs提供的这些特性会比Nestjs更好用吗？&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;限于篇幅，这里不展开讲解Vona ORM所有的知识点，而是以&lt;code&gt;目录树&lt;/code&gt;为例，演示如何查询一棵目录树，以及如何动态生成DTO，并最终生成Swagger元数据。Vona框架作者正在直播撰写Vona文档。围观官方文档的实时编写过程，有利于加深对框架设计的理解，探索不一样的架构设计路径。有兴趣的欢迎移步：&lt;a href=&quot;https://space.bilibili.com/454737998&quot;&gt;B站濮水代码直播间&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;1. 创建Entity&lt;/h2&gt;
&lt;p&gt;在VSCode中，可以通过右键菜单&lt;code&gt;Vona Create/Entity&lt;/code&gt;创建Entity的代码骨架：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;@Entity(&amp;#x27;demoStudentCategory&amp;#x27;)
export class EntityCategory extends EntityBase {
  @Api.field()
  name: string;

  @Api.field(v.optional())
  categoryIdParent?: TableIdentity;
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;行2: 继承自EntityBase，就会自动提供5个基础字段：id、createdAt、updatedAt、deleted、iid
&lt;ul&gt;
&lt;li&gt;iid：是实例Id，通过多实例的机制支持多租户系统的开发&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;行4、7: 定义两个业务字段：name、categoryIdParent&lt;/li&gt;
&lt;li&gt;@Api.field：通过此装饰器定义的信息，可同时应用于参数验证和Swagger元数据&lt;/li&gt;
&lt;li&gt;v.optional：声明为可选字段&lt;/li&gt;
&lt;li&gt;更多信息，参见：&lt;a href=&quot;https://vona.js.org/zh/guide/essentials/scope/entity.html&quot;&gt;Vona Entity&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 创建Model&lt;/h2&gt;
&lt;p&gt;在VSCode中，可以通过右键菜单&lt;code&gt;Vona Create/Model&lt;/code&gt;创建Model的代码骨架：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;import { EntityCategory } from &amp;#x27;..&amp;#x2F;entity&amp;#x2F;category.ts&amp;#x27;;

@Model({ entity: EntityCategory })
export class ModelCategory extends BeanModelBase&amp;lt;EntityCategory&amp;gt; {}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;行3: entity：指定Model所对应的Entity&lt;/li&gt;
&lt;li&gt;行4: 继承自BeanModelBase，从而拥有大量操作数据库的方法，如：CRUD、聚合、分组，等等&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. 创建树形结构&lt;/h2&gt;
&lt;p&gt;如果要创建一棵目录树，本质就是建立Model引用自身的递归结构。Vona ORM同样支持4种关系：&lt;code&gt;1对1&lt;/code&gt;、&lt;code&gt;1对多&lt;/code&gt;、&lt;code&gt;多对1&lt;/code&gt;，&lt;code&gt;多对多&lt;/code&gt;。那么，在这里，我们就需要采用&lt;code&gt;1对多&lt;/code&gt;来创建目录的自身引用关系。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;import { EntityCategory } from &amp;#x27;..&amp;#x2F;entity&amp;#x2F;category.ts&amp;#x27;;

@Model({
  entity: EntityCategory,
+ relations: {
+   children: $relation.hasMany(() =&amp;gt; ModelCategory, &amp;#x27;categoryIdParent&amp;#x27;, {
+     autoload: true,
+     columns: [&amp;#x27;id&amp;#x27;, &amp;#x27;name&amp;#x27;],
+   }),
+ },
})
export class ModelCategory extends BeanModelBase&amp;lt;EntityCategory&amp;gt; {}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;行5: relations：可以定义多个关系&lt;/li&gt;
&lt;li&gt;行6: children：定义一个1对多的关系&lt;/li&gt;
&lt;li&gt;$relation.hasMany：
&lt;ul&gt;
&lt;li&gt;参数1: 引用自身ModelCategory&lt;/li&gt;
&lt;li&gt;参数2: 设置关联外键categoryIdParent&lt;/li&gt;
&lt;li&gt;参数3: 关联选项
&lt;ul&gt;
&lt;li&gt;autoload：是否自动加载关联数据&lt;/li&gt;
&lt;li&gt;columns：控制关联数据的字段列表&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. 创建Controller&lt;/h2&gt;
&lt;p&gt;在VSCode中，可以通过右键菜单&lt;code&gt;Vona Create/Controller&lt;/code&gt;创建Controller的代码骨架：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;@Controller()
export class ControllerCategory extends BeanBase {}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;接下来我们创建一个Api，用于获取目录树：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;export class ControllerCategory extends BeanBase {
  @Web.get(&amp;#x27;getCategoryTree&amp;#x27;)
  async getCategoryTree() {
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;行2: 通过@Web.get定义一个api，path为getCategoryTree&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5. 查询目录树&lt;/h2&gt;
&lt;p&gt;一般而言，我们还需要创建一个Service，从而实现以下调用链：Controller-&amp;gt;Service-&amp;gt;Model-&amp;gt;操作数据库。为了简化起见，在这里，我们直接在Controller中调用Model方法：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;export class ControllerCategory extends BeanBase {
  @Web.get(&amp;#x27;getCategoryTree&amp;#x27;)
  async getCategoryTree() {
    const tree = await this.scope.model.category.select({
      columns: [&amp;#x27;id&amp;#x27;, &amp;#x27;name&amp;#x27;],
    });
    return tree;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;行4: 通过&lt;code&gt;this.scope&lt;/code&gt;取得Category Model，然后调用select方法
&lt;ul&gt;
&lt;li&gt;columns：指定要查询的字段列表&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于前面我们设置children关系为&lt;code&gt;autoload: true&lt;/code&gt;，因此，查询结果&lt;code&gt;tree&lt;/code&gt;就是一棵完整的目录树。下面我们看一下&lt;code&gt;tree&lt;/code&gt;的类型推断效果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://origin.picgo.net/2025/08/05/1c0db0a4260be4663.png&quot; alt=&quot;1.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://origin.picgo.net/2025/08/05/2093623b1ca3e0dea.png&quot; alt=&quot;2.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;6. 自动推断DTO&lt;/h2&gt;
&lt;p&gt;现在我们自动推断DTO，并且设为API的返回数据的类型：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;export class ControllerCategory extends BeanBase {
  @Web.get(&amp;#x27;getCategoryTree&amp;#x27;)
+ @Api.body(v.array(v.object($Dto.get(() =&amp;gt; ModelCategory, { columns: [&amp;#x27;id&amp;#x27;, &amp;#x27;name&amp;#x27;] }))))
  async getCategoryTree() {
    const tree = await this.scope.model.category.select({
      columns: [&amp;#x27;id&amp;#x27;, &amp;#x27;name&amp;#x27;],
    });
    return tree;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;行3: 通过@Api.body定义API返回数据的类型：
&lt;ul&gt;
&lt;li&gt;v.array：定义数组类型&lt;/li&gt;
&lt;li&gt;v.object：定义对象类型&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;$Dto.get：动态推断DTO
&lt;ul&gt;
&lt;li&gt;参数1：指定目标Model&lt;/li&gt;
&lt;li&gt;参数2：指定选项
&lt;ul&gt;
&lt;li&gt;columns：指定要提取的字段列表&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;同样，由于前面我们设置children关系为&lt;code&gt;autoload: true&lt;/code&gt;，因此，&lt;code&gt;$Dto.get&lt;/code&gt;生成的DTO就是一棵完整的目录树。下面我们看一下API的Swagger效果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://origin.picgo.net/2025/08/05/3f78f6b398ff25403.png&quot; alt=&quot;3.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://origin.picgo.net/2025/08/05/45d0f5351e610e51b.png&quot; alt=&quot;4.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://origin.picgo.net/2025/08/05/5892fcfd4e6028088.png&quot; alt=&quot;5.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;从示意图中，我们可以清晰的看到，这棵树引用的children类型是名称为&lt;code&gt;demo-student.entity.category_2c7d642ee581efa300341e343180fbb0ecdc785d&lt;/code&gt;的动态Entity的数组，从而形成一种递归的引用关系。&lt;/p&gt;
&lt;h2&gt;7. 封装DTO&lt;/h2&gt;
&lt;p&gt;虽然我们已经实现了预期的目标，但是Vona ORM提供的能力还没有结束。我们可以创建一个新的DTO，将前面的代码&lt;code&gt;$Dto.get(() =&amp;gt; ModelCategory, { columns: [&apos;id&apos;, &apos;name&apos;] })&lt;/code&gt;封装起来，从而用于其他地方：&lt;/p&gt;
&lt;p&gt;在VSCode中，可以通过右键菜单&lt;code&gt;Vona Create/Dto&lt;/code&gt;创建DTO的代码骨架：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;@Dto()
export class DtoCategoryTree {}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后我们通过继承机制来封装DTO：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;@Dto()
export class DtoCategoryTree 
+ extends $Dto.get(() =&amp;gt; ModelCategory, { columns: [&amp;#x27;id&amp;#x27;, &amp;#x27;name&amp;#x27;] }) {}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在，我们再使用新创建的DTO来改造前面的API代码：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;export class ControllerCategory extends BeanBase {
  @Web.get(&amp;#x27;getCategoryTree&amp;#x27;)
+ @Api.body(v.array(v.object(DtoCategoryTree)))
+ async getCategoryTree(): Promise&amp;lt;DtoCategoryTree[]&amp;gt;{
    const tree = await this.scope.model.category.select({
      columns: [&amp;#x27;id&amp;#x27;, &amp;#x27;name&amp;#x27;],
    });
    return tree;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;行3: 直接传入&lt;code&gt;DtoCategoryTree&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;行4: 返回类型为&lt;code&gt;Promise&amp;lt;DtoCategoryTree[]&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;Vonajs已开源：&lt;a href=&quot;https://github.com/vonajs/vona&quot;&gt;https://github.com/vonajs/vona&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;Vonajs作者正在B站直播撰写技术文档，工作日每晚8:30，欢迎围观：&lt;a href=&quot;https://space.bilibili.com/454737998&quot;&gt;濮水代码直播间&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>zhennann</author><pubDate>Tue, 05 Aug 2025 08:50:12 GMT</pubDate></item><item><title>docx-handlebars 一个用于处理 DOCX 文件 Handlebars 模板的 Rust 库，支持多平台使用</title><link>https://cnodejs.org/topic/6880d73ef135762c2e083153</link><guid>https://cnodejs.org/topic/6880d73ef135762c2e083153</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h1&gt;docx-handlebars&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://crates.io/crates/docx-handlebars&quot;&gt;&lt;img src=&quot;https://img.shields.io/crates/v/docx-handlebars.svg&quot; alt=&quot;Crates.io&quot;&gt;&lt;/a&gt;
&lt;a href=&quot;https://docs.rs/docx-handlebars&quot;&gt;&lt;img src=&quot;https://docs.rs/docx-handlebars/badge.svg&quot; alt=&quot;Documentation&quot;&gt;&lt;/a&gt;
&lt;a href=&quot;https://github.com/sail-sail/docx-handlebars#license&quot;&gt;&lt;img src=&quot;https://img.shields.io/crates/l/docx-handlebars.svg&quot; alt=&quot;License&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;中文文档 | &lt;a href&gt;English&lt;/a&gt; | &lt;a href=&quot;https://sail-sail.github.io/docx-handlebars-demo/&quot;&gt;Demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;一个用于处理 DOCX 文件 Handlebars 模板的 Rust 库，支持多平台使用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; Rust 原生&lt;/li&gt;
&lt;li&gt; WebAssembly (WASM)&lt;/li&gt;
&lt;li&gt; npm 包&lt;/li&gt;
&lt;li&gt; Node.js&lt;/li&gt;
&lt;li&gt; Deno&lt;/li&gt;
&lt;li&gt; 浏览器端&lt;/li&gt;
&lt;li&gt; JSR (JavaScript Registry)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;功能特性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;智能合并&lt;/strong&gt;：自动处理被 XML 标签分割的 Handlebars 语法&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;DOCX 验证&lt;/strong&gt;：内置文件格式验证，确保输入文件有效&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Handlebars 支持&lt;/strong&gt;：完整的模板引擎，支持变量、条件、循环、Helper 函数&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;跨平台&lt;/strong&gt;：Rust 原生 + WASM 支持多种运行时&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;TypeScript&lt;/strong&gt;：完整的类型定义和智能提示&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;零依赖&lt;/strong&gt;：WASM 二进制文件，无外部依赖&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;错误处理&lt;/strong&gt;：详细的错误信息和类型安全的错误处理&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;h3&gt;Rust&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;cargo add docx-handlebars
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;npm&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;npm install docx-handlebars
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Deno&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;import { render_template, init } from &amp;quot;jsr:@sail&amp;#x2F;docx-handlebars&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;使用示例&lt;/h2&gt;
&lt;h3&gt;Rust&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-rust&quot;&gt;&lt;code&gt;use docx_handlebars::render_template;
use serde_json::json;

fn main() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {
    &amp;#x2F;&amp;#x2F; 读取 DOCX 模板文件
    let template_bytes = std::fs::read(&amp;quot;template.docx&amp;quot;)?;
    
    &amp;#x2F;&amp;#x2F; 准备数据
    let data = json!({
        &amp;quot;name&amp;quot;: &amp;quot;张三&amp;quot;,
        &amp;quot;company&amp;quot;: &amp;quot;ABC科技有限公司&amp;quot;,
        &amp;quot;position&amp;quot;: &amp;quot;软件工程师&amp;quot;,
        &amp;quot;projects&amp;quot;: [
            {&amp;quot;name&amp;quot;: &amp;quot;项目A&amp;quot;, &amp;quot;status&amp;quot;: &amp;quot;已完成&amp;quot;},
            {&amp;quot;name&amp;quot;: &amp;quot;项目B&amp;quot;, &amp;quot;status&amp;quot;: &amp;quot;进行中&amp;quot;}
        ],
        &amp;quot;has_bonus&amp;quot;: true,
        &amp;quot;bonus_amount&amp;quot;: 5000
    });
    
    &amp;#x2F;&amp;#x2F; 渲染模板
    let result = render_template(template_bytes, &amp;amp;data)?;
    
    &amp;#x2F;&amp;#x2F; 保存结果
    std::fs::write(&amp;quot;output.docx&amp;quot;, result)?;
    
    Ok(())
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;JavaScript/TypeScript (Node.js)&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;import { render_template, init } from &amp;#x27;docx-handlebars&amp;#x27;;
import fs from &amp;#x27;fs&amp;#x27;;

async function processTemplate() {
    &amp;#x2F;&amp;#x2F; 初始化 WASM 模块
    await init();
    
    &amp;#x2F;&amp;#x2F; 读取模板文件
    const templateBytes = fs.readFileSync(&amp;#x27;template.docx&amp;#x27;);
    
    &amp;#x2F;&amp;#x2F; 准备数据
    const data = {
        name: &amp;quot;李明&amp;quot;,
        company: &amp;quot;XYZ技术有限公司&amp;quot;,
        position: &amp;quot;高级开发工程师&amp;quot;,
        projects: [
            { name: &amp;quot;E-commerce平台&amp;quot;, status: &amp;quot;已完成&amp;quot; },
            { name: &amp;quot;移动端APP&amp;quot;, status: &amp;quot;开发中&amp;quot; }
        ],
        has_bonus: true,
        bonus_amount: 8000
    };
    
    &amp;#x2F;&amp;#x2F; 渲染模板
    const result = render_template(templateBytes, JSON.stringify(data));
    
    &amp;#x2F;&amp;#x2F; 保存结果
    fs.writeFileSync(&amp;#x27;output.docx&amp;#x27;, new Uint8Array(result));
}

processTemplate().catch(console.error);
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Deno&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;import { render_template, init } from &amp;quot;https:&amp;#x2F;&amp;#x2F;deno.land&amp;#x2F;x&amp;#x2F;docx_handlebars&amp;#x2F;mod.ts&amp;quot;;

async function processTemplate() {
    &amp;#x2F;&amp;#x2F; 初始化 WASM 模块
    await init();
    
    &amp;#x2F;&amp;#x2F; 读取模板文件
    const templateBytes = await Deno.readFile(&amp;quot;template.docx&amp;quot;);
    
    &amp;#x2F;&amp;#x2F; 准备数据
    const data = {
        name: &amp;quot;王小明&amp;quot;,
        department: &amp;quot;研发部&amp;quot;,
        projects: [
            { name: &amp;quot;智能客服系统&amp;quot;, status: &amp;quot;已上线&amp;quot; },
            { name: &amp;quot;数据可视化平台&amp;quot;, status: &amp;quot;开发中&amp;quot; }
        ]
    };
    
    &amp;#x2F;&amp;#x2F; 渲染模板
    const result = render_template(templateBytes, JSON.stringify(data));
    
    &amp;#x2F;&amp;#x2F; 保存结果
    await Deno.writeFile(&amp;quot;output.docx&amp;quot;, new Uint8Array(result));
}

if (import.meta.main) {
    await processTemplate();
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;浏览器端&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;DOCX Handlebars 示例&amp;lt;&amp;#x2F;title&amp;gt;
&amp;lt;&amp;#x2F;head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;input type=&amp;quot;file&amp;quot; id=&amp;quot;fileInput&amp;quot; accept=&amp;quot;.docx&amp;quot;&amp;gt;
    &amp;lt;button onclick=&amp;quot;processFile()&amp;quot;&amp;gt;处理模板&amp;lt;&amp;#x2F;button&amp;gt;
    
    &amp;lt;script type=&amp;quot;module&amp;quot;&amp;gt;
        import { render_template, init } from &amp;#x27;.&amp;#x2F;pkg&amp;#x2F;docx_handlebars.js&amp;#x27;;
        
        &amp;#x2F;&amp;#x2F; 初始化 WASM
        await init();
        
        window.processFile = async function() {
            const fileInput = document.getElementById(&amp;#x27;fileInput&amp;#x27;);
            const file = fileInput.files[0];
            
            if (!file) return;
            
            const arrayBuffer = await file.arrayBuffer();
            const templateBytes = new Uint8Array(arrayBuffer);
            
            const data = {
                name: &amp;quot;张三&amp;quot;,
                company: &amp;quot;示例公司&amp;quot;
            };
            
            try {
                const result = render_template(templateBytes, JSON.stringify(data));
                
                &amp;#x2F;&amp;#x2F; 下载结果
                const blob = new Blob([new Uint8Array(result)], {
                    type: &amp;#x27;application&amp;#x2F;vnd.openxmlformats-officedocument.wordprocessingml.document&amp;#x27;
                });
                const url = URL.createObjectURL(blob);
                const a = document.createElement(&amp;#x27;a&amp;#x27;);
                a.href = url;
                a.download = &amp;#x27;processed.docx&amp;#x27;;
                a.click();
            } catch (error) {
                console.error(&amp;#x27;处理失败:&amp;#x27;, error);
            }
        };
    &amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;&amp;#x2F;body&amp;gt;
&amp;lt;&amp;#x2F;html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;模板语法&lt;/h2&gt;
&lt;h3&gt;基础变量替换&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;员工姓名: {{name}}
公司: {{company}}
职位: {{position}}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;条件渲染&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;{{#if has_bonus}}
奖金: ¥{{bonus_amount}}
{{else}}
无奖金
{{&amp;#x2F;if}}

{{#unless is_intern}}
正式员工
{{&amp;#x2F;unless}}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;循环渲染&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;项目经历:
{{#each projects}}
- {{name}}: {{description}} ({{status}})
{{&amp;#x2F;each}}

技能列表:
{{#each skills}}
{{@index}}. {{this}}
{{&amp;#x2F;each}}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Helper 函数&lt;/h3&gt;
&lt;p&gt;内置的 Helper 函数：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;{{upper name}}           &amp;lt;!-- 转大写 --&amp;gt;
{{lower company}}        &amp;lt;!-- 转小写 --&amp;gt;
{{len projects}}         &amp;lt;!-- 数组长度 --&amp;gt;
{{#if (eq status &amp;quot;completed&amp;quot;)}}已完成{{&amp;#x2F;if}}    &amp;lt;!-- 相等比较 --&amp;gt;
{{#if (gt score 90)}}优秀{{&amp;#x2F;if}}               &amp;lt;!-- 大于比较 --&amp;gt;
{{#if (lt age 30)}}年轻{{&amp;#x2F;if}}                 &amp;lt;!-- 小于比较 --&amp;gt;

&amp;lt;!-- 图片插入 --&amp;gt;
{{img base64_data}}                    &amp;lt;!-- 嵌入式图片 --&amp;gt;
{{img base64_data 300 200}}           &amp;lt;!-- 指定宽高的嵌入式图片 --&amp;gt;
{{img base64_data 300 200 options}}   &amp;lt;!-- 带定位选项的浮动图片 --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;复杂示例&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-handlebars&quot;&gt;&lt;code&gt;=== 员工报告 ===

基本信息:
姓名: {{employee.name}}
部门: {{employee.department}}
职位: {{employee.position}}
入职时间: {{employee.hire_date}}

{{#if employee.has_bonus}}
 奖金: ¥{{employee.bonus_amount}}
{{&amp;#x2F;if}}

项目经历 (共{{len projects}}个):
{{#each projects}}
{{@index}}. {{name}}
   描述: {{description}}
   状态: {{status}}
   团队规模: {{team_size}}人
   
{{&amp;#x2F;each}}

技能评估:
{{#each skills}}
- {{name}}: {{level}}&amp;#x2F;10 ({{years}}年经验)
{{&amp;#x2F;each}}

在表格中若需要删除一整行, 只需要在任意单元格上添加:
{{removeTableRow}}


{{#if (gt performance.score 90)}}
 绩效评级: 优秀
{{else if (gt performance.score 80)}}
 绩效评级: 良好
{{else}}
 绩效评级: 需改进
{{&amp;#x2F;if}}

图片:
{{img base64_image_data [width] [height] [options]}}
  只传 height：{{img base64 &amp;quot;&amp;quot; 200}}
  只传 width：{{img base64 300}}
  都不传：{{img base64}}
  都传：{{img base64 300 200}}
  
  浮动图片示例：
  {{img base64 200 100 options}}
  
  其中 options 可以是：
  {
    &amp;quot;anchor&amp;quot;: true,          &amp;#x2F;&amp;#x2F; 是否使用锚点定位（浮动图片）
    &amp;quot;behind_doc&amp;quot;: false,     &amp;#x2F;&amp;#x2F; 图片是否在文字下方（false=覆盖文字，true=文字覆盖图片）
    &amp;quot;allow_overlap&amp;quot;: true,   &amp;#x2F;&amp;#x2F; 是否允许与其他对象重叠
    &amp;quot;position_h&amp;quot;: 40,        &amp;#x2F;&amp;#x2F; 水平偏移，单位为像素（负值表示向左偏移）
    &amp;quot;position_v&amp;quot;: -14        &amp;#x2F;&amp;#x2F; 垂直偏移，单位为像素（负值表示向上偏移）
  }
  
  图片定位说明：
  - anchor: false（默认）- 嵌入式图片，随文字流动
  - anchor: true - 浮动图片，可自由定位和覆盖文字
  - position_h&amp;#x2F;position_v: 不传参数时，默认使用图片尺寸的一半作为偏移量实现居中
  - 支持负数偏移，实现精确的图片定位
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;构建和开发&lt;/h2&gt;
&lt;h3&gt;构建 WASM 包&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;# 构建所有目标
npm run build

# 或分别构建
npm run build:web    # 浏览器版本
npm run build:npm    # Node.js 版本 
npm run build:jsr    # Deno 版本
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;运行示例&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;# Rust 示例
cargo run --example rust_example

# Node.js 示例
node examples&amp;#x2F;node_example.js

# Deno 示例  
deno run --allow-read --allow-write examples&amp;#x2F;deno_example.ts

# 浏览器示例
cd tests&amp;#x2F;npm_test
node serve.js
# 然后在浏览器中打开 http:&amp;#x2F;&amp;#x2F;localhost:8080
# 选择 examples&amp;#x2F;template.docx 文件测试
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;性能和兼容性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;零拷贝&lt;/strong&gt;: Rust 和 WASM 之间高效的内存管理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流式处理&lt;/strong&gt;: 适合处理大型 DOCX 文件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨平台&lt;/strong&gt;: 支持 Windows、macOS、Linux、Web&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;现代浏览器&lt;/strong&gt;: 支持所有支持 WASM 的现代浏览器&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;许可证&lt;/h2&gt;
&lt;p&gt;本项目采用 MIT 许可证 - 详见 &lt;a href&gt;LICENSE-MIT&lt;/a&gt; 文件。&lt;/p&gt;
&lt;h2&gt;支持&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt; &lt;a href=&quot;https://docs.rs/docx-handlebars&quot;&gt;文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt; &lt;a href=&quot;https://github.com/sail-sail/docx-handlebars/issues&quot;&gt;问题反馈&lt;/a&gt;&lt;/li&gt;
&lt;li&gt; &lt;a href=&quot;https://github.com/sail-sail/docx-handlebars/discussions&quot;&gt;讨论&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;/div&gt;</description><author>151263</author><pubDate>Wed, 23 Jul 2025 12:36:14 GMT</pubDate></item><item><title>好有链接 | 我们做了一款支持中文口令分享的多数字名片小程序</title><link>https://cnodejs.org/topic/687c97a1f135766cc50830d7</link><guid>https://cnodejs.org/topic/687c97a1f135766cc50830d7</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;『好有链接』是我们第一个独立开发的正式作品。欢迎大家尝试，也欢迎大家提出意见帮助我们完善它～
『好有链接』拥有数字名片该有的一切基础功能：整理并整合你的社交账号、内容链接、联系方式，让别人快速了解你是谁，不用一遍遍自我介绍。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://links.haoyou.tech/Drawing_bed/Slide_16_9_-_22.png&quot; alt=&quot;Slide 16_9 - 22.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;但我们想让它和诸如 Linkree 之类的数字名片工具有些差异，可以更加贴合国内的环境。&lt;/p&gt;
&lt;p&gt;于是，&lt;/p&gt;
&lt;h1&gt;1. 多身份名片&lt;/h1&gt;
&lt;p&gt;我们每个人或多或少都有多个身份，并且会随时切换它们：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用飞书开会，企微回复客户，Linkedin 更新工作成果的职场打工人&lt;/li&gt;
&lt;li&gt;用 notion 撰写博客，知乎分享经验， 在 behance 上发布作品的设计博主&lt;/li&gt;
&lt;li&gt;用微博晒狗，小红书种草宠物玩具，微信群寻找遛狗搭子的铲屎官&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://links.haoyou.tech/Drawing_bed/Slide_16_9_-_14.png&quot; alt=&quot;好有链接 数字名片-2.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;当我们面对不同人的时候，为了匹配不同的关系场景，我们往往会刻意的展示我们其中一面。那么你有没有想过，&lt;strong&gt;这一次，我应该如何介绍和分享自己？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了解决这个问题，为了快速且准确地展示那个 “正确的我”。
我们在『好有链接』中增加了多身份名片这个功能：你可以为不同的关系场景，创建专属的『身份名片』。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://links.haoyou.tech/Drawing_bed/Slide_16_9_-_15.png&quot; alt=&quot;好有链接 数字名片-3.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;每张『身份名片』都是你的一种侧面。你可以为它们设置不同的头像与简介，组合出专属于这一面的人设信息：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;添加社交卡片，绑定你的平台账号（微信、微博、小红书、B 站等），让别人更快找到你。&lt;/li&gt;
&lt;li&gt;添加笔记卡片，展示你的爱好特长、知识经验、观点和作品，让别人更快了解你。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://links.haoyou.tech/Drawing_bed/Slide_16_9_-_23.png&quot; alt=&quot;好有链接 数字名片-4.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;这些名片彼此独立。你不需要把所有账号塞进一个页面中，这样也不会担心 “我是不是不该分享这个微信” 或是 “我发一大段自我介绍 Ta 会看吗？”。
只需根据场合，挑选并分享那个恰当的自己。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;『好有链接』就是这样一本属于你的自我使用说明书，每一页都写给不同的人看。&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;2. 中文口令&lt;/h1&gt;
&lt;p&gt;出现中文口令的原因是因为我们觉得在社交平台上分享自己的链接和二维码太麻烦了。每次都要绞尽脑汁和平台斗争。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://links.haoyou.tech/Drawing_bed/Slide_16_9_-_6.png&quot; alt=&quot;未命名-13.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;于是，在『好有链接』中，每一张卡片都可以被赋予一个自定义的中文口令。他人只需在小程序中输入口令就能直达你想展示的内容。
用中文口令可以替代冗长的链接，可以更加自然地嵌入评论、对话甚至图片中，不容易被屏蔽或误删。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://links.haoyou.tech/Drawing_bed/Slide_16_9_-_19.png&quot; alt=&quot;好有链接 数字名片-6.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;但我们觉得既然有了这个功能，那就让它更加有趣一些吧。
我们觉得分享不仅是把一段信息传递出去，这个行为也可以是一种轻巧、有趣的表达。&lt;/p&gt;
&lt;p&gt;于是就有了这些玩法：&lt;/p&gt;
&lt;h2&gt;1. 个性表达&lt;/h2&gt;
&lt;p&gt;口令可以不只是链接的媒介。它可以是一句玩笑、一个暗号，也可以是一种情绪。它是你和被分享者之间的沟通方式。
它可以让每次分享，都成为一次有趣的互动，这也会让别人更容易记住你。
&lt;img src=&quot;https://links.haoyou.tech/Drawing_bed/Slide_16_9_-_20.png&quot; alt=&quot;好有链接 数字名片-8.png&quot;&gt;
发挥你的想象力，试着给你的卡片绑定一个独特的中文口令吧！&lt;/p&gt;
&lt;h2&gt;2. 互动游戏&lt;/h2&gt;
&lt;p&gt;既然有了口令。那么是不是可以把这次分享互动变得更加有趣？
我们为此给卡片增加了隐私功能，你可以设置卡片仅可以通过口令发现。
&lt;img src=&quot;https://links.haoyou.tech/Drawing_bed/Slide_16_9_-_16.png&quot; alt=&quot;好有链接 数字名片-9.png&quot;&gt;
那么此时，口令分享就成了一场寻宝游戏：将这张卡片的谜底，制作成下一张卡片的口令。用口令将不同的卡片关联起来，引导对方逐步探索，在一场轻量的互动游戏中更好的了解你。&lt;/p&gt;
&lt;h1&gt;浏览器插件（即将上线）&lt;/h1&gt;
&lt;p&gt;为了方便你更方便的获取分享的内容，我们为『好有链接』开发了浏览器插件。
在插件的搜索框中输入你获得的口令，对应的卡片就会被展示出来，并且可以快速访问其中的内容和链接。
&lt;img src=&quot;https://links.haoyou.tech/Drawing_bed/Vivaldi.gif&quot; alt=&quot;gif&quot;&gt;&lt;/p&gt;
&lt;h1&gt;使用方法&lt;/h1&gt;
&lt;p&gt;目前，『好有链接』只上线了微信小程序版本。
如果你想体验它，可以在小程序中搜索『好有链接 数字名片』，或者扫描下方的小程序码。
&lt;img src=&quot;https://links.haoyou.tech/Drawing_bed/Slide_16_9_-_25.png&quot; alt=&quot;好有链接 数字名片-11.png&quot;&gt;&lt;/p&gt;
&lt;h1&gt;我们的计划&lt;/h1&gt;
&lt;p&gt;『好有链接 数字名片』是我们第一个正式的产品，我们还在慢慢的打磨并优化它。
接下来我们会：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;完善合集功能&lt;/li&gt;
&lt;li&gt;上线『好有链接 数字名片』的网页版&lt;/li&gt;
&lt;li&gt;可以自定义属于你的短链 / 二维码&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;如果你想要帮助我们完善它&lt;/strong&gt;
点击这里，可以给『好有链接 数字名片』提意见。
点击这里，可以关注我们的最新进度。
点击这里，访问我们的官网。
如果你想加入我们的用户群，可以在『好有链接 数字名片』中输入口令『好多用户』。&lt;/p&gt;
&lt;p&gt;————————————————
原文作者：q_jin
转自链接：&lt;a href=&quot;https://learnku.com/laravel/t/90417&quot;&gt;https://learnku.com/laravel/t/90417&lt;/a&gt;
版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请保留以上作者信息和原文链接。&lt;/p&gt;
&lt;/div&gt;</description><author>qjin33</author><pubDate>Sun, 20 Jul 2025 07:15:45 GMT</pubDate></item></channel></rss>