[易学易懂系列|rustlang语言|零基础|快速入门|(27)|实战4:从零实现BTC区块链]

项目实战

实战4:从零实现BTC区块链

我们今天来开发我们的BTC区块链系统。

简单来说,从数据结构的角度上来说,区块链,就是区块组成的链。

以下就是BTC区块链典型的结构:

那最小单元就是区块:block。

这个block包含两部分:区块头,区块体。

我们先忽略Merkle树,先简化所有数据结构,只保留最基本的数据结构。

那区块头,就包含:时间截;前一个区块地址

区块体,就包含交易数据,我们用一个vector来存储。

代码如下 :

///交易结构体
#[derive(Clone, Hash, Serialize, Deserialize, Debug)]
pub struct Transaction {
    sender: String,    //发送者
    recipient: String, //接收者
    amount: i64,       //交易数量
}
/// 区块结构体
#[derive(Clone, Hash, Serialize, Deserialize, Debug)]
pub struct Block {
    pub index: u64,                     //区块高度
    timestamp: DateTime<Utc>,           //时间截
    pub transactions: Vec<Transaction>, //交易
    pub proof: u64,                     //证明
    pub previous_hash: String,          //上一个区块哈希地址
}
//区块链结构体
#[derive(Default)]
pub struct Blockchain {
    pub chain: Vec<Block>,                  //区块链帐本
    current_transactions: Vec<Transaction>, //交易集合
    pub nodes: HashSet<String>,             //节点集合
}

现在我们创建了一个基本的区块数据结构,现在我们来让矿工来创建区块吧。

怎么让不同的矿工,积极地创建区块呢?

我们引入一个机制叫:POW共识机制。

什么叫POW?简单来说,就是大家根据给定的一个数值proof,进行hash计算,谁最先算出来的结果值符合某个条件,就拥有创建新的区块,并把这个区块连接到原来的区块链上的权力。

比如,困难程度为5,那有个矿工用proof数据进行SHA哈希计算,出如下结果:

0x0000010000000000000000000000000000000000000000000000000000000000

这个结果,前面的0(除了0x外)是5个,则这就是结果值。

如果,没有计算出上面的结果值,矿工将proof自增1,再进行SHA哈希计算,直到计算出这个符合条件的结果值为止。

而那个给定的数据值proof,也要放在区块头,这个值在每次创建新区块的时候由矿工产生并写入区块头。

当然,如果 两个节点都算出结果并加入了新区块,这时,会产生链的分叉,这时如何决定冲突呢?

我们用最长链原则,即给定周期内,哪个节点拥有的链最长,就用哪个。

所以我们的共识机制是:POW+最长链原则

这个共识机制核心 代码如下:

impl Blockchain {
    //创建创世区块
    pub fn new() -> Blockchain {
        let mut blockchain = Blockchain {
            chain: vec![],
            current_transactions: vec![],
            nodes: HashSet::new(),
        };
        blockchain.new_block(100, Some("1"));
        blockchain
    }
    /// Create a new Block in the Blockchain
    ///
    /// :param proof: The proof given by the Proof of Work algorithm
    /// :param previous_hash: (Optional) hash of previous Block
    /// :return: New Bloc
    /// 创建新区块
    pub fn new_block(&mut self, proof: u64, previous_hash: Option<&str>) -> Block {
        let block = Block {
            index: (self.chain.len() + 1) as u64,
            timestamp: Utc::now(),
            transactions: self.current_transactions.drain(0..).collect(),
            proof,
            previous_hash: previous_hash.unwrap_or("0").to_string(),
        };

        self.chain.push(block.clone());
        block
    }
    /// Creates a new transaction to go into the next mined Block
    ///
    /// :param sender: Address of the ??ender
    /// :param recipient: Address of the recipient
    /// :param amount: Amount
    /// :return: The index of the Block that will hold this transaction
    /// 发起一个新交易,将写入下一个区块
    pub fn new_transaction(&mut self, sender: &str, recipient: &str, amount: i64) -> u64 {
        self.current_transactions.push(Transaction {
            sender: sender.to_string(),
            recipient: recipient.to_string(),
            amount,
        });
        self.last_block().unwrap().index + 1
    }
    /// Simple Proof of Work Algorithm:
    /// - Find a number p' such that hash(pp') contains 4 leading zeroes,
    ///   where p is the previous proof, and p' is the new proof
    /// POW工作量证明共识机制算法
    pub fn proof_of_work(last_block: &Block) -> u64 {
        let mut proof = 0;
        let last_proof = last_block.proof;
        let last_hash = &last_block.previous_hash;
        while !Self::valid_proof(last_proof, proof, last_hash) {
            proof += 1;
        }
        proof
    }
    /// Validates the Proof: Does hash(last_proof, proof, last_hash) containt 4 leading zeroes
    //验证工作证明数字
    fn valid_proof(last_proof: u64, proof: u64, last_hash: &String) -> bool {
        let guess = format!("{}{}{}", last_proof, proof, last_hash);
        let guess_hash = hex_digest(Algorithm::SHA256, guess.as_bytes());
        guess_hash.ends_with("00000") //困难度为5
    }

