ホームページ > テクノロジー周辺機器 > IT業界 > ビルディングイーサリアムダップ:カスタムトークンでの投票

ビルディングイーサリアムダップ:カスタムトークンでの投票

Christopher Nolan
リリース: 2025-02-16 10:35:10
オリジナル
196 人が閲覧しました

Building Ethereum DApps: Voting with Custom Tokens

コアポイント

  • エントリの削除やブラックリストのアドレスを削除し、分散型ガバナンスと運用上の柔軟性を高めるなど、提案投票のためにDAOのカスタムトークンを使用します。
  • 操作中にトークンの所有権を検証し、投票中にトークンをロックすることを要求する強力な投票メカニズムを実装して、操作を防ぎ、コミットメントを確保します。
  • 整然としたDAO運用を維持するための構造化された期限と実装基準を備えた提案および投票システムを設計します。
  • 提案の作成をメンバーのみに制限し、DAOの所有者によって制御される緊急削除機能を有効にすることにより、潜在的なセキュリティの問題に対処します。
  • イーサリアムに大規模なスマート契約を展開するための技術的課題とソリューションを探り、ガスコストを管理し、展開を成功させるためのコード最適化の重要性を強調します。
このチュートリアルシリーズの5番目の部分では、イーサリアムを使用してDAPPの構築を紹介します。ストーリーにコンテンツを追加することと、DAOからトークンを購入し、参加者のストーリーにコンテンツを提出する機能を追加する方法について説明します。今こそ、DAOの最終的な形式の時です:投票、ブラックリストの包含/赦免、および配当分布と撤退。追加のヘルパー関数を追加します。

これらに混乱している場合、完全なソースコードはコードベースに記載されています。

投票と提案投票に提案と投票を使用します。 2つの新しい構造が必要です

提案には、人々が1つの提案で2回投票するのを防ぐための有権者マップと、自明であるべき他のメタデータが含まれます。投票は、はいまたは反対の投票であり、投票者の理由と投票権、つまりこの提案に投票するために使用したいトークンの数を覚えています。また、提案の数をカウントするためのカウンターをどこかに保存できるように、提案の配列も追加しました。

投票機能から始めて、サポート機能を構築しましょう。
<code>struct Proposal {
    string description;
    bool executed;
    int256 currentResult;
    uint8 typeFlag; // 1 = delete
    bytes32 target; // 提案目标的ID。例如,标志1,目标XXXXXX(哈希)表示删除submissions[hash]的提案
    uint256 creationDate;
    uint256 deadline;
    mapping (address => bool) voters;
    Vote[] votes;
    address submitter;
}

Proposal[] public proposals;
uint256 proposalCount = 0;
event ProposalAdded(uint256 id, uint8 typeFlag, bytes32 hash, string description, address submitter);
event ProposalExecuted(uint256 id);
event Voted(address voter, bool vote, uint256 power, string justification);

struct Vote {
    bool inSupport;
    address voter;
    string justification;
    uint256 power;
}</code>
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

関数修飾子に注意:この修飾子を契約に追加することにより、将来の関数に追加し、トークンホルダーのみが関数を実行できることを確認できます。これは再利用可能なセキュリティチェックです!

投票機能は、投票権が肯定的であり、投票者は実際に投票するのに十分なトークンを持っているなど、いくつかの衛生チェックを実行します。次に、ストレージから提案を受け取り、それが期限切れも実行されていないことを確認します。完成した提案に投票することは意味がありません。また、この人がまだ投票していないことを確認する必要があります。投票権を変更することはできますが、これにより、土壇場で投票を撤回するなど、いくつかの抜け穴に直面することができます。おそらく将来のバージョンの候補者ですか?

<code>modifier tokenHoldersOnly() {
    require(token.balanceOf(msg.sender) >= 10**token.decimals());
    _;
}

