1. 程式人生 > >Truechain主網Beta版交易流程解析

Truechain主網Beta版交易流程解析

初鏈主網Beta版於新加坡時間2018年09月28日08:00正式上線,在此之前,07:56分PBFT委員會第一次共識出塊和TrueChain fPOW創世區塊被挖出 今天我們主要看看初鏈主網Beta版的交易部分,本文主要淺談原始碼,所以懂go是前提,我們先看下啟動流程再看交易流程。

啟動的流程

當我們使用命令./build/bin/getrue --datadir ./data --cache 4096 --rpc --rpcport 33333 --rpcaddr 0.0.0.0 開啟節點時的流程:

首先整個true專案的主函式在cmd/getrue/main.go中,這個檔案中有一個main() 和init() 函式,先執行init() 初始化配置一個解析命令的庫。其中app.Action = getrue 則說明如果使用者在沒有輸入其他的子命令的情況下會呼叫這個欄位指向的函式app.Action = getrue

,即main.go中的func getrue(ctx *cli.Context) error函式。

func init() {
	// Initialize the CLI app and start Getrue 初始化CLI APP庫
	app.Action = getrue 
	app.HideVersion = true // we have a command to print the version
	app.Copyright = "Copyright 2013-2018 The getrue Authors"
	app.Commands = []cli.Command{
		// See chaincmd.go:
initCommand, importCommand,

然後再呼叫主函式main(),app 是一個第三方包gopkg.in/urfave/cli.v1的實列,這個第三方包的大用法大致就是首先構造這個app物件,通過程式碼配置app物件的行為,提供一些回撥函式。然後執行的時候直接在main函式裡執行app.Run(os.Args)就ok.

func main() {
	if err := app.Run(os.Args); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

如果沒有指定特殊的子命令,那麼getrue 是系統的主要入口,它會根據提供的引數建立一個預設的節點。並且以阻塞的模式執行這個節點,並且等待著節點被終止

func getrue(ctx *cli.Context) error {
	node := makeFullNode(ctx)
	startNode(ctx, node)
	node.Wait()
	return nil
}

我們可以看看makeFullNode函式,在cmd/getrue/config.go

func makeFullNode(ctx *cli.Context) *node.Node {
      根據命令列引數和一些特殊配置來建立一個node
	stack, cfg := makeConfigNode(ctx)
       把etrue 的服務註冊到這個節點上面。
	utils.RegisterEthService(stack, &cfg.Etrue)

	if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
		utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
	}
	// Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
.....

交易流程

true 基本交易流程 流程圖

大致流程分為以下幾個步驟:

  • 發起交易:指定目標地址和交易金額,以及需要的gas/gaslimit
  • 交易簽名:使用賬戶私鑰隊對交易進行簽名
  • 提交交易:把交易加入到交易緩衝池txpool中(會先對交易進行簽名驗證)
  • 廣播交易:通知EVM(true目前還是以太坊的EVM)執行,同時

1、發起交易

使用者通過JONS RPC 發起 etrue.sendTransacton 交易請求,最終會呼叫PublicTransactionPoolAPISendTransaction 實現 首先會根據from地址查詢對應的wallet,檢查一下引數值,然後通過SendTxArgs.toTransaction()建立交易,通過Wallet.SignTx()對交易進行簽名。 通過submitTransaction()提交交易

我們先看看SendTransaction的原始碼,程式碼位於internal/trueapi/api.go

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

	// Look up the wallet containing the requested signe
  解鎖發起交易的賬戶
	account := accounts.Account{Address: args.From}
	wallet, err := s.b.AccountManager().Find(account)
	if err != nil {
		return common.Hash{}, err
	}

	if args.Nonce == nil {
		// Hold the addresse's mutex around signing to prevent concurrent assignment of
		// the same nonce to multiple accounts.
		s.nonceLock.LockAddr(args.From)
		defer s.nonceLock.UnlockAddr(args.From)
	}

	// Set some sanity defaults and terminate on failure
	if err := args.setDefaults(ctx, s.b); err != nil {
		return common.Hash{}, err
	}
	// Assemble the transaction and sign with the wallet
    //  建立交易
	tx := args.toTransaction()

	var chainID *big.Int
	if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
		chainID = config.ChainID
	}
  // 交易簽名
	signed, err := wallet.SignTx(account, tx, chainID)
	if err != nil {
		return common.Hash{}, err
	}
	return submitTransaction(ctx, s.b, signed)
}

2、建立交易

tx := args.toTransaction() 建立交易程式碼 我們先看SendTxArgs型別的定義 。程式碼位於internal/trueapi/api.go

// SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool.
type SendTxArgs struct {
	From     common.Address  `json:"from"`
	To       *common.Address `json:"to"`
	Gas      *hexutil.Uint64 `json:"gas"`
	GasPrice *hexutil.Big    `json:"gasPrice"`
	Value    *hexutil.Big    `json:"value"`
	Nonce    *hexutil.Uint64 `json:"nonce"`
	// We accept "data" and "input" for backwards-compatibility reasons. "input" is the
	// newer name and should be preferred by clients.
	Data  *hexutil.Bytes `json:"data"`
	Input *hexutil.Bytes `json:"input"`
}

可以看到的是和JSON欄位對應的,包括地址、gas、金額這些交易資訊,nonce值是一個隨賬戶交易次數遞增的數字,一般會自動填充,交易還可以攜帶一些額外資料,存放在data或者input欄位中.

我們看下toTransaction()函式:

func (args *SendTxArgs) toTransaction() *types.Transaction {
	var input []byte
	if args.Data != nil {
		input = *args.Data
	} else if args.Input != nil {
		input = *args.Input
	}
	if args.To == nil {
		return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input)
	}
	return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input)
}