    /// Creates a SHA-256 hash of a Block
    ///
    /// :param block: Block
    /// :return hash for the block
    /// 创建一个区块 的哈希值,基SHA-256算法
    pub fn hash(block: &Block) -> String {
        let serialized = serde_json::to_string(&block).unwrap();
        hex_digest(Algorithm::SHA256, serialized.as_bytes())
    }
    /// Returns the last Block in the chain
    /// 返回最后一个区块
    pub fn last_block(&self) -> Option<&Block> {
        self.chain.last()
    }

    /// Add a new node to the list of nodes
    ///
    /// :param address: Address of the node. Eg. 'http://192.168.0.5:5000'
    ///
    /// 节点注册,即新节点加入区块链网络,注册地址参数为节点服务器地址,如:'http://192.168.0.5:5000‘
    pub fn register_node(&mut self, address: &str) {
        let parsed_url = urlparse(address);
        self.nodes.insert(parsed_url.netloc);
    }

    /// Determine if a given blockchain is valid
    /// 链的验证
    fn valid_chain(&self, chain: &[Block]) -> bool {
        let mut last_block = &chain[0];
        let mut current_index: usize = 1;
        while current_index < chain.len() {
            let block = &chain[current_index];
            println!("{:?}", last_block);
            println!("{:?}", block);
            println!("-----------");
            if block.previous_hash != Blockchain::hash(last_block) {
                return false;
            }
            if !Blockchain::valid_proof(last_block.proof, block.proof, &last_block.previous_hash) {
                return false;
            }

            last_block = block;
            current_index += 1;
        }
        true
    }

    /// This is our Consensus Algorithm, it resolves conflicts
    /// by replacing our chain with the longest one in the network.
    ///
    /// :return True if our chain was replaced and false otherwise
    /// 解决冲突的机制,即共识机制,最长链原则处理逻辑,即共识机制为(POw+最长链原则)
    pub fn resolve_conflicts(&mut self) -> bool {
        let mut max_length = self.chain.len();
        let mut new_chain: Option<Vec<Block>> = None;

        // Grab and verify the chains from all the nodes in our network
        for node in &self.nodes {
            let mut response = reqwest::get(&format!("http://{}/chain", node)).unwrap();
            if response.status().is_success() {
                let node_chain: Chain = response.json().unwrap();
                if node_chain.length > max_length && self.valid_chain(&node_chain.chain) {
                    max_length = node_chain.length;
                    new_chain = Some(node_chain.chain);
                }
            }
        }
        // Replace our chain if we discovered a new, valid chain longer than ours
        match new_chain {
            Some(x) => {
                self.chain = x;
                true
            }
            None => false,
        }
    }
}

以上代码,我们放在当前工程目录下的src/blockchain.rs,完整代码如下 :

use crate::api::Chain;
use chrono::{DateTime, Utc};
use crypto_hash::{hex_digest, Algorithm};
use reqwest;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use urlparse::urlparse;
///交易结构体
#[derive(Clone, Hash, Serialize, Deserialize, Debug)]
pub struct Transaction {
    sender: String,    //发送者
    recipient: String, //接收者
    amount: i64,       //交易数量
}
/// 区块结构体
#[derive(Clone, Hash, Serialize, Deserialize, Debug)]
pub struct Block {
    pub index: u64,                     //区块高度
    timestamp: DateTime<Utc>,           //时间截
    pub transactions: Vec<Transaction>, //交易
    pub proof: u64,                     //证明
    pub previous_hash: String,          //上一个区块哈希地址
}
//区块链结构体
#[derive(Default)]
pub struct Blockchain {
    pub chain: Vec<Block>,                  //区块链帐本
    current_transactions: Vec<Transaction>, //交易集合
    pub nodes: HashSet<String>,             //节点集合
}