function vote(uint256 _proposalId, bool _vote, string _description, uint256 _votePower) tokenHoldersOnly public returns (int256) {

    require(_votePower > 0, "At least some power must be given to the vote.");
    require(uint256(_votePower) <= token.balanceOf(msg.sender), "Vote power exceeds token balance.");
    Proposal storage p = proposals[_proposalId];

    require(p.executed == false, "Proposal must not have been executed already.");
    require(p.deadline > now, "Proposal must not have expired.");
    require(p.voters[msg.sender] == false, "User must not have already voted.");

    uint256 voteid = p.votes.length++;
    Vote storage pvote = p.votes[voteid];
    pvote.inSupport = _vote;
    pvote.justification = _description;
    pvote.voter = msg.sender;
    pvote.power = _votePower;

    p.voters[msg.sender] = true;

    p.currentResult = (_vote) ? p.currentResult + int256(_votePower) : p.currentResult - int256(_votePower);
    token.increaseLockedAmount(msg.sender, _votePower);

    emit Voted(msg.sender, _vote, _votePower, _description);
    return p.currentResult;
}</code>
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
次に、新しい投票を提案に登録し、現在の結果を変更してスコアを見つけ、最終的に投票イベントを発行します。しかし、token.increaselockedamountとは何ですか?

このロジックは、ユーザーのロックされたトークンの数を増やします。この関数は、トークン契約の所有者によってのみ実行できます(今回はDAOになることが期待されています)、ユーザーがアカウントに登録されたロックされた金額以上を送信することを防ぎます。このロックは、提案が失敗した後、または実行された後にリリースされます。

削除エントリを提示するための関数を書きましょう。

削除およびブラックリストへの投票

このシリーズの最初の部分では、3つのエントリ削除関数を計画しました。

    削除エントリ:確認確認後、ターゲットエントリを削除します。投票時間:48時間。
  1. 緊急削除エントリ[所有者のみ]:所有者によってのみトリガーされます。確認確認後、ターゲットエントリを削除します。投票時間:24時間。
  2. 緊急削除写真[所有者のみ]:画像エントリのみに適用できます。所有者によってのみトリガーされます。確認確認後、ターゲットエントリを削除します。投票時間:4時間。
単一のアドレスの5つのエントリを削除すると、ブラックリストが表示されます。

私たちが今これをどのように行うか見てみましょう。最初に、関数を削除します:

<code>struct Proposal {
    string description;
    bool executed;
    int256 currentResult;
    uint8 typeFlag; // 1 = delete
    bytes32 target; // 提案目标的ID。例如,标志1,目标XXXXXX(哈希)表示删除submissions[hash]的提案
    uint256 creationDate;
    uint256 deadline;
    mapping (address => bool) voters;
    Vote[] votes;
    address submitter;
}

Proposal[] public proposals;
uint256 proposalCount = 0;
event ProposalAdded(uint256 id, uint8 typeFlag, bytes32 hash, string description, address submitter);
event ProposalExecuted(uint256 id);
event Voted(address voter, bool vote, uint256 power, string justification);

struct Vote {
    bool inSupport;
    address voter;
    string justification;
    uint256 power;
}</code>
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
提出後、提案は提案リストに追加され、ターゲットエントリはエントリハッシュによって記録されます。説明を保存し、いくつかのデフォルト値を追加し、提案タイプに基づいて締め切りを計算します。追加プロポザルイベントが発行され、提案の総数が増加します。

次に、提案を実装する方法を見てみましょう。強制力を持たせるには、提案には十分な投票が必要であり、その期限を超えなければなりません。実行関数は、実行される提案のIDを受け入れます。 EVMにすべての保留中の提案を一度に実行させる簡単な方法はありません。実行する保留中の提案が多すぎる可能性があり、DAOのデータに大幅な変更を加える可能性があります。これは、イーサリアムブロックのガス制限を超えてトランザクションの障害をもたらす可能性があります。明確に定義されたルールに準拠している人が呼び出すことができる手動実行機能を構築する方がはるかに簡単です。そうすれば、コミュニティは実行する必要がある提案に集中できます。

<code>modifier tokenHoldersOnly() {
    require(token.balanceOf(msg.sender) >= 10**token.decimals());
    _;
}

