Solidity定时任务:让你的合约按点做事稳如泰山

Solidity定时任务!在区块链上,智能合约要想自动干活,比如每天分红、定期锁仓释放,或者按时更新数据,咋整?以太坊可没内置定时器!定时任务得靠外部触发或预言机来搞定。这篇干货从基础的时间检查到ChainlinkKeeper、外部调用触发,再到防重入和权限控制,配合OpenZeppelin和Ha

Solidity定时任务!在区块链上,智能合约要想自动干活,比如每天分红、定期锁仓释放,或者按时更新数据,咋整?以太坊可没内置定时器!定时任务得靠外部触发或预言机来搞定。这篇干货从基础的时间检查到Chainlink Keeper、外部调用触发,再到防重入和权限控制,配合OpenZeppelin和Hardhat测试,带你一步步打造准时又安全的定时机制。

定时任务的核心概念

先搞清楚几个关键点:

  • 定时任务:合约在特定时间或周期执行特定逻辑,如分红、数据更新。
  • 以太坊限制:EVM无内置定时器,需外部触发(如用户、预言机)。
  • 实现方式
    • 时间戳检查:用block.timestamp判断时间,简单但依赖外部调用。
    • Chainlink Keeper:去中心化自动化网络,定时触发任务。
    • 外部脚本:用Hardhat或脚本手动触发。
  • 安全风险
    • 时间戳操控:矿工可能微调block.timestamp
    • 权限控制:谁能触发任务?没限制可能被滥用。
    • 重入攻击:外部调用可能引发重入。
    • Gas限制:复杂任务可能耗尽Gas。
  • 工具
    • Solidity 0.8.x:安全的时间戳和数学操作。
    • Chainlink Keeper:可靠的去中心化触发。
    • OpenZeppelin:权限和安全库。
    • Hardhat:测试和调试任务。
  • 事件:记录任务执行,便于链上追踪。

咱们用Solidity 0.8.20,结合Chainlink Keeper、OpenZeppelin和Hardhat,从基础到复杂,逐一实现定时任务。

环境准备

用Hardhat搭建开发环境,集成Chainlink。

mkdir timed-task-demo
cd timed-task-demo
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox @openzeppelin/contracts @chainlink/contracts
npm install ethers

初始化Hardhat:

npx hardhat init

选择TypeScript项目,安装依赖:

npm install --save-dev ts-node typescript @types/node @types/mocha

目录结构:

timed-task-demo/
├── contracts/
│   ├── BasicTimedTask.sol
│   ├── KeeperTimedTask.sol
│   ├── MultiSigTimedTask.sol
│   ├── ConditionalTimedTask.sol
│   ├── RewardTimedTask.sol
├── scripts/
│   ├── deploy.ts
├── test/
│   ├── TimedTask.test.ts
├── hardhat.config.ts
├── tsconfig.json
├── package.json

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "strict": true,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "outDir": "./dist",
    "rootDir": "./"
  },
  "include": ["hardhat.config.ts", "scripts", "test"]
}

hardhat.config.ts

import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

const config: HardhatUserConfig = {
  solidity: {
    version: "0.8.20",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  },
  networks: {
    hardhat: {
      chainId: 1337,
    },
  },
};

export default config;

跑本地节点:

npx hardhat node

基础时间戳检查

block.timestamp实现简单的定时任务。

合约代码

contracts/BasicTimedTask.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";

contract BasicTimedTask is Ownable {
    uint256 public lastExecution;
    uint256 public interval = 1 days;
    uint256 public reward = 1 ether;
    mapping(address => uint256) public userRewards;

    event TaskExecuted(address indexed caller, uint256 timestamp);

    constructor() Ownable() {
        lastExecution = block.timestamp;
    }

    function executeTask() public {
        require(block.timestamp >= lastExecution + interval, "Too soon");
        lastExecution = block.timestamp;
        userRewards[msg.sender] += reward;
        emit TaskExecuted(msg.sender, block.timestamp);
    }

    function withdrawReward() public {
        uint256 amount = userRewards[msg.sender];
        require(amount > 0, "No rewards");
        userRewards[msg.sender] = 0;
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }

    function deposit() public payable onlyOwner {}
}

