日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

以太坊geth结构解析和源码分析

發布時間:2025/3/15 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 以太坊geth结构解析和源码分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文地址: http://blog.csdn.net/DDFFR/article/details/74389051

第一部分 看看geth客戶端的整體結構?
創建私鏈的時候已經指定所有的信息都放在private-geth目錄下,現在是已經有過挖礦的目錄。

當時我們把創世文件genesis.json放在該目錄下了、

root@i-5tthrr8u:/home/ubuntu/private-geth# ll total 16 drwxr-xr-x 3 root root 4096 Jul 2 17:02 ./ drwxr-xr-x 6 ubuntu ubuntu 4096 Jul 4 14:07 ../ drwx------ 5 root root 4096 Jul 2 17:41 data/ -rw-r--r-- 1 root root 529 Jul 2 16:29 genesis.json
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

進入真正的存放數據的目錄private-geth/data/00?
geth中保存的是區塊鏈的相關數據?
keystore中保存的是該鏈條中的用戶信息

root@i-5tthrr8u:/home/ubuntu/private-geth/data/00# ll total 20 drwx------ 4 root root 4096 Jul 2 17:23 ./ drwx------ 5 root root 4096 Jul 2 17:41 ../ drwxr-xr-x 5 root root 4096 Jul 2 17:02 geth/ -rw------- 1 root root 1391 Jul 2 17:58 history drwx------ 2 root root 4096 Jul 2 17:10 keystore/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

之前我們這個節點已經創建了兩個賬戶,現在我們可以看到keystore里面有兩個賬戶信息的文件

root@i-5tthrr8u:/home/ubuntu/private-geth/data/00/keystore# ll total 16 drwx------ 2 root root 4096 Jul 2 17:10 ./ drwx------ 4 root root 4096 Jul 2 17:23 ../ -rw------- 1 root root 491 Jul 2 17:02 UTC--2017-07-02T09-02-56.470592674Z--28b769b3b9109afd1e9e50a9312c5a3bfae8a699 -rw------- 1 root root 491 Jul 2 17:10 UTC--2017-07-02T09-10-28.087401309Z--b4e2e2514eae3684157bf34a0cee2c07c431cf92
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

每個賬戶都由一對鑰匙定義,一個私鑰和一個公鑰。 賬戶以地址為索引,地址由公鑰衍生而來,取公鑰的最后 20個字節。每對私鑰 /地址都編碼在一個鑰匙文件里。鑰匙文件是JSON文本文件,可以用任何文本編輯器打開和瀏覽。鑰匙文件的關鍵部分,賬戶私鑰,通常用你創建帳戶時設置的密碼進行加密。鑰匙文件的文件名格式為UTC。賬號列出時是按字母順序排列,但是由于時間戳格式,實際上它是按創建順序排列。如果把秘鑰丟了鑰匙文件可以在以太坊節點數據目錄的keystore子目錄下找到,接下來我們進入一個keystore目錄文件看看他的信息:

root@i-5tthrr8u:/home/ubuntu/private-geth/data/00/keystore# vim UTC--2017-07-02T09-02-56.470592674Z--28b769b3b9109afd1e9e50a9312c5a3bfae8a699 {"address":"28b769b3b9109afd1e9e50a9312c5a3bfae8a699", "crypto":{ "cipher":"aes-128-ctr", "ciphertext":"89ce1513b4b5a325735891b559c361ce696bb2c173a7a1b290549e79dad8f847", "cipherparams":{"iv":"982c86418fae2dd39e04d1e51528cffa"}, "kdf":"scrypt", "kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"4227384ea0e3d15af1bac190f7e01d392543d0a5ca1ec931c1d340f87845f771"}, "mac":"46cffc6e4f57fa27b69e53dc4ae43a03ce1b93f24c132aa4655f53ddf215f112"}, "id":"e516b9d4-2161-4648-b3db-fc2ef1c3739c", "version":3 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

警告:記住密碼并”備份鑰匙文件”。為了從賬號發送交易,包括發送以太幣,你必須同時有鑰匙文件和密碼。確保鑰匙文件有個備份并牢記密碼,盡可能安全地存儲它們。這里沒有逃亡路徑,如果鑰匙文件丟失或忘記密碼,就會丟失所有的以太幣。沒有密碼不可能進入賬號,也沒有忘記密碼選項。所以一定不要忘記密碼。

接下來進入geth可以看到chaindata,lightchaindata,nodes目錄

root@i-5tthrr8u:/home/ubuntu/private-geth/data/00/geth# ll total 24 drwxr-xr-x 5 root root 4096 Jul 2 17:02 ./ drwx------ 4 root root 4096 Jul 2 17:23 ../ drwxr-xr-x 2 root root 4096 Jul 4 14:12 chaindata/ drwxr-xr-x 2 root root 4096 Jul 2 17:02 lightchaindata/ -rw-r--r-- 1 root root 0 Jul 2 17:02 LOCK -rw------- 1 root root 64 Jul 2 17:02 nodekey drwxr-xr-x 2 root root 4096 Jul 4 15:55 nodes/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

