Arbitrum Stylus 合约实战 :Rust 实现 ERC20
在《Arbitrum Stylus 深入解析与 Rust 合约部署实战》篇中,我们深入探讨了 Arbitrum Stylus 的核心技术架构,包括其 MultiVM 机制、Rust 合约开发环境搭建,以及通过 cargo stylus 实现简单计数器合约的部署与测试。Stylus 作为 Arbitrum Nitro 的升级,允许开发者使用 Rust、C++ 等语言编写高效的 WebAssembly(WASM)合约,显著降低了 Gas 成本并提升了性能。本文将更进一步,使用 Rust 在 Stylus 上实现 ERC20 标准合约,并在 Arbitrum Sepolia 上完成部署实战,带您从代码到上链一步到位
1. 前置准备:开发环境与工具链
在开始编写 ERC20 合约之前,确保开发环境已正确配置,在我的《Arbitrum Stylus 深入解析与rust合约部署实战》中,已经有这段内容了,也可以移步到那里先配置好环境:
- Rust 工具链:安装 Rust 和 Cargo
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
- cargo-stylus 是 Stylus 合约开发的 CLI 工具,用于编译、检查和部署
cargo install --force cargo-stylus
- WASM (WebAssembly) 设置 WASM 作为 Rust 编译器的构建目标,可以看到这里执行的第三个和第四个命令有重叠的地方,官方文档是只需要执行第四个命令,但是经过我的实践,有可能会报错,提示说需要执行第三个命令,看过我上一篇《Arbitrum Stylus 深入解析与rust合约部署实战》的观众就会知道有这个问题, 所以为了保险起见,这里一并执行了
rustup install 1.81
rustup default 1.81
rustup target add wasm32-unknown-unknown
rustup target add wasm32-unknown-unknown --toolchain 1.81
- 安装好docker并启动
2. ERC20 合约:设计与实现
ERC20 是代币标准,支持转账、余额查询等功能,关于ERC协议,我会专门出一期来讲,这里就不展开讲了。这里我们将实现一个简单的 ERC20 代币,我们先来创建项目:
cargo stylus new stylus-tokens
然后在 vscode 中打开项目,我是在 wsl 中,直接执行 code . 就OK。然后,我们修改rust-toolchain.toml 中的版本 为 1.81.0
接下来我们在src 下面创建 erc20.rs 文件,并复制或者手敲一遍我给的代码,在代码中,每一行我都加上了详细的注释:
// 引入 alloc 模块中的 String 类型,用于动态字符串
use alloc::string::String;
// 引入 alloy_primitives 库中的 Address 和 U256 类型,用于处理以太坊地址和256位无符号整数
use alloy_primitives::{Address, U256};
// 引入 alloy_sol_types 库中的 sol 宏,用于定义 Solidity 风格的数据结构和事件
use alloy_sol_types::sol;
// 引入 PhantomData,用于在泛型中占位,标记类型但不实际存储数据
use core::marker::PhantomData;
// 引入 stylus_sdk 的 msg evm 和 prelude 模块,提供以太坊虚拟机交互和消息处理功能
use stylus_sdk::{evm, msg, prelude::*};// 定义 Erc20Params 特质,用于指定 ERC20 代币的静态参数
pub trait Erc20Params {const NAME: &'static str; // 代币名称,静态字符串const SYMBOL: &'static str; // 代币符号,静态字符串const DECIMALS: u8; // 代币小数位数
}// 使用 sol_storage 宏定义 Solidity 风格的存储结构
sol_storage! {// 定义泛型结构体 Erc20,T 需实现 Erc20Params 特质pub struct Erc20<T> {// 地址到余额的映射,存储每个地址的代币余额mapping(address => uint256) balances;// 地址到授权额度的映射,记录每个地址对其他地址的代币授权mapping(address => mapping(address => uint256)) allowances;// 代币总供应量uint256 total_supply;// 占位符,确保泛型 T 被使用但不占用存储空间PhantomData<T> phantom;}
}// 使用 sol 宏定义 Solidity 风格的事件和错误
sol! {// 定义 Transfer 事件,记录代币转账信息event Transfer(address indexed from, address indexed to, uint256 value);// 定义 Approval 事件,记录代币授权信息event Approval(address indexed owner, address indexed spender, uint256 value);// 定义错误:余额不足error InsufficientBalance(address from, uint256 have, uint256 want);// 定义错误:授权额度不足error InsufficientAllowance(address owner, address spender, uint256 have, uint256 want);
}// 标记 Erc20Error 为 Solidity 风格的错误类型
#[derive(SolidityError)]// 定义 ERC20 错误枚举
pub enum Erc20Error {// 余额不足错误InsufficientBalance(InsufficientBalance),// 授权额度不足错误InsufficientAllowance(InsufficientAllowance),
}// 为 Erc20 结构体实现方法,T 需实现 Erc20Params 特质
impl<T: Erc20Params> Erc20<T> {// 内部转账函数,执行代币转账逻辑pub fn _transfer(&mut self, from: Address, to: Address, value: U256) -> Result<(), Erc20Error> {// 获取发送者余额的 setterlet mut sender_balance = self.balances.setter(from);// 获取发送者的当前余额let old_sender_balance = sender_balance.get();if old_sender_balance < value {// 检查发送者余额是否足够return Err(Erc20Error::InsufficientBalance(InsufficientBalance {// 返回余额不足错误from, // 发送者地址have: old_sender_balance, // 当前余额want: value, // 所需金额}));}// 扣除发送者余额sender_balance.set(old_sender_balance - value);// 获取接收者余额的 setterlet mut to_balance = self.balances.setter(to);// 计算接收者的新余额let new_to_balance = to_balance.get() + value;// 更新接收者余额to_balance.set(new_to_balance);// 记录转账事件到 EVM 日志evm::log(Transfer { from, to, value });Ok(())}// 铸造代币函数pub fn mint(&mut self, address: Address, value: U256) -> Result<(), Erc20Error> {// 获取目标地址余额的 setterlet mut balance = self.balances.setter(address);// 计算新余额let new_balance = balance.get() + value;// 更新目标地址余额balance.set(new_balance);// 增加总供应量self.total_supply.set(self.total_supply.get() + value);// 记录铸造事件(从零地址转账)evm::log(Transfer {from: Address::ZERO, // 零地址表示铸造to: address, // 目标地址value, // 铸造数量});Ok(())}// 销毁代币函数pub fn burn(&mut self, address: Address, value: U256) -> Result<(), Erc20Error> {// 获取目标地址余额的 setterlet mut balance = self.balances.setter(address);// 获取当前余额let old_balance = balance.get();if old_balance < value {// 检查余额是否足够销毁return Err(Erc20Error::InsufficientBalance(InsufficientBalance {// 返回余额不足错误from: address, // 目标地址have: old_balance, // 当前余额want: value, // 所需销毁金额}));}// 扣除余额balance.set(old_balance - value);// 减少总供应量self.total_supply.set(self.total_supply.get() - value);// 记录销毁事件(转账到零地址)evm::log(Transfer {from: address, // 目标地址to: Address::ZERO, // 零地址表示销毁value, // 销毁数量});Ok(())}
}// 标记以下方法为公开,暴露给外部调用
#[public]
// 为 Erc20 实现公开方法
impl<T: Erc20Params> Erc20<T> {// 返回代币名称pub fn name() -> String {// 将静态名称转换为 StringT::NAME.into()}// 返回代币符号pub fn symbol() -> String {// 将静态符号转换为 StringT::SYMBOL.into()}// 返回代币小数位数pub fn decimals() -> u8 {// 返回静态小数位数T::DECIMALS}// 返回代币总供应量pub fn total_supply(&self) -> U256 {// 获取存储中的总供应量self.total_supply.get()}// 查询指定地址的余额pub fn balance_of(&self, owner: Address) -> U256 {// 从映射中获取余额self.balances.get(owner)}// 转账函数pub fn transfer(&mut self, to: Address, value: U256) -> Result<bool, Erc20Error> {// 调用内部转账函数,从调用者转账self._transfer(msg::sender(), to, value)?;Ok(true)}// 授权转账函数,允许 spender 从 from 地址转账pub fn transfer_from(&mut self,from: Address,to: Address,value: U256,) -> Result<bool, Erc20Error> {// 获取 from 地址的授权映射let mut sender_allowances = self.allowances.setter(from);// 获取调用者的授权额度let mut allowance = sender_allowances.setter(msg::sender());// 获取当前授权额度let old_allowance = allowance.get();// 检查授权额度是否足够if old_allowance < value {// 返回授权不足错误return Err(Erc20Error::InsufficientAllowance(InsufficientAllowance {owner: from, // 拥有者地址spender: msg::sender(), // 花费者地址have: old_allowance, // 当前授权额度want: value, // 所需授权额度}));}// 扣除授权额度allowance.set(old_allowance - value);// 执行转账self._transfer(from, to, value)?;Ok(true)}// 授权函数,允许 spender 花费指定金额pub fn approve(&mut self, spender: Address, value: U256) -> bool {// 设置授权额度self.allowances.setter(msg::sender()).insert(spender, value);// 记录授权事件evm::log(Approval {owner: msg::sender(), // 授权者地址spender, // 被授权者地址value, // 授权金额});true}// 查询授权额度pub fn allowance(&self, owner: Address, spender: Address) -> U256 {// 从映射中获取指定授权额度self.allowances.getter(owner).get(spender)}
}
接着在 src 文件夹 中创建 lib.rs:
// 条件编译属性:除非启用 export-abi 或 test 功能,否则不生成 main 函数
#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
// 引入 alloc 模块,支持动态内存分配
extern crate alloc;
// 引入 erc20 模块,包含 ERC20 代币逻辑
mod erc20;// 从 erc20 模块导入 Erc20 结构体、错误类型和参数特质
use crate::erc20::{Erc20, Erc20Error, Erc20Params};
// 引入 Address 和 U256 类型
use alloy_primitives::{Address, U256};
// 引入 stylus_sdk 的消息处理和预定义功能
use stylus_sdk::{msg, prelude::*};// 定义 StylusTokenParams 结构体,用于指定代币参数
struct StylusTokenParams;
// 为 StylusTokenParams 实现 Erc20Params 特质
impl Erc20Params for StylusTokenParams {const NAME: &'static str = "StylusToken";const SYMBOL: &'static str = "STK";// 代币小数位数:18const DECIMALS: u8 = 18;
}// 使用 sol_storage 宏定义存储结构
sol_storage! {// 标记 StylusToken 为合约入口点#[entrypoint]// 定义 StylusToken 结构体struct StylusToken {// 标记 erc20 字段为借用,继承 Erc20 功能#[borrow]// 嵌入 Erc20 结构体,使用 StylusTokenParams 参数Erc20<StylusTokenParams> erc20;}
}// 标记以下方法为公开
#[public]
// 继承 Erc20<StylusTokenParams> 的方法
#[inherit(Erc20<StylusTokenParams>)]
// 为 StylusToken 实现方法
impl StylusToken {// 铸造代币到调用者地址pub fn mint(&mut self, value: U256) -> Result<(), Erc20Error> {// 调用 Erc20 的 mint 方法self.erc20.mint(msg::sender(), value)?;Ok(())}// 铸造代币到指定地址pub fn mint_to(&mut self, to: Address, value: U256) -> Result<(), Erc20Error> {// 调用 Erc20 的 mint 方法self.erc20.mint(to, value)?;Ok(())}// 销毁调用者的代币pub fn burn(&mut self, value: U256) -> Result<(), Erc20Error> {// 调用 Erc20 的 burn 方法self.erc20.burn(msg::sender(), value)?;Ok(())}
}
接着是 main.rs 中的内容:
// 条件编译属性:除非启用 test 或 export-abi 功能,否则不生成 main 函数
#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]// 条件编译:当 test 和 export-abi 均未启用时
#[cfg(not(any(test, feature = "export-abi")))] // 禁止名称修饰,确保函数名在编译后保持不变
#[no_mangle]
// 定义空的 main 函数,用于合约入口
pub extern "C" fn main() {} // 条件编译:当启用 export-abi 功能时
#[cfg(feature = "export-abi")]
// 定义 main 函数,用于导出 ABI
fn main() { // 调用 print_abi 函数,生成 Solidity ABI,指定许可证和 Solidity 版本stylus_tokens::print_abi("MIT-OR-APACHE-2.0", "pragma solidity ^0.8.23;");
}
Cargo.toml 中的配置:
[package]
name = "stylus_tokens"
version = "0.1.11"
edition = "2021"
license = "MIT OR Apache-2.0"
homepage = "https://github.com/OffchainLabs/stylus-hello-world"
repository = "https://github.com/OffchainLabs/stylus-hello-world"
keywords = ["arbitrum", "ethereum", "stylus", "alloy"]
description = "Stylus tokens example"[dependencies]
alloy-primitives = "=0.8.20"
alloy-sol-types = "=0.8.20"
mini-alloc = "0.4.2"
stylus-sdk = "0.8.0"
hex = "0.4.3"
dotenv = "0.15.0"[dev-dependencies]
tokio = { version = "1.12.0", features = ["full"] }
ethers = "2.0"
eyre = "0.6.8"[features]
export-abi = ["stylus-sdk/export-abi"]
debug = ["stylus-sdk/debug"][[bin]]
name = "stylus_tokens"
path = "src/main.rs"[lib]
crate-type = ["lib", "cdylib"][profile.release]
codegen-units = 1
strip = true
lto = true
panic = "abort"
opt-level = "s"
3. 合约部署上链并mint代币
一切准备就绪之后,我们来编译并且在链上验证我们的代码:
cargo stylus check -e https://sepolia-rollup.arbitrum.io/rpc
我们将一些参数导出成变量
export ARB_RPC_URL=https://sepolia-rollup.arbitrum.io/rpc
export PRIVATE_KEY=你的私钥
然后我们 可以来估算部署合约所需的 gas,这一个步骤不是必需的:
cargo stylus deploy --endpoint=$ARB_RPC_URL --private-key=$PRIVATE_KEY --estimate-gas
OK,开始部署:
cargo stylus deploy --endpoint=$ARB_RPC_URL --private-key=$PRIVATE_KEY
到这里已经部署成功,可以看到合约地址与交易hash,接下来我们开始铸造代币,如果你没有安装 foundry,(foundry 我会出一期详细的教程),请参考我的《Arbitrum Stylus 深入解析与rust合约部署实战》中的方式,导出ABI,然后在 remix 中去操作,这里我使用 foundry cast 命令去mint 代币:
cast send --rpc-url $ARB_RPC_URL --private-key $PRIVATE_KEY 0xb032fb53175b9c24ac157f4a7896ad200fd93468 "mint(uint256)" 100000000000000000000000000
可以看到我成功mint了一亿枚代币,因为有18位小数,所以在你想要mint的数量后面,再加上18个0,0xb032fb53175b9c24ac157f4a7896ad200fd93468 是合约的地址,到时候替换成你们部署成功的合约地址,我们去钱包导入代币,看看代币有没有到账:
可以看到我们代币已经到账了,接下来演示使用命令查看某个地址的代币余额:
OK,如果你走到了这里,恭喜你,你已经完成了 使用 Rust 在 Stylus 上实现 ERC20 合约,重复是最好的老师,希望大家多多练习,后面我也会继续更新系列教程,我是红烧6,关注我,带你上车 web3!
Arbitrum官方文档:官方文档
stylus 官方示例:stylus-by-example
代码仓库:stylus-tokens
相关文章:

Arbitrum Stylus 合约实战 :Rust 实现 ERC20
在《Arbitrum Stylus 深入解析与 Rust 合约部署实战》篇中,我们深入探讨了 Arbitrum Stylus 的核心技术架构,包括其 MultiVM 机制、Rust 合约开发环境搭建,以及通过 cargo stylus 实现简单计数器合约的部署与测试。Stylus 作为 Arbitrum Nitr…...
电脑故障基础知识
1.1 了解电脑故障 分类:分为软件故障(系统感染病毒、程序错误)和硬件故障(硬件物理损坏、接触不良)。 原因:人为操作失误、病毒破坏、工作环境恶劣(高温 / 灰尘)、硬件老化。 准备工…...
12.2Swing中JButton简单分析
JButton 的继承结构 public class JButton extends AbstractButton implements Accessible AbstractButton 是所有 Swing 按钮类(如 JToggleButton, JRadioButton, JCheckBox)的基类。它封装了按钮的核心逻辑:图标、文本、边框、动作事件等…...

内存管理--《Hello C++ Wrold!》(8)--(C/C++)--深入剖析new和delete的使用和底层实现
文章目录 前言C/C内存分布new和deletenew和delete的底层定位new表达式 内存泄漏作业部分 前言 在C/C编程中,内存管理是理解程序运行机制的核心基础,也是开发高效、稳定程序的关键。无论是局部变量的存储、动态内存的分配,还是对象生命周期的…...
JavaScript性能优化实战指南(详尽分解版)
JavaScript性能优化实战指南 一、加载优化 减少HTTP请求 // 合并CSS/JS文件 // 使用雪碧图CSS Sprites .icon {background-image: url(sprites.png);background-position: -20px 0; }代码分割与懒加载 // 动态导入模块 button.addEventListener(click, async () > {cons…...
从 AMQP 到 RabbitMQ:核心组件设计与工作原理(一)
一、引言 ** 在当今分布式系统盛行的时代,消息队列作为一种关键的中间件技术,承担着系统间异步通信、解耦和削峰填谷的重要职责。AMQP(Advanced Message Queuing Protocol)作为一种高级消息队列协议,为消息队列的实现…...

