整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          如何使用零知識證明在以太坊區塊鏈上構建匿名投票系統

          如何使用零知識證明在以太坊區塊鏈上構建匿名投票系統

          已經開始處理零知識證明技術,因為我很好奇是否有可能在區塊鏈上創建一個匿名的、不可破解的投票系統。

          紙質投票(如選舉)是一種非常昂貴的投票方式。我沒有確切的數據,但它花費了數十億美元,而且一直有傳言說有人作弊。

          基于區塊鏈的系統成本只有這個價格的一小部分,而且它超級安全,因為區塊鏈是公開的,每個人都可以跟蹤它的所有內容。

          由于零知識證明,唯一無法追蹤的是誰投票給了哪個政黨(更詳細的解釋,請閱讀我之前的文章)。因此,基于區塊鏈的投票似乎是投票的圣杯。

          在我之前的文章中,我承諾了一個簡單的概念證明。它現在已經完成并可以在GitHub上使用。我將在本文中向您展示它是如何工作的。

          我用的方法和龍卷風現金的方法是一樣的。Tornado Cash是基于zkSNARKs的非托管以太坊和ERC20隱私解決方案。

          它的主要組成部分是一個智能合約,你可以把你的錢存入一個承諾,你可以在以后通過一個撤銷器提取。一個承諾只分配給一個無效符,但是沒有人知道哪個無效符分配給哪個承諾。

          這也可用于匿名投票,因為每個人只投一次票,但沒有人應該知道哪張票分配給了哪位選民。(我有一篇關于這個話題的完整文章。)

          我的投票應用是一個以太坊dApp。它是一個帶有JavaScript的靜態頁面,沒有任何后端(后端是以太坊上的智能合約)。正因為如此,很難攻擊系統,因為在區塊鏈上,沒有單點故障。

          如果您想在本地測試它,最簡單的方法是通過以下方式運行本地區塊鏈:

          npx hardhat node

          在MetaMask上注冊賬戶我們將使用前兩個帳戶。

          第一步,克隆repo,部署智能合約,并啟動應用程序:

          git clone https://github.com/TheBojda/zktree-vote
          cd zktree-vote
          npm i
          npm run prepare (optional, npm i should run it)
          npm run deploy
          npm start

          dApp使用MetaMask來與區塊鏈通信,所以如果你從桌面瀏覽器使用它,MetaMask必須安裝,如果你從手機使用它,在MetaMask應用程序的嵌入式瀏覽器中打開它。

          當你打開http://localhost:1234/,你會看到應用程序的菜單:


          點擊“registration to vote”打開注冊頁面。當您打開頁面時,它會在后臺自動生成提交和無效符,并將其存儲在瀏覽器的本地存儲中。

          選民的確認可以在投票地點親自進行,也可以通過視頻會議系統等在網上進行。驗證器可以使用“Validator tool”:

          為了方便起見,我在頁面上添加了一個QR閱讀器,因此可以通過掃描選民的QR碼來發送承諾給驗證者,或者選民可以復制它并在聊天中發送(在線驗證)。

          當驗證者通過檢查她的身份證等來檢查選民的身份時,他將具有唯一哈希值的承諾發送給區塊鏈,該哈希值可以是用戶身份證或任何唯一標識符的哈希值。

          這確保了一個選民只登記一次,承諾一次,這意味著她只能投票一次。

          投票過程很簡單。你選擇一個選項并將其發送到區塊鏈。應用程序將使用在后臺生成的nullifier。

          在最后一頁,您可以實時查看投票結果。

          就是這樣。簡單和非常舒適的投票方式,但它有紙質投票的所有優點,沒有所有的缺點。

          在這個簡短的介紹之后,讓我們來看一些代碼。

          為了開發這個投票dApp,我使用了我的zk-merkel-tree庫。它是一個JavaScript庫,使用Tornado Cash的基于zksnark的方法,并隱藏了零知識證明的所有復雜性。

          我計劃寫一篇關于這個庫的完整文章,所以在本文中,我不會詳細介紹它。

          投票系統的智能合約是這樣的:

          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.17;
          
          import "zk-merkle-tree/contracts/ZKTree.sol";
          
          contract ZKTreeVote is ZKTree {
              address public owner;
              mapping(address=> bool) public validators;
              mapping(uint256=> bool) uniqueHashes;
              uint numOptions;
              mapping(uint=> uint) optionCounter;
          
              constructor(
                  uint32 _levels,
                  IHasher _hasher,
                  IVerifier _verifier,
                  uint _numOptions
              ) ZKTree(_levels, _hasher, _verifier) {
                  owner=msg.sender;
                  numOptions=_numOptions;
                  for (uint i=0; i <=numOptions; i++) optionCounter[i]=0;
              }
          
              function registerValidator(address _validator) external {
                  require(msg.sender==owner, "Only owner can add validator!");
                  validators[_validator]=true;
              }
          
              function registerCommitment(
                  uint256 _uniqueHash,
                  uint256 _commitment
              ) external {
                  require(validators[msg.sender], "Only validator can commit!");
                  require(
                      !uniqueHashes[_uniqueHash],
                      "This unique hash is already used!"
                  );
                  _commit(bytes32(_commitment));
                  uniqueHashes[_uniqueHash]=true;
              }
          
              function vote(
                  uint _option,
                  uint256 _nullifier,
                  uint256 _root,
                  uint[2] memory _proof_a,
                  uint[2][2] memory _proof_b,
                  uint[2] memory _proof_c
              ) external {
                  require(_option <=numOptions, "Invalid option!");
                  _nullify(
                      bytes32(_nullifier),
                      bytes32(_root),
                      _proof_a,
                      _proof_b,
                      _proof_c
                  );
                  optionCounter[_option]=optionCounter[_option] + 1;
              }
          
              function getOptionCounter(uint _option) external view returns (uint) {
                  return optionCounter[_option];
              }
          }

          智能合約繼承自ZKTree(來自zk-merkel-tree庫),并使用它的_commit和_nullify方法。_commit方法存儲承諾,_nullify方法存儲無效符并驗證它的零知識證明。

          所有者可以通過調用registerValidator方法來添加驗證器。只有驗證者才能在檢查選民的身份后向智能合約發送承諾。

          最后一個方法是getOptionCounter,您可以使用它實時查詢投票結果。

          這是所有。由于zk-merkel-tree,投票合約超級簡單,所有的復雜性都隱藏在庫后面。

          dApp本身是一個vue.js單頁應用程序。通過使用zk-merkel-tree中的generatcommit,在VoterRegistration組件中生成承諾和無效符,并存儲在本地存儲中。

          this.commitment=JSON.parse(
            localStorage.getItem("zktree-vote-commitment")
          );
          if (!this.commitment) {
            this.commitment=await generateCommitment();
            localStorage.setItem(
              "zktree-vote-commitment",
              JSON.stringify(this.commitment)
            );
          }

          驗證器使用ValidatorTool組件將承諾發送到區塊鏈。它從合同中讀取合同地址。Json(由部署過程生成),并將具有唯一哈希值的承諾發送給投票合約。

          const abi=[
            "function registerCommitment(uint256 _uniqueHash, uint256 _commitment)",
          ];
          const provider=new ethers.providers.Web3Provider(
            (window as any).ethereum
          );
          await provider.send("eth_requestAccounts", []);
          const signer=provider.getSigner();
          const contracts=await (await fetch("contracts.json")).json();
          const contract=new ethers.Contract(contracts.zktreevote, abi, signer);
          try {
            await contract.registerCommitment(this.uniqueHash, this.commitment);
          } catch (e) {
            alert(e.reason);
          }

          投票人使用Vote組件進行投票。它通過使用ZK-merkel-tree中的calculateMerkleRootAndZKProof方法生成ZK證明,并將其發送到帶有無效符的區塊鏈。

          const commitment=JSON.parse(
            localStorage.getItem("zktree-vote-commitment")
          );
          
          const abi=[
            "function vote(uint _option,uint256 _nullifier,uint256 _root, 
             uint[2] memory _proof_a,uint[2][2] memory _proof_b,
             uint[2] memory _proof_c)",
          ];
          const provider=new ethers.providers.Web3Provider(
            (window as any).ethereum
          );
          await provider.send("eth_requestAccounts", []);
          const signer=provider.getSigner();
          const contracts=await (await fetch("contracts.json")).json();
          const contract=new ethers.Contract(contracts.zktreevote, abi, signer);
          const cd=await calculateMerkleRootAndZKProof(
            contracts.zktreevote,
            signer,
            TREE_LEVELS,
            commitment,
            "verifier.zkey"
          );
          try {
            await contract.vote(
              this.option,
              cd.nullifierHash,
              cd.root,
              cd.proof_a,
              cd.proof_b,
              cd.proof_c
            );
          } catch (e) {
            alert(e.reason);
          }

          正如您所看到的,代碼非常簡單,因為ZKP的所有復雜性都隱藏在zk-merkel-tree庫中。基于此代碼,您可以輕松構建自己的投票系統。

          可能的改進:

          • 選民白名單。您可以在投票前根據投票者的唯一散列構建Merkle樹,并在驗證器發送承諾時檢查投票者是否存在于其中。它可以防止使用假身份。
          • 更好的驗證器管理。如果有數千個驗證器,那么Merkle樹是批量注冊它們的更好方法。如果選民和驗證者被劃分為地區,那么您可以為地區生成單獨的Merkle樹,并將驗證者分配給這些樹。
          • 更好的驗證方法。在這個系統中作弊的唯一方法是有人使用假身份進行投票。這就是驗證非常重要的原因。有更多的方法可以防止惡意驗證器。例如,系統可以為一個選民隨機選擇2或3個驗證者。它們都是惡意的可能性很小。或者它可以記錄驗證過程,其他驗證者隨機檢查錄制的視頻。投票作弊是一種犯罪行為,因此驗證者惡意的風險非常高。
          • 集成Jitsi Meet等視頻會議系統。如果集成了視頻會議系統,發送承諾和整個驗證過程可以非常容易。選民可以填寫一張表格,填寫他們的數據,并在確認之前發送出去。驗證者只需通過攝像頭檢查身份證,如果一切正常,他就可以通過點擊按鈕將承諾和唯一哈希發送到區塊鏈。
          • 數字身份證支持。如果選民擁有可用于對承諾進行數字簽名的數字身份證,則可以跳過整個驗證過程。這種方法非常便宜,因為不需要人力資源,所以可以經常使用,而且選民可以更多地參與決策。

          去中心化是區塊鏈最重要的方面,Web3和基于區塊鏈的匿名投票是它的圣杯。

          我希望這篇短文和zk-merkel-tree庫可以成為其他人構建自己的系統的一個良好起點。

          JavaScript 開發者,我們經常忘記并不是所有人都像我們一樣了解 JavaScript,這被稱為知識的詛咒:當我們精通某個內容的時候,我們就不記得自己作為新人的時候有多么困惑。我們總是對其他人的能力估計過高,因此我們覺得,自己寫的類庫需要一些 JavaScript 代碼去初始化和配置也很正常。然而,一些用戶卻在使用過程中大費周折,他們瘋狂地從文檔中復制粘貼例子并隨機組合這些代碼,直到它們生效為止。

          • JavaScript(JS)中如何檢查一個對象(Object)是否包含指定的鍵(屬性)

          你或許會想:“所有寫 HTML 和 CSS 的人都會 JavaScript,對吧?”

          你錯了。來看看我的調查結果吧,這是我所知道的唯一相關數據了。

          根據投票結果來看,每兩個寫 HTML 和 CSS 的人中,就有一個對 JavaScript 沒有好感。 這是個值得讓人深思的數據結果。

          舉個例子,以下的代碼用來初始化一個 jQuery UI 自動完成庫。

          toml<div class="ui-widget">
              <label for="tags">Tags: </label>
              <input id="tags">
          </div>
          
          toml$( function() {
              var availableTags=[
                  "ActionScript",
                  "AppleScript",
                  "Asp",
                  "BASIC",
                  "C"
              ];
              $( "#tags" ).autocomplete({
                  source: availableTags
              });
          } );
          

          你覺得這很簡單,甚至覺得即使那些根本不會 JavaScript 的人也會覺得簡單,對吧?

          錯!非程序員在文檔中看到這個例子的時候,腦子里會閃過各種問題:“我該把這段代碼放哪兒呢?”“這些花括號、冒號和方括號都是什么意思?”“我要用這些嗎?”“如果我的元素沒有 ID 怎么辦?”等等。所以即使這段極其簡短的代碼也要求人們了解對象字面量、數組、變量、字符串、如何獲取 DOM 元素的引用、事件、 DOM 樹何時構建完畢等等更多知識。這些對于程序員來說微不足道的事情,對于不會 JavaScript 、只會寫 HTML 的人來說都是一場艱難的攻堅戰。

          • JavaScript(JS)中怎么遍歷數組?一文講解 JS 遍歷數組的方法

          現在來看一下 HTML5 中的等效聲明性代碼:

          html<div class="ui-widget">
              <label for="tags">Tags: </label>
              <input id="tags" list="languages">
              <datalist id="languages">
                  <option>ActionScript</option>
                  <option>AppleScript</option>
                  <option>Asp</option>
                  <option>BASIC</option>
                  <option>C</option>
              </datalist>
              </div>
          

          這不僅讓寫 HTML 的人看得更清楚更明白,也對程序員來說更為簡單。我們看到所有的內容都同時被設置好,不必關心什么時候初始化、如何獲取元素的引用以及如何設置每個內容,無需知道哪個函數是用來初始化或者它需要什么參數。在更高級的使用情況中,還會添加一個 JavaScript API 來允許動態創建屬性和元素。這遵循了一條最基本的 API 設計原則:讓簡單的內容變得更簡單,讓復雜的內容得以簡單實現。

          • REST API 設計規范:最佳實踐和示例

          這給我們上了一堂關于 HTML API 的重要一課:HTML API 不光要給那些了解 JavaScript 但水平有限的人帶來福音,還要讓我們程序員在普通的工作中也要不惜犧牲程序的靈活性來換取更高的表述性。然而不知道為什么,我們在寫自己的類庫的時卻總忘記這些原則。

          那么什么是 HTML API 呢?根據維基百科的定義,API(也就是應用程序接口)是“用于構建應用程序軟件的一組子程序定義、協議和工具”。在 HTML API 中,定義和協議就是 HTML ,工具在 HTML 中配置。HTML API 通常由可用于現有 HTML 內容的類和屬性模式組成。通過 Web 組件,甚至可以像玩游戲一般自定義元素名稱和 Shadow DOM,HTML API 甚至能擁有完整的內部結構,并且對頁面其余部分隱藏實現細節。但是這并不是一篇關于 Web 組件的文章,Web 組件給予了 HTML API 設計者更多的能力和選擇,但是良好的(HTML)API 設計原則都是可以舉一反三的。

          HTML API 加強了設計師和工程師之間的合作,減輕工程師肩上的工作負擔,還能讓設計師創造更具還原度的原型。在類庫中引入 HTML API 不僅讓社區更具包容性,最終還能造福程序員。

          并不是每個類庫都需要 HTML API。 HTML API 在使用了 UI 元素的類庫中非常有用,比如 galleries、drag-and-drop、accordions、tabs、carousels 等等。經驗表明,如果一個非程序員不能理解該類庫的功能,它就不需要 HTML API。比如,那些簡化代碼或者幫助管理代碼的庫就不需要 HTML API。那 MVC 框架或者 DOM 助手之類的庫又怎會需要 HTML API 呢?

          目前為止,我們只討論了 HTML API 的定義、功能和用處,文章剩下的部分是關于如何設計一個好的 HTML API。

          初始化選擇器

          在 JavaScript API 中,初始化是被類庫的用戶嚴格控制的:因為他們必須手動調用函數或者創建對象,精確地控制著其運行的時間和基礎。在 HTML API 中,我們要幫用戶選擇,同時也要確保不會妨礙那些仍然使用 JavaScript 的用戶,因為他們可能希望得到完全控制。

          最常見的兼容兩種使用場景的辦法是:只有匹配到給定選擇器(通常是一個特定的類)時才會自動初始化。Awesomplete 就是采用的這種方法,只選取具有 class="awesomplete" 的 input 元素進行初始化。

          有時候,簡化自動初始化比做顯式選擇初始化更重要。當你的類庫需要運行在眾多元素之上時,避免手動給每個元素單獨添加類比顯式選擇初始化更加重要。比如,Prism 自動高亮任何包含 language-xxx 類的 <code> 元素(HTML5 的說明中建議指定代碼段的語言)及其包含languate-xxx 類的元素內部的 <code> 元素。這是因為 Prism 可能會用在一個有著成千上萬代碼段的博客系統中,回過頭去給每一個元素添加類將會是一項非常巨大的工程。

          在可以自由地使用 init 選擇器的情況下,最好的做法是允許選擇是否自動化。比如,Stretchy 默認自動調整每個 <input>、<select><textarea>的尺寸,但是也允許通過 data-stretchy-filter 屬性自定義指定其他元素為 init 選擇器。Prism 支持 <script> 元素的 data-manual 屬性來完全取消自動初始化。良好的實踐應該允許 HTML 和 JavaScript 都能設置這個選項,來適應 HTML 和 JavaScript 兩種類庫的用戶。

          最小化初始標記

          那么,對于 init 選擇器的每個元素,你的類庫都需要有一個封包、三個內部的按鈕和兩個相鄰的 div 該怎么辦呢?小問題,自己生成就好了。但是這種瑣碎的工作更適合機器,而不是人。不要期望每個使用類庫的人都同時使用了模板系統:許多人還在使用手動添加標記,他們會發現這樣建造系統太過復雜。因此,我們應該讓他們更輕松些。

          這種做法也最小化了錯誤風險:如果一個用戶僅僅引入了用來初始化的類卻沒有引入所有需要的標記怎么辦?如果不需要添加額外的標記,就不會產生錯誤。

          這條規則中有一個例外:優雅地退化并漸進地增強。比如,即使單個具有 data- * 屬性的元素并在 data-* 中添加所有選項就可以實現,在嵌入推文的時候也還是會涉及很多標記。這樣做是為了在 JavaScript 加載和運行之前推文就可讀。一個良好的經驗法則就是捫心自問“即使在沒有 JavaScript ,多余的標記能否給終端用戶帶來好處?”如果是,那么就引入;如果不是,那就要用類庫生成。

          便于用戶使用還是讓用戶自定義也是一組經典的矛盾:自動生成所有的標記會易于用戶使用,讓用戶自定義又顯得更加靈活。在你需要的時候,靈活性如雪中送炭,在不需要的時候卻適得其反,因為你不得不手動設置所有的參數。為了平衡這兩種需要,你可以生成那些需要但不存在的標記。比如,假設你需要給所有的 .foo 元素外層添加一個 .foo-container 元素。首先,通過element.closest(".foo-container") 檢查 .foo 元素的父元素或者任何的祖先元素(這樣最好了)是否含有 foo-container 類,如果有的話,你就不用生成新的元素,直接使用就可以了。

          設置

          通常,設置應該通過在恰當的元素上使用 data-* 屬性來實現。如果你的類庫中添加了成千上萬的屬性,然后你希望給它們添加命名空間來避免和其他類庫混淆,比如這樣 data-foo-*(foo 是基于類庫名字的一到三個字母長度的前綴)。如果名字顯得太長,你可以使用 foo-*,但你要知道,這種方式會打破 HTML 驗證并且會使得一些勤奮的 HTML 作者因此而棄用你的類庫。理想情況下,只要代碼不會太臃腫,以上兩種情況都應該支持。目前還沒有完美的解決辦法,因此在 WHATWG 中展開了一場如火如荼的討論:是否應該讓自定義的屬性前綴合法化。

          盡可能地遵從 HTML 的慣例。比如,你使用了一個屬性來做布爾類型的設置,當該屬性出現時無論其值如何都被視為 true,若不出現則被視為 false,不要期望可以用 data-foo="true" 或者 data-foo="false" 來代替。

          你也可以使用類進行布爾值設置。一般情況下它的語法和布爾屬性類似:類存在的時候是 true 不出現的時候就是 false。如果你想反過來設置,那就用一個 no- 前綴(比如,no-line-number)。但是要記住,類名可不像屬性一樣只有 data-*,因此這種方式很可能會和用戶現存的類名沖突,因此你可以考慮一下在類名中使用 foo- 這樣的前綴來避免沖突。但也有可能在后期的維護中發現這些類并未被 CSS 使用所以誤刪,這又是另一個隱患。

          當你需要設置一組相關的布爾值時,使用空格區分會比使用多個分隔符的方式好很多。

          html<!-- 第一種-->
          <div data-permissions="read add edit delete save logout"> 
          
          html<!-- 第二種-->
          <div data-read data-add data-edit data-delete data-save data-logout">
          
          html <!-- 第三種-->
           <div class="read add edit delete save logout">
          

          比如,第一種當時就比后面兩種好得多,因為后者可能會造成很多的沖突。你還可以使用 ~= 屬性選擇器來定位單個元素,比如 element.matches("[data-permissions~=read]") 可以檢查該元素是否有 read 權限。

          如果設置內容的類型是數組(array)或者對象(object) ,那么你就可以使用 data-* 屬性來關聯到另一個元素。比如, HTML5 中的自動完成:因為自動完成需要一個建議列表,你可以使用 data-* 屬性并通過 ID 聯系到包含建議內容的 <datalist> 元素。

          HTML 有一個慣例很讓人頭痛:在 HTML 中,用屬性聯系到另一個元素通常是靠引用其 ID 實現的(試想一下 <label for="...">)。然而,這種方法相當受限制:如果能夠允許使用選擇器或者甚至允許嵌套將更為方便,其效果將會極大地依賴于你的使用情況。要記住,穩定性重要,但實用性更加重要。

          即使有些設置內容不能在 HTML 中指定也沒關系。在 JavaScript 中以函數為設置值的部分被稱作“高級自定義”。試想一下 Awesomplete:所有數字、布爾值、字符串和對象都可以通過 data-* 屬性(list、minChars、maxItems、autoFirst)設置,所有的函數設置只能通過 JavaScript 使用(filter、sort、item、replace、data),這樣會寫 JavaScript 函數來配置類庫的人就可以使用 JavaScript API 了。

          正則表達式(regex)處在灰色地帶:通常只有程序員才知道正則表達式(甚至程序員在使用的時候也會有問題?。?;那么,乍看之下,在 HTML API 中引入正則表達式類型的設置并沒有意義。然而,HTML5 確實引入了這樣的設置(<input pattern="regex">),并且我覺得很成功,因為非程序員能在正則詞典中找到他們的用例并復制粘貼。

          繼承

          如果你的 UI 庫在每個頁面只會調用一兩次,繼承可能不是很重要。然而,如果要應用于多個元素,通過類或者屬性給每個元素做相同的配置將會非常令人頭疼。咱要記住并不是每個人都用了構建系統,尤其是非程序員。在這些情況下,定義能夠從祖先元素繼承設置將會變得非常有用,那樣多個實例就可以被批量設置了。

          還拿 Smashing Magazine 中使用的時下流行的語法高亮類庫 —— Prism 來舉例。高亮語句是通過 language-xxx 形式的類來配置的。如你所見,這違反了我們在前文中談過的規則,但這只是一種主觀決策,因為 HTML5 手冊中建議如此。在有許多代碼段的頁面上(想象一下,在博客文章中使用內聯 <code> 元素的頻率!),在每個 <code> 元素中指定代碼語句將會非常煩人。為了減輕這種痛苦,Prism 支持繼承這些類:如果一個 <code> 元素自己沒有 language-xxx 類,那么將會使用其最近的祖先元素的 language-xxx 類。這使得用戶可以設置全局的代碼語句(通過在 <body> 或者 <html> 元素上設置類)或者設置區塊的代碼語句,并且可以在擁有不同語句的元素或者區塊上重寫設置。

          現在 CSS 變量已經被所有的瀏覽器支持,它們可以用于以下設置:他們默認可以被繼承,并且可以以內聯的方式通過 style 屬性設置,也可以通過 CSS 或者 JavaScript 設置。在代碼中,你可以通過getComputedStyle(element).getPropertyValue("--variablename") 獲取它們。除了瀏覽器支持,其主要的劣勢就是開發者們還沒習慣使用它們,但是那已經發生改變了。并且,你不能像監視元素和屬性的一般通過 MutationObserver 來監視其改變。

          全局設置

          大多數 UI 類庫都有兩組設置:定義每個組件表現形式的設置和定義整個類庫表現形式的全局設置。目前為止,我們主要討論了前者,你現在可能好奇全局設置該在設置在哪里。

          進行全局設置的一個好地方就引入類庫的 <script> 元素。你可以通過 document.currentScript 獲取該元素,這有著非常好的瀏覽器支持。好處就是,這對于設置的作用域非常清楚,因此它們的名字可以起的更短(比如 data-filter 而不是 data-stretchy-filter)。

          然而,你不能只在 <script> 元素中進行設置,因為一些用戶可能會在 CMS 中使用你的類庫,而 CMS 中不允許用戶自定義 <script> 元素。你也可以在 <html><body> 元素或者甚至任何地方設置,只要你清楚地聲明了屬性值重復的時候哪個會生效。

          文檔

          那么,你已經掌握了如何在類庫中設置一個漂亮的聲明性的 API,那自然很好,然而,如果你所有的文檔都寫得只有會 JavaScript 的用戶才看得懂,那么就只有很少人能使用了。我記得曾經看過一個很酷的類庫,基于 URL 并通過切換元素的 HTML 屬性來切換元素的表現形式。然而,這漂亮的 HTML API 并不能被其目標人群所使用,因為整篇文檔中都充滿了 JavaScript 引用。最開始的例子開頭就是“這和 location.href.match(/foo/)等價”。非程序員哪能看懂這個呀?

          同時要記得許多人并不會任何編程語言而不僅僅是 JavaScript。你希望用戶能夠讀懂并理解的文中的模型、視圖、控制器或者其他軟件工程觀念,但結果無非是讓他們感到迷惑。

          當然,你應該在文檔中寫 API 里 JavaScript 的內容,你可以寫在“高級使用”部分。然而,如果你在文檔一開頭就引用 JavaScript 對象和函數或者軟件工程的觀念,那么你實質上就是在告訴非程序員這個類庫不是給他們用的,因此你就排除了一大批潛在用戶。不幸的是,大部分的 HTML API 類庫文檔都受這些問題困擾著,因為 HTML API 經常被視為是程序員的捷徑,而并不是給非程序員使用的。慶幸的是,這種狀況在未來可以有改變。

          Web 組件

          在不遠的未來,Web 組件百分之百將會徹底改變 HTML API。<template> 元素將會允許作者提供惰性加載的腳本。自定義元素將使得用戶可以像原生的 HTML 一樣使用更多優雅的 init 標記。引入 HTML 也將使得作者能夠僅引入一個文件來替代三個樣式表、五個腳本和十個模板(如果瀏覽器能夠同時獲取并且不再認為 ES6 模塊是一種競爭技術)。Shadow DOM 使得類庫可以將復雜的 DOM 結構適當壓縮并且不會影響用戶自己的標記。

          然而除了 <template>,瀏覽器對其他三個特征的支持目前受限。因此他們需要更高的聚合度,以此來減少對類庫的影響。這將會是我們在未來一段時間里需要不斷關注的東西。

          • 源于:https://www.smashingmagazine.com/2017/02/designing-html-apis/



          主介紹:?在職Java研發工程師、專注于程序設計、源碼分享、技術交流、專注于Java技術領域和畢業設計?


          項目名稱


          SSM框架基于JavaWeb在線投票系統的設計與實現源碼


          視頻效果


          SSM框架基于JavaWeb在線投票系統的設計與實現源碼



          SSM框架基于JavaWeb在線投票系統的設計與實現源碼




          系統說明


          根據系統分析,將在線投票管理系統分為前臺功能模塊和后臺功能模塊。其中系統前臺功能實現用戶注冊、用戶登錄、瀏覽、投票、投票中心和投票歷史等功能。系統前臺功能如圖5-2所示:


          ?編輯


          圖5-2 系統前臺功能模塊結構圖


          用戶注冊:用戶填寫用戶名、密碼和性別,點擊注冊按鈕進行注冊。


          用戶登錄:用戶填寫已經注冊的用戶名和密碼,點擊登錄按鈕進行登錄。


          瀏覽:用戶可以瀏覽在線投票管理系統中公共開放內容。


          投票:用戶選擇自己需要的投票,針對主題,勾選選項,進行投票操作。投票方式支持單選只能投一次、單選一天只能投一次、多選只能投一次、多選一天只能投一次等四種方式。


          投票中心:在線投票管理系統展示所有投票主題供用戶選擇。


          投票歷史:存儲用戶已經投票的歷史內容,用戶登陸后方可查看。


          系統后臺功能實現以下功能,投票信息管理、詳細投票查看、用戶信息管理、投票信息統計和管理員登錄等功能。系統后臺功能如圖5-3所示:


          ?編輯


          圖5-3 系統后臺功能模塊結構圖


          投票信息管理:管理員進行投票信息管理,可以管理投票主題和投票選項。針對投票主題,可以添加投票主題(需要填寫主題名稱、主題類型、開始時間、結束時間和主題簡介)、查看主題、修改主題和刪除主題;針對投票選項管理,可以添加選項(需要填寫選項名稱和選擇所屬主題)、查看選項、修改選項和刪除選項。


          詳細投票查看:管理員查看投票的詳細信息,以列表形式顯示,每條投票詳細信息包括:投票ID、用戶名、投票主題、投票選項、總投票數和投票時間等信息。


          用戶信息管理:管理員進行用戶信息管理,可以添加用戶(需要填寫用戶名、密碼、性別和狀態)、查看用戶信息、修改用戶信息和刪除用戶。


          投票信息統計:管理員進行投票信息統計,默認顯示所有的投票主題統計,輸入搜索主題名稱后顯示單個主題的投票統計。


          管理員登錄:管理員輸入用戶名、密碼和驗證碼,點擊登錄按鈕,進行登錄操作。


          環境需要


          1.運行環境:最好是java jdk 1.8,我們在這個平臺上運行的。其他版本理論上也可以。
          2.IDE環境:IDEA,Eclipse,Myeclipse都可以。推薦IDEA;
          3.tomcat環境:Tomcat 7.x,8.x,9.x版本均可
          4.硬件環境:windows 7/8/10 1G內存以上;或者 Mac OS;
          5.數據庫:MySql 5.7版本;
          6.是否Maven項目:否;


          技術棧


          1. 后端:Spring+SpringMVC+Mybatis
          2. 前端:JSP+CSS+JavaScript+jQuery


          使用說明


          1. 使用Navicat或者其它工具,在mysql中創建對應名稱的數據庫,并導入項目的sql文件;
          2. 使用IDEA/Eclipse/MyEclipse導入項目,Eclipse/MyEclipse導入時,若為maven項目請選擇maven;
          若為maven項目,導入成功后請執行maven clean;maven install命令,然后運行;
          3. 將項目中springmvc-servlet.xml配置文件中的數據庫配置改為自己的配置;
          4. 運行項目,在瀏覽器中輸入http://localhost:8080/ 登錄


          運行截圖


          ?編輯

          ?編輯

          ?編輯

          ?編輯

          ?編輯

          ?編輯

          ?編輯

          ?編輯

          ?編輯

          ?編輯


          用戶管理控制層:


          package com.houserss.controller;
          
          import javax.servlet.http.HttpSession;
          
          import org.apache.commons.lang3.StringUtils;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.stereotype.Controller;
          import org.springframework.web.bind.annotation.RequestMapping;
          import org.springframework.web.bind.annotation.RequestMethod;
          import org.springframework.web.bind.annotation.RequestParam;
          import org.springframework.web.bind.annotation.ResponseBody;
          
          import com.houserss.common.Const;
          import com.houserss.common.Const.Role;
          import com.houserss.common.ServerResponse;
          import com.houserss.pojo.User;
          import com.houserss.service.IUserService;
          import com.houserss.service.impl.UserServiceImpl;
          import com.houserss.util.MD5Util;
          import com.houserss.util.TimeUtils;
          import com.houserss.vo.DeleteHouseVo;
          import com.houserss.vo.PageInfoVo;
          
          /**
           * Created by admin
           */
          @Controller
          @RequestMapping("/user/")
          public class UserController {
              @Autowired
              private IUserService iUserService;
          
              /**
               * 用戶登錄
               * @param username
               * @param password
               * @param session
               * @return
               */
              @RequestMapping(value="login.do",method=RequestMethod.POST)
              @ResponseBody
              public ServerResponse<User> login(User user,String uvcode, HttpSession session){
                  String code=(String)session.getAttribute("validationCode");
                  if(StringUtils.isNotBlank(code)) {
                      if(!code.equalsIgnoreCase(uvcode)) {
                          return ServerResponse.createByErrorMessage("驗證碼不正確");
                      }
                  }
                  ServerResponse<User> response=iUserService.login(user.getUsername(),user.getPassword());
                  if(response.isSuccess()){
                      session.setAttribute(Const.CURRENT_USER,response.getData());
                  }
                  return response;
              }
          
            
              
              
          }
          



          管理員管理控制層:


          
          package com.sxl.controller.admin;
          
          import java.util.List;
          import java.util.Map;
          
          import javax.servlet.http.HttpServletRequest;
          
          import org.springframework.http.ResponseEntity;
          import org.springframework.stereotype.Controller;
          import org.springframework.ui.Model;
          import org.springframework.web.bind.annotation.RequestMapping;
          
          import com.sxl.controller.MyController;
          
          @Controller("adminController")
          @RequestMapping(value="/admin")
          public class AdminController extends MyController {
          	
          
          	@RequestMapping(value="/index")
          	public String frame(Model model, HttpServletRequest request)throws Exception {
          		return "/admin/index";
          	}
          	
          	@RequestMapping(value="/main")
          	public String main(Model model, HttpServletRequest request)throws Exception {
          		return "/admin/main";
          	}
          	
          	@RequestMapping(value="/tj1")
          	public String tj1(Model model, HttpServletRequest request)throws Exception {
          		String sql="select DATE_FORMAT(insertDate,'%Y-%m-%d') dates,sum(allPrice) price from t_order order by DATE_FORMAT(insertDate,'%Y-%m-%d')  desc";
          		List<Map> list=db.queryForList(sql);
          		model.addAttribute("list", list);
          		System.out.println(list);
          		return "/admin/tj/tj1";
          	}
          	
          	
          	@RequestMapping(value="/password")
          	public String password(Model model, HttpServletRequest request)throws Exception {
          		return "/admin/password";
          	}
          	
          	
          	@RequestMapping(value="/changePassword")
          	public ResponseEntity<String> loginSave(Model model,HttpServletRequest request,String oldPassword,String newPassword) throws Exception {
          		Map admin=getAdmin(request);
          		if(oldPassword.equals(admin.get("password").toString())){
          			String sql="update t_admin set password=? where id=?";
          			db.update(sql, new Object[]{newPassword,admin.get("id")});
          			return renderData(true,"1",null);
          		}else{
          			return renderData(false,"1",null);
          		}
          	}
          }
          



          修改密碼業務邏輯:


          
          package com.sxl.controller.admin;
          
          import java.util.Map;
          
          import javax.servlet.http.HttpServletRequest;
          
          import org.springframework.http.ResponseEntity;
          import org.springframework.stereotype.Controller;
          import org.springframework.ui.Model;
          import org.springframework.web.bind.annotation.RequestMapping;
          
          import com.sxl.controller.MyController;
          
          @Controller("userController")
          @RequestMapping(value="/user")
          public class UserController extends MyController {
          	
          
          	@RequestMapping(value="/index")
          	public String frame(Model model, HttpServletRequest request)throws Exception {
          		return "/user/index";
          	}
          	
          	@RequestMapping(value="/main")
          	public String main(Model model, HttpServletRequest request)throws Exception {
          		return "/user/main";
          	}
          	
          	
          	@RequestMapping(value="/password")
          	public String password(Model model, HttpServletRequest request)throws Exception {
          		return "/user/password";
          	}
          	
          	
          	@RequestMapping(value="/changePassword")
          	public ResponseEntity<String> loginSave(Model model,HttpServletRequest request,String oldPassword,String newPassword) throws Exception {
          		Map user=getUser(request);
          		if(oldPassword.equals(user.get("password").toString())){
          			String sql="update t_user set password=? where id=?";
          			db.update(sql, new Object[]{newPassword,user.get("id")});
          			return renderData(true,"1",null);
          		}else{
          			return renderData(false,"1",null);
          		}
          	}
          	@RequestMapping(value="/mine")
          	public String mine(Model model, HttpServletRequest request)throws Exception {
          Map user=getUser(request);Map map=db.queryForMap("select * from t_user where id=?",new Object[]{user.get("id")});model.addAttribute("map", map);		return "/user/mine";
          	}
          	
          	
          
          	@RequestMapping(value="/mineSave")
          	public ResponseEntity<String> mineSave(Model model,HttpServletRequest request,Long id
          		,String username,String password,String name,String gh,String mobile) throws Exception{
          		int result=0;
          			String sql="update t_user set name=?,gh=?,mobile=? where id=?";
          			result=db.update(sql, new Object[]{name,gh,mobile,id});
          		if(result==1){
          			return renderData(true,"操作成功",null);
          		}else{
          			return renderData(false,"操作失敗",null);
          		}
          	}
          	}
          



          通用管理模塊:


          package com.sxl.controller;
          
          
          import java.nio.charset.Charset;
          import java.util.Locale;
          import java.util.ResourceBundle;
          
          import javax.servlet.http.HttpServletRequest;
          
          import org.apache.commons.lang.StringUtils;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.http.HttpHeaders;
          import org.springframework.http.HttpStatus;
          import org.springframework.http.MediaType;
          import org.springframework.http.ResponseEntity;
          
          import com.sxl.util.JacksonJsonUtil;
          import com.sxl.util.StringUtil;
          import com.sxl.util.SystemProperties;
          
          
          public class BaseController {
          	public static final Long EXPIRES_IN=1000 * 3600 * 24 * 1L;// 1天
          
          	@Autowired
          	private SystemProperties systemProperties;
          
          	/**
          	 * 獲得配置文件內容
          	 */
          	public String getConfig(String key) {
          		return systemProperties.getProperties(key);
          	}
          
          	/**
          	 * 返回服務器地址 like http://192.168.1.1:8441/UUBean/
          	 */
          	public String getHostUrl(HttpServletRequest request) {
          		String hostName=request.getServerName();
          		Integer hostPort=request.getServerPort();
          		String path=request.getContextPath();
          
          		if (hostPort==80) {
          			return "http://" + hostName + path + "/";
          		} else {
          			return "http://" + hostName + ":" + hostPort + path + "/";
          		}
          	}
          
          	/***
          	 * 獲取當前的website路徑 String
          	 */
          	public static String getWebSite(HttpServletRequest request) {
          		String returnUrl=request.getScheme() + "://"
          				+ request.getServerName();
          
          		if (request.getServerPort() !=80) {
          			returnUrl +=":" + request.getServerPort();
          		}
          
          		returnUrl +=request.getContextPath();
          
          		return returnUrl;
          	}
          
          
          
          	/**
          	 * 初始化HTTP頭.
          	 * 
          	 * @return HttpHeaders
          	 */
          	public HttpHeaders initHttpHeaders() {
          		HttpHeaders headers=new HttpHeaders();
          		MediaType mediaType=new MediaType("text", "html",
          				Charset.forName("utf-8"));
          		headers.setContentType(mediaType);
          		return headers;
          	}
          
          	/**
          	 * 返回 信息數據
          	 * 
          	 * @param status
          	 * @param msg
          	 * @return
          	 */
          	public ResponseEntity<String> renderMsg(Boolean status, String msg) {
          		if (StringUtils.isEmpty(msg)) {
          			msg="";
          		}
          		String str="{\"status\":\"" + status + "\",\"msg\":\"" + msg + "\"}";
          		ResponseEntity<String> responseEntity=new ResponseEntity<String>(str,
          				initHttpHeaders(), HttpStatus.OK);
          		return responseEntity;
          	}
          
          	/**
          	 * 返回obj數據
          	 * 
          	 * @param status
          	 * @param msg
          	 * @param obj
          	 * @return
          	 */
          	public ResponseEntity<String> renderData(Boolean status, String msg,
          			Object obj) {
          		if (StringUtils.isEmpty(msg)) {
          			msg="";
          		}
          		StringBuffer sb=new StringBuffer();
          		sb.append("{");
          		sb.append("\"status\":\"" + status + "\",\"msg\":\"" + msg + "\",");
          		sb.append("\"data\":" + JacksonJsonUtil.toJson(obj) + "");
          		sb.append("}");
          
          		ResponseEntity<String> responseEntity=new ResponseEntity<String>(
          				sb.toString(), initHttpHeaders(), HttpStatus.OK);
          		return responseEntity;
          	}
          
          
          	/***
          	 * 獲取IP(如果是多級代理,則得到的是一串IP值)
          	 */
          	public static String getIpAddr(HttpServletRequest request) {
          		String ip=request.getHeader("x-forwarded-for");
          		if (ip==null || ip.length()==0 || "unknown".equalsIgnoreCase(ip)) {
          			ip=request.getHeader("Proxy-Client-IP");
          		}
          
          		if (ip==null || ip.length()==0 || "unknown".equalsIgnoreCase(ip)) {
          			ip=request.getHeader("WL-Proxy-Client-IP");
          		}
          
          		if (ip==null || ip.length()==0 || "unknown".equalsIgnoreCase(ip)) {
          			ip=request.getRemoteAddr();
          		}
          
          		if (ip !=null && ip.length() > 0) {
          			String[] ips=ip.split(",");
          			for (int i=0; i < ips.length; i++) {
          				if (!"unknown".equalsIgnoreCase(ips[i])) {
          					ip=ips[i];
          					break;
          				}
          			}
          		}
          
          		return ip;
          	}
          
          	/**
          	 * 國際化獲得語言內容
          	 * 
          	 * @param key
          	 *            語言key
          	 * @param args
          	 * @param argsSplit
          	 * @param defaultMessage
          	 * @param locale
          	 * @return
          	 */
          	public static String getLanguage(String key, String args, String argsSplit,
          			String defaultMessage, String locale) {
          		String language="zh";
          		String contry="cn";
          		String returnValue=defaultMessage;
          
          		if (!StringUtil.isEmpty(locale)) {
          			try {
          				String[] localeArray=locale.split("_");
          				language=localeArray[0];
          				contry=localeArray[1];
          			} catch (Exception e) {
          			}
          		}
          
          		try {
          			ResourceBundle resource=ResourceBundle.getBundle("lang.resource",
          					new Locale(language, contry));
          			returnValue=resource.getString(key);
          			if (!StringUtil.isEmpty(args)) {
          				String[] argsArray=args.split(argsSplit);
          				for (int i=0; i < argsArray.length; i++) {
          					returnValue=returnValue.replace("{" + i + "}",
          							argsArray[i]);
          				}
          			}
          		} catch (Exception e) {
          		}
          
          		return returnValue;
          	}
          }
          


          ?


          主站蜘蛛池模板: 69久久精品无码一区二区| 一区二区视频在线播放| 国产成人精品a视频一区| 色偷偷av一区二区三区| 制服丝袜一区二区三区| 国产丝袜美女一区二区三区| 中文字幕一区二区在线播放| 超清无码一区二区三区| 国产一区二区三区免费| 国产亚洲综合一区柠檬导航 | 2021国产精品一区二区在线 | 三上悠亚日韩精品一区在线| 国产精品女同一区二区久久 | 国产精品亚洲午夜一区二区三区 | 精品亚洲A∨无码一区二区三区| 99久久精品日本一区二区免费| 亚洲一区二区三区高清视频| 无码人妻精品一区二区蜜桃网站| 国产一区二区三区四| 久久久久人妻精品一区蜜桃| 日本国产一区二区三区在线观看| 精品免费国产一区二区三区| 久久久久人妻一区精品果冻| 一区二区三区在线观看| 欧洲精品无码一区二区三区在线播放| 国产成人一区在线不卡| 日韩最新视频一区二区三| 亚洲AV无码一区东京热久久 | 精品国产伦一区二区三区在线观看| 日韩在线一区二区三区视频| 亚洲老妈激情一区二区三区| 国产成人精品视频一区| 91在线一区二区| 少妇激情av一区二区| 精品无码综合一区| 日本一区二区三区爆乳| 无码人妻精品一区二区三区久久久 | 国产天堂一区二区综合| 亚洲AV无码片一区二区三区| 精品欧洲av无码一区二区14| 无码免费一区二区三区免费播放|