進入nodes(我們這條私鏈有三個節點,所以這里有三個ldb文件)

root@i-5tthrr8u:/home/ubuntu/private-geth/data/00/geth/nodes# ll total 5316 drwxr-xr-x 2 root root 4096 Jul 4 15:55 ./ drwxr-xr-x 5 root root 4096 Jul 2 17:02 ../ -rw-r--r-- 1 root root 405250 Jul 4 15:57 000033.log -rw-r--r-- 1 root root 2132979 Jul 4 15:55 000035.ldb -rw-r--r-- 1 root root 2131238 Jul 4 15:55 000036.ldb -rw-r--r-- 1 root root 739354 Jul 4 15:55 000037.ldb -rw-r--r-- 1 root root 16 Jul 4 14:12 CURRENT -rw-r--r-- 1 root root 0 Jul 2 17:02 LOCK -rw-r--r-- 1 root root 8187 Jul 4 15:55 LOG -rw-r--r-- 1 root root 4557 Jul 4 15:55 MANIFEST-000013
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

進入chaindata,區塊鏈最后的本地存儲都是以ldb文件的形勢(但這里是不是應該每個區塊一個ldb文件呢?)

root@i-5tthrr8u:/home/ubuntu/private-geth/data/00/geth/chaindata# ll total 52 drwxr-xr-x 2 root root 4096 Jul 5 09:51 ./ drwxr-xr-x 5 root root 4096 Jul 2 17:02 ../ -rw-r--r-- 1 root root 5288 Jul 2 17:56 000008.ldb -rw-r--r-- 1 root root 11681 Jul 4 14:12 000009.ldb -rw-r--r-- 1 root root 8921 Jul 4 14:13 000010.log -rw-r--r-- 1 root root 16 Jul 4 14:12 CURRENT -rw-r--r-- 1 root root 0 Jul 2 17:02 LOCK -rw-r--r-- 1 root root 2807 Jul 4 14:12 LOG -rw-r--r-- 1 root root 346 Jul 4 14:12 MANIFEST-000011
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

進入Lightchaindata

root@i-5tthrr8u:/home/ubuntu/private-geth/data/00/geth/lightchaindata# ll total 24 drwxr-xr-x 2 root root 4096 Jul 2 17:02 ./ drwxr-xr-x 5 root root 4096 Jul 2 17:02 ../ -rw-r--r-- 1 root root 1237 Jul 2 17:02 000001.log -rw-r--r-- 1 root root 16 Jul 2 17:02 CURRENT -rw-r--r-- 1 root root 0 Jul 2 17:02 LOCK -rw-r--r-- 1 root root 358 Jul 2 17:02 LOG -rw-r--r-- 1 root root 54 Jul 2 17:02 MANIFEST-000000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

第二部分 看看源碼的結構

1 Core/types/block.go?
首先看到的是一個區塊的結構

// Block represents an entire block in the Ethereum blockchain.type Block struct {header *Headeruncles []*Headertransactions Transactions// caches hashsize字段是cache之用,避免多次 hash/sign導致性能損失hash atomic.Valuesize atomic.Value// Td is used by package core to store the total difficulty// of the chain up to and including the block.挖礦難度td *big.Int// These fields are used by package eth to track// inter-peer block relay.ReceivedAt time.TimeReceivedFrom interface{} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
這是一個區塊體的結構,區塊體是動態的存儲數據的,主要包含了交易列表和uncle列表 // Body is a simple (mutable, non-safe) data container for storing and moving // a block's data contents (transactions and uncles) together. type Body struct {Transactions []*TransactionUncles []*Header }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
區塊頭的結構體,里面的參數我們都很熟悉就不解釋了 // Header represents a block header in the Ethereum blockchain. type Header struct { ParentHash common.Hash `json:"parentHash" gencodec:"required"` UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` Coinbase common.Address `json:"miner" gencodec:"required"` Root common.Hash `json:"stateRoot" gencodec:"required"` TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` Difficulty *big.Int `json:"difficulty" gencodec:"required"` Number *big.Int `json:"number" gencodec:"required"` GasLimit *big.Int `json:"gasLimit" gencodec:"required"` GasUsed *big.Int `json:"gasUsed" gencodec:"required"` Time *big.Int `json:"timestamp" gencodec:"required"` Extra []byte `json:"extraData" gencodec:"required"` MixDigest common.Hash `json:"mixHash" gencodec:"required"` Nonce BlockNonce `json:"nonce" gencodec:"required"` }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2 這是一個交易的結構體?
Core/types/transaction.go