可以看到,如果目標地址為空的話,表示這是一個建立智慧合約的交易,呼叫NewContractCreation(),否則說明這是一個普通的交易,呼叫NewTransaction()方法,不管呼叫哪個都會生成一個Transaction實列,我們先看看這個Transaction型別的定義:原始碼位於core/types/transaction.go

type Transaction struct {
	data txdata
	// 快取
	hash atomic.Value
	size atomic.Value
	from atomic.Value
}

type txdata struct {
	AccountNonce uint64          `json:"nonce"    gencodec:"required"`
	Price        *big.Int        `json:"gasPrice" gencodec:"required"`
	GasLimit     uint64          `json:"gas"      gencodec:"required"`
	Recipient    *common.Address `json:"to"       rlp:"nil"` // nil means contract creation
	Amount       *big.Int        `json:"value"    gencodec:"required"`
	Payload      []byte          `json:"input"    gencodec:"required"`

	// 簽名資料
	V *big.Int `json:"v" gencodec:"required"`
	R *big.Int `json:"r" gencodec:"required"`
	S *big.Int `json:"s" gencodec:"required"`

	// This is only used when marshaling to JSON.
	Hash *common.Hash `json:"hash" rlp:"-"`
}

3、簽名交易

簽名交易的原始碼位於internal/trueapi/api.go

func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args SendTxArgs, passwd string) (*types.Transaction, error) {
	// Look up the wallet containing the requested signer
	account := accounts.Account{Address: args.From}
	wallet, err := s.am.Find(account)
	if err != nil {
		return nil, err
	}
	// Set some sanity defaults and terminate on failure
	if err := args.setDefaults(ctx, s.b); err != nil {
		return nil, err
	}
	// Assemble the transaction and sign with the wallet
	tx := args.toTransaction()

	var chainID *big.Int
	if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
		chainID = config.ChainID
	}
	return wallet.SignTxWithPassphrase(account, passwd, tx, chainID)
}

