基于Vue、web3的以太坊项目开发及交易内幕初探

本文通过宏观和微观两个层面窥探以太坊底层执行逻辑。

宏观层面描述创建并运行一个小型带钱包的发币APP的过程,微观层面是顺藤摸瓜从http api深入go-ethereum源码执行过程。

分析思路:自上而下,从APP深入EVM。

从应用入手,如果一头扎进ethereum,收获的可能是纯理论的东西,要想有所理解还得结合以后的实践才能恍然大悟。所以我始终坚持从应用入手、自上而下是一种正确、事半功倍的方法论。

我在讲解以太坊基础概念的那篇专题文章里,用的是从整体到局部的方法论,因为研究目标就是一个抽象的理论的东西,我对一个全然未知的东西的了解总是坚持从整体到局部的思路。

项目创建、部署合约到私链

之前用truffle框架做项目开发,这个框架封装了合约的创建、编译、部署过程,为了研究清楚自上至下的架构,这里就不用truffle构建项目了。

项目前端基于vue,后端是geth节点,通过web3 http api通信。

开发vue、solidity等前端IDE还是webstorm好,Atom和goland就免了不太好用!

1、全局安装vue-cli

npm i -g vue-cli

2、初始化一个基于webpack的vue项目

vue init webpack XXXProject

3、项目里安装web3依赖

web3.js是ethereum的javascript api库,通过rpc方式与以太坊节点交互。

npm install --save web3@1.0.0-beta.34

尽量用npm安装,不要用cnpm,有时候是个坑玩意,会生成“_”开头的很多垃圾还要求各种install。也可以写好了package.json,删除node_modules文件夹,再执行npm i。

web3版本用1.0以上,和1.0以下语法有很大不同。

4、项目里创建全局web3对象

用vuex有点啰嗦,这里就写个vue插件,提供全局的web3对象。

import Web3 from "web3"

export default {

install: function (Vue, options) {

var web3 = window.web3

if (typeof web3 !== 'undefined') {

web3 = new Web3(web3.currentProvider)

} else {

web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))

}

Vue.prototype.$web3 = web3

}

}

在main.js里启用该插件,以后就可以这样使用this.$web3这个全局对象了。

Vue.use(插件名)

5、写一个ERC20合约

代码省略

项目全部代码地址:

https://github.com/m3shine/To...

6、编译&部署合约

有必要说明一下编译和部署方式的选择,它严重关系到你实际项目的开发:

1)使用Remix,把自己写好的合约拷贝到Remix里进行编译和部署。这种方式最方便。

2)使用truffle这类的框架,这种方式是需要项目基于框架开发了,编译和部署也是在truffle控制台进行。

3)基于web3和solc依赖,写编译(solc)和部署(web3)程序,这些代码就独立(vue是前端,nodejs是后端,运行环境不同)于项目了,用node单独运行。

4)本地安装solidity进行编译,部署的话也需要自己想办法完成。

5)使用geth钱包、mist等编译部署。

……

从geth1.6.0开始,剥离了solidity编译函数,所以web3也不能调用编译方法了。可以本地安装solidity带编译器,也可以在项目里依赖solc进行编译。

编译部署的方式眼花缭乱,这里选择方式3。

编译部署参考代码(web3的1.0及以上版本):token_deploy.js

const Web3 = require('web3')

const fs = require('fs')

const solc = require('solc')

const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));

const input = fs.readFileSync('../contracts/Token.sol');

const output = solc.compile(input.toString(), 1);

fs.writeFile('../abi/Token.abi', output.contracts[':Token'].interface, err => {

if (err) {

console.log(err)

}

})

const bytecode = output.contracts[':Token'].bytecode;

const abi = JSON.parse(output.contracts[':Token'].interface);

const tokenContract = new web3.eth.Contract(abi);

let log = {

time: new Date(Date.now()),

transactionHash: '',

contractAddress: ''

}