function vote(uint256 _proposalId, bool _vote, string _description, uint256 _votePower) tokenHoldersOnly public returns (int256) {

    require(_votePower > 0, "At least some power must be given to the vote.");
    require(uint256(_votePower) <= token.balanceOf(msg.sender), "Vote power exceeds token balance.");
    Proposal storage p = proposals[_proposalId];

    require(p.executed == false, "Proposal must not have been executed already.");
    require(p.deadline > now, "Proposal must not have expired.");
    require(p.voters[msg.sender] == false, "User must not have already voted.");

    uint256 voteid = p.votes.length++;
    Vote storage pvote = p.votes[voteid];
    pvote.inSupport = _vote;
    pvote.justification = _description;
    pvote.voter = msg.sender;
    pvote.power = _votePower;

    p.voters[msg.sender] = true;

    p.currentResult = (_vote) ? p.currentResult + int256(_votePower) : p.currentResult - int256(_votePower);
    token.increaseLockedAmount(msg.sender, _votePower);

    emit Voted(msg.sender, _vote, _votePower, _description);
    return p.currentResult;
}</code>
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
そのIDで提案を取得し、それが不明確で締め切りが期限切れになっていることを確認します。その後、提案の種類が提案を削除し、投票が肯定的である場合、書かれた削除関数を使用し、最後に新しいイベントを発行します(契約の一番上に追加します)。 Assert Callは、必要なステートメントと同じように機能します。通常、アサートは結果が真実であることを「アサート」するために使用されます。要求は前提条件に使用されます。機能的には同じですが、違いは、アサートステートメントがメッセージパラメーターを受け入れて、故障した状況を処理できないことです。この関数は、提案のすべての票に対してトークンのロックを解除することで終了します。

同じ方法を使用して他のタイプの提案を追加できますが、最初に、deletesubmission関数を更新して、アカウントに5つ以上の削除を持つユーザーを禁止しましょう。これは、コミュニティが異議を唱えたコンテンツを常に送信していることを意味します。 deleteSubmission関数を更新しましょう:

<code>struct Proposal {
    string description;
    bool executed;
    int256 currentResult;
    uint8 typeFlag; // 1 = delete
    bytes32 target; // 提案目标的ID。例如,标志1,目标XXXXXX(哈希)表示删除submissions[hash]的提案
    uint256 creationDate;
    uint256 deadline;
    mapping (address => bool) voters;
    Vote[] votes;
    address submitter;
}

Proposal[] public proposals;
uint256 proposalCount = 0;
event ProposalAdded(uint256 id, uint8 typeFlag, bytes32 hash, string description, address submitter);
event ProposalExecuted(uint256 id);
event Voted(address voter, bool vote, uint256 power, string justification);

struct Vote {
    bool inSupport;
    address voter;
    string justification;
    uint256 power;
}</code>
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

これはより良いです。自動的にブラックリストに登録され、5回削除されました。ブラックリストに登録されたアドレスに償還の機会を与えないことは不公平です。また、ブラックリスト機能自体を定義する必要があります。これらの両方を実行し、たとえば0.05エーテルにブラックリストをキャンセルする料金を設定しましょう。

<code>modifier tokenHoldersOnly() {
    require(token.balanceOf(msg.sender) >= 10**token.decimals());
    _;
}

function vote(uint256 _proposalId, bool _vote, string _description, uint256 _votePower) tokenHoldersOnly public returns (int256) {

    require(_votePower > 0, "At least some power must be given to the vote.");
    require(uint256(_votePower) <= token.balanceOf(msg.sender), "Vote power exceeds token balance.");
    Proposal storage p = proposals[_proposalId];

    require(p.executed == false, "Proposal must not have been executed already.");
    require(p.deadline > now, "Proposal must not have expired.");
    require(p.voters[msg.sender] == false, "User must not have already voted.");

    uint256 voteid = p.votes.length++;
    Vote storage pvote = p.votes[voteid];
    pvote.inSupport = _vote;
    pvote.justification = _description;
    pvote.voter = msg.sender;
    pvote.power = _votePower;

    p.voters[msg.sender] = true;

    p.currentResult = (_vote) ? p.currentResult + int256(_votePower) : p.currentResult - int256(_votePower);
    token.increaseLockedAmount(msg.sender, _votePower);

    emit Voted(msg.sender, _vote, _votePower, _description);
    return p.currentResult;
}</code>
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

ブラックリストのアカウントのトークンは、ブラックリストをキャンセルするために料金を送信するまでロックされることに注意してください。