我們可以看到最後一句程式碼就是簽名方法,傳遞賬戶和密碼,以及交易和鏈的id,我們來看看SignTxWithPassphrase這個方法,這個方法的程式碼位於 accounts/keystore/keystore_wallet.go

func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
	// Make sure the requested account is contained within
	if account.Address != w.account.Address {
		return nil, accounts.ErrUnknownAccount
	}
	if account.URL != (accounts.URL{}) && account.URL != w.account.URL {
		return nil, accounts.ErrUnknownAccount
	}
	// Account seems valid, request the keystore to sign
	return w.keystore.SignTxWithPassphrase(account, passphrase, tx, chainID)
}

w.keystore.SignTxWithPassphrase(account, passphrase, tx, chainID)的程式碼位於 accounts/keystore/keystore.go 主要就是通過SignTx進行簽名

func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
	_, key, err := ks.getDecryptedKey(a, passphrase)
	if err != nil {
		return nil, err
	}
	defer zeroKey(key.PrivateKey)

	// Depending on the presence of the chain ID, sign with EIP155 or homestead
	if chainID != nil {
		return types.SignTx(tx, types.NewEIP155Signer(chainID), key.PrivateKey)
	}
	return types.SignTx(tx, types.HomesteadSigner{}, key.PrivateKey)
}

這裡會首先判斷賬戶是否已經解鎖,如果已經解鎖的話就可以獲取它的私鑰,然後建立簽名器,如果要符合EIP155規範的話就需要把chainId傳進去也就是我們的--networkid命令列的引數,最後呼叫一個全域性函式SignTx()完成簽名,SignTx()這個方法位於core/types/transaction_signing.go

func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, error) {
	h := s.Hash(tx)
	sig, err := crypto.Sign(h[:], prv)
	if err != nil {
		return nil, err
	}
	return tx.WithSignature(s, sig)
}

SignTx()方法主要分為3個步驟,並且不繼續展開講解了,

  • 生成交易的hash值
  • 根據hash值和私鑰生成簽名
  • 把簽名資料填充到Transaction實列中

4、提交交易

簽名完成後就需要呼叫submitTransaction()函式提交到Txpool緩衝池中,我們先看下TxPool中的欄位,原始碼位於core/tx_pool.go

type TxPool struct {
	config       TxPoolConfig
	chainconfig  *params.ChainConfig
	chain        blockChain
	gasPrice     *big.Int
	txFeed       event.Feed
	scope        event.SubscriptionScope
	chainHeadCh  chan ChainHeadEvent
	chainHeadSub event.Subscription
	signer       types.Signer
	mu           sync.RWMutex

	currentState  *state.StateDB      // Current state in the blockchain head
	pendingState  *state.ManagedState // Pending state tracking virtual nonces
	currentMaxGas uint64              // Current gas limit for transaction caps

	locals  *accountSet // Set of local transaction to exempt from eviction rules
	journal *txJournal  // Journal of local transaction to back up to disk

	pending map[common.Address]*txList   // All currently processable transactions
	queue   map[common.Address]*txList   // Queued but non-processable transactions
	beats   map[common.Address]time.Time // Last heartbeat from each known account
	all     *txLookup                    // All transactions to allow lookups
	priced  *txPricedList                // All transactions sorted by price

	wg sync.WaitGroup // for shutdown sync

	homestead bool
}

pending欄位中包含了當前所有可被處理的交易列表,而queue欄位包含了所有不可以被處理,也就是新加入進來的交易。

然後我們再看看submitTransaction()函式,原始碼位於internal/trueapi/api.go

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
}

可以看到submitTransaction函式裡先呼叫了SendTx()函式提交交易,然後如果發現目標地址為空,表明這是一個建立智慧合約的交易,會建立合約地址。

提交交易到txpool, 原始碼位於etrue/api_backend.go

func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
	return b.etrue.txPool.AddLocal(signedTx)
}