解析

  • 逻辑
    • interval:任务间隔(1天)。
    • lastExecution:记录上次执行时间。
    • executeTask:检查block.timestamp,执行任务,奖励调用者。
    • withdrawReward:提取奖励。
    • deposit:为合约充值ETH。
  • 安全特性
    • block.timestamp检查防止过早执行。
    • onlyOwner限制充值。
    • 事件记录执行。
  • 问题
    • 依赖外部调用,需手动触发。
    • block.timestamp可被矿工微调(±15秒)。
    • 无权限控制,任何人可触发。
  • Gas
    • executeTask~30k Gas。
    • withdrawReward~20k Gas。

测试

test/TimedTask.test.ts

import { ethers } from "hardhat";
import { expect } from "chai";
import { BasicTimedTask } from "../typechain-types";

describe("BasicTimedTask", function () {
  let task: BasicTimedTask;
  let owner: any, user1: any;

  beforeEach(async function () {
    [owner, user1] = await ethers.getSigners();
    const TaskFactory = await ethers.getContractFactory("BasicTimedTask");
    task = await TaskFactory.deploy();
    await task.deployed();
    await task.deposit({ value: ethers.utils.parseEther("10") });
  });

  it("should execute task after interval", async function () {
    await ethers.provider.send("evm_increaseTime", [86400]); // 1 day
    await ethers.provider.send("evm_mine", []);
    await expect(task.connect(user1).executeTask())
      .to.emit(task, "TaskExecuted")
      .withArgs(user1.address, await ethers.provider.getBlock("latest").then(b => b.timestamp));
    expect(await task.userRewards(user1.address)).to.equal(ethers.utils.parseEther("1"));
  });

  it("should revert if too soon", async function () {
    await expect(task.connect(user1).executeTask()).to.be.revertedWith("Too soon");
  });

  it("should withdraw rewards", async function () {
    await ethers.provider.send("evm_increaseTime", [86400]);
    await ethers.provider.send("evm_mine", []);
    await task.connect(user1).executeTask();
    const initialBalance = await ethers.provider.getBalance(user1.address);
    await task.connect(user1).withdrawReward();
    expect(await ethers.provider.getBalance(user1.address)).to.be.gt(initialBalance);
  });
});

跑测试:

npx hardhat test
  • 解析
    • 1天后执行任务,奖励1 ETH。
    • 过早执行失败。
    • 奖励可提取,余额更新。
  • 问题:需外部触发,安全性依赖block.timestamp

Chainlink Keeper触发

用Chainlink Keeper实现去中心化定时任务。

合约代码

contracts/KeeperTimedTask.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@chainlink/contracts/src/v0.8/KeeperCompatible.sol";

contract KeeperTimedTask is Ownable, KeeperCompatibleInterface {
    uint256 public lastExecution;
    uint256 public interval = 1 days;
    uint256 public reward = 1 ether;
    mapping(address => uint256) public userRewards;

    event TaskExecuted(address indexed caller, uint256 timestamp);

    constructor() Ownable() {
        lastExecution = block.timestamp;
    }

    function checkUpkeep(bytes calldata) external view override returns (bool upkeepNeeded, bytes memory) {
        upkeepNeeded = block.timestamp >= lastExecution + interval;
        return (upkeepNeeded, bytes(""));
    }

    function performUpkeep(bytes calldata) external override {
        require(block.timestamp >= lastExecution + interval, "Too soon");
        lastExecution = block.timestamp;
        userRewards[msg.sender] += reward;
        emit TaskExecuted(msg.sender, block.timestamp);
    }

    function withdrawReward() public {
        uint256 amount = userRewards[msg.sender];
        require(amount > 0, "No rewards");
        userRewards[msg.sender] = 0;
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }

    function deposit() public payable onlyOwner {}
}