その他の種類の投票

上記の機能のインスピレーションに基づいて、他の提案を書いてみてください。ネタバレについては、プロジェクトのGitHubコードベースをチェックして、そこから最終コードをコピーしてください。簡潔にするために、DAOにまだ持っている他の機能に進みましょう。

章の終わり

ストーリーの時間または章の制限に達したら、ストーリーを終了する時が来ました。日付の後、誰でも最終関数を呼び出すことができます。これにより、配当が撤回されます。まず、新しいStoryDaoのプロパティとイベントが必要です:

<code>modifier memberOnly() {
    require(whitelist[msg.sender]);
    require(!blacklist[msg.sender]);
    _;
}

function proposeDeletion(bytes32 _hash, string _description) memberOnly public {

    require(submissionExists(_hash), "Submission must exist to be deletable");

    uint256 proposalId = proposals.length++;
    Proposal storage p = proposals[proposalId];
    p.description = _description;
    p.executed = false;
    p.creationDate = now;
    p.submitter = msg.sender;
    p.typeFlag = 1;
    p.target = _hash;

    p.deadline = now + 2 days;

    emit ProposalAdded(proposalId, 1, _hash, _description, msg.sender);
    proposalCount = proposalId + 1;
}

function proposeDeletionUrgent(bytes32 _hash, string _description) onlyOwner public {

    require(submissionExists(_hash), "Submission must exist to be deletable");

    uint256 proposalId = proposals.length++;
    Proposal storage p = proposals[proposalId];
    p.description = _description;
    p.executed = false;
    p.creationDate = now;
    p.submitter = msg.sender;
    p.typeFlag = 1;
    p.target = _hash;

    p.deadline = now + 12 hours;

    emit ProposalAdded(proposalId, 1, _hash, _description, msg.sender);
    proposalCount = proposalId + 1;
}    

function proposeDeletionUrgentImage(bytes32 _hash, string _description) onlyOwner public {

    require(submissions[_hash].image == true, "Submission must be existing image");

    uint256 proposalId = proposals.length++;
    Proposal storage p = proposals[proposalId];
    p.description = _description;
    p.executed = false;
    p.creationDate = now;
    p.submitter = msg.sender;
    p.typeFlag = 1;
    p.target = _hash;

    p.deadline = now + 4 hours;

    emit ProposalAdded(proposalId, 1, _hash, _description, msg.sender);
    proposalCount = proposalId + 1;
}</code>
ログイン後にコピー

次に、関数を構築しましょう:

<code>function executeProposal(uint256 _id) public {
    Proposal storage p = proposals[_id];
    require(now >= p.deadline && !p.executed);

    if (p.typeFlag == 1 && p.currentResult > 0) {
        assert(deleteSubmission(p.target));
    }

    uint256 len = p.votes.length;
    for (uint i = 0; i < len; i++) {
        token.decreaseLockedAmount(p.votes[i].voter, p.votes[i].power);
    }

    p.executed = true;
    emit ProposalExecuted(_id);
}</code>
ログイン後にコピー

シンプル:収集された料金を所有者に送信してイベントを送信した後、ストーリーを無効にします。しかし、実際には、これはDAOの全体的な状況を実際に変えません。他の機能はその終わりに反応しません。それでは、別の修飾子を構築しましょう:

<code>function deleteSubmission(bytes32 hash) internal returns (bool) {
    require(submissionExists(hash), "Submission must exist to be deletable.");
    Submission storage sub = submissions[hash];

    sub.exists = false;
    deletions[submissions[hash].submitter] += 1;
    if (deletions[submissions[hash].submitter] >= 5) {
        blacklistAddress(submissions[hash].submitter);
    }

    emit SubmissionDeleted(
        sub.index,
        sub.content,
        sub.image,
        sub.submitter
    );

    nonDeletedSubmissions -= 1;
    return true;
}</code>
ログイン後にコピー
次に、以下に示すように、この修飾子をすべての関数に追加します。