此txPool.AddLocl()函式中有2個主要的函式add函式,和promoteExecuteables(),原始碼位於core/tx_pool.go自行去看,add()會判斷是否應該把當前交易加入到queue列表中,promoteExecuteables()則會從queue中選取一些交易放入pending列表中等待執行。這裡就不展開那2個函數了。

5、廣播交易

交易提交到txpool中後,還需要廣播出去,一方面通知EVM執行該交易,另外就是要把資訊廣播給其他的節點,具體呼叫再promoteExecutables中的promoteTx()函式中,原始碼位於core/tx_pool.go

func (pool *TxPool) promoteExecutables(accounts []common.Address) {
...
for _, tx := range list.Ready(pool.pendingState.GetNonce(addr)) {
			hash := tx.Hash()
			if pool.promoteTx(addr, hash, tx) {
				log.Trace("Promoting queued transaction", "hash", hash)
				promoted = append(promoted, tx)
			}
		}
...
// Notify subsystem for new promoted transactions.
	if len(promoted) > 0 {
		go pool.txFeed.Send(NewTxsEvent{promoted})
	}
....
}

promoteTx 程式碼

func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) bool {
	// Try to insert the transaction into the pending queue
	if pool.pending[addr] == nil {
		pool.pending[addr] = newTxList(true)
	}
	list := pool.pending[addr]

	inserted, old := list.Add(tx, pool.config.PriceBump)
	if !inserted {
		// An older transaction was better, discard this
		pool.all.Remove(hash)
		pool.priced.Removed()

		pendingDiscardCounter.Inc(1)
		return false
	}
	// Otherwise discard any previous transaction and mark this
	if old != nil {
		pool.all.Remove(old.Hash())
		pool.priced.Removed()

		pendingReplaceCounter.Inc(1)
	}
	// Failsafe to work around direct pending inserts (tests)
	if pool.all.Get(hash) == nil {
		pool.all.Add(tx)
		pool.priced.Put(tx)
	}
	// Set the potentially new pending nonce and notify any subsystems of the new tx
	pool.beats[addr] = time.Now()
	pool.pendingState.SetNonce(addr, tx.Nonce()+1)

	return true
}

先更新了最後一次心跳時間,然後更新賬戶的nonce值。pool.txFeed.Send 傳送一個TxPreEvent事件,外部可以通過 SubscribeNewTxsEvent()函式訂閱該事件:

func (pool *TxPool) SubscribeNewTxsEvent(ch chan<-	core.NewTxsEvent) event.Subscription {
 return pool.scope.Track(pool.txFeed.Subscribe(ch))
}

我們只要全域性搜尋SubscribeNewTxsEvent這個函式,就知道有哪些元件訂閱了該事件,其中一個訂閱的地方在etrue/handler.go