impl Blockchain {
    //创建创世区块
    pub fn new() -> Blockchain {
        let mut blockchain = Blockchain {
            chain: vec![],
            current_transactions: vec![],
            nodes: HashSet::new(),
        };
        blockchain.new_block(100, Some("1"));
        blockchain
    }
    /// Create a new Block in the Blockchain
    ///
    /// :param proof: The proof given by the Proof of Work algorithm
    /// :param previous_hash: (Optional) hash of previous Block
    /// :return: New Bloc
    /// 创建新区块
    pub fn new_block(&mut self, proof: u64, previous_hash: Option<&str>) -> Block {
        let block = Block {
            index: (self.chain.len() + 1) as u64,
            timestamp: Utc::now(),
            transactions: self.current_transactions.drain(0..).collect(),
            proof,
            previous_hash: previous_hash.unwrap_or("0").to_string(),
        };

        self.chain.push(block.clone());
        block
    }
    /// Creates a new transaction to go into the next mined Block
    ///
    /// :param sender: Address of the ??ender
    /// :param recipient: Address of the recipient
    /// :param amount: Amount
    /// :return: The index of the Block that will hold this transaction
    /// 发起一个新交易,将写入下一个区块
    pub fn new_transaction(&mut self, sender: &str, recipient: &str, amount: i64) -> u64 {
        self.current_transactions.push(Transaction {
            sender: sender.to_string(),
            recipient: recipient.to_string(),
            amount,
        });
        self.last_block().unwrap().index + 1
    }
    /// Simple Proof of Work Algorithm:
    /// - Find a number p' such that hash(pp') contains 4 leading zeroes,
    ///   where p is the previous proof, and p' is the new proof
    /// POW工作量证明共识机制算法
    pub fn proof_of_work(last_block: &Block) -> u64 {
        let mut proof = 0;
        let last_proof = last_block.proof;
        let last_hash = &last_block.previous_hash;
        while !Self::valid_proof(last_proof, proof, last_hash) {
            proof += 1;
        }
        proof
    }
    /// Validates the Proof: Does hash(last_proof, proof, last_hash) containt 4 leading zeroes
    //验证工作证明数字
    fn valid_proof(last_proof: u64, proof: u64, last_hash: &String) -> bool {
        let guess = format!("{}{}{}", last_proof, proof, last_hash);
        let guess_hash = hex_digest(Algorithm::SHA256, guess.as_bytes());
        guess_hash.ends_with("00000") //困难度为5
    }

    /// Creates a SHA-256 hash of a Block
    ///
    /// :param block: Block
    /// :return hash for the block
    /// 创建一个区块 的哈希值,基SHA-256算法
    pub fn hash(block: &Block) -> String {
        let serialized = serde_json::to_string(&block).unwrap();
        hex_digest(Algorithm::SHA256, serialized.as_bytes())
    }
    /// Returns the last Block in the chain
    /// 返回最后一个区块
    pub fn last_block(&self) -> Option<&Block> {
        self.chain.last()
    }

    /// Add a new node to the list of nodes
    ///
    /// :param address: Address of the node. Eg. 'http://192.168.0.5:5000'
    ///
    /// 节点注册,即新节点加入区块链网络,注册地址参数为节点服务器地址,如:'http://192.168.0.5:5000‘
    pub fn register_node(&mut self, address: &str) {
        let parsed_url = urlparse(address);
        self.nodes.insert(parsed_url.netloc);
    }

    /// Determine if a given blockchain is valid
    /// 链的验证
    fn valid_chain(&self, chain: &[Block]) -> bool {
        let mut last_block = &chain[0];
        let mut current_index: usize = 1;
        while current_index < chain.len() {
            let block = &chain[current_index];
            println!("{:?}", last_block);
            println!("{:?}", block);
            println!("-----------");
            if block.previous_hash != Blockchain::hash(last_block) {
                return false;
            }
            if !Blockchain::valid_proof(last_block.proof, block.proof, &last_block.previous_hash) {
                return false;
            }

            last_block = block;
            current_index += 1;
        }
        true
    }