// 部署合约

tokenContract.deploy({

data: '0x' + bytecode,

arguments: ['200000000', '魔法币', 'MFC'] // Token.sol构造参数

})

.send({

from: '0x2d2afb7d0ef71f85dfbdc89d288cb3ce8e049e10', //写你自己的矿工(发币)地址

gas: 5000000, // 这个值很微妙

}, (err, transactionHash) => {

if (err) {

console.log(err);

return;

}

log.transactionHash = transactionHash

})

.on('error', error => {

console.log(error);

})

// 不是总能成功获取newContractInstance, 包括监听receipt也可能发生异常,原因是receipt获取时机可能发生在交易未完成前。

.then(function (newContractInstance) {

if(newContractInstance){

log.contractAddress = newContractInstance.options.address

}

fs.appendFile('Token_deploy.log',JSON.stringify(log) + '\r\n', err => {

if (err) {

console.log(err)

}

})

});

;

7、在执行部署脚本前,需要有一个账户并解锁,在geth控制台执行以下命令:

personal.newAccount('密码')

personal.unlockAccount(eth.coinbase,'密码','20000')

8、发布合约是需要eth币的,所以先挖矿弄点以太币:

miner.start()

9、现在可以执行编译部署脚本了:

node token_deploy.js

如果前面miner.stop()过,那么在执行部署的时候,要确保miner.start(),有矿工打包才能出块。

这里还要知道,因为就是本矿工账户创建合约,所以交易费又回到了本账户,因此余额看起来总是没有减少。

至此,我们已经在私链上部署了一个合约,产生了一笔交易(即创建合约本身这个交易)、一个矿工账户、一个合约账户。

常见错误

Error: insufficient funds for gas * price + value

意思是账户里没有足够的eth币,给创建合约的账户里弄些比特币。

Error: intrinsic gas too low

调高以下发布合约时的gas值。

Error: Invalid number of parameters for "undefined". Got 0 expected 3! (类似这样的)

没有传入合约构造函数参数

调用链上合约

合约部署成功,就有了合约地址,根据合约地址构建合约实例。

let tokenContract = new this.$web3.eth.Contract(JSON.parse(abi),'合约地址')

tokenContract.methods.myMethod.

call()调用的都是abi里的constant方法,即合约里定义的状态属性,EVM里不会发送交易,不会改变合约状态。

send()调用的是合约里定义的函数,是要发送交易到合约并执行合约方法的,会改变合约状态。

以上就简单说一下,不写太多了。看官可以自行下载本项目源码(上面第5步有github链接),自己运行起来看看界面和发币/转账操作。

源码交易过程分析

当我们在项目中创建一个合约的时候,发生了什么?

geth节点默认开放了8545 RPC端口,web3通过连接这个rpc端口,以http的方式调用geth开放的rpc方法。从这一web3与以太坊节点交互基本原理入手,先分析web3源码是怎样调用rpc接口,对应的geth接口是否同名,再看geth源码该接口又是怎么执行的。

new web3.eth.Contract(jsonInterface[, address][, options])

这个函数,jsonInterface就是abi,不管传不传options,options.data属性总是abi的编码。

这个web3接口源码中调用eth.sendTransaction,options.data编码前面加了签名,options.to赋值一个地址,最后返回这笔交易的hash。

再返回上面第6步看一下部署脚本,代码截止到deploy都是在构造web3里的对象,首次与本地geth节点通信的方法是send,它是web3的一个接口方法。

deploy返回的是个web3定义的泛型TransactionObject。

Contract对send接口方法的实现如下:

var sendTransaction = (new Method({

name: 'sendTransaction',

call: 'eth_sendTransaction',

params: 1,

inputFormatter: [formatters.inputTransactionFormatter],

requestManager: _this._parent._requestManager,

accounts: Contract._ethAccounts, // is eth.accounts (necessary for wallet signing)

defaultAccount: _this._parent.defaultAccount,

defaultBlock: _this._parent.defaultBlock,

extraFormatters: extraFormatters

})).createFunction();