解析

  • 逻辑
    • 继承KeeperCompatibleInterface
    • checkUpkeep:检查是否可执行(间隔1天)。
    • performUpkeep:执行任务,奖励调用者。
    • withdrawReward/deposit:同上。
  • 安全特性
    • Chainlink Keeper去中心化触发,减少依赖。
    • 事件记录执行。
  • 问题
    • 需注册Keeper(需LINK代币)。
    • block.timestamp仍可微调。
  • Gas
    • performUpkeep~35k Gas。
    • Keeper调用需支付LINK。

测试

test/TimedTask.test.ts(add):

import { KeeperTimedTask } from "../typechain-types";

describe("KeeperTimedTask", function () {
  let task: KeeperTimedTask;
  let owner: any, keeper: any;

  beforeEach(async function () {
    [owner, keeper] = await ethers.getSigners();
    const TaskFactory = await ethers.getContractFactory("KeeperTimedTask");
    task = await TaskFactory.deploy();
    await task.deployed();
    await task.deposit({ value: ethers.utils.parseEther("10") });
  });

  it("should check upkeep correctly", async function () {
    await ethers.provider.send("evm_increaseTime", [86400]);
    await ethers.provider.send("evm_mine", []);
    const { upkeepNeeded } = await task.checkUpkeep("0x");
    expect(upkeepNeeded).to.be.true;
  });

  it("should perform upkeep", async function () {
    await ethers.provider.send("evm_increaseTime", [86400]);
    await ethers.provider.send("evm_mine", []);
    await expect(task.connect(keeper).performUpkeep("0x"))
      .to.emit(task, "TaskExecuted")
      .withArgs(keeper.address, await ethers.provider.getBlock("latest").then(b => b.timestamp));
    expect(await task.userRewards(keeper.address)).to.equal(ethers.utils.parseEther("1"));
  });

  it("should revert if too soon", async function () {
    await expect(task.connect(keeper).performUpkeep("0x")).to.be.revertedWith("Too soon");
  });
});
  • 解析
    • checkUpkeep确认任务可执行。
    • performUpkeep成功执行,奖励1 ETH。
    • 过早执行失败。
  • 优势:Chainlink Keeper自动化,适合生产环境。

多签控制定时任务

用多签机制限制任务触发。

合约代码

contracts/MultiSigTimedTask.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";

contract MultiSigTimedTask is Ownable {
    address[] public executors;
    uint256 public required;
    uint256 public transactionCount;
    mapping(uint256 => Transaction) public transactions;
    mapping(uint256 => mapping(address => bool)) public confirmations;
    uint256 public lastExecution;
    uint256 public interval = 1 days;
    uint256 public reward = 1 ether;
    mapping(address => uint256) public userRewards;

    struct Transaction {
        bool executed;
        uint256 confirmationCount;
    }

    event SubmitTask(uint256 indexed txId);
    event ConfirmTask(uint256 indexed txId, address indexed executor);
    event ExecuteTask(uint256 indexed txId, address indexed executor, uint256 timestamp);
    event RevokeConfirmation(uint256 indexed txId, address indexed executor);

    modifier onlyExecutor() {
        bool isExecutor = false;
        for (uint256 i = 0; i < executors.length; i++) {
            if (executors[i] == msg.sender) {
                isExecutor = true;
                break;
            }
        }
        require(isExecutor, "Not executor");
        _;
    }

    constructor(address[] memory _executors, uint256 _required) Ownable() {
        require(_executors.length > 0, "Executors required");
        require(_required > 0 && _required <= _executors.length, "Invalid required");
        executors = _executors;
        required = _required;
        lastExecution = block.timestamp;
    }

    function submitTask() public onlyExecutor {
        uint256 txId = transactionCount++;
        transactions[txId] = Transaction({
            executed: false,
            confirmationCount: 0
        });
        emit SubmitTask(txId);
    }

    function confirmTask(uint256 txId) public onlyExecutor {
        Transaction storage transaction = transactions[txId];
        require(!transaction.executed, "Transaction executed");
        require(!confirmations[txId][msg.sender], "Already confirmed");
        confirmations[txId][msg.sender] = true;
        transaction.confirmationCount++;
        emit ConfirmTask(txId, msg.sender);
        if (transaction.confirmationCount >= required) {
            executeTask(txId);
        }
    }

    function executeTask(uint256 txId) internal {
        Transaction storage transaction = transactions[txId];
        require(!transaction.executed, "Transaction executed");
        require(block.timestamp >= lastExecution + interval, "Too soon");
        transaction.executed = true;
        lastExecution = block.timestamp;
        userRewards[msg.sender] += reward;
        emit ExecuteTask(txId, msg.sender, block.timestamp);
    }

    function revokeConfirmation(uint256 txId) public onlyExecutor {
        Transaction storage transaction = transactions[txId];
        require(!transaction.executed, "Transaction executed");
        require(confirmations[txId][msg.sender], "Not confirmed");
        confirmations[txId][msg.sender] = false;
        transaction.confirmationCount--;
        emit RevokeConfirmation(txId, msg.sender);
    }

    function withdrawReward() public {
        uint256 amount = userRewards[msg.sender];
        require(amount > 0, "No rewards");
        userRewards[msg.sender] = 0;
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }

    function deposit() public payable onlyOwner {}
}