    /// This is our Consensus Algorithm, it resolves conflicts
    /// by replacing our chain with the longest one in the network.
    ///
    /// :return True if our chain was replaced and false otherwise
    /// 最长链原则处理逻辑,即共识机制为(POw+最长链原则)
    pub fn resolve_conflicts(&mut self) -> bool {
        let mut max_length = self.chain.len();
        let mut new_chain: Option<Vec<Block>> = None;

        // Grab and verify the chains from all the nodes in our network
        for node in &self.nodes {
            let mut response = reqwest::get(&format!("http://{}/chain", node)).unwrap();
            if response.status().is_success() {
                let node_chain: Chain = response.json().unwrap();
                if node_chain.length > max_length && self.valid_chain(&node_chain.chain) {
                    max_length = node_chain.length;
                    new_chain = Some(node_chain.chain);
                }
            }
        }
        // Replace our chain if we discovered a new, valid chain longer than ours
        match new_chain {
            Some(x) => {
                self.chain = x;
                true
            }
            None => false,
        }
    }
}

现在 我们向外界提供一些可用的API。

我们新建一个文件:src/api.rs,代码如下 :

use crate::blockchain::{Block, Blockchain, Transaction};

use actix_web::{web, HttpRequest, HttpResponse};
use serde::{Deserialize, Serialize};
use std::sync::Mutex;
///返回消息体
#[derive(Serialize, Deserialize)]
pub struct MessageResponse {
    message: String,
}
//交易请求信息
#[derive(Serialize, Deserialize)]
pub struct TransactionRequest {
    sender: String,
    recipient: String,
    amount: i64,
}
///挖矿响应消息
#[derive(Serialize)]
pub struct MiningRespose {
    message: String,
    index: u64,
    transactions: Vec<Transaction>,
    proof: u64,
    previous_hash: String,
}
///链结构体,代表现在网络上的最长链
#[derive(Serialize, Deserialize)]
pub struct Chain {
    pub chain: Vec<Block>,
    pub length: usize,
}
///节点注册请求信息
#[derive(Deserialize)]
pub struct RegisterRequest {
    nodes: Vec<String>,
}
///节点注册响应信息
#[derive(Serialize)]
pub struct RegisterResponse {
    message: String,
    total_nodes: Vec<String>,
}
//解决冲突响应信息
#[derive(Serialize)]
pub struct ResolveResponse {
    message: String,
    chain: Vec<Block>,
}
///发起新交易
pub fn new_transaction(
    state: web::Data<Mutex<Blockchain>>,
    req: web::Json<TransactionRequest>,
) -> HttpResponse {
    let sender = req.sender.to_owned();
    let recipient = req.recipient.to_owned();
    let index = state
        .lock()
        .unwrap()
        .new_transaction(&sender, &recipient, req.amount);
    HttpResponse::Created().json(MessageResponse {
        message: format! {"Transaction will be added to Block {}", index},
    })
}
///矿工挖矿
pub fn mine(
    node_identifier: web::Data<String>,
    state: web::Data<Mutex<Blockchain>>,
    _req: HttpRequest,
) -> HttpResponse {
    let (proof, previous_hash) = {
        let blockchain = state.lock().unwrap();
        let last_block = blockchain.last_block().unwrap();
        let proof = Blockchain::proof_of_work(&last_block);
        let previous_hash = Blockchain::hash(last_block);
        (proof, previous_hash)
    };
    let mut blockchain = state.lock().unwrap();
    blockchain.new_transaction("0", &*node_identifier, 1);
    let block = blockchain.new_block(proof, Some(&previous_hash));
    HttpResponse::Ok().json(MiningRespose {
        message: "New Block Forged".to_string(),
        index: block.index,
        transactions: block.transactions,
        proof,
        previous_hash,
    })
}
///当前最新链的信息
pub fn chain(state: web::Data<Mutex<Blockchain>>, _req: HttpRequest) -> HttpResponse {
    let length = state.lock().unwrap().chain.len();
    HttpResponse::Ok().json(Chain {
        chain: state.lock().unwrap().chain.clone(),
        length,
    })
}
///节点注册
pub fn register_node(
    state: web::Data<Mutex<Blockchain>>,
    req: web::Json<RegisterRequest>,
) -> HttpResponse {
    if req.nodes.is_empty() {
        return HttpResponse::BadRequest().json(MessageResponse {
            message: "Error: Please supply a valid list of nodes".to_string(),
        });
    }
    let mut blockchain = state.lock().unwrap();
    for node in req.nodes.iter() {
        blockchain.register_node(node)
    }
    HttpResponse::Created().json(RegisterResponse {
        message: "New nodes have been added".to_string(),
        total_nodes: blockchain.nodes.iter().cloned().collect(),
    })
}
///跟网络上其他节点达成共识,即解决冲突
pub fn resolve_nodes(state: web::Data<Mutex<Blockchain>>, _req: HttpRequest) -> HttpResponse {
    let mut blockchain = state.lock().unwrap();
    let replaced = blockchain.resolve_conflicts();
    let message = if replaced {
        "Our chain was replaced"
    } else {
        "Our chain is authorative"
    };
    HttpResponse::Ok().json(ResolveResponse {
        message: message.to_string(),
        chain: blockchain.chain.clone(),
    })
}