DAOにまだ残っているトークンがある場合は、それらを取り戻してそれらのトークンの所有権を取って、後で別のストーリーに使用できるようにしましょう。
<code>function blacklistAddress(address _offender) internal {
    require(blacklist[_offender] == false, "Can't blacklist a blacklisted user :/");
    blacklist[_offender] == true;
    token.increaseLockedAmount(_offender, token.getUnlockedAmount(_offender));
    emit Blacklisted(_offender, true);
}

function unblacklistMe() payable public {
    unblacklistAddress(msg.sender);
}

function unblacklistAddress(address _offender) payable public {
    require(msg.value >= 0.05 ether, "Unblacklisting fee");
    require(blacklist[_offender] == true, "Can't unblacklist a non-blacklisted user :/");
    require(notVoting(_offender), "Offender must not be involved in a vote.");
    withdrawableByOwner = withdrawableByOwner.add(msg.value);
    blacklist[_offender] = false;
    token.decreaseLockedAmount(_offender, token.balanceOf(_offender));
    emit Blacklisted(_offender, false);
}

function notVoting(address _voter) internal view returns (bool) {
    for (uint256 i = 0; i < proposals.length; i++) {
        if (proposals[i].executed == false && proposals[i].voters[_voter] == true) {
            return false;
        }
    }
    return true;
}</code>
ログイン後にコピー

lockmytokens関数は、特定のユーザーがロックする可能性のあるすべてのロックされたトークンのロックを解除するために使用されます。これは発生しないでください。この機能は、多くのテストで削除する必要があります。

<code>bool public active = true;
event StoryEnded();</code>
ログイン後にコピー

配当分布と撤回

浸漬 ストーリーが終了したので、提出に対して請求される料金はすべてのトークン保有者に割り当てる必要があります。ホワイトリストを再利用して、すでに料金を撤回したすべての人をマークすることができます。

これらの配当が特定の時間制限内に撤回されない場合、所有者は残りの部分を取得できます。

宿題として、この展開されたスマートコントラクトを再利用し、データをクリアし、プールにトークンを保持し、再配置せずにここから別の章を再起動することがどれほど簡単か困難であるかを検討してください。これを自分で行ってみて、このシリーズがカバーする将来の更新のためにコードベースに従ってください!また、追加のインセンティブを検討してください。アカウント内のトークンの数は、ストーリーの終わりから受け取る配当に影響を与える可能性がありますか?あなたの想像力は無限です!
<code>function endStory() storyActive external {
    withdrawToOwner();
    active = false;
    emit StoryEnded();
}</code>
ログイン後にコピー

展開の問題
<code>modifier storyActive() {
    require(active == true);
    _;
}</code>
ログイン後にコピー

私たちの契約が非常に大きいことを考えると、それを展開および/またはテストすることは、イーサリアムブロックのガス制限を超える可能性があります。これは、大規模なアプリケーションがEthereum Networkへの展開を制限するものです。とにかくそれを展開するには、Truffle.jsファイルを変更して最適化されたSOLC設定を次のように含めることにより、コンパイル中にコードオプティマイザーを使用してみてください。
<code>struct Proposal {
    string description;
    bool executed;
    int256 currentResult;
    uint8 typeFlag; // 1 = delete
    bytes32 target; // 提案目标的ID。例如,标志1,目标XXXXXX(哈希)表示删除submissions[hash]的提案
    uint256 creationDate;
    uint256 deadline;
    mapping (address => bool) voters;
    Vote[] votes;
    address submitter;
}

Proposal[] public proposals;
uint256 proposalCount = 0;
event ProposalAdded(uint256 id, uint8 typeFlag, bytes32 hash, string description, address submitter);
event ProposalExecuted(uint256 id);
event Voted(address voter, bool vote, uint256 power, string justification);

struct Vote {
    bool inSupport;
    address voter;
    string justification;
    uint256 power;
}</code>
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
これにより、コード上でオプティマイザーを200回実行して、展開前に絞り、削除、または抽象化できるエリアを見つけます。これにより、展開コストが大幅に削減されます。

結論

これで詳細なDAO開発が終了しますが、コースはまだ終わっていません!このストーリーのためにUIを構築して展開する必要があります。幸いなことに、バックエンドがブロックチェーンで完全にホストされているため、フロントエンドの構築ははるかに簡単です。このシリーズの最後から2番目の部分でこれを見てみましょう。