return sendTransaction(args.options, args.callback);

这个send最终由XMLHttpRequest2的request.send(JSON.stringify(payload))与节点通信。

var sendSignedTx = function(sign){

payload.method = 'eth_sendRawTransaction';
payload.params = [sign.rawTransaction]; method.requestManager.send(payload, sendTxCallback);
};

所以send方法对应的节点api是eth_sendRawTransaction。

go-ethereum/ethclient/ethclient.go

func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error {

data, err := rlp.EncodeToBytes(tx)

if err != nil {

return err

}

return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(data))

}

找到该api执行入口

go-ethereum/internal/ethapi.SendTransaction

func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {

// Look up the wallet containing the requested signer
account := accounts.Account{Address: args.From} wallet, err := s.b.AccountManager().Find(account)
if err != nil {
return common.Hash{}, err
}
……
return submitTransaction(ctx, s.b, signed)

}

我们在这个函数处打一个断点!然后执行部署脚本(可以多次执行),运行到断点处:

要调试geth需要对其重新编译,去掉它原来编译的优化,参见下面“调试源码”一节。

(dlv) p args

github.com/ethereum/go-ethereum/internal/ethapi.SendTxArgs {

From: github.com/ethereum/go-ethereum/common.Address [45,42,251,125,14,247,31,133,223,189,200,157,40,140,179,206,142,4,158,16],

To: *github.com/ethereum/go-ethereum/common.Address nil,

Gas: *5000000,

GasPrice: *github.com/ethereum/go-ethereum/common/hexutil.Big {

neg: false,

abs: math/big.nat len: 1, cap: 1, [18000000000],},

Value: *github.com/ethereum/go-ethereum/common/hexutil.Big nil,

Nonce: *github.com/ethereum/go-ethereum/common/hexutil.Uint64 nil,

Data: *github.com/ethereum/go-ethereum/common/hexutil.Bytes len: 2397, cap: 2397, [96,96,96,64,82,96,2,128,84,96,255,25,22,96,18,23,144,85,52,21,97,0,28,87,96,0,128,253,91,96,64,81,97,8,125,56,3,128,97,8,125,131,57,129,1,96,64,82,128,128,81,145,144,96,32,1,128,81,130,1,145,144,96,32,...+2333 more],

Input: github.com/ethereum/go-ethereum/common/hexutil.Bytes nil,}

(dlv) p wallet

github.com/ethereum/go-ethereum/accounts.Wallet(
github.com/ethereum/go-ethereum/accounts/keystore.keystoreWallet) {

account: github.com/ethereum/go-ethereum/accounts.Account {

Address: github.com/ethereum/go-ethereum/common.Address [45,42,251,125,14,247,31,133,223,189,200,157,40,140,179,206,142,4,158,16],

URL: (
github.com/ethereum/go-ethereum/accounts.URL)(0xc4200d9f18),},

keystore: github.com/ethereum/go-ethereum/accounts/keystore.KeyStore {

storage: github.com/ethereum/go-ethereum/accounts/keystore.keyStore(
github.com/ethereum/go-ethereum/accounts/keystore.keyStorePassphrase) ...,

cache: (github.com/ethereum/go-ethereum/accounts/keystore.accountCache)(0xc4202fe360),

changes: chan struct {} {

qcount: 0,

dataqsiz: 1,

buf: [1]struct struct {} [

{},

],

elemsize: 0,

closed: 0,

elemtype: runtime._type {

size: 0,

ptrdata: 0,

hash: 670477339,

tflag: 2,

align: 1,

fieldalign: 1,

kind: 153,

alg: (runtime.typeAlg)(0x59e69d0),

gcdata: 1,

str: 67481,

ptrToThis: 601472,},

sendx: 0,

recvx: 0,

recvq: waitq<struct {}> {

first: (sudog<struct {}>)(0xc42006ed20),

last: (sudog<struct {}>)(0xc42006ed20),},

sendq: waitq<struct {}> {

first: sudog<struct {}> nil,

last: sudog<struct {}> nil,},

lock: runtime.mutex {key: 0},},

unlocked: map[github.com/ethereum/go-ethereum/common.Address]
github.com/ethereum/go-ethereum/accounts/keystore.unlocked [...],

wallets: []github.com/ethereum/go-ethereum/accounts.Wallet len: 2, cap: 2, [

...,

...,

],

updateFeed: (
github.com/ethereum/go-ethereum/event.Feed)(0xc4202c4040),

updateScope: (
github.com/ethereum/go-ethereum/event.SubscriptionScope)(0xc4202c40b0),

updating: true,

mu: (
sync.RWMutex)(0xc4202c40cc),},}