解析

  • 逻辑
    • executorsrequired控制多签。
    • submitTask:提交任务提案。
    • confirmTask:确认提案,达标后执行。
    • executeTask:检查时间,执行任务,奖励调用者。
    • revokeConfirmation:撤销确认。
  • 安全特性
    • 多签防止单人误触发。
    • 时间检查确保间隔。
    • 事件记录提案和执行。
  • 问题
    • 需外部触发,依赖多签成员。
    • 多签增加Gas成本。
  • Gas
    • 提案10k Gas,确认10k Gas,执行~30k Gas。

测试

test/TimedTask.test.ts(add):

import { MultiSigTimedTask } from "../typechain-types";

describe("MultiSigTimedTask", function () {
  let task: MultiSigTimedTask;
  let owner: any, executor1: any, executor2: any, executor3: any;

  beforeEach(async function () {
    [owner, executor1, executor2, executor3] = await ethers.getSigners();
    const TaskFactory = await ethers.getContractFactory("MultiSigTimedTask");
    task = await TaskFactory.deploy([executor1.address, executor2.address, executor3.address], 2);
    await task.deployed();
    await task.deposit({ value: ethers.utils.parseEther("10") });
  });

  it("should execute task with multi-sig", async function () {
    await ethers.provider.send("evm_increaseTime", [86400]);
    await ethers.provider.send("evm_mine", []);
    await task.connect(executor1).submitTask();
    await task.connect(executor2).confirmTask(0);
    await expect(task.connect(executor3).confirmTask(0))
      .to.emit(task, "ExecuteTask")
      .withArgs(0, executor3.address, await ethers.provider.getBlock("latest").then(b => b.timestamp));
    expect(await task.userRewards(executor3.address)).to.equal(ethers.utils.parseEther("1"));
  });

  it("should not execute without enough confirmations", async function () {
    await ethers.provider.send("evm_increaseTime", [86400]);
    await ethers.provider.send("evm_mine", []);
    await task.connect(executor1).submitTask();
    await task.connect(executor2).confirmTask(0);
    expect(await task.userRewards(executor2.address)).to.equal(0);
  });

  it("should allow revoking confirmation", async function () {
    await ethers.provider.send("evm_increaseTime", [86400]);
    await ethers.provider.send("evm_mine", []);
    await task.connect(executor1).submitTask();
    await task.connect(executor2).confirmTask(0);
    await task.connect(executor2).revokeConfirmation(0);
    await task.connect(executor3).confirmTask(0);
    expect(await task.userRewards(executor3.address)).to.equal(0);
  });
});
  • 解析
    • 2/3确认后执行任务,奖励1 ETH。
    • 单人确认不触发任务。
    • 撤销确认阻止执行。
  • 优势:多签增加安全性,适合团队管理。