1ContractTransaction的區別在于:Recipient == nil ; 2. Transaction能以RLP算法進行Encode和Decode; 3. hash/size/from字段是cache之用,避免多次 hash/sign導致性能損失; type Transaction struct {data txdata// cacheshash atomic.Valuesize atomic.Valuefrom atomic.Value }type txdata struct {AccountNonce uint64 `json:"nonce" gencodec:"required"`Price *big.Int `json:"gasPrice" gencodec:"required"`GasLimit *big.Int `json:"gas" gencodec:"required"`Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creationAmount *big.Int `json:"value" gencodec:"required"`Payload []byte `json:"input" gencodec:"required"`// Signature values 簽名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:"-"` }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

3 Receiptroot我們剛剛在區塊頭有看到,那他具體包含的是什么呢?它是一個交易的結果,主要包括了poststate,交易所花費的gas,bloom和logs

// Receipt represents the results of a transaction. type Receipt struct {// Consensus fields PostState []byte `json:"root" gencodec:"required"` CumulativeGasUsed *big.Int `json:"cumulativeGasUsed" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"` // Implementation fields (don't reorder!) TxHash common.Hash `json:"transactionHash" gencodec:"required"` ContractAddress common.Address `json:"contractAddress"` GasUsed *big.Int `json:"gasUsed" gencodec:"required"` }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

4 一個個交易被打包到區塊上面,那區塊又是怎么變成去快鏈的呢??
Core/blockchain.go

// BlockChain represents the canonical chain given a database with a genesis block. The Blockchain manages chain imports, reverts, chain reorganisations. // Importing blocks in to the block chain happens according to the set of rules defined by the two stage Validator. (需要兩個階段的驗證)Processing of blocks is done using the Processor which processes the included transaction.(第一階段交易的驗證) The validation of the state is done in the second part of the Validator.(第二階段state的驗證) Failing results in aborting of the import. // The BlockChain also helps in returning blocks from **any** chain included in the database as well as blocks that represents the canonical chain. It's important to note that GetBlock can return any block and does not need to be included in the canonical one where as GetBlockByNumber always represents the canonical chain.type BlockChain struct {config *params.ChainConfig // chain & network configurationhc *HeaderChainchainDb **ethdb**.Database 本地數據庫eventMux *event.TypeMuxgenesisBlock *types.Blockmu sync.RWMutex // global mutex for locking chain operationschainmu sync.RWMutex // blockchain insertion lockprocmu sync.RWMutex // block processor lockcheckpoint int // checkpoint counts towards the new checkpointcurrentBlock *types.Block // Current head of the block chaincurrentFastBlock *types.Block // Current head of the fast-sync chain (may be above the block chain!)stateCache *state.StateDB // State database to reuse between imports (contains state cache)bodyCache *lru.Cache // Cache for the most recent block bodiesbodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded formatblockCache *lru.Cache // Cache for the most recent entire blocksfutureBlocks *lru.Cache // future blocks are blocks added for later processingquit chan struct{} // blockchain quit channelrunning int32 // running must be called atomically// procInterrupt must be atomically calledprocInterrupt int32 // interrupt signaler for block processingwg sync.WaitGroup // chain processing wait group for shutting downengine consensus.Engineprocessor Processor // block processor interfacevalidator Validator // block and state validator interfacevmConfig vm.ConfigbadBlocks *lru.Cache // Bad block cache }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

注意:1. BlockChain無結構化查詢需求,僅Hash查詢, Key/Value數據庫最方便; 2. 低層用LevelDB存儲,性能好

5 stateDB用來存儲世界狀態?
Core/state/statedb.go

// StateDBs within the ethereum protocol are used to store anything // within the merkle trie. StateDBs take care of caching and storing // nested states. It's the general query interface to retrieve: // * Contracts // * Accounts type StateDB struct {db ethdb.Database //本地數據庫trie *trie.SecureTriepastTries []*trie.SecureTriecodeSizeCache *lru.Cache// This map holds 'live' objects, which will get modified while processing a state transition.stateObjects map[common.Address]*stateObjectstateObjectsDirty map[common.Address]struct{}stateObjectsDestructed map[common.Address]struct{}// The refund counter, also used by state transitioning.refund *big.Intthash, bhash common.HashtxIndex intlogs map[common.Hash][]*types.LoglogSize uintpreimages map[common.Hash][]byte// Journal of state modifications. This is the backbone of// Snapshot and RevertToSnapshot.journal journalvalidRevisions []revisionnextRevisionId intlock sync.Mutex }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

注意:1. StateDB完整記錄Transaction的執行情況; 2. StateDB的重點是StateObjects; 3. StateDB中的 stateObjects,Account的Address為 key,記錄其Balance、nonce、code、codeHash ,以及tire中的 {string:Hash}等信息;

那我們接下來看看stateObject結構體?
Core/state/state_object.go

// stateObject represents an Ethereum account which is being modified. // // The usage pattern is as follows: // First you need to obtain a state object. // Account values can be accessed and modified through the object. // Finally, call CommitTrie to write the modified storage trie into a database. type stateObject struct {address common.Address // Ethereum address of this accountdata Accountdb *StateDB// DB error.// State objects are used by the consensus core and VM which are// unable to deal with database-level errors. Any error that occurs// during a database read is memoized here and will eventually be returned// by StateDB.Commit.dbErr error// Write caches.trie *trie.SecureTrie // storage trie, which becomes non-nil on first accesscode Code // contract bytecode, which gets set when code is loadedcachedStorage Storage // Storage entry cache to avoid duplicate readsdirtyStorage Storage // Storage entries that need to be flushed to disk// Cache flags.// When an object is marked suicided it will be delete from the trie// during the "update" phase of the state transition.dirtyCode bool // true if the code was updatedsuicided booltouched booldeleted boolonDirty func(addr common.Address) // Callback method to mark a state object newly dirty }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

再看看state的一個接口,可以查看賬戶的余額,nonce,代碼和storage

// ChainStateReader wraps access to the state trie of the canonical blockchain. Note that implementations of the interface may be unable to return state values for old blocks. // In many cases, using CallContract can be preferable to reading raw contract storage.type ChainStateReader interface {BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error)StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error)CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

所有的結構湊明朗了,那具體的驗證過程是怎么樣的呢?
Core/state_processor.go?
Core/state_transition.go?
Core/block_validator.go

StateProcessor 1. 調用StateTransition,驗證(執行)Transaction; 2. 計算Gas、Recipt、Uncle Reward

// StateProcessor is a basic Processor, which takes care of transitioning // state from one point to another. // // StateProcessor implements Processor. type StateProcessor struct {config *params.ChainConfig // Chain configuration optionsbc *BlockChain // Canonical block chainengine consensus.Engine // Consensus engine used for block rewards }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

StateTransition?
1. 驗證(執行)Transaction;?
3. 扣除transaction.data.payload計算數據所需要消耗的gas;?
4. 在vm中執行code(生成contract or 執行contract);vm執 行過程中,其gas會被自動消耗。如果gas不足,vm會自 選退出;?
5. 將多余的gas退回到sender.balance中;?
6. 將消耗的gas換成balance加到當前env.Coinbase()中;

/* The State Transitioning ModelA state transition is a change made when a transaction is applied to the current world state The state transitioning model does all all the necessary work to work out a valid new state root.1) Nonce handling 2) Pre pay gas 3) Create a new state object if the recipient is \0*32 4) Value transfer == If contract creation == 4a) Attempt to run transaction data 4b) If valid, use result as code for the new state object == end == 5) Run Script section 6) Derive new state root */ type StateTransition struct {gp *GasPool msg Message gas uint64 gasPrice *big.Int initialGas *big.Int value *big.Int data []byte state vm.StateDB evm *vm.EVM }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

BlockValidator?
1. 驗證UsedGas?
2. 驗證Bloom?
3. 驗證receiptSha?
4. 驗證stateDB.IntermediateRoot

// BlockValidator is responsible for validating block headers, uncles and // processed state. // // BlockValidator implements Validator. type BlockValidator struct {config *params.ChainConfig // Chain configuration optionsbc *BlockChain // Canonical block chainengine consensus.Engine // Consensus engine used for validating }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可以注意到剛才的state和block都是寫進db數據庫的,那我們看一下leveldb數據庫結構

type LDBDatabase struct {fn string // filename for reportingdb *leveldb.DB // LevelDB instancegetTimer gometrics.Timer // Timer for measuring the database get request counts and latenciesputTimer gometrics.Timer // Timer for measuring the database put request counts and latenciesdelTimer gometrics.Timer // Timer for measuring the database delete request counts and latenciesmissMeter gometrics.Meter // Meter for measuring the missed database get requestsreadMeter gometrics.Meter // Meter for measuring the database get request data usagewriteMeter gometrics.Meter // Meter for measuring the database put request data usagecompTimeMeter gometrics.Meter // Meter for measuring the total time spent in database compactioncompReadMeter gometrics.Meter // Meter for measuring the data read during compactioncompWriteMeter gometrics.Meter // Meter for measuring the data written during compactionquitLock sync.Mutex // Mutex protecting the quit channel accessquitChan chan chan error // Quit channel to stop the metrics collection before closing the databaselog log.Logger // Contextual logger tracking the database path }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
type Database interface {Put(key []byte, value []byte) errorGet(key []byte) ([]byte, error)Delete(key []byte) errorClose()NewBatch() Batch }??????
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

總結

以上是生活随笔為你收集整理的以太坊geth结构解析和源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。