(dlv) p s.b

github.com/ethereum/go-ethereum/internal/ethapi.Backend(
github.com/ethereum/go-ethereum/eth.EthApiBackend) *{

eth: *github.com/ethereum/go-ethereum/eth.Ethereum {

config: (github.com/ethereum/go-ethereum/eth.Config)(0xc420153000),

chainConfig: (github.com/ethereum/go-ethereum/params.ChainConfig)(0xc4201da540),

shutdownChan: chan bool {

qcount: 0,

dataqsiz: 0,

buf: *[0]bool [],

elemsize: 1,

closed: 0,

elemtype: *runtime._type {

size: 1,

ptrdata: 0,

hash: 335480517,

tflag: 7,

align: 1,

fieldalign: 1,

kind: 129,

alg: (runtime.typeAlg)(0x59e69e0),

gcdata: *1,

str: 21072,

ptrToThis: 452544,},

sendx: 0,

recvx: 0,

recvq: waitq {

first: (sudog)(0xc420230ba0),

last: (sudog)(0xc420231440),},

sendq: waitq {

first: sudog nil,

last: sudog nil,},

lock: runtime.mutex {key: 0},},

stopDbUpgrade: nil,

txPool: (github.com/ethereum/go-ethereum/core.TxPool)(0xc420012380),

blockchain: (github.com/ethereum/go-ethereum/core.BlockChain)(0xc42029c000),

protocolManager: (github.com/ethereum/go-ethereum/eth.ProtocolManager)(0xc420320270),

lesServer: github.com/ethereum/go-ethereum/eth.LesServer nil,

chainDb: github.com/ethereum/go-ethereum/ethdb.Database(
github.com/ethereum/go-ethereum/ethdb.LDBDatabase) ...,

eventMux: (github.com/ethereum/go-ethereum/event.TypeMux)(0xc4201986c0),

engine: github.com/ethereum/go-ethereum/consensus.Engine(
github.com/ethereum/go-ethereum/consensus/ethash.Ethash) ...,

accountManager: (github.com/ethereum/go-ethereum/accounts.Manager)(0xc420089860),

bloomRequests: chan chan *github.com/ethereum/go-ethereum/core/bloombits.Retrieval {

qcount: 0,

dataqsiz: 0,

buf: *[0]chan *github.com/ethereum/go-ethereum/core/bloombits.Retrieval [],

elemsize: 8,

closed: 0,

elemtype: *runtime._type {

size: 8,

ptrdata: 8,

hash: 991379238,

tflag: 2,

align: 8,

fieldalign: 8,

kind: 50,

alg: (runtime.typeAlg)(0x59e6a10),

gcdata: *1,

str: 283111,

ptrToThis: 0,},

sendx: 0,

recvx: 0,

recvq: waitq<chan *github.com/ethereum/go-ethereum/core/bloombits.Retrieval> {

first: (sudog<chan *github.com/ethereum/go-ethereum/core/bloombits.Retrieval>)(0xc420230c00),

last: (sudog<chan *github.com/ethereum/go-ethereum/core/bloombits.Retrieval>)(0xc4202314a0),},

sendq: waitq<chan *github.com/ethereum/go-ethereum/core/bloombits.Retrieval> {

first: *sudog<chan github.com/ethereum/go-ethereum/core/bloombits.Retrieval> nil,

last: sudog<chan github.com/ethereum/go-ethereum/core/bloombits.Retrieval> nil,},

lock: runtime.mutex {key: 0},},

bloomIndexer: unsafe.Pointer(0xc4201b23c0),

ApiBackend: (github.com/ethereum/go-ethereum/eth.EthApiBackend)(0xc4202b8910),

miner: (github.com/ethereum/go-ethereum/miner.Miner)(0xc420379540),

gasPrice: (math/big.Int)(0xc420233c40),

etherbase: github.com/ethereum/go-ethereum/common.Address [45,42,251,125,14,247,31,133,223,189,200,157,40,140,179,206,142,4,158,16],

networkId: 13,

netRPCService: (github.com/ethereum/go-ethereum/internal/ethapi.PublicNetAPI)(0xc42007feb0),

lock: (
sync.RWMutex)(0xc4202ea528),},

gpo: github.com/ethereum/go-ethereum/eth/gasprice.Oracle {

backend: github.com/ethereum/go-ethereum/internal/ethapi.Backend(
github.com/ethereum/go-ethereum/eth.EthApiBackend) ...,

lastHead: github.com/ethereum/go-ethereum/common.Hash [139,147,220,247,224,227,136,250,220,62,217,102,160,96,23,182,90,90,108,254,82,158,234,95,150,120,163,5,61,248,168,168],

lastPrice: (math/big.Int)(0xc420233c40),

cacheLock: (
sync.RWMutex)(0xc420010938),

fetchLock: (
sync.Mutex)(0xc420010950),

checkBlocks: 20,

maxEmpty: 10,

maxBlocks: 100,

percentile: 60,},}