イーサリアムダップの構築とカスタムトークンでの投票に関するよくある質問

ブロックチェーン投票は実際にどのように機能しますか?

ブロックチェーン投票は、ブロックチェーンテクノロジーの透明性とセキュリティを利用する分散型投票システムです。理論的には、完全に機能するはずですが、実際には課題に遭遇することがよくあります。投票プロセスには、Ethereumブロックチェーンにスマートコントラクトを作成することが含まれ、各投票は検証可能な取引です。ただし、有権者の匿名性、投票操作、ブロックチェーンプラットフォームの使用の技術的な複雑さなどの問題は、実際の実装を妨げる可能性があります。

DAO投票メカニズムは何ですか?

dao(分散型自律組織)投票メカニズムは、DAOのトークン保有者がトークンの所有権に基づいて提案に投票できるようにするシステムです。最も一般的なメカニズムには、単純な多数票が含まれます。これは、提案が投票の50%以上を受け取った場合に受け入れられます。また、提案に対する複数票を投票する費用が指数関数的に増加します。

ガバナンスはセキュリティトークンでどのように機能しますか?

安全なトークンのガバナンスは、通常、トークン保有者がプロジェクトのさまざまな側面に投票できる投票システムを介して処理されます。これには、プロジェクト開発、トークン経済学、さらにはガバナンスシステム自体の変化に関する決定が含まれる場合があります。トークン保有者の投票権は通常、保有するトークンの数に比例します。

DAOガバナンスをセットアップする方法は?

DAOガバナンスのセットアップには、投票権と提案メカニズムを含む組織のルールの概要を示すイーサリアムブロックチェーン上のスマートコントラクトを作成することが含まれます。この契約はブロックチェーンに展開され、投票権を表すトークンはメンバーに分配されます。メンバーは、組織の変更を提案して投票できます。

DAOガバナンストークンを保持するリスクは何ですか?

DAOガバナンストークンを保持することは、暗号通貨のボラティリティとDAOを取り巻く規制の不確実性のために危険になる可能性があります。たとえば、商品先物取引委員会(CFTC)は、投票にDAOトークンを使用することは市場操作の一形態と見なされる可能性があると警告しています。さらに、DAOの管理が不十分な場合、またはハッカー攻撃の犠牲者になった場合、トークン保有者は投資を失う可能性があります。

Ethereum DAPPでの投票用のカスタムトークンの作成には、イーサリアムブロックチェーンにスマートコントラクトを作成および展開することが含まれます。この契約は、その名前、シンボル、総供給など、トークンの属性を定義します。契約が展開されると、トークンをユーザーに配布し、ユーザーを使用してDAPPの提案に投票できます。

ブロックチェーンを使用して投票することの利点は何ですか?

ブロックチェーン投票は、透明性、セキュリティ、不変性など、さまざまな利点を提供します。投票はブロックチェーンのトランザクションとして記録され、透明で検証可能になります。また、ブロックチェーンの分散型の性質により、単一の当事者が投票プロセスを操作することも困難になります。

ブロックチェーン投票における有権者の匿名性を確保する方法は?

ブロックチェーン投票における有権者の匿名性は、ブロックチェーントランザクションの透明な性質のために困難になる可能性があるためです。ただし、ゼロ知識証明などのテクニックを使用して、有権者の身元を明らかにすることなく、有権者の有効性を検証できます。

ブロックチェーン投票を実装することの課題は何ですか?

ブロックチェーン投票の実装は、技術的な複雑さ、規制の不確実性、潜在的なセキュリティリスクのために困難な場合があります。ユーザーは投票プロセスに参加するためにブロックチェーンテクノロジーに精通する必要があり、規制当局はブロックチェーン投票システムの正当性とセキュリティについて懸念を表明する場合があります。

DAOガバナンストークンに関連するリスクを緩和する方法は?

DAOガバナンストークンに関連するリスクの緩和には、DAOの慎重な管理、徹底的なセキュリティ対策、常に規制の発展に注目することが含まれます。また、ポートフォリオを多様化し、余裕がある以上に投資しないことも重要です。

以上がビルディングイーサリアムダップ:カスタムトークンでの投票の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート