日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

以太坊Dapp项目-网页钱包开发手册

發(fā)布時間:2025/3/15 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 以太坊Dapp项目-网页钱包开发手册 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

以太坊Dapp項(xiàng)目-網(wǎng)頁錢包開發(fā)手冊

修訂日期姓名郵箱
2018-10-10brucefengbrucefeng@brucefeng.com

前言

在之前的一篇文章以太坊智能合約項(xiàng)目-Token合約開發(fā)與部署中,我們提到了錢包了錢包的概念以及主流的幾種錢包,如Mist,MyEtherWallet,MetaMask等,之前我們主要將錢包作為一個開發(fā)工具使用,用于智能合約的開發(fā)與調(diào)試工作,使用較多的是瀏覽器插件錢包MetaMask。

在本文中,我們主要介紹MyEtherWallet以及如何實(shí)現(xiàn)一個簡易版的MyEtherWallet網(wǎng)頁版應(yīng)用,在本文中,我們能夠?qū)W習(xí)到如下內(nèi)容

  • node.js開發(fā)基礎(chǔ)
  • web3.js API使用
  • 以太幣轉(zhuǎn)賬實(shí)現(xiàn)
  • Token轉(zhuǎn)賬實(shí)現(xiàn)

目前主流的錢包平臺已經(jīng)有太多了,而且有很多已經(jīng)做得比較完善了,所以我們本文的開發(fā)工作只是為了學(xué)習(xí)以太坊開發(fā)技術(shù),并非去設(shè)計一個新的錢包軟件,重復(fù)造輪子幾乎沒有任何價值。

一. MyEtherWallet介紹

MyEtherWallet 是一個輕錢包,無需下載,所有操作在直接在網(wǎng)頁上就可以完成

主要功能如下

  • Net Wallet : 新建錢包
  • Send Ether && Tokens :以太幣或者Token轉(zhuǎn)賬
  • Contract: 部署智能合約
  • ENS:以太坊域名平臺
  • Check TX Status: 查看交易狀態(tài)
  • View Wallet Info: 查看錢包信息

由于操作比較簡單,這里不做詳細(xì)講解,在下文中我們對其主要功能,如新建錢包,以太幣或者Token轉(zhuǎn)賬,查看交易狀態(tài)進(jìn)行參照開發(fā)。

二.node.js與web.js

1.Node.js

Node.js是一個JS運(yùn)行時環(huán)境,可以解析,執(zhí)行JavaScript代碼,在這個執(zhí)行環(huán)境中,為JS提供了一些服務(wù)器級別的操作API,如文件讀寫,網(wǎng)絡(luò)通信,Http服務(wù)器等,其使用事件驅(qū)動,非阻塞IO模型(異步),輕量高效,大多數(shù)與JS相關(guān)的包都放在npm上,通過命令就可以下載不同的庫跟框架,無需在去各個模塊的官網(wǎng)上面單獨(dú)下載,如安裝koa直接通過npm install koa即可完成。

2. Web3.js

web3.js是一個庫集合,允許使用HTTP或者RPC連接與本地或者遠(yuǎn)程以太坊節(jié)點(diǎn)進(jìn)行交互,包含以太坊生態(tài)系統(tǒng)的特定功能,開發(fā)者利用web3模塊主要連接以太坊的RPC層,從而與區(qū)塊鏈進(jìn)行交互

  • web3-eth : 與以太坊區(qū)塊鏈和智能合約之間的交互
  • web3-ssh: 用于進(jìn)行通信的p2p和廣播
  • web-bzz: 用于群協(xié)議,分散的文件存儲
  • web3-utils: 主要用于Dapp開發(fā)的輔助函數(shù)

官方文檔: https://web3js.readthedocs.io/en/1.0/

web3.eth.accounts

  • 創(chuàng)建錢包賬戶

web3.eth.accounts.create();

  • 生成錢包配置

web3.eth.accounts.encrypt(privateKey, password);

  • 通過私鑰生成賬戶對象

web3.eth.accounts.privateKeyToAccount(privateKey);

  • 查詢余額

web3.eth.getBalance(address [, defaultBlock] [, callback])

  • 通過私鑰跟密碼生成配置文件

web3.eth.accounts.encrypt(privateKey, password);

  • 通過配置文件和密碼解鎖賬戶

web3.eth.accounts.decrypt(keystoreJsonV3, password);

  • 發(fā)送簽名交易

web3.eth.sendSignedTransaction(signedTransactionData [, callback])

Example

var Tx = require('ethereumjs-tx'); var privateKey = new Buffer('e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', 'hex')var rawTx = {nonce: '0x00',gasPrice: '0x09184e72a000',gasLimit: '0x2710',to: '0x0000000000000000000000000000000000000000',value: '0x00',data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057' }var tx = new Tx(rawTx); tx.sign(privateKey);var serializedTx = tx.serialize();// console.log(serializedTx.toString('hex')); // 0xf889808609184e72a00082271094000000000000000000000000000000000000000080a47f74657374320000000000000000000000000000000000000000000000000000006000571ca08a8bbf888cfa37bbf0bb965423625641fc956967b81d12e23709cead01446075a01ce999b56a8a88504be365442ea61239198e23d1fce7d00fcfc5cd3b44b7215fweb3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex')) .on('receipt', console.log);> // see eth.getTransactionReceipt() for details

(1) nonce

web3.eth.getTransactionCount(address [, defaultBlock] [, callback])

(2) gasPrice

web3.eth.getGasPrice([callback])

(3) gasLimit

  • 預(yù)估gas值

3. Koa中間件

Koa號稱是基于Node.js平臺的下一代web開發(fā)框架,Koa 是一個新的 web 框架,由 Express 幕后的原班人馬打造, 致力于成為 web 應(yīng)用和 API 開發(fā)領(lǐng)域中的一個更小、更富有表現(xiàn)力、更健壯的基石。 通過利用 async 函數(shù),Koa 幫你丟棄回調(diào)函數(shù),并有力地增強(qiáng)錯誤處理。 Koa 并沒有捆綁任何中間件, 而是提供了一套優(yōu)雅的方法,幫助您快速而愉快地編寫服務(wù)端應(yīng)用程序。

其使用方法可以參考https://koa.bootcss.com/

Koa的最大特色就是中間件,Koa中間件是簡單的函數(shù),調(diào)用app.user()傳入,MiddlewareFunction函數(shù)帶有兩個參數(shù)(ctx,next),中間件作為HTTP Request和HTTP Reponse的橋梁,用來實(shí)現(xiàn)連接功能,如

app.use(async (ctx, next) =>{console.log(`Process ${ctx.request.method} ${ctx.request.url} ...`);await next(); });

ctx是一個請求的上下文,封裝了傳入的http消息,并對該消息進(jìn)行響應(yīng),koa提供了一個Request/Response對象提供了Context的request/response屬性與用于處理Http請求的方法。

next是被調(diào)用來執(zhí)行上下游中間件的函數(shù),必須手動調(diào)用next()運(yùn)行下游中間件,否則下游中間件不會被正常執(zhí)行??梢圆捎脙煞N不同的方法來實(shí)現(xiàn)中間件

  • async function
  • common function

(1) 安裝啟動Koa服務(wù)

  • 安裝koa
$ mkdir koaTest ; cd koaTest $ npm init -y $ npm install koa
  • 創(chuàng)建啟動文件
$ vim index.js var Koa = require("koa") //導(dǎo)入Koa庫 var app = new Koa(); //創(chuàng)建Koa應(yīng)用對象,帶有node http服務(wù)的koa接口 app.listen(3003); //啟動服務(wù)的快捷方法
  • 啟動服務(wù)
$ node index.js

此時會監(jiān)聽3003端口,通過瀏覽器訪問

(2) Context

context是一個請求的上下文,該對象封裝了一個傳入的http消息,context有request和response屬性,我們可以通過涉案值兩個屬性來處理和相應(yīng)不同的請求。

$ vim index.js var Koa = require("koa"); var app = new Koa(); // app.use(function(ctx,next){console.log(ctx.request.path);//通過設(shè)置ctx的response的body值可以返回數(shù)據(jù)到客戶端ctx.response.body = "my koa app";console.log(ctx.response.type); }); app.listen("3003"); console.log("koa server is listening on port 3003");
  • app.use(function):將給定的function作為中間件加載到應(yīng)用中。
  • ctx: 每一個請求都會創(chuàng)建一段上下文,在控制業(yè)務(wù)邏輯的中間件中,上下文被寄存在ctx對象中,許多上線文屬性和方法都被存在ctx.request和ctx.response中,比如訪問ctx.type和ctx.length都被代理到reponse對象,ctx.path和ctx.method都被代理到request對象中。

(3) 網(wǎng)頁模板

在實(shí)際開發(fā)中,返回給用戶的網(wǎng)頁都是通過模板文件進(jìn)行返回的,可以讓Koa先讀取模板文件,然后將這個模板返回給用戶,需要指定response的type為text/html類型

var Koa = require("koa"); var app = new Koa(); var fs = require("fs"); app.use(ctx=>{console.log(ctx.path);//必須指定typectx.type = "text/html";ctx.body = fs.createReadStream("./views/teest.html");console.log(ctx.type); }); app.listen(3003); console.log("koa server is listening on port 3003");

(4) 中間件

Koa所有的功能都是通過中間件實(shí)現(xiàn)的,中間件處于HTTP Request和HTTP Response之間。

Koa的中間件之間按照編碼書序在棧內(nèi)以此執(zhí)行,允許執(zhí)行操作并向下傳遞請求,之后過濾并必須返回響應(yīng),響應(yīng)的測試代碼與步驟如下

var Koa = require("koa"); var app = new Koa(); //es6新語法: //函數(shù)名 =>(參數(shù)) =>{}var one = (ctx,next) =>{console.log("1.1");next();console.log("1.2"); };var two = (ctx,next) =>{console.log("2.1");next();console.log("2.2"); };var three = (ctx, next) =>{console.log("3.1");next();console.log("3.2"); };app.use(one); app.use(two); app.use(three);app.listen(3003); console.log("服務(wù)啟動完畢");

返回結(jié)果

2.1 3.1 3.2 2.2 1.2

(5) 異步中間件

由async標(biāo)記的函數(shù)被稱為異步函數(shù),在異步函數(shù)中,可以通過await調(diào)用另外一個異步函數(shù),使用await時,其所在的方法必須使用關(guān)鍵字async

var Koa = require("koa"); var app = new Koa(); app.use(async(ctx,next) =>{var start = Date.now();await next();console.log(`${ctx.url} ${Date.now - start}`); }); app.use(async (ctx,next)=>{ctx.response.body = "async test" }); app.listen(3003); console.log("服務(wù)啟動完畢");

(6) 原生路由

var Koa = require("koa"); var app = new Koa(); //es6新語法: //函數(shù)名 =>(參數(shù)) =>{} app.use((ctx,next)=> {console.log("%s %s", ctx.method, ctx.url);next(); });app.use((ctx,next) =>{if (ctx.request.path == '/'){ctx.response.body = 'index page';}else {next();} });app.use((ctx,next) =>{if (ctx.request.path == '/error'){ctx.response.body = 'error page';} });app.listen(3003); console.log("服務(wù)啟動完畢");

(7) koa-router路由

由于原生路由使用比較繁瑣,所以可以通過封裝好的koa-router模塊,使用router.routers()綁定到中間件

安裝koa-router

$ npm install koa-router var Koa = require("koa"); var app = new Koa(); //導(dǎo)入koa-router,注意要加上()才能生效 var router = require("koa-router")() router.get("/hello",function(ctx,next){ctx.response.body = "hello,brucefeng"; });router.get("/bye",function (ctx,next){ctx.response.body = "good bye brucefeng"; });//將router路由注冊到中間件 app.use(router.routes());app.listen(3003); console.log("服務(wù)啟動完畢");

(8) 請求重定向

一般在如下情況下需要使用到重定向

  • 后臺系統(tǒng)升級,對之前的頁面不在支持,此時需要使用重定向到新的API上滿足用戶的訪問準(zhǔn)確性
  • 完成某個操作后自動跳轉(zhuǎn)至其他頁面,如注冊成功,登錄成功等等
var Koa = require("koa"); var app = new Koa(); //導(dǎo)入koa-router,注意要加上()才能生效 var router = require("koa-router")() router.get("/hello",function(ctx,next){ctx.response.body = "hello,brucefeng"; });router.get("/hi",function (ctx,next){ctx.response.redirect("/hello") });//將router路由注冊到中間件 app.use(router.routes());app.listen(3003); console.log("服務(wù)啟動完畢");

通過 ctx.response.redirect("/hello")將"/hi"請求重定向到/hello對應(yīng)的頁面

在node.js中訪問的url中有中文時,需要通過全局encodeURIComponent(string)進(jìn)行編碼

(9) 獲取get請求參數(shù)

客戶端在請求獲取服務(wù)的數(shù)據(jù)時,獲取的URL中通常會攜帶各種參數(shù),服務(wù)端如何獲取到get請求的參數(shù)呢?

  • 格式1:http://127.0.0.1:3003/hello/brucefeng

獲取方式: ctx.params.name

  • 格式2:http://127.0.0.1:3003/bye?name=brucefeng

獲取方式: ctx.query.name

調(diào)用params獲取參數(shù)的時候,params不是request的屬性,需要通過ctx直接調(diào)用獲取。

(10) 獲取post請求參數(shù)

Get請求的參數(shù)附帶在了url上,Post請求的參數(shù)在請求體body里面,所以要獲取body的數(shù)據(jù),需要使用到插件koa-body,通過ctx.request.body.name獲取參數(shù).

$ npm install koa-router var Koa = require("koa"); var app = new Koa();//導(dǎo)入koa-router,注意要加上()才能生效 var router = require("koa-router")() //引入koa-body var koaBody = require("koa-body")router.post("/hello",function(ctx,next){var body = ctx.request.body;ctx.response.body = "hello,bruce";console.log(body);console.log(body.username); });//設(shè)置multipart : true,支持多個參數(shù) app.use(koaBody({multipart:true }))//將router路由注冊到中間件 app.use(router.routes());app.listen(3003); console.log("服務(wù)啟動完畢");

//通過命令使用curl插件模擬調(diào)用一個Post請求
//curl -H "Content-Type:application/json" -X POST --data '{"username":"brucefeng"}' http://localhost:3003/hello

brucefengdeMBP:ETHWalletDemo brucefeng$ node index.js 服務(wù)啟動完畢 { username: 'brucefeng' } brucefeng

(11) 加載靜態(tài)資源

加載靜態(tài)資源,如圖片,字體,樣式表,腳本等,編碼指定靜態(tài)資源的路徑是相對于./static的路徑。

$ npm install koa-static var Koa = require("koa"); var app = new Koa();//導(dǎo)入koa-router,注意要加上()才能生效 var router = require("koa-router")(); var static = require("koa-static"); var path = require("path")router.get("/hello",function (ctx,next){ctx.response.body = "<html> <a href='/0.png'>看我</html>" }) //靜態(tài)資源的路徑是相對于./static的路徑 app.use(static(path.join(__dirname,"./static")))//將router路由注冊到中間件 app.use(router.routes());app.listen(3003); console.log("服務(wù)啟動完畢");

啟動服務(wù),通過瀏覽器訪問測試

(12) 模板引擎

模板引擎ejs需要配上模板渲染中間件koa-views使用,如果需要支持其他后綴的文件,需要將文件擴(kuò)展名映射到引擎中。

$ npm install ejs koa-views

index.js

var Koa = require("koa"); var app = new Koa();//導(dǎo)入koa-router,注意要加上()才能生效 var router = require("koa-router")(); var static = require("koa-static"); var path = require("path") var views = require("koa-views")router.get("/hello",async (ctx,next) =>{//將json里面的值替換為文件里面的變量var name = "brucefeng";await ctx.render("test.ejs",{name,"sex":"帥哥"}) })router.get("/bye",async (ctx,next)=>{await ctx.render("home.html",{"name": "fengyingcong"}) })app.use(views(//默認(rèn)是views下面獲取ejs后綴的文件,如果是其他類型的文件需要指定文件類型path.join(__dirname,"./static/views"),{extension:"ejs", map:{html: "ejs"}} ))//靜態(tài)資源的路徑是相對于./static的路徑 app.use(static(path.join(__dirname,"./static")))//將router路由注冊到中間件 app.use(router.routes());app.listen(3003); console.log("服務(wù)啟動完畢");

static/views/test.ejs

<!DOCTYPE <!DOCTYPE html> <html> <head><meta charset="utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge"><title>Page Title</title><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" type="text/css" media="screen" href="main.css" /><script src="main.js"></script> </head> <body><div>姓名: <%= name %> 性別: <%= sex %></div> </body> </html>

static/views/home.html

<!DOCTYPE <!DOCTYPE html> <html> <head><meta charset="utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge"><title>Page Title</title><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" type="text/css" media="screen" href="main.css" /><script src="main.js"></script> </head> <body><div>姓名: <%= name %> </div> </body> </html>

三.初始化項(xiàng)目環(huán)境

1.導(dǎo)入項(xiàng)目依賴

$ mkdir ETHWalletDemo ;cd ETHWalletDemo $ npm init -y $ npm i web3 koa koa-body koa-static koa-views koa-router ejs

2.創(chuàng)建路由文件

$ mkdir router ; cd router $ vim router.js var router = require("koa-router")() //定義路由newaccount router.get("/newaccount",(ctx,next)=>{ctx.response.body = "創(chuàng)建錢包" })module.exports = router

3.創(chuàng)建入口文件

ETHWalletDemo/index.js

//導(dǎo)入./router/router包 var router = require("./router/router.js") //引入Koa庫 var Koa = require("koa") //通過koa創(chuàng)建一個應(yīng)用程序 var app = new Koa()app.use(router.routes()) app.listen(3003) console.log("錢包啟動成功,請訪問http://127.0.0.1:3003/newaccount進(jìn)行測試")

4.創(chuàng)建MVC框架

ETHWalletDemo下創(chuàng)建

$ mkdir models views controllers #創(chuàng)建MVC框架目錄 $ mkdir utils #創(chuàng)建幫助文件目錄 $ mkdir -p static/{css,js,html} #創(chuàng)建靜態(tài)文件目錄

5.完善項(xiàng)目框架

ETHWalletDemo/index.js

//引入Koa庫 var Koa = require("koa") //通過koa創(chuàng)建一個應(yīng)用程序 var app = new Koa()//導(dǎo)入./router/router包 var router = require("./router/router.js") var static = require("koa-static") var path = require("path") var views = require("koa-views") var koaBody = require("koa-body")//攔截獲取網(wǎng)絡(luò)請求,自定義的function需要使用next() app.use(async (ctx,next)=>{console.log(`${ctx.url} ${ctx.method}...`)await next(); })//注冊中間件 //注冊靜態(tài)文件的庫到中間件 app.use(static(path.join(__dirname,"static"))) //注冊模板引擎的庫到中間件 app.use(views(path.join(__dirname,"views"),{extension:"ejs", map:{html: "ejs"}})) //針對于文件上傳時,可以解析多個字段 app.use(koaBody({multipart:true})) app.use(router.routes()) app.listen(3003) console.log("錢包啟動成功,請訪問http://127.0.0.1:3003/...進(jìn)行測試")

四.創(chuàng)建錢包賬戶

1.創(chuàng)建錢包賬戶

(1) 封裝web3庫調(diào)用

utils/myUtils.js

var web3 = require("../utils/myUtils").getWeb3() module.exports = {getWeb3: ()=>{var Web3 = require("web3");var web3 = new Web3(Web3.givenProvider || 'http://127.0.0.1:8545');return web3;} }

(2) 創(chuàng)建控制器

controllers/newAccount.js

var web3 = require("../utils/myUtils").getWeb3()module.exports = {//獲取創(chuàng)建賬號的頁面newAccountHtml: async (ctx) =>{await ctx.render("newaccount.html")},//表單提交被觸發(fā)的方法newAccount: (ctx) =>{console.log("newAccount");var password = ctx.request.body.password;//通過密碼創(chuàng)建錢包賬戶var account = web3.eth.accounts.create(password);console.log(account.address);ctx.response.body = "錢包賬戶: "+account.address +" 創(chuàng)建成功";} }

(3) 創(chuàng)建前端頁面

views/newaccount.html

<html> <head><title>創(chuàng)建錢包</title> </head> <body><div id="main"><h1>創(chuàng)建一個新的錢包</h1><form method="POST" action="/newaccount"><input type="text" placeholder="請輸入密碼" name="password"><button type="submit">創(chuàng)建錢包</button></form></div> </body> </html>

(4) 配置路由

router/router.js

var router = require("koa-router")() var newAccount = require("../controllers/newAccount") //創(chuàng)建賬號的頁面 router.get("/newaccount",newAccount.newAccountHtml) //提交創(chuàng)建賬號表單 router.post("/newaccount",newAccount.newAccount)module.exports = router

2.下載配置文件

(1) 創(chuàng)建目錄保存配置文件

$ mkdir static/keystore

(2) 實(shí)現(xiàn)配置文件的保存

controllers/newAccount.js

var web3 = require("../utils/myUtils").getWeb3() var fs = require("fs") var path = require("path") module.exports = {//獲取創(chuàng)建賬號的頁面newAccountHtml: async (ctx) =>{await ctx.render("newaccount.html")},//表單提交被觸發(fā)的方法newAccount: async (ctx) =>{console.log("newAccount");var password = ctx.request.body.password;//通過密碼創(chuàng)建錢包賬戶var account = web3.eth.accounts.create(password);console.log(account.address);//根據(jù)賬號私鑰跟密碼生成keystore文件var keystore = web3.eth.accounts.encrypt(account.privateKey, password);//keystore文件保存到文件中,var keystoreString = JSON.stringify(keystore);//格式如下:UTC--Date--Adress//UTC--2018-09-26T05-07-57.260Z--937d091780693ab7f51331bb52797a9267bb9ed2var fileTime = new Date().toDateString()var fileName = 'UTC--' + fileTime + '--' + account.address.slice(2) ;var filePath = path.join(__dirname,"../static/keystore",fileName)fs.writeFileSync(filePath,keystoreString)await ctx.render("downloadkeystore.html",{"downloadurl":path.join("keystore",fileName),"privatekey":account.privateKey})} }

此時生成的Keystore文件將會被保存至static/keystore目錄中

(3) 創(chuàng)建前端頁面

view/downloadkeystore.html

<html><head><title>保存KeyStore文件</title><script src="js/lib/jquery-3.3.1.min.js"></script><script src="js/lib/jquery.url.js"></script><script src="js/wallet.js"></script> </head><body><div id="nav"><script>$("#nav").load("html/nav.html")</script></div><div id="main"><div id="save-keystore"><h1>保存KeyStore文件</h1><a href="<%= downloadurl %>">保存KeyStore文件</a><br><button onclick="saveKeystoreNext()">Next Step</button></div><div id="save-privatekey" style="display:none"><h1>保存錢包私鑰</h1><div><%= privatekey %><span>請務(wù)必妥善保管!</span></div></div></div></body></html>

由于涉及到了onclick,所以,我們現(xiàn)在需要創(chuàng)建js代碼實(shí)現(xiàn)相關(guān)方法

實(shí)現(xiàn)saveKeystoreNext方法

  • 導(dǎo)入jquery文件
$ mkdir js/lib

將jquery.url.js與jquery-3.3.1.min.js拷貝進(jìn)lib目錄中

  • 實(shí)現(xiàn)方法

static/js/wallet.js

function saveKeystoreNext(){//隱藏保存keystore頁面$("#save-keystore").hide()//顯示保存private頁面$("#save-privatekey").show() }

3.導(dǎo)航頁設(shè)計

此處的導(dǎo)航頁前端用的是藍(lán)鯨智云的MagicBox組件,作為藍(lán)鯨智云的忠實(shí)粉絲,推薦大家使用

http://magicbox.bk.tencent.com/

(1) 創(chuàng)建導(dǎo)航頁

static/html/nav.html

<link href="https://magicbox.bk.tencent.com/static_api/v3/assets/bootstrap-3.3.4/css/bootstrap.min.css" rel="stylesheet"> <link href="https://magicbox.bk.tencent.com/static_api/v3/bk/css/bk.css" rel="stylesheet"><div class="king-horizontal-nav4"><div class="logo_wrap"><a class="logo" title="" href="javascript:;"><img src="https://brucefeng-1251273438.cos.ap-shanghai.myqcloud.com/8.%E4%BB%A5%E5%A4%AA%E5%9D%8A%E7%BD%91%E9%A1%B5%E9%92%B1%E5%8C%85%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C/logo.png"alt="" class="logo mt10 ml20"></a></div><div class="nav_wrap tmp"><ul><li><a href="/"><span>首頁</span></a></li><li><a href="/newaccount.html"><span>創(chuàng)建錢包</span></a></li><li><a href="/transaction.html"><span>轉(zhuǎn)賬</span></a></li><li><a href="/queryTransaction.html"><span>查詢交易</span></a></li><li><a href="https://github.com/DiaboFong/ETHWalletDemo" target="_blank"><span>項(xiàng)目代碼</span></a></li><li><a href="http://blog.51cto.com/clovemfong" target="_blank"><span>我的博客</span></a></li></ul></div> </div>

(2) 集成導(dǎo)航頁

將nav.html集成到所有相關(guān)網(wǎng)頁中即可,如newaccount.html

<html><head><title>創(chuàng)建錢包</title><script src="js/lib/jquery-3.3.1.min.js"></script><script src="js/lib/jquery.url.js"></script><script src="js/wallet.js"></script> </head><body><div id="nav"><script>$("#nav").load("html/nav.html")</script></div><div id="main"><h1>創(chuàng)建一個新的錢包</h1><form method="POST" action="/newaccount"><input type="text" placeholder="請輸入密碼" name="password"><button type="submit">創(chuàng)建錢包</button></form></div> </body></html>

五.解鎖錢包賬戶

在通過錢包轉(zhuǎn)賬之前,我們都需要先對錢包賬戶進(jìn)行解鎖后才能進(jìn)行正常交易,目前主要的解鎖方式為

  • 私鑰解鎖
  • 配置文件+密碼解鎖
  • 助記詞解鎖

本文我們主要講解如何開發(fā)通過私鑰解鎖跟配置文件解鎖來進(jìn)行賬戶解鎖。

我們模仿MyEtherWallet的方式,將解鎖錢包的功能放在轉(zhuǎn)賬交易模塊下面,便于整合。

1. 搭建代碼框架

(1) 創(chuàng)建控制器

controllers/transaction.js

module.exports = {transactionHtml: async (ctx) =>{await ctx.render("transaction.html")}, }

(2) 創(chuàng)建前端頁面

views/transaction.html

<html><head><title>轉(zhuǎn)賬</title><script src="js/lib/jquery-3.3.1.min.js"></script><script src="js/lib/jquery.url.js"></script><script src="js/wallet.js"></script> </head><body><div id="nav"><script>$("#nav").load("html/nav.html")</script></div><div id="main"><h3>發(fā)送以太幣/Token</h3><input type="radio" id="unlockAccountType0" name="unlockAccountType" value="0"><label for="unlockAccountType0">Keystore File</label><br><input type="radio" id="unlockAccountType1" name="unlockAccountType" vaule="1"><label for="unlockAccountType1">Private Key</label><!-- unlockAccount0 表示通過Keystore解鎖賬戶 unlockAccount1 表示通過私鑰解鎖賬戶--><div id="unlockAccount0" style="display:none"></div><div id="unlockAccount1" style="display:none"></div></div> </body></html>

(3) 配置路由

router/router.js

var router = require("koa-router")(); var newAccount = require("../controllers/newAccount"); var transactionController = require("../controllers/transaction");router.get("/",newAccount.homeHtml); //創(chuàng)建賬號的頁面 router.get("/newaccount.html",newAccount.newAccountHtml); //提交創(chuàng)建賬號表單 router.post("/newaccount",newAccount.newAccount);//獲取轉(zhuǎn)賬頁面 router.get("/transaction.html",transactionController.transactionHtml);module.exports = router

2. 通過私鑰解鎖賬戶

(1) 修改前端頁面

views/transaction.html

<html><head><title>轉(zhuǎn)賬</title><script src="js/lib/jquery-3.3.1.min.js"></script><script src="js/lib/jquery.url.js"></script><script src="js/wallet.js"></script><link rel="stylesheet" href="css/wallet.css"> </head><body><div id="nav"><script>$("#nav").load("html/nav.html")</script></div><div id="main"><h3>發(fā)送以太幣/Token</h3><input type="radio" id="unlockAccountType0" name="unlockAccountType" value="0"><label for="unlockAccountType0">Keystore File</label><br><input type="radio" id="unlockAccountType1" name="unlockAccountType" vaule="1"><label for="unlockAccountType1">Private Key</label><!-- unlockAccount0 表示通過Keystore解鎖賬戶 unlockAccount1 表示通過私鑰解鎖賬戶--><div id="unlockAccount0" style="display:none"></div><div id="unlockAccount1" style="display:none"><h3>請輸入錢包私鑰[請認(rèn)準(zhǔn)官網(wǎng),防止釣魚]</h3><textarea id="inputAccountType1" cols="" rows="3"></textarea><button onclick="unlockAccountWithPK()">解鎖</button></div></div> </body></html>

修改js/wallet.js代碼

function saveKeystoreNext(){//隱藏保存keystore頁面$("#save-keystore").hide()//顯示保存private頁面$("#save-privatekey").show() }function unlockAccountWithPK(){var privateKey = $("#inputAccountType1").val()console.log(privateKey)//將私鑰傳至服務(wù)端$post("/unlockWithPK",`privatekey=${privateKey}`,function(res,status){console.log(status + JSON.stringify(res))}) }// 對元素的操作需要等文檔加載完畢后才能調(diào)用成功 $(document).ready(function (){$("input[name=unlockAccountType]").change(function(){if (this.value == 0) {//如果點(diǎn)擊keystore,則顯示keystore操作$("#unlockAccount0").show()$("#unlockAccount1").hide()}else {$("#unlockAccount0").hide()$("#unlockAccount1").show()}}) })

(2) 創(chuàng)建控制器

controllers/account.js

var web3 = require("../utils/myUtils").getWeb3()module.exports = {unlockWithPK: (ctx) => {var privateKey = ctx.request.body.privatekeyconsole.log(privateKey)var account = web3.eth.accounts.privateKeyToAccount(privateKey);console.log(account.address);ctx.response.body = "解鎖成功";} }

(3) 修改路由

router/router.js

var router = require("koa-router")(); var newAccount = require("../controllers/newAccount"); var transactionController = require("../controllers/transaction"); var accountController = require("../controllers/account")router.get("/",newAccount.homeHtml); //創(chuàng)建賬號的頁面 router.get("/newaccount.html",newAccount.newAccountHtml); //提交創(chuàng)建賬號表單 router.post("/newaccount",newAccount.newAccount);//獲取轉(zhuǎn)賬頁面 router.get("/transaction.html",transactionController.transactionHtml); router.post("/unlockWithPK",accountController.unlockWithPK);module.exports = router

(4) 階段性測試

  • 啟動服務(wù)
$ cd ETHWalletDemo $ $ node index.js 錢包啟動成功,請訪問http://127.0.0.1:3003/...進(jìn)行測試
  • 瀏覽器訪問

http://127.0.0.1:3003/

[1] 首頁

[2] 創(chuàng)建錢包

輸入密碼,此處先不隱藏,后期設(shè)計即可

)

點(diǎn)擊保存Keystore文件,并點(diǎn)擊Next Step

注意:此處的賬號私鑰不做隱藏,僅僅是為了測試需要,大家自己實(shí)際使用的私鑰請務(wù)必保存妥當(dāng)!!!

[3] 解鎖賬戶

后端返回

!

前端返回

前后端調(diào)試成功

3. 顯示賬戶余額

賬戶剛剛創(chuàng)建完畢,余額為0,我們可以通過私鏈轉(zhuǎn)賬的方式給新賬戶轉(zhuǎn)入10以太幣,具體操作可以參考博客:以太坊聯(lián)盟鏈-多節(jié)點(diǎn)私鏈搭建手冊中轉(zhuǎn)賬交易章節(jié)。

(1) 修改控制器

controllers/account.js

var web3 = require("../utils/myUtils").getWeb3()async function getAccountBalance(address) {var balance = await web3.eth.getBalance(address);var balanceEther = web3.utils.fromWei(balance, 'ether')return balanceEther; }module.exports = {unlockWithPK: async (ctx) => {//1.獲取私鑰var privateKey = ctx.request.body.privatekeyconsole.log(privateKey)//2.通過私鑰解鎖賬戶var account = web3.eth.accounts.privateKeyToAccount(privateKey);console.log(account)//3.獲取賬戶余額var balance = await getAccountBalance(account.address)console.log(balance)responseData = {code: 0,status: "success",data: {balance: balance,address: account.address}}ctx.response.body = responseData}}

(2) 修改前端頁面

views/transaction.html

<html><head><title>轉(zhuǎn)賬</title><script src="js/lib/jquery-3.3.1.min.js"></script><script src="js/lib/jquery.url.js"></script><script src="js/wallet.js"></script><link rel="stylesheet" href="css/wallet.css"> </head><body><div id="nav"><script>$("#nav").load("html/nav.html")</script></div><div id="main"><h3>發(fā)送以太幣/Token</h3><div id="transaction0"><input type="radio" id="unlockAccountType0" name="unlockAccountType" value="0"><label for="unlockAccountType0">Keystore File</label><br><input type="radio" id="unlockAccountType1" name="unlockAccountType" vaule="1"><label for="unlockAccountType1">Private Key</label><!-- unlockAccount0 表示通過Keystore解鎖賬戶 unlockAccount1 表示通過私鑰解鎖賬戶--><div id="unlockAccount0" style="display:none"></div><div id="unlockAccount1" style="display:none"><h3>請輸入錢包私鑰[請認(rèn)準(zhǔn)官網(wǎng),防止釣魚]</h3><textarea id="inputAccountType1" cols="" rows="3"></textarea><button onclick="unlockAccountWithPK()">解鎖</button></div></div><div id="transaction1" style="display: none"><div id="sendTransaction"></div><div id="accountInfo"></div><div><span>賬戶地址:</span> <span id="accountAddress"></span></div><div><span>賬戶余額:</span> <span id="accountBalance"></span></div></div></div> </body></html>
  • 將解鎖界面跟賬戶顯示頁面分離
  • 根據(jù)響應(yīng)是否成功顯示不同頁面

修改wallet.js文件

function saveKeystoreNext(){//隱藏保存keystore頁面$("#save-keystore").hide()//顯示保存private頁面$("#save-privatekey").show() }function unlockAccountWithPK(){var privateKey = $("#inputAccountType1").val()console.log(privateKey)//將私鑰傳至服務(wù)端$.post("/unlockWithPK",`privatekey=${privateKey}`,function(res,status){console.log(status + JSON.stringify(res))//將服務(wù)端返回的賬戶信息顯示到頁面上if (res.code == 0){console.log("success yes")$("#accountAddress").text(res.data.address)$("#accountBalance").text(res.data.balance + " ETH")// 隱藏$("#transaction0").hide()$("#transaction1").show()}}) }// 對元素的操作需要等文檔加載完畢后才能調(diào)用成功 $(document).ready(function (){$("input[name=unlockAccountType]").change(function(){if (this.value == 0) {//如果點(diǎn)擊keystore,則顯示keystore操作$("#unlockAccount0").show()$("#unlockAccount1").hide()}else {$("#unlockAccount0").hide()$("#unlockAccount1").show()}}) })

3. 通過配置文件解鎖賬戶

(1) 封裝響應(yīng)消息

utils/myUtils.js

module.exports = {getWeb3: () => {var Web3 = require("web3");var web3 = new Web3(Web3.givenProvider || 'http://127.0.0.1:8545');return web3;},success: (data) => {responseData = {code: 0,status: "success",data: data}return responseData},fail :(msg) => {responseData = {code: 1,status: "fail",msg: msg}return responseData} }

(2) 修改控制器

controller/account.js

var web3 = require("../utils/myUtils").getWeb3() var {success,fail} = require("../utils/myUtils") var fs = require("fs")async function getAccountBalance(address) {var balance = await web3.eth.getBalance(address);var balanceEther = web3.utils.fromWei(balance, 'ether')return balanceEther; }module.exports = {unlockWithPK: async (ctx) => {//1.獲取私鑰var privateKey = ctx.request.body.privatekeyconsole.log(privateKey)//2.通過私鑰解鎖賬戶var account = web3.eth.accounts.privateKeyToAccount(privateKey);console.log(account)//3.獲取賬戶余額var balance = await getAccountBalance(account.address)console.log(balance)ctx.response.body = success({balance:balance,address:account.address})},unlockWithKS: async (ctx) => {//獲取前端傳遞的數(shù)據(jù),password跟keystorevar password = ctx.request.body.passwordconsole.log(password)var keystore = ctx.request.files.fileconsole.log(keystore)//讀取緩存文件中keystore的數(shù)據(jù)var keystoreData = fs.readFileSync(keystore.path, "utf8")console.log(keystoreData)// 通過keystore和密碼解鎖賬戶var account = web3.eth.accounts.decrypt(JSON.parse(keystoreData), password)console.log(account)//獲取賬戶余額var balance = await getAccountBalance(account.address)console.log(balance)ctx.response.body = success({balance:balance,address:account.address})} }

(2) 修改前端頁面

views/transaction.html

<html><head><title>轉(zhuǎn)賬</title><script src="js/lib/jquery-3.3.1.min.js"></script><script src="js/lib/jquery.url.js"></script><script src="js/wallet.js"></script><link rel="stylesheet" href="css/wallet.css"> </head><body><div id="nav"><script>$("#nav").load("html/nav.html")</script></div><div id="main"><h3>發(fā)送以太幣/Token</h3><div id="transaction0"><input type="radio" id="unlockAccountType0" name="unlockAccountType" value="0"><label for="unlockAccountType0">Keystore File</label><br><input type="radio" id="unlockAccountType1" name="unlockAccountType" vaule="1"><label for="unlockAccountType1">Private Key</label><!-- unlockAccount0 表示通過Keystore解鎖賬戶 unlockAccount1 表示通過私鑰解鎖賬戶--><div id="unlockAccount0" style="display:none"><h3>請選擇KeyStore文件</h3><input type="file"><input type="password" id="inputAccountType0"><button onclick="unlockAccountWithKS()">解鎖</button></div><div id="unlockAccount1" style="display:none"><h3>請輸入錢包私鑰[請認(rèn)準(zhǔn)官網(wǎng),防止釣魚]</h3><textarea id="inputAccountType1" cols="" rows="3"></textarea><button onclick="unlockAccountWithPK()">解鎖</button></div></div><div id="transaction1" style="display: none"><div id="sendTransaction"></div><div id="accountInfo"></div><div><span>賬戶地址:</span> <span id="accountAddress"></span></div><div><span>賬戶余額:</span> <span id="accountBalance"></span></div></div></div> </body></html>

修改wallet.js文件

function saveKeystoreNext(){//隱藏保存keystore頁面$("#save-keystore").hide()//顯示保存private頁面$("#save-privatekey").show() }function configAccountInfo(data) {$("#accountAddress").text(data.address)$("#accountBalance").text(data.balance + " ETH")// 隱藏$("#transaction0").hide()$("#transaction1").show() }function unlockAccountWithPK(){var privateKey = $("#inputAccountType1").val()console.log(privateKey)//將私鑰傳至服務(wù)端$.post("/unlockWithPK",`privatekey=${privateKey}`,function(res,status){console.log(status + JSON.stringify(res))//將服務(wù)端返回的賬戶信息顯示到頁面上if (res.code == 0){configAccountInfo(res.data)}}) }function unlockAccountWithKS(){var filedata = $("#inputAccountType0").val()if (filedata.length <=0 ){alert("未選擇文件,請選擇文件上傳!")return}//文件上傳通過Formdata去存儲文件的數(shù)據(jù)var data = new FormData()data.append("file", $("#inputAccountType0")[0].files[0])data.append("password",$("#inputAccountTypePassword").val())//提交到后端的路徑var urlStr = "/unlockWithKS"$.ajax({url: urlStr,type: "post",dataType: "json",contentType : false,data :data,processData: false,success : function(res, status) {alert("解鎖成功,可以使用該賬戶進(jìn)行轉(zhuǎn)賬操作")if (res.code == 0) {configAccountInfo(res.data)}},error: function(res, status){alert("KeyStore文件與密碼不匹配")}})}// 對元素的操作需要等文檔加載完畢后才能調(diào)用成功 $(document).ready(function (){$("input[name=unlockAccountType]").change(function(){if (this.value == 0) {//如果點(diǎn)擊keystore,則顯示keystore操作$("#unlockAccount0").show()$("#unlockAccount1").hide()}else {$("#unlockAccount0").hide()$("#unlockAccount1").show()}}) })

(3) 配置路由

var router = require("koa-router")(); var newAccount = require("../controllers/newAccount"); var transactionController = require("../controllers/transaction"); var accountController = require("../controllers/account")router.get("/",newAccount.homeHtml); //創(chuàng)建賬號的頁面 router.get("/newaccount.html",newAccount.newAccountHtml); //提交創(chuàng)建賬號表單 router.post("/newaccount",newAccount.newAccount);//獲取轉(zhuǎn)賬頁面 router.get("/transaction.html",transactionController.transactionHtml); //通過私鑰解鎖賬戶 router.post("/unlockWithPK",accountController.unlockWithPK); //通過配置文件解鎖賬戶 router.post("/unlockWithKS",accountController.unlockWithKS);module.exports = router

(4) 階段性測試

!

選擇配置文件,輸入密碼解鎖

驗(yàn)證成功

顯示賬戶信息

至此,我們現(xiàn)在完成了私鑰解鎖賬戶以及配置文件解鎖賬戶的功能了。

六.實(shí)現(xiàn)交易轉(zhuǎn)賬

1. 以太幣轉(zhuǎn)賬

$ npm install ethereumjs-tx

(1) 修改控制器

controllers/transaction.js

var {success, fail } = require("../utils/myUtils") var web3 = require("../utils/myUtils").getWeb3() module.exports = {transactionHtml: async (ctx) => {await ctx.render("transaction.html")},sendTransaction: async (ctx) => {var { fromAddress, toAddress, amount, privateKey } = ctx.request.bodyvar Tx = require('ethereumjs-tx');var privateKey = new Buffer(privateKey.slice(2), 'hex')var nonce = await web3.eth.getTransactionCount(fromAddress)var gasPrice = await web3.eth.getGasPrice()var amountToWei = web3.utils.toWei(amount,'ether')var rawTx = {nonce: nonce ,gasPrice: gasPrice,gasLimit: '0x2710',to: toAddress,value: amountToWei,data: '0x00' }//對交易的數(shù)據(jù)進(jìn)行g(shù)as計算,然后將gas值設(shè)置到參數(shù)中var gas = await web3.eth.estimateGas(rawTx)rawTx.gas = gasvar tx = new Tx(rawTx);tx.sign(privateKey);var serializedTx = tx.serialize();var responseData await web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'),function(err,data){console.log(err)console.log(data)if (err) {responseData = fail(err)}}).then(function (data){console.log(data)if (data) {responseData = success({"blockHash": data.blockHash,"transactionHash": data.transactionHash})}else {responseData = fail("交易失敗")}})} }

(2) 修改前端頁面

views/transaction.html

<html><head><title>轉(zhuǎn)賬</title><script src="js/lib/jquery-3.3.1.min.js"></script><script src="js/lib/jquery.url.js"></script><script src="js/wallet.js"></script><link rel="stylesheet" href="css/wallet.css"> </head><body><div id="nav"><script>$("#nav").load("html/nav.html")</script></div><div id="main"><h3>發(fā)送以太幣/Token</h3><div id="transaction0"><input type="radio" id="unlockAccountType0" name="unlockAccountType" value="0"><label for="unlockAccountType0">Keystore File</label><br><input type="radio" id="unlockAccountType1" name="unlockAccountType" vaule="1"><label for="unlockAccountType1">Private Key</label><!-- unlockAccount0 表示通過Keystore解鎖賬戶 unlockAccount1 表示通過私鑰解鎖賬戶--><div id="unlockAccount0" style="display:none"><h3>請選擇KeyStore文件</h3><input type="file" id="inputAccountType0"><span>輸入密碼:</span><input type="password" id="inputAccountTypePassword"><br><button onclick="unlockAccountWithKS()">解鎖</button></div><div id="unlockAccount1" style="display:none"><h3>請輸入錢包私鑰[請認(rèn)準(zhǔn)官網(wǎng),防止釣魚]</h3><textarea id="inputAccountType1" cols="" rows="3"></textarea><button onclick="unlockAccountWithPK()">解鎖</button></div></div><div id="transaction1" style="display: none"><div id="sendTransaction"><form id="sendTransactionForm"><div><span>對方地址</span><input type="text" name="toAddress"></div><div><span>發(fā)送金額</span><input type="text" name="amount"></div><input name="fromAddress" hidden="hidden"><input name="privateKey" hidden="hidden"><button type="submit">發(fā)送交易</button></form></div><div id="accountInfo"></div><div><span>賬戶地址:</span> <span id="accountAddress"></span></div><div><span>賬戶余額:</span> <span id="accountBalance"></span></div><div id="transactionComplete" style="display:none"><div><span>交易Hash:</span><span id="transactionCompleteHash0"></span></div><div><span>Block Hash:</span><span id="transactionCompleteHash1"></span></div></div></div></div> </body></html>

修改wallet.js文件

function saveKeystoreNext(){//隱藏保存keystore頁面$("#save-keystore").hide()//顯示保存private頁面$("#save-privatekey").show() }function configAccountInfo(data) {$("#accountAddress").text(data.address)$("#accountBalance").text(data.balance + " ETH")// 隱藏$("#transaction0").hide()$("#transaction1").show()$("input[name=fromAddress]").val(data.address)$("input[name=privateKey]").val(data.privatekey) }function unlockAccountWithPK(){var privateKey = $("#inputAccountType1").val()console.log(privateKey)//將私鑰傳至服務(wù)端$.post("/unlockWithPK",`privatekey=${privateKey}`,function(res,status){console.log(status + JSON.stringify(res))//將服務(wù)端返回的賬戶信息顯示到頁面上if (res.code == 0){configAccountInfo(res.data)}}) }function unlockAccountWithKS(){var filedata = $("#inputAccountType0").val()if (filedata.length <=0 ){alert("未選擇文件,請選擇文件上傳!")return}//文件上傳通過Formdata去存儲文件的數(shù)據(jù)var data = new FormData()data.append("file", $("#inputAccountType0")[0].files[0])data.append("password",$("#inputAccountTypePassword").val())//提交到后端的路徑var urlStr = "/unlockWithKS"$.ajax({url: urlStr,type: "post",dataType: "json",contentType : false,data :data,processData: false,success : function(res, status) {alert("解鎖成功,可以使用該賬戶進(jìn)行轉(zhuǎn)賬操作")if (res.code == 0) {configAccountInfo(res.data)}},error: function(res, status){alert("KeyStore文件與密碼不匹配")}})}//轉(zhuǎn)賬 對元素的操作需要等文檔加載完畢后才能調(diào)用成功 $(document).ready(function (){$("input[name=unlockAccountType]").change(function(){if (this.value == 0) {//如果點(diǎn)擊keystore,則顯示keystore操作$("#unlockAccount0").show()$("#unlockAccount1").hide()}else {$("#unlockAccount0").hide()$("#unlockAccount1").show()} })$("#sendTransactionForm").validate({rules: {toAddress:{required:true,},amount:{required:true,}, },messages: {toAddress:{required:"請輸入對方錢包地址",},amount:{required:"請輸入轉(zhuǎn)賬金額",},},submitHandler: function(form){var urlStr = "/sendtransaction"alert("urlStr:" +urlStr)$(form).ajaxSubmit({url:urlStr,type:"post",dataType:"json",success:function(res,status){console.log(status + JSON.stringify(res))if (res.code == 0){$("#transactionCompleteHash0").text(res.data.transactionHash)$("#transactionCompleteHash1").text(res.data.blockHash)$("#transactionComplete").show()}},error:function(res,status){console.log(status + JSON.stringify(res))}})}})})

(3) 配置路由

router/router.js

var router = require("koa-router")(); var newAccount = require("../controllers/newAccount"); var transactionController = require("../controllers/transaction"); var accountController = require("../controllers/account")router.get("/",newAccount.homeHtml); //創(chuàng)建賬號的頁面 router.get("/newaccount.html",newAccount.newAccountHtml); //提交創(chuàng)建賬號表單 router.post("/newaccount",newAccount.newAccount);//獲取轉(zhuǎn)賬頁面 router.get("/transaction.html",transactionController.transactionHtml); router.post("/sendtransaction",transactionController.sendTransaction) //通過私鑰解鎖賬戶 router.post("/unlockWithPK",accountController.unlockWithPK); //通過配置文件解鎖賬戶 router.post("/unlockWithKS",accountController.unlockWithKS);module.exports = router

2. 交易信息查詢

(1) 修改控制器

controller/transaction.js

var { success, fail } = require("../utils/myUtils") var web3 = require("../utils/myUtils").getWeb3() module.exports = {transactionHtml: async (ctx) => {await ctx.render("transaction.html")},sendTransaction: async (ctx) => {var { fromAddress, toAddress, amount, privateKey } = ctx.request.bodyvar Tx = require('ethereumjs-tx');var privateKey = new Buffer(privateKey.slice(2), 'hex')var nonce = await web3.eth.getTransactionCount(fromAddress)var gasPrice = await web3.eth.getGasPrice()var amountToWei = web3.utils.toWei(amount, 'ether')var rawTx = {nonce: nonce,gasPrice: gasPrice,gasLimit: '0x2710',to: toAddress,value: amountToWei,data: '0x00'}//對交易的數(shù)據(jù)進(jìn)行g(shù)as計算,然后將gas值設(shè)置到參數(shù)中var gas = await web3.eth.estimateGas(rawTx)rawTx.gas = gasvar tx = new Tx(rawTx);tx.sign(privateKey);var serializedTx = tx.serialize();var responseData;await web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'), function (err, data) {console.log(err)console.log(data)if (err) {responseData = fail(err)}}).then(function (data) {console.log(data)if (data) {responseData = success({"blockHash": data.blockHash,"transactionHash": data.transactionHash})} else {responseData = fail("交易失敗")}})ctx.response.body = responseData},queryTransactionHtml: async (ctx) => {await ctx.render("queryTransaction.html")},queryTransaction: async (ctx) => {var txHash = ctx.request.body.txHashawait web3.eth.getTransaction(txHash, function (err, res) {if (err) {responseData = fail(err)} }).then(function(res){if (res) {responseData = success(res)}else {responseData = fail("查詢失敗")}})ctx.response.body = responseData} }

(2) 創(chuàng)建前端頁面

views/queryTransaction.html

<html><head><title>查詢交易詳情</title><script src="js/lib/jquery-3.3.1.min.js"></script><script src="js/lib/jquery.url.js"></script><script src="js/wallet.js"></script><link rel="stylesheet" href="css/wallet.css"> </head><body><div id="nav"><script>$("#nav").load("html/nav.html")</script></div><div id="main"><h1>查詢交易詳情</h1><input type="text" id="txHash"><button onclick="queryTransaction()">查詢</button><!-- 用于顯示代碼塊 --><pre id="transactionInfo"></pre></div> </body></html>

修改wallet.js文件

function saveKeystoreNext() {//隱藏保存keystore頁面$("#save-keystore").hide()//顯示保存private頁面$("#save-privatekey").show() }function configAccountInfo(data) {$("#accountAddress").text(data.address)$("#accountBalance").text(data.balance + " ETH")// 隱藏$("#transaction0").hide()$("#transaction1").show()$("input[name=fromAddress]").val(data.address)$("input[name=privateKey]").val(data.privatekey) }function unlockAccountWithPK() {var privateKey = $("#inputAccountType1").val()console.log(privateKey)//將私鑰傳至服務(wù)端$.post("/unlockWithPK", `privatekey=${privateKey}`, function (res, status) {console.log(status + JSON.stringify(res))//將服務(wù)端返回的賬戶信息顯示到頁面上if (res.code == 0) {configAccountInfo(res.data)}}) }function unlockAccountWithKS() {var filedata = $("#inputAccountType0").val()if (filedata.length <= 0) {alert("未選擇文件,請選擇文件上傳!")return}//文件上傳通過Formdata去存儲文件的數(shù)據(jù)var data = new FormData()data.append("file", $("#inputAccountType0")[0].files[0])data.append("password", $("#inputAccountTypePassword").val())//提交到后端的路徑var urlStr = "/unlockWithKS"$.ajax({url: urlStr,type: "post",dataType: "json",contentType: false,data: data,processData: false,success: function (res, status) {alert("解鎖成功,可以使用該賬戶進(jìn)行轉(zhuǎn)賬操作")if (res.code == 0) {configAccountInfo(res.data)}},error: function (res, status) {alert("KeyStore文件與密碼不匹配")}})}//轉(zhuǎn)賬 對元素的操作需要等文檔加載完畢后才能調(diào)用成功 $(document).ready(function () {$("input[name=unlockAccountType]").change(function () {if (this.value == 0) {//如果點(diǎn)擊keystore,則顯示keystore操作$("#unlockAccount0").show()$("#unlockAccount1").hide()} else {$("#unlockAccount0").hide()$("#unlockAccount1").show()}})$("#sendTransactionForm").validate({rules: {toAddress: {required: true,},amount: {required: true,},},messages: {toAddress: {required: "請輸入對方錢包地址",},amount: {required: "請輸入轉(zhuǎn)賬金額",},},submitHandler: function (form) {var urlStr = "/sendtransaction"alert("urlStr:" + urlStr)$(form).ajaxSubmit({url: urlStr,type: "post",dataType: "json",success: function (res, status) {console.log(status + JSON.stringify(res))if (res.code == 0) {$("#transactionCompleteHash0").text(res.data.transactionHash)$("#transactionCompleteHash1").text(res.data.blockHash)$("#transactionComplete").show()}},error: function (res, status) {console.log(status + JSON.stringify(res))}})}})})//查詢交易詳情 function queryTransaction() {var txHash = $("#txHash").val()$.post("/queryTransaction", "txHash=" + txHash, function (res, status) {console.log(status + JSON.stringify(res))if (res.code == 0) {alert("查詢成功")$("#transactionInfo").text(JSON.stringify(res.data, null, 4))} else {alert("查詢失敗")}}) }

(3) 配置路由

router/router.js

var router = require("koa-router")(); var newAccount = require("../controllers/newAccount"); var transactionController = require("../controllers/transaction"); var accountController = require("../controllers/account")router.get("/",newAccount.homeHtml); //創(chuàng)建賬號的頁面 router.get("/newaccount.html",newAccount.newAccountHtml); //提交創(chuàng)建賬號表單 router.post("/newaccount",newAccount.newAccount);//獲取轉(zhuǎn)賬頁面 router.get("/transaction.html",transactionController.transactionHtml); //發(fā)送交易 router.post("/sendtransaction",transactionController.sendTransaction) //查詢交易詳情 router.get("/queryTransaction.html",transactionController.queryTransactionHtml) router.post("/queryTransaction",transactionController.queryTransaction) //通過私鑰解鎖賬戶 router.post("/unlockWithPK",accountController.unlockWithPK); //通過配置文件解鎖賬戶 router.post("/unlockWithKS",accountController.unlockWithKS);module.exports = router

(4) 階段性測試

執(zhí)行賬戶解鎖

執(zhí)行轉(zhuǎn)賬交易

以太坊私有鏈終端產(chǎn)生交易,執(zhí)行挖礦

顯示交易Hash以及區(qū)塊Hash

通過交易Hash返回交易詳情

3. 實(shí)現(xiàn)Token轉(zhuǎn)賬

以上內(nèi)容,我們實(shí)現(xiàn)了對以太幣的轉(zhuǎn)賬與查詢功能,而現(xiàn)實(shí)情況中,我們很多區(qū)塊鏈公司都會開發(fā)適用于以太坊與自己Token的錢包,本文直接沿用以太坊智能合約項(xiàng)目-Token合約開發(fā)與部署中編寫的合約代碼。

(1) 獲取合約信息

  • 獲取Token的ABI

  • 獲取Token合約地址

通過Remix進(jìn)行部署后獲取地址:0xa77a5c71b9cf71e89215ceec9767c536e79ced68

(2) 創(chuàng)建控制器

controller/token.js

var { success, fail } = require("../utils/myUtils") var web3 = require("../utils/myUtils").getWeb3() var myContract = require("../models/contract").getContract()module.exports = {sendTokenTransaction: async (ctx) => {var { fromAddress, toAddress, amount, privateKey } = ctx.request.bodyvar Tx = require('ethereumjs-tx');var privateKey = new Buffer(privateKey.slice(2), 'hex')var nonce = await web3.eth.getTransactionCount(fromAddress)var gasPrice = await web3.eth.getGasPrice()//獲取Token合約的decimalsvar decimals = await myContract.methods.decimals().call()var amountToWei = amount * Math.pow(10, decimals)var myBalance = await myContract.methods.balanceOf(fromAddress).call()if (myBalance < amountToWei) {ctx.response.body = fail("余額不足")return}var tokenData = await myContract.methods.transfer(toAddress, amountToWei).encodeABI()var rawTx = {nonce: nonce,gasPrice: gasPrice,gasLimit: '0x2710',to: myContract.options.address, //如果是發(fā)送token ,此處應(yīng)該填寫合約地址,此處需要注意value: amountToWei,// data: tokenDatadata: "0x00"}//對交易的數(shù)據(jù)進(jìn)行g(shù)as計算,然后將gas值設(shè)置到參數(shù)中var gas = await web3.eth.estimateGas(rawTx)rawTx.gas = gasvar tx = new Tx(rawTx);tx.sign(privateKey);var serializedTx = tx.serialize();var responseData;await web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'), function (err, data) {console.log(err)console.log(data)if (err) {responseData = fail(err)}}).then(function (data) {console.log(data)if (data) {responseData = success({"blockHash": data.blockHash,"transactionHash": data.transactionHash})} else {responseData = fail("交易失敗")}})ctx.response.body = responseData} }

(3) 創(chuàng)建模型

models/contract.js

var web3 = require("../utils/myUtils").getWeb3()module.exports = {getContract :(ctx)=> {var ABI = [{"constant": true,"inputs": [],"name": "name","outputs": [{"name": "","type": "string"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": false,"inputs": [{"name": "_spender","type": "address"},{"name": "_value","type": "uint256"}],"name": "approve","outputs": [{"name": "success","type": "bool"}],"payable": false,"stateMutability": "nonpayable","type": "function"},{"constant": true,"inputs": [],"name": "totalSupply","outputs": [{"name": "","type": "uint256"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": false,"inputs": [{"name": "_from","type": "address"},{"name": "_to","type": "address"},{"name": "_value","type": "uint256"}],"name": "transferFrom","outputs": [{"name": "success","type": "bool"}],"payable": false,"stateMutability": "nonpayable","type": "function"},{"constant": true,"inputs": [],"name": "decimals","outputs": [{"name": "","type": "uint8"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": true,"inputs": [{"name": "_owner","type": "address"}],"name": "balanceOf","outputs": [{"name": "balance","type": "uint256"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": true,"inputs": [],"name": "symbol","outputs": [{"name": "","type": "string"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": false,"inputs": [{"name": "_to","type": "address"},{"name": "_value","type": "uint256"}],"name": "transfer","outputs": [{"name": "success","type": "bool"}],"payable": false,"stateMutability": "nonpayable","type": "function"},{"constant": true,"inputs": [{"name": "_owner","type": "address"},{"name": "_spender","type": "address"}],"name": "allowance","outputs": [{"name": "remaining","type": "uint256"}],"payable": false,"stateMutability": "view","type": "function"},{"inputs": [{"name": "_name","type": "string"},{"name": "_symbol","type": "string"},{"name": "_decimals","type": "uint8"},{"name": "_totalSupply","type": "uint256"}],"payable": false,"stateMutability": "nonpayable","type": "constructor"},{"anonymous": false,"inputs": [{"indexed": true,"name": "_from","type": "address"},{"indexed": true,"name": "_to","type": "address"},{"indexed": false,"name": "_value","type": "uint256"}],"name": "Transfer","type": "event"},{"anonymous": false,"inputs": [{"indexed": true,"name": "_owner","type": "address"},{"indexed": true,"name": "_spender","type": "address"},{"indexed": false,"name": "_value","type": "uint256"}],"name": "Approval","type": "event"}]var contractAddress = "0xa77a5c71b9cf71e89215ceec9767c536e79ced68"var myContract = new web3.eth.Contract(ABI,contractAddress)return myContract} }

(4) 修改控制器

controller/account.js

var web3 = require("../utils/myUtils").getWeb3() var { success, fail } = require("../utils/myUtils") var fs = require("fs") var myContract = require("..//models/contract").getContract()async function getAccountBalance(address) {var balance = await web3.eth.getBalance(address);var balanceEther = web3.utils.fromWei(balance, 'ether')return balanceEther; }async function setResponseData(account) {//配置返回給前端的數(shù)據(jù):以太幣跟Token的數(shù)據(jù)var balance = await getAccountBalance(account.address)console.log(balance)//獲取Token數(shù)據(jù)var tokenBalance = await myContract.methods.balanceOf(account.address).call()var tokenSymbol = await myContract.methods.symbol().call()return success({balance: balance,address: account.address,privatekey: account.privateKey,tokenBalance: tokenBalance,tokenSymbol: tokenSymbol})}module.exports = {unlockWithPK: async (ctx) => {//1.獲取私鑰var privateKey = ctx.request.body.privatekeyconsole.log(privateKey)//2.通過私鑰解鎖賬戶var account = web3.eth.accounts.privateKeyToAccount(privateKey);console.log(account)//3.獲取賬戶余額var balance = await getAccountBalance(account.address)//將賬戶信息返回給前端 ctx.response.body = await setResponseData(account)},unlockWithKS: async (ctx) => {//獲取前端傳遞的數(shù)據(jù),password跟keystorevar password = ctx.request.body.passwordconsole.log(password)var keystore = ctx.request.files.fileconsole.log(keystore)//讀取緩存文件中keystore的數(shù)據(jù)var keystoreData = fs.readFileSync(keystore.path, "utf8")console.log(keystoreData)// 通過keystore和密碼解鎖賬戶var account = web3.eth.accounts.decrypt(JSON.parse(keystoreData), password)console.log(account)//將賬戶信息返回給前端 ctx.response.body = await setResponseData(account)} }

修改前端頁面

<html><head><title>轉(zhuǎn)賬</title><script src="js/lib/jquery-3.3.1.min.js"></script><script src="js/lib/jquery.url.js"></script><script src="js/wallet.js"></script><link rel="stylesheet" href="css/wallet.css"> </head><body><div id="nav"><script>$("#nav").load("html/nav.html")</script></div><div id="main"><h3>發(fā)送以太幣/Token</h3><div id="transaction0"><input type="radio" id="unlockAccountType0" name="unlockAccountType" value="0"><label for="unlockAccountType0">Keystore File</label><br><input type="radio" id="unlockAccountType1" name="unlockAccountType" vaule="1"><label for="unlockAccountType1">Private Key</label><!-- unlockAccount0 表示通過Keystore解鎖賬戶 unlockAccount1 表示通過私鑰解鎖賬戶--><div id="unlockAccount0" style="display:none"><h3>請選擇KeyStore文件</h3><input type="file" id="inputAccountType0"><span>輸入密碼:</span><input type="password" id="inputAccountTypePassword"><br><button onclick="unlockAccountWithKS()">解鎖</button></div><div id="unlockAccount1" style="display:none"><h3>請輸入錢包私鑰[請認(rèn)準(zhǔn)官網(wǎng),防止釣魚]</h3><textarea id="inputAccountType1" cols="" rows="3"></textarea><button onclick="unlockAccountWithPK()">解鎖</button></div></div><div id="transaction1" style="display: none"><div id="sendTransaction"><form id="sendTransactionForm"><div><span>對方地址</span><input type="text" name="toAddress"></div><div><span>發(fā)送金額</span><input type="text" name="amount"></div><input name="fromAddress" hidden="hidden"><input name="privateKey" hidden="hidden"><button type="submit">發(fā)送交易</button></form></div><div id="accountInfo"></div><div><span>賬戶地址:</span> <span id="accountAddress"></span></div><div><span>賬戶余額:</span> <span id="accountBalance"></span><br><span id="accountTokenInfo"></span></div><div id="transactionComplete" style="display:none"><div><span>交易Hash:</span><span id="transactionCompleteHash0"></span></div><div><span>Block Hash:</span><span id="transactionCompleteHash1"></span></div></div></div></div> </body></html>

修改wallet.js文件

function saveKeystoreNext() {//隱藏保存keystore頁面$("#save-keystore").hide()//顯示保存private頁面$("#save-privatekey").show() }function configAccountInfo(data) {$("#accountAddress").text(data.address)$("#accountBalance").text(data.balance + " ETH")// 隱藏$("#transaction0").hide()$("#transaction1").show()$("input[name=fromAddress]").val(data.address)$("input[name=privateKey]").val(data.privatekey)$("#accountTokenInfo").text(data.tokenBalance + " " + data.tokenSymbol)$("#TokenSymbol").text(data.tokenSymbol) }function unlockAccountWithPK() {var privateKey = $("#inputAccountType1").val()console.log(privateKey)//將私鑰傳至服務(wù)端$.post("/unlockWithPK", `privatekey=${privateKey}`, function (res, status) {console.log(status + JSON.stringify(res))//將服務(wù)端返回的賬戶信息顯示到頁面上if (res.code == 0) {configAccountInfo(res.data)}}) }function unlockAccountWithKS() {var filedata = $("#inputAccountType0").val()if (filedata.length <= 0) {alert("未選擇文件,請選擇文件上傳!")return}//文件上傳通過Formdata去存儲文件的數(shù)據(jù)var data = new FormData()data.append("file", $("#inputAccountType0")[0].files[0])data.append("password", $("#inputAccountTypePassword").val())//提交到后端的路徑var urlStr = "/unlockWithKS"$.ajax({url: urlStr,type: "post",dataType: "json",contentType: false,data: data,processData: false,success: function (res, status) {alert("解鎖成功,可以使用該賬戶進(jìn)行轉(zhuǎn)賬操作")if (res.code == 0) {configAccountInfo(res.data)}},error: function (res, status) {alert("KeyStore文件與密碼不匹配")}})}//轉(zhuǎn)賬 對元素的操作需要等文檔加載完畢后才能調(diào)用成功 $(document).ready(function () {$("input[name=unlockAccountType]").change(function () {if (this.value == 0) {//如果點(diǎn)擊keystore,則顯示keystore操作$("#unlockAccount0").show()$("#unlockAccount1").hide()} else {$("#unlockAccount0").hide()$("#unlockAccount1").show()}})$("#sendTransactionForm").validate({rules: {toAddress: {required: true,},amount: {required: true,},},messages: {toAddress: {required: "請輸入對方錢包地址",},amount: {required: "請輸入轉(zhuǎn)賬金額",},},submitHandler: function (form) {var urlStrvar tokenType = $("#TokenType").val()if (tokenType == 0) {urlStr = "/sendtransaction"}else {urlStr = "/sendToken"}alert("urlStr:" + urlStr)$(form).ajaxSubmit({url: urlStr,type: "post",dataType: "json",success: function (res, status) {console.log(status + JSON.stringify(res))if (res.code == 0) {$("#transactionCompleteHash0").text(res.data.transactionHash)$("#transactionCompleteHash1").text(res.data.blockHash)$("#transactionComplete").show()}},error: function (res, status) {console.log(status + JSON.stringify(res))}})}})})//查詢交易詳情 function queryTransaction() {var txHash = $("#txHash").val()$.post("/queryTransaction", "txHash=" + txHash, function (res, status) {console.log(status + JSON.stringify(res))if (res.code == 0) {alert("查詢成功")$("#transactionInfo").text(JSON.stringify(res.data, null, 4))} else {alert("查詢失敗")}}) }

(5) 配置路由

var router = require("koa-router")(); var newAccount = require("../controllers/newAccount"); var transactionController = require("../controllers/transaction"); var accountController = require("../controllers/account") var tokenController = require("../controllers/token")router.get("/",newAccount.homeHtml); //創(chuàng)建賬號的頁面 router.get("/newaccount.html",newAccount.newAccountHtml); //提交創(chuàng)建賬號表單 router.post("/newaccount",newAccount.newAccount);//獲取轉(zhuǎn)賬頁面 router.get("/transaction.html",transactionController.transactionHtml); //發(fā)送交易 router.post("/sendtransaction",transactionController.sendTransaction) //查詢交易詳情 router.get("/queryTransaction.html",transactionController.queryTransactionHtml) router.post("/queryTransaction",transactionController.queryTransaction) //通過私鑰解鎖賬戶 router.post("/unlockWithPK",accountController.unlockWithPK); //通過配置文件解鎖賬戶 router.post("/unlockWithKS",accountController.unlockWithKS);//token轉(zhuǎn)賬 router.post("/sendToken", tokenController.sendTokenTransaction)module.exports = router

總結(jié)

以上是生活随笔為你收集整理的以太坊Dapp项目-网页钱包开发手册的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

av电影中文| 日韩一级片网址 | 欧美综合在线视频 | 欧洲性视频 | 天天插狠狠干 | 免费看片日韩 | 成人精品国产 | 一区二区三区免费网站 | 久久久2o19精品 | 久久精品欧美一区二区三区麻豆 | 国产精品欧美久久久久三级 | aaa免费毛片 | 久久久久久黄色 | 国产原厂视频在线观看 | 97在线看| 中文字幕中文字幕在线中文字幕三区 | 狠狠撸电影 | av免费网站在线观看 | 久久www免费人成看片高清 | 亚洲精品av在线 | 久久乐九色婷婷综合色狠狠182 | 国产欧美精品在线观看 | 麻豆视频免费入口 | 国产激情久久久 | 一区二区视频在线看 | 久99久中文字幕在线 | 黄色av电影在线观看 | 中文字幕二区三区 | 色视频网址 | 福利视频导航网址 | 久久午夜精品 | 99视频国产在线 | 国产美女主播精品一区二区三区 | 久久成人亚洲欧美电影 | 亚洲综合成人婷婷小说 | 欧美亚洲另类在线视频 | 国产在线一区观看 | 友田真希x88av | 日韩精品免费在线观看视频 | a黄色影院 | 久久九九九九 | 国产欧美日韩精品一区二区免费 | 国产视频美女 | 欧美日韩a视频 | 日日爱av| 91三级视频 | 国产精品久久久久久久久久三级 | 久草99| 亚洲精品乱码久久久久久 | 中文字幕网站 | 午夜精品福利一区二区三区蜜桃 | av亚洲产国偷v产偷v自拍小说 | 国产在线精品一区二区不卡了 | 国产精品美女久久久久aⅴ 干干夜夜 | 国产美腿白丝袜足在线av | 成人av一区二区在线观看 | 成片免费观看视频999 | 亚洲人精品午夜 | 国产成人精品久久久久蜜臀 | 五月婷婷天堂 | 国产97在线看 | 久久视频在线 | 国产中文字幕在线视频 | 精品美女在线视频 | 亚洲爱爱视频 | 国产精品久久久久久久久软件 | 黄色大片日本 | 精品国产精品久久一区免费式 | 在线观看成人小视频 | 在线视频欧美精品 | 99久热在线精品视频 | 美女在线国产 | 麻豆高清免费国产一区 | 亚洲精品高清在线观看 | 久久综合网色—综合色88 | 一区 在线 影院 | 91福利国产在线观看 | 亚洲精品乱码久久久久久蜜桃91 | 亚洲国产精品激情在线观看 | 欧美日韩天堂 | 国产欧美综合视频 | 欧美日韩精品在线一区二区 | 91福利视频免费观看 | 日韩欧美视频在线免费观看 | 91网在线观看| 99在线观看精品 | 日韩av图片 | 最新中文字幕在线资源 | 国产伦理精品一区二区 | 99久久精品网 | 黄色片网站av | 日韩美女高潮 | 亚洲精品视频在线观看免费视频 | 2019中文| 色永久免费视频 | 国产成人高清 | 成人一区二区三区中文字幕 | 国产精品久久综合 | 人人爽人人乐 | 日本视频精品 | 色婷婷激情综合 | 色悠悠久久综合 | 色综合亚洲精品激情狠狠 | 日韩美女久久 | 亚洲午夜精品久久久 | 日韩一区二区三免费高清在线观看 | 国产夫妻性生活自拍 | 国产一区电影在线观看 | 在线视频一区观看 | 五月开心激情 | 深爱激情亚洲 | 狠狠狠狠狠狠狠狠 | 高清av免费看 | 99久久婷婷国产综合亚洲 | 国产精品青青 | 国产高清在线 | 在线亚洲天堂网 | 婷婷丁香激情网 | 国产成人精品女人久久久 | 国产精品一区电影 | 亚洲精品国偷拍自产在线观看 | 久久中文欧美 | 久久午夜鲁丝片 | 日韩免费一级a毛片在线播放一级 | 亚洲人在线 | 国产中文欧美日韩在线 | 色91在线视频 | 亚洲精品影院在线观看 | 91插插插免费视频 | 国产精品毛片久久久久久久久久99999999 | 东方av在线免费观看 | 久久亚洲在线 | 久久精品国产免费 | 97色视频在线 | 中文在线免费观看 | 91插插插免费视频 | 国产不卡免费视频 | 丁香资源影视免费观看 | 免费日韩一区二区三区 | 久久久久久国产精品 | 伊人天堂网 | 在线视频欧美精品 | 欧美性黄网官网 | 欧美另类xxx| 精品一二三四五区 | 中文字幕黄色网址 | 国内精品久久久久影院男同志 | 色瓜| a极黄色片| 日韩一区二区三区视频在线 | 欧美 国产 视频 | 精品国内自产拍在线观看视频 | 2020天天干夜夜爽 | 在线中文字幕观看 | 最近更新中文字幕 | 国产精品美女在线 | 亚洲视频一区二区三区在线观看 | 欧洲激情在线 | 91av在线播放视频 | 亚洲伦理一区 | 久久久久亚洲天堂 | 天天干,狠狠干 | 日本精品xxxx | 国产精品 亚洲精品 | 免费日韩一区二区 | 男女免费视频观看 | 色网站在线看 | 色婷婷久久久综合中文字幕 | 偷拍福利视频一区二区三区 | 精品视频 | 国产精品网红直播 | 一级精品视频在线观看宜春院 | 日韩欧美视频免费观看 | 久久五月天综合 | 日韩av一区二区在线影视 | 国产精品午夜久久久久久99热 | 国内丰满少妇猛烈精品播 | 五月婷婷丁香在线观看 | 亚洲国产美女久久久久 | 欧美一级片播放 | 日本公乱妇视频 | 亚洲国产资源 | 久久公开视频 | 成人激情开心网 | 亚洲区另类春色综合小说 | 亚洲精品av中文字幕在线在线 | 国产成人免费观看久久久 | 免费网站v | 国产一级片播放 | 日韩欧在线 | 999成人国产 | 久久婷婷国产 | 狠狠伊人 | 亚洲精品动漫久久久久 | 国产精品毛片久久久久久久 | 在线电影 一区 | www免费黄色 | 日韩精品久久久免费观看夜色 | 欧美日韩国产精品一区二区亚洲 | 中文字幕视频免费观看 | 一区二区三区免费在线播放 | 麻豆va一区二区三区久久浪 | 91精品视频播放 | 中文字幕 在线看 | 成人av资源网站 | 天天干,夜夜爽 | 欧美孕交vivoestv另类 | 91九色在线观看 | 在线精品亚洲一区二区 | 成人免费视频视频在线观看 免费 | 欧美另类sm图片 | 激情视频91 | 久久久高清免费视频 | 永久免费毛片在线观看 | 亚洲国产精品久久久久 | 高清av在线免费观看 | 欧美日韩国产一二 | 免费在线观看毛片网站 | 精品国产成人av在线免 | 最近中文字幕国语免费av | 婷婷综合久久 | 狠狠88综合久久久久综合网 | 一区二区三区在线免费播放 | www日韩视频 | 国产a精品 | 免费又黄又爽视频 | 黄网站免费久久 | 91精品在线视频观看 | 日韩三级视频在线观看 | 日韩网站在线 | av免费电影在线 | 欧美最猛性xxxxx(亚洲精品) | 日韩免费观看一区二区 | 999久久久久久久久 69av视频在线观看 | 99久久久久国产精品免费 | 在线观看免费国产小视频 | 久久精品中文字幕少妇 | 国产伦理久久精品久久久久_ | 天天干天天上 | 91天堂素人约啪 | 国产精品国产三级国产aⅴ入口 | 午夜美女视频 | 色老板在线视频 | 成人a视频 | 国产a精品 | 97超碰国产精品女人人人爽 | a色视频 | 中文字幕一区二区三区四区在线视频 | 69视频在线 | 日本中文字幕在线观看 | 亚洲国产小视频在线观看 | 中国黄色一级大片 | 久久久综合香蕉尹人综合网 | 亚洲综合黄色 | aaa日本高清在线播放免费观看 | 国产视频资源 | 久热av在线 | 国产精品久久9 | 国产视频999 | 精品成人在线 | 亚洲欧美日本一区二区三区 | 日韩免费在线观看网站 | 免费色视频在线 | 中文字幕第 | 人人爽人人射 | 午夜精品电影 | 99精品视频在线看 | 欧美最爽乱淫视频播放 | 天天操天天操天天操天天操天天操 | 在线电影 你懂得 | 国产1区2区 | 精品国产免费人成在线观看 | 中文字幕人成人 | 亚洲激色 | 国产精品1区2区3区在线观看 | 中文字幕av网站 | 婷婷看片| 福利片视频区 | 毛片随便看 | 日韩欧美不卡 | 99视频偷窥在线精品国自产拍 | 亚洲精品福利在线观看 | 国产成人高清 | 婷色在线 | 婷婷视频在线播放 | 最新日韩视频 | 久久国产视频网站 | 日韩欧美在线视频一区二区三区 | 欧美一二三区在线观看 | 蜜臀一区二区三区精品免费视频 | 免费在线国产黄色 | 开心激情五月婷婷 | 在线免费高清一区二区三区 | 在线观看国产一区二区 | 久热色超碰 | 日日日日干 | 中文av日韩 | 香蕉成人在线视频 | 99re久久精品国产 | 日精品| 福利av影院 | 亚洲自拍偷拍色图 | 99久久精品日本一区二区免费 | 国产激情小视频在线观看 | 国产999视频在线观看 | 日本精品久久久久久 | 欧美日韩视频在线播放 | 日韩精品一区二区在线 | 激情久久综合 | 激情在线网站 | 五月天综合网站 | 日韩精品一卡 | 精品久久一级片 | 黄色一级大片在线免费看产 | 久久激情网站 | 国产欧美最新羞羞视频在线观看 | 黄色毛片一级 | 国产理论在线 | 天天操天天操天天操天天操天天操天天操 | 欧美精品一区二区蜜臀亚洲 | 99久久久久成人国产免费 | 在线观看中文字幕2021 | av电影免费在线看 | 欧美精品日韩 | 色视频网站在线 | 免费在线激情电影 | 国产一区二区网址 | 中文字幕在线观看播放 | 久久精品视频网址 | 中文字幕日韩高清 | 中文字幕一区二区三区四区在线视频 | 激情久久一区二区三区 | 黄色片免费在线 | 在线观看国产永久免费视频 | 久久国产精品一区二区三区四区 | 久久久久在线观看 | 日韩高清无线码2023 | 99热最新网址 | 美女黄频网站 | 精品国产一区二区三区四区在线观看 | 午夜视频福利 | 欧美另类xxxxx | 国产精品入口久久 | 99久久精品国产观看 | 亚洲成av片人久久久 | 综合久久久久久久 | 成人午夜电影网站 | 国产人成在线视频 | 久久精品国产亚洲精品2020 | 在线视频 你懂得 | 92精品国产成人观看免费 | 国产黄在线| 精品主播网红福利资源观看 | 另类老妇性bbwbbw高清 | 精品久久久久久久久久久久 | 懂色av一区二区三区蜜臀 | 天天操综合网站 | 国产视频2 | 毛片一区二区 | 五月天激情视频在线观看 | 亚洲精品国精品久久99热一 | 在线超碰av | 国产精品久一 | 久久夜色电影 | 999电影免费在线观看2020 | 日韩精品播放 | 天天爱天天操天天爽 | 日韩久久精品一区二区三区 | 中文字幕免费国产精品 | 午夜影院在线观看18 | 午夜性盈盈 | 一级片免费视频 | 91精选在线 | 久久99电影 | 日韩视频在线观看视频 | 啪嗒啪嗒免费观看完整版 | 免费不卡中文字幕视频 | 色黄视频免费观看 | 中文字幕视频三区 | 日韩视频在线播放 | 一区二区理论片 | 一区二区三区 亚洲 | 久久综合欧美精品亚洲一区 | 一级片在线 | 国产免费又爽又刺激在线观看 | 在线视频 影院 | 国产不卡一二三区 | 久久国产精品成人免费浪潮 | 国产一区二区在线免费 | 久久草在线视频国产 | 亚洲黄色免费电影 | 国产亚洲午夜高清国产拍精品 | 久久伊99综合婷婷久久伊 | 成人网中文字幕 | www.888av| 五月激情久久 | 日韩特黄一级欧美毛片特黄 | 在线播放日韩av | 日韩精品中文字幕一区二区 | 免费观看黄色12片一级视频 | 四虎成人精品在永久免费 | 九九久久成人 | 免费国产在线精品 | 国产精品自在线 | 国产91精品久久久久久 | 日本久久视频 | 久草影视在线观看 | 中文av字幕在线观看 | 在线91av| 超碰在线观看99 | 精品久久一区 | 精品一区av | 最近日本韩国中文字幕 | 亚洲国产字幕 | 丁香婷婷色月天 | 最近日韩中文字幕中文 | 国产热re99久久6国产精品 | 91九色自拍 | 五月婷婷狠狠 | 国产午夜免费视频 | av在线免费观看不卡 | 九九免费精品视频在线观看 | 麻豆视频国产 | 亚洲久在线 | 精品久久久久免费极品大片 | 免费观看视频黄 | 久久久久久综合 | 96精品视频| 亚洲在线精品视频 | 中文字幕成人 | 国产超碰在线 | 久久精品国产亚洲精品 | 五月婷综合 | 亚洲黄色av网址 | 久久久久久久久久福利 | 精油按摩av | 精品一二 | 日本成址在线观看 | 麻豆国产露脸在线观看 | 欧美精品v国产精品 | 久久av影院| 成人a级网站 | 97免费| 97精品国产一二三产区 | 成人在线视频网 | 国产一区二区三区午夜 | 91精品久久久久久久久久久久久 | 99爱精品在线 | av在线免费播放网站 | 国产精品福利在线播放 | 亚洲国产婷婷 | 丁香网五月天 | aaa日本高清在线播放免费观看 | 激情综合交 | 黄色小视频在线观看免费 | 91系列在线观看 | 超碰在线人人97 | 久久久久久蜜av免费网站 | 在线观看视频一区二区三区 | 天天操夜操视频 | 香蕉视频在线观看免费 | 国产美女精彩久久 | 人人草在线视频 | 亚洲高清视频在线 | 日日夜夜国产 | 精品视频在线免费观看 | 天天干com | 波多野结衣在线视频免费观看 | 国产黄色av网站 | 亚洲四虎 | 久久久久综合 | 三级av网 | 欧美色婷婷 | 国产一级a毛片视频爆浆 | 精品亚洲一区二区三区 | 99在线免费观看 | 999成人 | 久久精品视频在线观看免费 | 日韩欧美专区 | 午夜 免费 | 欧美疯狂性受xxxxx另类 | 五月婷婷视频在线观看 | 国产爽妇网 | 免费成人黄色 | 超碰公开在线观看 | 国产精品大片在线观看 | 曰本三级在线 | 亚洲精品久久久久58 | 国产999精品久久久久久绿帽 | 亚洲最大av网站 | 九九交易行官网 | 美女国内精品自产拍在线播放 | 91久久久久久久一区二区 | 韩国av不卡 | 国产成人在线免费观看 | 97在线观看免费 | 91精品国自产在线观看 | 人人爽人人爽人人 | 欧美另类美少妇69xxxx | 91成人蝌蚪 | 成人中文字幕在线 | 日韩av电影国产 | 精品999在线观看 | 国产高清精品在线 | av午夜电影| 国产成人三级 | 久久久国产精品亚洲一区 | 国产又粗又硬又爽的视频 | 国产精品综合av一区二区国产馆 | 亚洲mv大片欧洲mv大片免费 | 在线超碰av | 久草精品在线播放 | 91麻豆精品国产午夜天堂 | 欧美日韩高清一区二区三区 | 97精品国产97久久久久久 | 免费日韩av电影 | 欧美最猛性xxxx | 欧美日韩国产精品爽爽 | 久久久久久久久福利 | 国产成人精品一区二 | 蜜桃视频成人在线观看 | 看片在线亚洲 | 精品96久久久久久中文字幕无 | 正在播放亚洲精品 | 国产小视频免费观看 | 亚洲国产精品va在线 | 91精品久久久久久粉嫩 | 欧美日韩亚洲在线观看 | 免费久久久久久久 | 久草精品在线观看 | 精品一区二区视频 | 国产aa精品 | 激情五月伊人 | 黄色一二级片 | 福利一区二区三区四区 | 久久国产精品网站 | 最新午夜电影 | 二区视频在线观看 | 视频 天天草 | 久久99精品久久久久久久久久久久 | 久久免费福利视频 | 国产精品色在线 | 久久这里只有精品首页 | 亚洲精品国精品久久99热 | 亚洲精品视频中文字幕 | 亚洲综合日韩在线 | 天天色天天 | 久久久午夜精品理论片中文字幕 | 免费精品视频在线 | 欧美日韩在线免费视频 | 精品一区 在线 | 中文字幕专区高清在线观看 | 五月婷网站 | 国产成人性色生活片 | 香蕉网址| 日韩成人黄色 | 久久99这里只有精品 | 不卡中文字幕在线 | 日韩免费三区 | 国产视频一区二区三区在线 | 久久艹久久 | 精品一区二区免费视频 | 欧美色综合久久 | 免费观看www视频 | 久久伊人婷婷 | 99精品视频在线观看视频 | 美女黄视频免费看 | 黄色成人av网址 | 伊人久久一区 | 亚洲精品自在在线观看 | 国产专区第一页 | 国产亚洲欧美日韩高清 | 久久久久综合视频 | www.久久久com| 中文字幕在线观看免费观看 | 一区二区三区在线影院 | 国产91成人| 久草在线视频精品 | 中文字幕一区二区三区在线视频 | 午夜18视频在线观看 | 日韩欧美高清视频在线观看 | 欧美成人在线免费观看 | 探花系列在线 | 日韩精品一区二区免费 | 2023av在线| www.超碰97.com| 一区二区三区www | 国产99自拍 | 2019天天干天天色 | 一区二区三区四区精品 | 国产精品成人一区二区三区吃奶 | 日本中文字幕在线一区 | 亚洲一区二区天堂 | 人人插人人插 | 久久精品爱爱视频 | 国产永久免费高清在线观看视频 | 欧美日韩精品在线播放 | 欧美日韩综合在线 | 视频一区二区在线观看 | 国产精品日韩在线播放 | 国产精品电影一区二区 | 亚洲精品国产电影 | 欧美精品久久久久久久久久丰满 | 日韩免费在线观看视频 | 性色大片在线观看 | 看片的网址 | 久久电影日韩 | 婷婷久久久 | 欧美做受69| 欧美性护士 | 97品白浆高清久久久久久 | 99精品乱码国产在线观看 | 黄色大片免费网站 | 国产精品1区2区 | 999亚洲国产996395 | 亚洲综合欧美日韩狠狠色 | 国产精品日韩高清 | 五月天综合婷婷 | 欧美日韩在线看 | 久久精品国产美女 | 精品国产资源 | 91成版人在线观看入口 | 久久国语露脸国产精品电影 | 日韩午夜电影网 | 午夜电影一区 | 天天综合中文 | 天天草夜夜 | 国产黄色一级片 | 99re8这里有精品热视频免费 | 久草在线在线精品观看 | 国产黄色大全 | 久久天堂精品视频 | 国产精品视频app | 成人免费毛片aaaaaa片 | 97精产国品一二三产区在线 | 天天操天天操天天操 | 九九天堂 | 欧美日韩国产精品一区二区三区 | 日日干综合 | 日韩av专区| 久久视频这里只有精品 | 98精品国产自产在线观看 | 狠狠黄 | 四虎在线免费观看 | 国产色在线 | av免费在线网站 | 国产区欧美 | 99在线精品视频 | 欧美一区二区三区特黄 | 97视频在线播放 | 一区二区三区在线观看中文字幕 | 亚洲欧美日韩在线一区二区 | 天天操天天干天天操天天干 | 97碰碰视频 | 日韩av在线一区二区 | 91人人爽久久涩噜噜噜 | 2022久久国产露脸精品国产 | 日本成人免费在线观看 | 中文字幕在线视频一区二区 | 去干成人网 | 婷婷在线播放 | 精品国产成人av在线免 | 中文高清av | 国产福利av在线 | 在线观看免费av片 | 亚洲精品综合在线观看 | 日日夜夜天天操 | 亚洲三区在线 | 久久超碰在线 | 欧美视频在线二区 | 91在线看黄| 欧美三人交 | 久久久久久久久久电影 | 视频一区二区国产 | 麻豆视频91 | 日韩专区中文字幕 | 五月天综合网 | 麻豆系列在线观看 | 国产精品九九久久久久久久 | 成人一区在线观看 | 在线观看完整版免费 | 国产精品久久久久久电影 | www.五月天 | 久久99精品国产麻豆婷婷 | 视频二区在线 | 国产高清视频免费 | 亚洲日韩欧美视频 | 天天色综合三 | 国产黄色高清 | 中文字幕av在线播放 | 中文字幕一区二区三区精华液 | 亚洲天堂毛片 | 精品黄色在线 | 欧美性另类| 在线观看国产v片 | 国产啊v在线 | 久9在线| 麻豆传媒视频在线免费观看 | 日韩一区正在播放 | 91精品一区二区三区蜜臀 | 97久久久免费福利网址 | 五月天亚洲综合小说网 | 免费国产黄线在线观看视频 | 午夜精品一二区 | 国产色就色 | 香蕉视频在线免费看 | 午夜精品福利一区二区三区蜜桃 | 免费看一及片 | 中文字幕乱码亚洲精品一区 | 久久久久亚洲精品男人的天堂 | 国产高h视频 | 亚洲精品福利视频 | 婷婷丁香六月天 | 在线看国产一区 | 在线观看视频 | 91亚色视频 | 国产精品久久久久久久久久妇女 | 日韩激情网 | 日韩免费一级电影 | 天天综合网~永久入口 | 欧美日韩在线播放 | 亚洲精品在线观看的 | 亚州av网站 | 午夜免费久久看 | 91porny九色在线播放 | 欧美激情第一区 | 亚洲九九| a在线播放| a在线观看国产 | 亚洲精品美女久久久 | 毛片播放网站 | 亚洲欧美国产精品18p | 久久激情视频 | 精品不卡视频 | 久久国产精品精品国产色婷婷 | 深夜福利视频在线观看 | 天天插狠狠干 | 九色视频自拍 | 国产视频网站在线观看 | av成人动漫在线观看 | 欧美日韩国产成人 | 亚洲狠狠操| 91看片麻豆 | 亚洲专区路线二 | 亚洲国产网站 | 人人澡人摸人人添学生av | 天天av在线播放 | 久草在线最新 | 天天爽天天摸 | 欧美性色xo影院 | 在线a视频 | 九九热免费精品视频 | 亚洲欧美视频网站 | 国产一区二区三区在线免费观看 | 在线韩国电影免费观影完整版 | 粉嫩av一区二区三区入口 | 夜夜夜草| 综合网天天色 | 三上悠亚一区二区在线观看 | 97爱爱爱 | 国产精品手机播放 | 国产精品高潮呻吟久久av无 | 9在线观看免费高清完整 | 狠狠亚洲| 韩国三级在线一区 | 最近免费中文字幕mv在线视频3 | 久久久久久高潮国产精品视 | 中文字幕在线看片 | 超碰免费久久 | 免费毛片aaaaaa | 91精品在线视频 | av 一区二区三区 | 中文字幕在线一区观看 | 国产成人精品一区二区三区免费 | 精品国产乱码一区二 | 97超碰资源站 | 中文字幕在线免费看 | 欧美一区二区精品在线 | 久久久免费少妇 | 亚洲国产精品一区二区久久hs | 色综合天天做天天爱 | 色www精品视频在线观看 | 日韩高清精品免费观看 | 一区二区三区视频 | 久久香蕉电影 | 国产丝袜一区二区三区 | 婷婷5月色 | 国产黄色电影 | 欧美日韩午夜 | 欧美精品久久99 | 人人玩人人添人人 | 日韩黄色免费在线观看 | 免费国产在线视频 | 久久成人麻豆午夜电影 | h文在线观看免费 | 国产免费不卡 | 精品国产乱码久久久久久1区二区 | 中文字幕二区 | 我爱av激情网 | 欧美日韩国产一区二区三区 | 91免费高清观看 | 亚洲黄色小说网 | 国产精品永久免费观看 | 久久久久久久精 | 久久久精品成人 | 9在线观看免费高清完整版在线观看明 | 黄色动态图xx | 欧美日韩在线视频一区二区 | 国产四虎在线 | 精品一区二三区 | 久久手机精品视频 | 不卡av在线 | 奇米网777| 99久久er热在这里只有精品15 | 国产精品精品国产色婷婷 | 99热这里只有精品久久 | 日本丶国产丶欧美色综合 | 午夜在线免费视频 | 日韩精品一区二区免费 | 国产99自拍 | av在线成人| 国内小视频在线观看 | 一级电影免费在线观看 | 久久成人亚洲欧美电影 | 久久精品电影院 | 999亚洲国产996395 | 成人一级在线观看 | 久久69精品 | av免费在线看网站 | 欧美最爽乱淫视频播放 | 中日韩免费视频 | 日韩一区二区三区高清在线观看 | 69中文字幕 | 久久精品国产久精国产 | 国产18精品乱码免费看 | 中文字幕视频一区 | 在线韩国电影免费观影完整版 | 久久在草 | 成人久久久久久久久 | 丁香六月av | 久久理论片 | 欧美性色黄 | 97av免费视频 | 久草 | 免费av网址大全 | 色婷av| 美女精品网站 | 热久久最新地址 | 中文乱码视频在线观看 | 最新av中文字幕 | 日韩精品中文字幕在线不卡尤物 | 免费看片在线观看 | 中文字幕在线观看91 | 91网页版在线观看 | 欧美日韩精品在线观看 | 欧美性网站 | 免费a视频在线 | 不卡的av电影在线观看 | 久久精品欧美一 | 五月天亚洲综合 | 一区二区三区日韩在线观看 | 91探花系列在线播放 | 国产天天爽 | 国产伦精品一区二区三区高清 | 免费中文字幕在线观看 | 干干日日 | 奇米网8888 | 在线看的av网站 | 精产嫩模国品一二三区 | 麻豆91在线播放 | 国产午夜精品一区二区三区嫩草 | 在线看岛国av | 国产又粗又猛又黄 | 久久国内精品视频 | 精品毛片在线 | 亚洲视频网站在线观看 | 亚洲一区二区三区在线看 | 国产剧情av在线播放 | 久久久久高清毛片一级 | www.色午夜 | 国产精品高清一区二区三区 | 国产91探花 | 成人av观看 | 久久久久在线视频 | 在线视频在线观看 | 亚洲国产精品电影在线观看 | 日韩电影一区二区在线 | 久久成人综合视频 | 中文字幕亚洲精品在线观看 | 亚洲高清视频在线观看免费 | 中文字幕在线观看视频一区二区三区 | 国产精品高清一区二区三区 | 婷婷午夜 | 亚洲精品国产成人av在线 | 综合影视 | 国产色爽 | 国产精品久久久久久久久久ktv | www.天天射 | 国产亚洲综合在线 | 久艹在线免费观看 | 99精品视频免费观看视频 | 在线观看深夜视频 | 亚洲精品乱码久久 | 国产无限资源在线观看 | 91免费高清在线观看 | 日本中文字幕一二区观 | 日本成址在线观看 | 97超碰免费在线观看 | av一级片在线观看 | 欧美一级大片在线观看 | 欧美一级性生活片 | 亚洲精品在线观看中文字幕 | 成人午夜影视 | 最新国产一区二区三区 | 911香蕉视频| 久久久久国 | 美女免费黄视频网站 | 天天草天天干天天 | av电影免费在线播放 | www黄com | 国产精品久久久久影院 | 在线观看av的网站 | av成人在线网站 | 狠狠的干狠狠的操 | 一区二区三区精品在线视频 | 久久9精品 | 亚洲精品美女免费 | 青青草华人在线视频 | 在线国产能看的 | 有码中文在线 | 97精品久久 | 尤物一区二区三区 | 麻豆久久久久 | 美女视频黄色免费 | 99视频在线观看视频 | 人人dvd| 成人中文字幕在线 | 免费黄色av片| 色婷婷综合激情 | 国内精品久久久久久久影视简单 | 亚洲欧洲久久久 | 欧美激情视频一区 | 久草久草久草久草 | 黄色网址a | 日韩中文字幕免费电影 | 激情欧美一区二区三区 | 色综合天天综合 | 日本中文字幕在线看 | 五月天色中色 | 日日干夜夜爱 | 丁香婷婷网 | 99爱精品视频 | 少妇bbbb搡bbbb搡bbbb | 国产成年免费视频 | 少妇bbbb搡bbbb桶| 色婷婷狠狠 | 色婷av| 日本中文不卡 | 亚洲色图色 | 色视频网站在线观看一=区 a视频免费在线观看 | 色偷偷888欧美精品久久久 | 成 人 免费 黄 色 视频 | 久草视频网 | 99热这里有精品 | 在线播放精品一区二区三区 | 玖玖爱国产在线 | 麻豆视频在线免费观看 | 成人免费观看网站 | 九色精品免费永久在线 | 国产片网站 | 久久麻豆精品 | 在线观看av不卡 | 日韩av成人 | 久久不射电影院 | 国产高清福利在线 | 亚洲欧美va | 三级黄色大片在线观看 | 国产97在线视频 | 欧美人交a欧美精品 | 久久免费a | 丁香婷婷激情国产高清秒播 | 国产一级免费av | 国产美女视频 | 久热免费在线观看 | 在线观看岛国片 | 国产精品成人一区二区 | 美女免费视频观看网站 | 欧美色图亚洲图片 | 探花视频免费观看高清视频 | 91高清免费看 | 日韩三级在线观看 | 国产精品精品久久久 | 99草视频 | 女女av在线| 欧美国产日韩一区 | 久久久久久国产精品999 | av资源在线看 | 日韩亚洲在线视频 |