条件触发定时任务

根据外部条件(如余额)触发任务。

合约代码

contracts/ConditionalTimedTask.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";

contract ConditionalTimedTask is Ownable {
    uint256 public lastExecution;
    uint256 public interval = 1 days;
    uint256 public reward = 1 ether;
    uint256 public minBalance = 10 ether;
    mapping(address => uint256) public userRewards;

    event TaskExecuted(address indexed caller, uint256 timestamp, string reason);

    constructor() Ownable() {
        lastExecution = block.timestamp;
    }

    function executeTask() public {
        require(block.timestamp >= lastExecution + interval, "Too soon");
        require(address(this).balance >= minBalance, "Insufficient balance");
        lastExecution = block.timestamp;
        userRewards[msg.sender] += reward;
        emit TaskExecuted(msg.sender, block.timestamp, "Balance condition met");
    }

    function withdrawReward() public {
        uint256 amount = userRewards[msg.sender];
        require(amount > 0, "No rewards");
        userRewards[msg.sender] = 0;
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }

    function deposit() public payable onlyOwner {}
}

解析

  • 逻辑
    • minBalance:触发需合约余额≥10 ETH。
    • executeTask:检查时间和余额,执行任务。
    • withdrawReward/deposit:同上。
  • 安全特性
    • 条件检查防止无效执行。
    • 事件记录触发原因。
  • 问题
    • 依赖外部调用。
    • 余额条件需谨慎设置。
  • Gas
    • executeTask~35k Gas(含条件检查)。

Test

test/TimedTask.test.ts(add):

import { ConditionalTimedTask } from "../typechain-types";

describe("ConditionalTimedTask", function () {
  let task: ConditionalTimedTask;
  let owner: any, user1: any;

  beforeEach(async function () {
    [owner, user1] = await ethers.getSigners();
    const TaskFactory = await ethers.getContractFactory("ConditionalTimedTask");
    task = await TaskFactory.deploy();
    await task.deployed();
    await task.deposit({ value: ethers.utils.parseEther("15") });
  });

  it("should execute task with sufficient balance", async function () {
    await ethers.provider.send("evm_increaseTime", [86400]);
    await ethers.provider.send("evm_mine", []);
    await expect(task.connect(user1).executeTask())
      .to.emit(task, "TaskExecuted")
      .withArgs(user1.address, await ethers.provider.getBlock("latest").then(b => b.timestamp), "Balance condition met");
    expect(await task.userRewards(user1.address)).to.equal(ethers.utils.parseEther("1"));
  });

  it("should revert if insufficient balance", async function () {
    await task.deposit({ value: ethers.utils.parseEther("5") });
    await ethers.provider.send("evm_increaseTime", [86400]);
    await ethers.provider.send("evm_mine", []);
    await expect(task.connect(user1).executeTask()).to.be.revertedWith("Insufficient balance");
  });
});
  • 解析
    • 余额≥10 ETH时任务执行。
    • 余额不足失败,验证条件。
  • 优势:条件触发增加灵活性。

定时奖励分发

定时分发代币奖励。

合约代码

contracts/RewardTimedTask.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract RewardTimedTask is ERC20, Ownable, ReentrancyGuard {
    uint256 public lastExecution;
    uint256 public interval = 1 days;
    uint256 public reward = 100 * 10**18;
    mapping(address => uint256) public userRewards;

    event TaskExecuted(address indexed caller, uint256 timestamp, uint256 reward);

    constructor() ERC20("RewardToken", "RTK") Ownable() {
        lastExecution = block.timestamp;
        _mint(address(this), 1000000 * 10**decimals());
    }

    function executeTask() public nonReentrant {
        require(block.timestamp >= lastExecution + interval, "Too soon");
        lastExecution = block.timestamp;
        userRewards[msg.sender] += reward;
        _transfer(address(this), msg.sender, reward);
        emit TaskExecuted(msg.sender, block.timestamp, reward);
    }

    function withdrawReward() public nonReentrant {
        uint256 amount = userRewards[msg.sender];
        require(amount > 0, "No rewards");
        userRewards[msg.sender] = 0;
        _transfer(address(this), msg.sender, amount);
    }
}