当然,我们要用到一些好用的库,在我们的Cargo.toml文件,我们加入依赖,完整代码如下:

[dependencies]
chrono = { version = "0.4.6", features = ["serde"] }
crypto-hash = "0.3.3"
serde = { version = "1.0.90", features = ["derive"] }
serde_json = "1.0"
actix-web = "1.0"
uuid = { version = "0.7", features = ["v4"] }
urlparse = "0.7.3"
reqwest = "=0.9.17"

最后我们的主程序 src/main.rs如下:

pub mod api;
pub mod blockchain;

use actix_web::{web, App, HttpServer};
use std::env;
use std::sync::Mutex;
use uuid::Uuid;

fn main() {
    let args: Vec<String> = env::args().collect();
    let port = match args.as_slice() {
        [_, key, value] => {
            if key == "--p" {
                value
            } else {
                panic!("Illegal arguments passed to the program.");
            }
        }
        _ => "5000",
    };
    // TODO: make chain shared across threads
    let sharedchain = web::Data::new(Mutex::new(blockchain::Blockchain::new()));
    let node_identifier = web::Data::new(Uuid::new_v4().to_simple().to_string());

    HttpServer::new(move || {
        App::new()
            .register_data(sharedchain.clone())
            .register_data(node_identifier.clone())
            .data(web::JsonConfig::default().limit(4096))
            .service(web::resource("/mine").route(web::get().to(api::mine)))
            .service(web::resource("/transactions/new").route(web::post().to(api::new_transaction)))
            .service(web::resource("/chain").route(web::get().to(api::chain)))
            .service(web::resource("/nodes/register").route(web::post().to(api::register_node)))
            .service(web::resource("/nodes/resolve").route(web::get().to(api::resolve_nodes)))
    })
    .bind(format!("127.0.0.1:{}", port))
    .unwrap()
    .run();
}

然后我们可以用以下命令来调用 :

挖矿:

curl http://localhost:5000/mine

创建新交易:

curl -H "Content-Type: application/json" --request POST --data '{"sender":"e79fcabd1d70433191701d17c4d13112", "recipient":"some-other-address", "amount":5}' http://localhost:5000/transactions/new

查看整条链信息:

curl http://localhost:5000/chain

注册节点:

curl -H "Content-Type: application/json" --request POST --data '{"nodes":["http://localhost:5001"]}' http://localhost:5000/nodes/register

与其他节点达成共识(共识机制):

curl http://localhost:5000/nodes/resolve

以上,希望对你有用。

如果遇到什么问题,欢迎加入:rust新手群,在这里我可以提供一些简单的帮助,加微信:360369487,注明:博客园+rust

https://asymmetric.github.io/2018/02/11/blockchain-rust/

https://jeiwan.net/posts/building-blockchain-in-go-part-1/

https://freestartupkits.com/articles/technology/cryptocurrency-news-and-tips/ultimate-rust-blockchain-tutorial/

https://hackernoon.com/learn-blockchains-by-building-one-117428612f46

https://medium.com/@vanflymen/learn-blockchains-by-building-one-117428612f46?

https://github.com/Koura/blockchain-example

原文地址:https://www.cnblogs.com/gyc567/p/12079503.html

时间: 2024-11-29 07:30:47

[易学易懂系列|rustlang语言|零基础|快速入门|(27)|实战4:从零实现BTC区块链]的相关文章

[易学易懂系列|rustlang语言|零基础|快速入门|(14)]

有意思的基础知识 Impls & Traits实现与特征 我之前说到的struct结构体,其实就类似于面向对象语言中的类class. 但这个struct,并没有定义方法或函数. 那要怎么办呢? Rust用关键词impls(实现)来定义struct和enum的方法或函数. 而trait(特征),类似于面向对象语言中的接口interface. 特征,是用来定义要实现的方法,一个类型可以有多个特征.特征可以有默认实现函数,这个默认函数可以在运行时重写. 我们来看看代码: 1.没有trait特征的imp

[易学易懂系列|rustlang语言|零基础|快速入门|(12)]