// submitTransaction is a helper function that submits tx to txPool and logs a message.

func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) {

if err := b.SendTx(ctx, tx); err != nil {

return common.Hash{}, err

}

if tx.To() == nil {

signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number())

from, err := types.Sender(signer, tx)

if err != nil {

return common.Hash{}, err

}

addr := crypto.CreateAddress(from, tx.Nonce())

log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex())

} else {

log.Info("Submitted transaction", "fullhash", tx.Hash().Hex(), "recipient", tx.To())

}

return tx.Hash(), nil

}

(dlv) p tx

*github.com/ethereum/go-ethereum/core/types.Transaction {

data: github.com/ethereum/go-ethereum/core/types.txdata {

AccountNonce: 27,

Price: (math/big.Int)(0xc4217f5640),

GasLimit: 5000000,

Recipient: *github.com/ethereum/go-ethereum/common.Address nil,

Amount: (math/big.Int)(0xc4217f5620),

Payload: []uint8 len: 2397, cap: 2397, [96,96,96,64,82,96,2,128,84,96,255,25,22,96,18,23,144,85,52,21,97,0,28,87,96,0,128,253,91,96,64,81,97,8,125,56,3,128,97,8,125,131,57,129,1,96,64,82,128,128,81,145,144,96,32,1,128,81,130,1,145,144,96,32,...+2333 more],

V: (math/big.Int)(0xc4217e0a20),

R: (math/big.Int)(0xc4217e09c0),

S: (math/big.Int)(0xc4217e09e0),

Hash: github.com/ethereum/go-ethereum/common.Hash nil,},

hash: sync/atomic.Value {

noCopy: sync/atomic.noCopy {},

v: interface {} nil,},

size: sync/atomic.Value {

noCopy: sync/atomic.noCopy {},

v: interface {} nil,},

from: sync/atomic.Value {

noCopy: sync/atomic.noCopy {},

v: interface {} nil,},}

(dlv) bt