func (pm *ProtocolManager) Start(maxPeers int) {
	pm.maxPeers = maxPeers

	// broadcast transactions 廣播交易
	pm.txsCh = make(chan core.NewTxsEvent, txChanSize)
	pm.txsSub = pm.txpool.SubscribeNewTxsEvent(pm.txsCh)
	go pm.txBroadcastLoop()

	//broadcast fruits  廣播水果
	pm.fruitsch = make(chan snailchain.NewFruitsEvent, fruitChanSize)
	pm.fruitsSub = pm.SnailPool.SubscribeNewFruitEvent(pm.fruitsch)
	go pm.fruitBroadcastLoop()
        ....

啟動了一個goroutine來接TxPreEvent事件, txBroadcastLoop()函式裡呼叫了BroadcastTxs()函式

func (pm *ProtocolManager) BroadcastTxs(txs types.Transactions) {
	var txset = make(map[*peer]types.Transactions)

	// Broadcast transactions to a batch of peers not knowing about it
	for _, tx := range txs {
		peers 
            
           

相關推薦

TruechainBeta交易流程解析

初鏈主網Beta版於新加坡時間2018年09月28日08:00正式上線,在此之前,07:56分PBFT委員會第一次共識出塊和TrueChain fPOW創世區塊被挖出 今天我們主要看看初鏈主網Beta版的交易部分,本文主要淺談原始碼,所以懂go是前提,我們先看下

C++絡數據包解析策略(升級)

bfd sum between protocol log class pro cli 分享 初版:http://www.cnblogs.com/wjshan0808/p/6580638.html 說明:在實現了對應的接口後該策略可以適合絕大多數的網絡數據包結構 首先,是

一個遊戲是如何被開發出來的:從立項到Beta,遊戲開發全流程解析

我在知乎回答“想要自己做一款遊戲,需要學習哪些知識”下面簡單列舉了四個能力,分別是:程式、設計、美術、音樂。但是礙於篇幅限制,我並

曹工說Redis原始碼(6)-- redis server 迴圈大體流程解析

文章導航 Redis原始碼系列的初衷,是幫助我們更好地理解Redis,更懂Redis,而怎麼才能懂,光看是不夠的,建議跟著下面的這一篇,把環境搭建起來,後續可以自己閱讀原始碼,或者跟著我這邊一起閱讀。由於我用c也是好幾年以前了,些許錯誤在所難免,希望讀者能不吝指出。 曹工說Redis原始碼(1)-- redi

分針——每日分享:HTML解析原理

html 原理 標準的web前端工程師需要知道 ◎瀏覽器(或者相應播放器)的渲染/重繪原理 這我得加把勁了。我還真的說的不是很清楚,我就G下,結果不是很多,找到了有一個,就記下來了。。。

幣勝虛擬貨幣交易平臺安裝說明

oid admin com not 後臺 根目錄 span 數據庫配置 域名 安裝教程: 服務器需要支持php 5.4 + MySQL + 偽靜態 重要說明:服務器如果不支持偽靜態是完全沒法用的。 1、將源碼包完整上傳至服務器空間,並解壓 2、修改數據庫配置文件 /

快速熟悉絡搭建的流程

網絡路由交換本筆記是本人在學習期間做一個網絡項目拓撲的簡單模擬,過程中將所遇到的所有問題都記錄在這裏,方便以後回顧。也希望能夠幫助他人解決一些相同的問題。--百家菜2017-6-16【實驗環境】華為模擬器eNSP、Windows系統【實驗目的】熟悉華為設備的配置、構建一個簡單的項目拓撲【實驗內容】依據給出的拓

BAT Androidproject師面試流程解析+還原最真實最完整的一線公司面試題

需要 綜合 沒有機會 渠道 考核 XML 升級 通訊 這也 尊重原創,轉載請寫明原文出處:http://blog.csdn.net/sk719887916/article/details/47040931 (skay) 求職和我

卡雙線路DNS解析分析

進行 dns解析 htm lib 子網 們的 1.10 位置 安裝 在企業網絡維護過程中我們經常會遇到這樣或那樣的奇怪問題,而很多問題需要有深厚的理論知識才能解決。而隨著網絡的飛速發展越來越多的中小企業開始嘗試通過多條線路來保證網絡的暢通,一方面雙網卡下的雙線接入可以保證我

手機電玩城app一款要多少錢?

網絡版電玩城app 移動街機電玩城成為了2016年後半年一個最熱門的遊戲創業項目。什麽是移動街機電玩城?開發定制(CALL陳生:185.2946.0296)移動街機電玩城是一個APP遊戲平臺,這個平臺裏面有很多款遊戲,比較有代表性的是金蟾捕魚,搖錢樹,森林舞會,水滸傳,鬥牛,彩金宏輝,單挑等,一平臺可以

純凈無捆綁裝機員U盤啟動PE Win10(UEFI+UD+自動安裝MSDN系統)

裝機員 pe啟動盤 簡介:裝機員U盤啟動盤制作工具讓您能夠完全獨立專業級的維護系統,更能夠拋開龐大的系統進行日常網絡辦公。工具簡單實用,操作方便快捷,兼容性超強,支持ISO制作、寫入U盤、光盤刻錄、自定義pe工具箱、自定義首頁等功能。並且帶有寫入保護,防病毒入侵等功能,只要帶上一個裝機員U盤PE就能縱

藥易通藥業供應鏈管理系統8.2.5.13連鎖100用戶+門店免狗

藥易通 《藥易通藥業供應鏈管理系統》結合企業經營管理制度,采用獨特的“傻瓜財務”理念開發,簡單而實用,能有效地幫助中小企業全面管理商品賬、資金賬、往來賬和收入賬等。使藥品經營的各物流過程與GSP管理充分結合在一起,對藥品在流通領域中

天力卓越精財生產管理系統v6.6.2.1(255用戶)專業

專業版 針對中小企業的特點,集進銷存財務管理一體化。幫助企業全面管理商品帳、資金帳、往來帳、費用帳,清晰地了解每一件商品、每一分資金、每一筆欠款、每一筆費用以及盈虧的來龍去脈,為企業提供良好的信息管理渠道,最大程度的保障企業業務處理的流暢和安全,促進企業的

藥易通藥業供應鏈管理系統8.2.2.3連鎖255用戶免狗

藥易通 《藥易通藥業供應鏈管理系統》結合企業經營管理制度,采用獨特的“傻瓜財務”理念開發,簡單而實用,能有效地幫助中小企業全面管理商品賬、資金賬、往來賬和收入賬等。使藥品經營的各物流過程與GSP管理充分結合在一起,對藥品在流通領域中

BETA 沖刺前準備

情況 改進 學習 緩沖區 準備 問題: 困難 log 知識 過去存在的問題: 各模塊整合交接困難。 對於需要用到的知識技術了解不足。 工作量分配還待改進。 計劃時未考慮到實際實施時會發生的變動。 對於界面交互方面的設計不足 我們已經做哪些調整/改進: 在下次沖

基於spec評論——王者榮耀交流協會Beta的PSP DAILY作品

spa eight 環境 技術分享 exc 王者榮耀 tps size com 一、運行環境   win10系統 二、運行程序及截圖 1、下載界面。 2、一直按“下一步”,安裝完後,桌面有該應用的圖標。 3、界面如圖。 4、添加任務。如圖。 5、點擊“導出exce

公客beta階段發布說明

cnblogs 缺陷 post 評價 說明 inf 郵箱 圖片 修復 項目 公客 公正客觀的課程評價網站 功能說明 評價的增刪改 對課程發表評價 限制評價次數(3次),刪除與增量修改評價 評價下的討論與點贊 在評價下添加討論,支持在討論中使用@與對方交

操蛋的EOS,蛋疼的“映射”

eos主網映射EOS眾籌將在北京時間2018年6月2日06:59:59結束。由於官方之前有明確要求用戶進行映射的說法,所以隨著EOS主網上線日期越來越近,EOS用戶盡早完成“映射”的心情也越來越迫切。但苦於EOS官方“映射”指導的繁瑣操作,眾多EOS用戶對“映射”倍感困難。更有甚者,知道“映射”這個事情,但”

區塊鏈數字貨幣虛擬幣交易平臺_法幣交易流程機制

區塊鏈 數字貨幣交易系統 虛擬幣交易平臺 法幣交易 隨著區塊鏈技術應用的持續火熱,國內各大互聯網公司相繼涉足區塊鏈應用開發領域。區塊鏈的火爆,也使人們加深了對數字貨幣的認識,本篇主要介紹常見的虛擬幣交易平臺中的法幣交易流程。1.用戶充幣操作註冊登錄平臺,進入充幣操作界面,填寫區塊鏈交易ID並選

zuul關Filter處理流程及異常處理

println 沒有 actor stat blog 一個地方 cli ram color 本文轉載自:https://blog.csdn.net/tianyaleixiaowu/article/details/77893822 上一篇介紹了java網關Zuul的簡單使用