有意思的基础知识 Enums 今天我们来讲讲枚举. 在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数.这两种类型经常(但不总是)重叠. 是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY.MONDAY.TUESDAY.WEDNESDAY.THURSDAY.FRIDAY.SATURDAY就是一个枚举. 如下定义: enum Day { Sunday, Monday, Tuesday, Wednesday, Thu

[易学易懂系列|rustlang语言|零基础|快速入门|(13)]

有意思的基础知识 Generics泛型 我们今天来看看泛型. 什么是泛型? 我们来看看这样的情景: 我们要写一个函数,这个函数可以处理不同类型的值,但这个值的类型,在运行时,才由调用者确定. 我们不可能在函数方法中,一开始就写死. 那要什么办? 用泛型. 比如:用x : T替换x : u8 我们来看看例子: 泛型函数: fn takes_anything<T>(x: T) { // x has type T, T is a generic type } fn takes_two_of_the_

[易学易懂系列|rustlang语言|零基础|快速入门|(21)|智能指针]

实用知识 智能指针 我们今天来讲讲Rust中的智能指针. 什么是指针? 在Rust,指针(普通指针),就是保存内存地址的值.这个值,指向堆heap的地址. 什么是智能指针? 在Rust,简单来说,相对普通指针,智能指针,除了保存内存地址外,还有额外的其他属性或元数据. 在Rust中,因为有所有权和借用的概念,所以引用和智能指针,又有一点不一样. 简单来说,智能指针,拥有数据所有权,而引用没有. 智能指针分以下几种: 1.Box,用于在堆里分配内存. 2.Rc,引用计数类型,用于多线程中的多个所有

[易学易懂系列|rustlang语言|零基础|快速入门|(29)|实战6:BDD工具cucumber_rust]

项目实战 实战6:BDD工具cucumber_rust 今天我们来学习下BDD行为驱动测试工具cucumber_rust. 关于BDD,可以简单看看这些介绍: https://www.cnblogs.com/superhin/p/11454716.html#bdd%E4%BB%8B%E7%BB%8D https://www.jianshu.com/p/0389360ac58f https://www.infoq.cn/article/2011/02/BDD-ATDD 简单来说,BDD是一种更好,

[易学易懂系列|rustlang语言|零基础|快速入门|(17)|装箱crates]

实用知识 装箱crates 我们今天来讲讲装箱技术crates. 什么是crates? 英语翻译是: 英 [kre?t] 美 [kre?t] n. 板条箱:篓 vt. 将某物装入大木箱或板条箱中 [ 过去式 crated 过去分词 crated 现在分词 crating 复数 crates 第三人称单数 crates ] 其实,它也就是一种模块化封装技术. 我们还是来看看代码,我们先用命令:cargo new greetings 生成一个新的工程,工程目录如下 : // # It generat

[易学易懂系列|rustlang语言|零基础|快速入门|(18)|use关键词]

实用知识 use关键词 我们今天来讲讲use关键词. 1.简单来说,use是给其他方法或资源定义一个别名,然后调用者,就可以直接用这个别名来调用,从而简化代码. 看下例子吧,我们先来看看没有用use的代码: // -- Initial code without the `use` keyword -- mod phrases { pub mod greetings { pub fn hello() { println!("Hello, world!"); } } } fn main()

小D课堂 - 零基础入门SpringBoot2.X到实战_第1节零基础快速入门SpringBoot2.0_1、SpringBoot2.x课程介绍和高手系列知识点

1 ======================1.零基础快速入门SpringBoot2.0 5节课 =========================== 1.SpringBoot2.x课程全套介绍和高手系列知识点     简介:介绍SpringBoot2.x课程大纲章节         java基础,jdk环境,maven基础 2.SpringBoot2.x依赖环境和版本新特性说明 简介:讲解新版本依赖环境和springboot2新特性概述 1.依赖版本jdk8以上, Springboot2

零基础快速入门web学习路线(含视频教程)

下面小编专门为广大web学习爱好者汇总了一条完整的自学线路:零基础快速入门web学习路线(含视频教程)(绝对纯干货)适合初学者的最新WEB前端学习路线汇总! 在当下来说web前端开发工程师可谓是高福利.高薪水的职业了.所以现在学习web前端开发的技术人员也是日益增多了,但是在学习web前端开发中盲目的去学习而没有一个完整的思路和学习路线也是不行的. 成为一个合格的web前端开发工程师的具备什么条件? 熟练的掌握HTML.CSS.JS.JQ等最基本的技术. 现在,只掌握这些已经远远不够了.无论是开