0 0x00000000048d9248 in github.com/ethereum/go-ethereum/internal/ethapi.submitTransaction

at ./go/src/github.com/ethereum/go-ethereum/internal/ethapi/api.go:1130

1 0x00000000048d9bd1 in github.com/ethereum/go-ethereum/internal/ethapi.(
PublicTransactionPoolAPI).SendTransaction

at ./go/src/github.com/ethereum/go-ethereum/internal/ethapi/api.go:1176

(dlv) frame 0 l

github.com/ethereum/go-ethereum/internal/ethapi.submitTransaction() ./go/src/github.com/ethereum/go-ethereum/internal/ethapi/api.go:1130 (PC: 0x48d9248)

Warning: debugging optimized function

1125: if err := b.SendTx(ctx, tx); err != nil {

1126: return common.Hash{}, err

1127: }

1128: if tx.To() == nil {

1129: signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number())

=>1130: from, err := types.Sender(signer, tx)

1131: if err != nil {

1132: return common.Hash{}, err

1133: }

1134: addr := crypto.CreateAddress(from, tx.Nonce())

1135: log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex())

(dlv) frame 1 l

Goroutine 3593 frame 1 at /Users/jiang/go/src/github.com/ethereum/go-ethereum/internal/ethapi/api.go:1176 (PC: 0x48d9bd1)

1171: }

1172: signed, err := wallet.SignTx(account, tx, chainID)

1173: if err != nil {

1174: return common.Hash{}, err

1175: }

=>1176: return submitTransaction(ctx, s.b, signed)

1177: }

1178:

1179: // SendRawTransaction will add the signed transaction to the transaction pool.

1180: // The sender is responsible for signing the transaction and using the correct nonce.