Java进阶---JVM
JVM概述 JVM作用: 负责将字节码翻译为机器码,管理运行时内存 JVM整体组成部分: 类加载系统(ClasLoader):负责将硬盘上的字节码文件加载到内存中 运行时数据区(RuntimeData Area):负责存储运行时各种数据 执行引擎(Ex…...
鸿蒙OSUniApp离线优先数据同步实战:打造无缝衔接的鸿蒙应用体验#三方框架 #Uniapp
UniApp离线优先数据同步实战:打造无缝衔接的鸿蒙应用体验 最近在开发一个面向鸿蒙生态的UniApp应用时,遇到了一个有趣的挑战:如何在网络不稳定的情况下保证数据的实时性和可用性。经过一番探索和实践,我们最终实现了一套行之有效…...
地震资料裂缝定量识别——学习计划
学习计划 地震资料裂缝定量识别——理解常规采集地震裂缝识别方法纵波各向异性方法蚁群算法相干体及倾角检测方法叠后地震融合属性方法裂缝边缘检测方法 非常规采集地震裂缝识别方法P-S 转换波方法垂直地震剖面方法 学习计划 地震资料裂缝定量识别——理解 地震资料裂缝识别&a…...

C++ 检查一条线是否与圆接触或相交(Check if a line touches or intersects a circle)
给定一个圆的圆心坐标、半径 > 1 的圆心坐标以及一条直线的方程。任务是检查给定的直线是否与圆相交。有三种可能性: 1、线与圆相交。 2、线与圆相切。 3、线在圆外。 注意:直线的一般方程是 a*x b*y c 0,因此输入中只给出常数 a、b、…...
23. Merge k Sorted Lists
目录 题目描述 方法一、k-1次两两合并 方法二、分治法合并 方法三、使用优先队列 题目描述 23. Merge k Sorted Lists 方法一、k-1次两两合并 选第一个链表作为结果链表,每次将后面未合并的链表合并到结果链表中,经过k-1次合并,即可得到…...
每日算法刷题计划Day20 6.2:leetcode二分答案3道题,用时1h20min
9.3048.标记所有下标的最早秒数(中等) 3048. 标记所有下标的最早秒数 I - 力扣(LeetCode) 思想 1.给你两个下标从 1 开始的整数数组 nums 和 changeIndices ,数组的长度分别为 n 和 m 。 一开始,nums 中所有下标都是未标记的&a…...
Spring Security安全实践指南
安全性的核心价值 用户视角的数据敏感性认知 从终端用户角度出发,每个应用程序都涉及不同级别的数据敏感度。以电子邮件服务与网上银行为例:前者内容泄露可能仅造成隐私困扰,而后者账户若被操控将直接导致财产损失。这种差异体现了安全防护需要分级实施的基本原则: // 伪…...

Unity + HybirdCLR热更新 入门篇
官方文档 HybridCLR | HybridCLRhttps://hybridclr.doc.code-philosophy.com/docs/intro 什么是HybirdCLR? HybridCLR(原名 huatuo)是一个专为 Unity 项目设计的C#热更新解决方案,它通过扩展 IL2CPP 运行时,使其支持动态加载和…...
QuickBASIC QB64 支持 64 位系统和跨平台Linux/MAC OS
QuickBASIC 的现代继任者 QB64 已发展成为一个功能强大的开源项目,支持 64 位系统和跨平台开发。以下是详细介绍: 项目首页 - QB64pe:The QB64 Phoenix Edition Repository - GitCode https://gitcode.com/gh_mirrors/qb/QB64pe 1. QB64 概述 官网&am…...

ElasticSearch迁移至openGauss
Elasticsearch 作为一种高效的全文搜索引擎,广泛应用于实时搜索、日志分析等场景。而 openGauss,作为一款企业级关系型数据库,强调事务处理与数据一致性。那么,当这两者的应用场景和技术架构发生交集时,如何实现它们之…...

【C语言极简自学笔记】项目开发——扫雷游戏
一、项目概述 1.项目背景 扫雷是一款经典的益智游戏,由于它简单而富有挑战性的玩法深受人们喜爱。在 C 语言学习过程中,开发扫雷游戏是一个非常合适的实践项目,它能够综合运用 C 语言的多种基础知识,如数组、函数、循环、条件判…...
Global Security Markets 第5章知识点总结
一、章节核心内容概述 《Global Securities Markets》第五章聚焦全球主要证券交易所、关联存管机构及跨境交易实务,重点解析“乘客市场(Passenger Markets)”概念与合规风险,同时涵盖交易费用、监管规则等实操要点。考虑到市场的…...
电子电路:4017计数器工作原理解析
4017是CMOS十进制计数器/分频器,它属于CD4000系列,工作电压范围比较宽,可能3V到15V。我记得它有10个译码输出端,每个输出端依次在高电平和低电平之间循环,可能用于时序控制或者LED显示什么的。 4017内部应该由计数器和译码器两部分组成。计数器部分可能是一个约翰逊计数器…...
Vim 中设置插入模式下输入中文
在 Vim 中设置插入模式下输入中文需要配置输入法切换和 Vim 的相关设置。以下是详细步骤: 1. 确保系统已安装中文输入法 在 Linux 系统中,常用的中文输入法有: IBus(推荐):支持拼音、五笔等Fcitx…...
GitHub 趋势日报 (2025年05月31日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 1153 prompt-eng-interactive-tutorial 509 BillionMail 435 ai-agents-for-begin…...

Maven概述,搭建,使用
一.Maven概述 Maven是Apache软件基金会的一个开源项目,是一个有优秀的项目构建(创建)工具,它用来帮助开发者管理项目中的jar,以及jar之间的依赖关系,完成项目的编译,测试,打包和发布等工作. 我在当前学习阶段遇到过的jar文件: MySQL官方提供的JDBC驱动文件,通常命名为mysql-…...
基于大模型的数据库MCP Server设计与实现
基于大模型的数据库MCP Server设计与实现 引言 随着大语言模型(LLM, Large Language Model)能力的不断提升,AI Agent(智能体)正在从简单的对话问答,向更复杂的自动化任务执行和业务流程管理演进。在企业和开发者的实际需求中,数据库操作是最常见、最核心的场景之一。如…...
【前端】macOS 的 Gatekeeper 安全机制阻止你加载 bcrypt_lib.node 文件 如何解决
这个弹窗是 macOS 的 Gatekeeper 安全机制阻止你加载 bcrypt_lib.node 文件,因为它不是 Apple 签名的文件。 你想 “忽视” 它,其实是让系统允许这个 .node 原生模块运行,解决方式如下: sudo xattr -d com.apple.quarantine nod…...

Unity 环境搭建
Unity是一款游戏引擎,可用于开发各种类型的游戏和交互式应用程序。它由Unity Technologies开发,并在多个平台上运行,包括Windows、macOS、Linux、iOS、Android和WebGL。Unity也支持虚拟现实(VR)和增强现实(AR)技术,允许用户构建逼…...
【入门】【练9.3】 加四密码
| 时间限制:C/C 1000MS,其他语言 2000MS 内存限制:C/C 64MB,其他语言 128MB 难度:中等 分数:100 OI排行榜得分:12(0.1*分数2*难度) 出题人:root | 描述 要将 China…...

使用 SASS 与 CSS Grid 实现鼠标悬停动态布局变换效果
最终效果概述 页面为 3x3 的彩色格子网格;当鼠标悬停任意格子,所在的行和列被放大;使用纯 CSS 实现,无需 JavaScript;利用 SASS 的模块能力大幅减少冗余代码。 HTML 结构 我们使用非常基础的结构,9 个 .i…...
Node.js 全栈开发方向常见面试题
Node.js 全栈开发”方向的面试题**,这类岗位通常包括: 后端:Node.js(Express/Nest)、数据库、REST API、安全、部署等 前端:React/Vue(部分可能含 Next.js)、API 调用、状态管理等 …...

Spring如何实现组件扫描与@Component注解原理
Spring如何实现组件扫描与Component注解原理 注解配置与包扫描的实现机制一、概述:什么是注解配置与包扫描?二、处理流程概览三、注解定义ComponentScope 四、核心代码结构1. ClassPathScanningCandidateComponentProvider2. ClassPathBeanDefinitionSca…...
历年四川大学计算机保研上机真题
2025四川大学计算机保研上机真题 2024四川大学计算机保研上机真题 2023四川大学计算机保研上机真题 在线测评链接:https://pgcode.cn/school 分数求和 题目描述 有一分数序列: 2 / 1 2/1 2/1, 3 / 2 3/2 3/2, 5 / 3 5/3 5/3, 8 / 5 8/5 8/5, 13 /…...