从 Web2 到 Web3 的思维转换

核心心智模型的转变

作为 Web2 开发者,你需要调整几个根深蒂固的思维习惯:

从"可变"到"追加"

在 Web2 中,数据是可变的。用户修改昵称?UPDATE users SET name = 'NewName' WHERE id = 123

在区块链上,历史是不可变的。用户修改昵称?实际上是创建一条新记录:"用户 123 在区块 #10000 将昵称更新为 NewName"。旧昵称仍然存在于历史中,只是"最新状态"变了。

这类似于 Git 的工作方式:每次提交都是追加的,你可以查看任何历史版本。

从"信任后端"到"验证一切"

在 Web2 中,前端信任后端返回的数据。用户余额是 100 元?后端说是,那就是。

在 Web3 中,"Don't trust, verify"(不要信任,去验证)。任何关键数据都应该从链上验证,因为你的前端可能连接到恶意 RPC 节点。

javascript
// Web2 思维:直接使用 API 返回值 const balance = await api.getBalance(userId); // Web3 思维:从链上获取并验证 const accountInfo = await connection.getAccountInfo(publicKey); const balance = accountInfo.lamports; // 还可以验证账户的 owner、data 结构等

从"服务端签名"到"用户签名"

在 Web2 中,用户的操作由服务器代为执行。用户点击"购买",服务器扣款、发货、记录,用户只需要输入密码授权。

在 Web3 中,每个重要操作都需要用户用私钥签名。服务器(或智能合约)无法代替用户做决定。这意味着用户体验的设计需要根本性的重新思考。

从"回滚"到"补偿"

在 Web2 中,事务出错可以回滚。数据库支持 ROLLBACK,最坏情况下可以恢复备份。

在区块链上,交易一旦上链就不可逆转。如果出了问题,你只能发起"补偿交易"——再发一笔交易来"修正"之前的错误。这要求你在设计时考虑所有边界情况。

开发流程的差异

传统 Web2 工作流

需求 → 设计 → 编码 → 单元测试 → 部署到测试环境 → 集成测试 → 部署到生产 → 监控 → 热修复

Solana 开发工作流

需求 → 设计(特别注意账户结构和安全性) → 编码程序(Rust/Anchor) → 本地测试(solana-test-validator) → 部署到 Devnet → 前端集成测试 → 安全审计(强烈建议) → 部署到 Mainnet → 监控 → 如需升级,通过升级授权发布新版本

关键区别:

  1. 更长的设计阶段:账户结构和权限模型一旦确定,修改成本很高
  2. 更严格的测试:没有"热修复"的奢侈,每次部署都是高风险操作
  3. 安全审计:对于处理真实资产的程序,专业审计几乎是必须的
  4. 渐进式发布:先 Devnet 验证,再小规模 Mainnet 测试,最后全量上线

常见模式映射

用户认证

Web2: JWT Token + Session Web3: 钱包签名验证

javascript
// 前端请求用户签名 const message = "Sign in to MyApp at " + Date.now(); const signature = await wallet.signMessage(Buffer.from(message)); // 后端验证签名 const isValid = nacl.sign.detached.verify( Buffer.from(message), signature, publicKey.toBytes() );

数据存储

Web2: MySQL/PostgreSQL + Redis Web3: 链上账户(关键数据)+ 链下存储(大文件)

// 链上存储(每字节都有成本)

  • 用户余额、NFT 所有权、投票记录

// 链下存储(IPFS/Arweave/传统数据库)

  • 图片、视频、详细描述
  • 在链上存储 hash 或 URI 作为指针

API 调用

Web2: REST API / GraphQL Web3: RPC 调用 + 事件监听

javascript
// 读取数据 const accountInfo = await connection.getAccountInfo(publicKey); // 写入数据(需要交易) const tx = await program.methods.createPost(content).accounts({...}).rpc(); // 监听事件 connection.onAccountChange(publicKey, (accountInfo) => { console.log("Account changed:", accountInfo); });

后台任务

Web2: Cron Job / Message Queue Web3: Clockwork (链上定时器) / 链下 Keeper 服务

javascript
// 链下 Keeper:监控链上状态,必要时发起交易 while (true) { const state = await program.account.auction.fetch(auctionPda); if (state.endTime < Date.now() && !state.settled) { await program.methods.settleAuction().accounts({...}).rpc(); } await sleep(1000); }

常见陷阱与最佳实践

陷阱 1:忽视交易原子性

错误做法:

javascript
// 危险!两个独立交易不是原子的 await program.methods.withdraw(amount).rpc(); await program.methods.deposit(amount, otherPool).rpc(); // 如果第二笔失败,资金可能丢失

正确做法:

javascript
// 把相关操作放在同一个交易中 const tx = new Transaction(); tx.add(withdrawInstruction); tx.add(depositInstruction); await sendAndConfirmTransaction(connection, tx, [user]);

陷阱 2:前端暴露敏感逻辑

在 Web2 中,我们习惯把业务逻辑放在后端。但在纯 Web3 应用中,很多逻辑必须放在前端或链上。

关键原则:

  • 所有安全检查必须在链上程序中完成
  • 前端只是帮助用户构造交易,不应该依赖前端来保证安全性
  • 假设任何人都可以直接调用你的程序,绕过你的前端

陷阱 3:低估 Gas 成本

即使 Solana 的交易费很低,频繁的链上操作也会累积。

优化策略:

  • 批量操作:把多个小操作合并成一个大交易
  • 链下计算:只把最终结果上链
  • 选择性上链:不是所有数据都需要上链

陷阱 4:忽视程序升级

部署后发现 bug?在 Web2 中,修改代码重新部署就好。在 Solana 上,你需要:

  1. 预先设置升级授权(upgrade authority)
  2. 部署新程序版本
  3. 通过升级指令更新程序账户

如果没有设置升级授权(或者主动放弃),程序将永远无法修改。

最佳实践清单

  1. 账户设计
    • 预先规划账户结构,考虑未来扩展
    • 使用 PDA 实现确定性寻址
    • 合理划分数据,避免单个账户过大
  2. 安全性
    • 所有输入都要验证
    • 检查所有账户的 owner 和权限
    • 使用成熟的库(如 Anchor)减少底层错误
    • 重要项目进行专业审计
  3. 用户体验
    • 提供清晰的交易预览
    • 处理好网络错误和重试逻辑
    • 显示交易状态(pending/confirmed/finalized)
  4. 可维护性
    • 保留升级能力(除非有充分理由放弃)
    • 编写完善的测试
    • 记录账户结构和安全性
JSPlayground
EDITOR ACTIVE
Initializing JS Environment...

思维范式转换器

Web2: Mutable Database
users_table
id: 1, name:"Alice"

更新操作会覆盖旧数据。历史记录丢失,除非专门备份。

Web3: Append-Only Log
#1000Set Name: "Alice"

状态变更是追加新记录。历史永久保存,可追溯。