1181: func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) {

先把调试结果展示出来,通过对一个交易的内部分析,可以了解EVM执行的大部分细节,此处需要另开篇幅详述。请关注后续专题。

源码调试

1、重新强制编译geth,去掉编译内联优化,方便跟踪调试。

cd path/go-ethereum

sudo go install -a -gcflags '-N -l' -v ./cmd/geth

编译后的geth执行文件就在$gopath/bin下。

2、在datadir下启动这个重新编译后的geth

geth --datadir "./" --rpc --rpccorsdomain="*" --networkid 13 console 2>>00.glog

3、调试这个进程

dlv attach

4、给交易api入口函数设置断点

b ethapi.(*PublicTransactionPoolAPI).SendTransaction

下面是一个区块链小程序,供大家参考。

图片描述

基于Vue、web3的以太坊项目开发及交易内幕初探 错误解决总结的更多相关文章

  1. 新建一个基于vue.js+Mint UI的项目

    上篇文章里面讲到如何新建一个基于vue,js的项目(详细文章请戳用Vue创建一个新的项目). 该项目如果需要组件等都需要自己去写,今天就学习一下如何新建一个基于vue.js+Mint UI的项目,直接 ...

  2. 以太坊钱包开发系列4 - 发送Token(代币)

    以太坊去中心化网页钱包开发系列,将从零开始开发出一个可以实际使用的钱包,本系列文章是理论与实战相结合,一共有四篇:创建钱包账号.账号Keystore文件导入导出.展示钱包信息及发起签名交易.发送Tok ...

  3. 以太坊钱包开发系列2 - 账号Keystore文件导入导出

    以太坊去中心化网页钱包开发系列,将从零开始开发出一个可以实际使用的钱包,本系列文章是理论与实战相结合,一共有四篇:创建钱包账号.账号Keystore文件导入导出.展示钱包信息及发起签名交易.发送Tok ...

  4. 基于Vue+Spring MVC+MyBatis+Shiro+Dubbo开发的分布式后台管理系统

    本文项目代码: 服务端:https://github.com/lining90567/dubbo-demo-server 前端:https://github.com/lining90567/dubbo ...

  5. 基于Vue的前后端分离项目实践

    一.为什么需要前后端分离 1.1什么是前后端分离  前后端分离这个词刚在毕业(15年)那会就听说过,但是直到17年前都没有接触过前后端分离的项目.怎么理解前后端分离?直观的感觉就是前后端分开去做,即功 ...

  6. 创建自己的区块链合约java版web3接口——以太坊代币(四)

    texas-web3j-solidity项目是一个java版本的,使用web3j包和eth网络交互的小程序. 主要实现了以下功能: 1.发布合约 2.发起转账 3.查询交易 4.调用智能合约方法 te ...

  7. luffy项目:基于vue与drf前后台分离项目(1)

    """ 1.业务逻辑:登录注册 - 主页(基础信息展示) - 课程页(复杂信息展示)- 课程订单生产与支付 - 上线订单生成 2.实际项目开发的技术点: git版本控制 ...

  8. 关于书籍《区块链以太坊DApp开发实战》的内容告示

    书中所列举的以太坊 etherscan 浏览器,原链接已经不能使用国内网络正常访问了,需要翻墙,下面的链接不需要翻墙,也是 etherscan 的官方浏览器链接: 以太坊浏览器:https://cn. ...

  9. 以太坊go-ethereum客户端查询交易列表(二)

    玩过比特币的朋友都知道,比特币是可以通过api(listtransactions)查询指定地址的历史交易的.但在eth中没有提供类似的查询api.今天这篇博客就简单介绍一下如果解决这个问题. 问题 以 ...

随机推荐

  1. webpack4——打包html报错解决

    ①先引入html-webpack-plugin插件,然后在终端下载 npm install --save-dev html-webpack-plugin ②我的文件结构 ③修改webpack.dev. ...

  2. CALayer创建图层(转)

    一.添加一个图层  添加图层的步骤:  1.创建layer  2.设置layer的属性(设置了颜色,bounds才能显示出来)  3.将layer添加到界面上(控制器view的layer上) @int ...

  3. JDBC编程:获取数据库连接

    JDBC(Java Database Connectivity),即Java数据库连接.通过JDBC编程,可以使Java应用程序和数据库进行交互. JDBC驱动的方式有很多种,我们常用的驱动方式为:本 ...

  4. vuex组件 vuex-persistedstate

    vuex用于管理项目中的全局状态,但是我们一刷新vuex中保存的全局状态就会被全部初始化,虽然我们也可以同事缓存到storage中做两步操作,但是vuex-persistedstate组件已经帮我们完 ...

  5. Asp.Net Core 生成图形验证码

    前几天有朋友问我怎么生成图片验证码,话不多说直接上代码. 支持.NET CORE开源.助力.NET Core社区发展. using System; using System.IO; using Sys ...

  6. jdk与tomcat的环境配置

    一.JDK的安装与配置 1.从官网下载jdk,注意是jdk不是jre.最好从官网下载,也可以直接度娘. 2.下载完毕后,安装jdk,​直接按照安装向导的提示安装即可,安装时可以自己选择安装路径,我的安 ...

  7. git push之后回滚(撤销)代码

    问题描述:首先,先说明一下,为什么会引发这次的话题,是这样的,我做完功能Agit push之后,2个月后需求部门要求不要功能A了,然后需要在没有功能A的基础上开发,怎么办?赶紧回滚代码呀. 然后我用g ...

  8. Leecode刷题之旅-C语言/python-100相同的树

    /* * @lc app=leetcode.cn id=100 lang=c * * [100] 相同的树 * * https://leetcode-cn.com/problems/same-tree ...

  9. 关于nodejs DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.

    const mongoose = require('mongoose') mongoose.connect("mongodb://localhost:27017/study", { ...

  10. 44- EF + Identity实现

    1-配置EF, 需要创建如下几个类 默认User主键为guid类型,现在改成int类型 namespace MvcCookieAuthSample.Models { public class Appl ...