解析

  • 逻辑
    • 继承ERC20ReentrancyGuard
    • executeTask:定时分发代币奖励,防重入。
    • withdrawReward:提取奖励。
  • 安全特性
    • nonReentrant防止重入攻击。
    • 事件记录分发。
  • 问题
    • 依赖外部触发。
    • 需确保合约有足够代币。
  • Gas
    • executeTask~40k Gas(含转账)。

Test

test/TimedTask.test.ts(add):

import { RewardTimedTask } from "../typechain-types";

describe("RewardTimedTask", function () {
  let task: RewardTimedTask;
  let owner: any, user1: any;

  beforeEach(async function () {
    [owner, user1] = await ethers.getSigners();
    const TaskFactory = await ethers.getContractFactory("RewardTimedTask");
    task = await TaskFactory.deploy();
    await task.deployed();
  });

  it("should execute task and reward tokens", async function () {
    await ethers.provider.send("evm_increaseTime", [86400]);
    await ethers.provider.send("evm_mine", []);
    await expect(task.connect(user1).executeTask())
      .to.emit(task, "TaskExecuted")
      .withArgs(user1.address, await ethers.provider.getBlock("latest").then(b => b.timestamp), ethers.utils.parseEther("100"));
    expect(await task.balanceOf(user1.address)).to.equal(ethers.utils.parseEther("100"));
  });

  it("should withdraw rewards", async function () {
    await ethers.provider.send("evm_increaseTime", [86400]);
    await ethers.provider.send("evm_mine", []);
    await task.connect(user1).executeTask();
    await task.connect(user1).withdrawReward();
    expect(await task.balanceOf(user1.address)).to.equal(ethers.utils.parseEther("200"));
  });
});
  • 解析
    • 任务执行分发100代币。
    • 奖励可累积提取。
  • 优势:代币奖励适合激励机制。

部署脚本

scripts/deploy.ts

import { ethers } from "hardhat";

async function main() {
  const [owner, executor1, executor2, executor3] = await ethers.getSigners();

  const BasicTaskFactory = await ethers.getContractFactory("BasicTimedTask");
  const basicTask = await BasicTaskFactory.deploy();
  await basicTask.deployed();
  console.log(`BasicTimedTask deployed to: ${basicTask.address}`);

  const KeeperTaskFactory = await ethers.getContractFactory("KeeperTimedTask");
  const keeperTask = await KeeperTaskFactory.deploy();
  await keeperTask.deployed();
  console.log(`KeeperTimedTask deployed to: ${keeperTask.address}`);

  const MultiSigTaskFactory = await ethers.getContractFactory("MultiSigTimedTask");
  const multiSigTask = await MultiSigTaskFactory.deploy([executor1.address, executor2.address, executor3.address], 2);
  await multiSigTask.deployed();
  console.log(`MultiSigTimedTask deployed to: ${multiSigTask.address}`);

  const ConditionalTaskFactory = await ethers.getContractFactory("ConditionalTimedTask");
  const conditionalTask = await ConditionalTaskFactory.deploy();
  await conditionalTask.deployed();
  console.log(`ConditionalTimedTask deployed to: ${conditionalTask.address}`);

  const RewardTaskFactory = await ethers.getContractFactory("RewardTimedTask");
  const rewardTask = await RewardTaskFactory.deploy();
  await rewardTask.deployed();
  console.log(`RewardTimedTask deployed to: ${rewardTask.address}`);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

跑部署:

npx hardhat run scripts/deploy.ts --network hardhat
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
天涯学馆
天涯学馆
0x9d6d...50d5
资深大厂程序员,12年开发经验,致力于探索前沿技术!