以太坊搭建私有链和部署智能合约_搭建以太坊私有链 部署合约-程序员宅基地

技术标签: 私有链  区块链  geth  感想心得  智能合约  说明文  环境搭建  solidity  

以太坊搭建私有链和部署智能合约


1. 实验环境

VMWare虚拟机 + CentOs 7 + Golang

2. 以太坊安装

2.1 安装相关组件

为了避免后续安装过程出错,这里提前安装一些需要用到的组件和依赖。如果不确定之前有没有安装过这些组件也没关系,检测到安装就不会重复安装。-y表示安装过程中弹出的问题一律回答yes。

yum update -y && yum install git wget bzip2 vim gcc-c++ ntp epel-release nodejs cmake -y
  • 相关组件的作用
组件 作用
git 版本管理工具,用于后面从github克隆geth
wget Linux命令行的下载工具,用于后面下载cmake
bzip2 无损压缩软件
vim 编辑工具,用于后面编辑一些环境配置文件
gcc-c++ c/c++编译工具,用于后面geth的编译
ntp 网络时钟同步组件
nodejs 用于前端js开发的包管理软件
epel 网络第三方的linux安装包源

2.2 安装及配置Golang环境

  • 我的虚拟机之前已经装过Golang环境,因此这里不再重现和赘述。
  • 贴一篇我当时配golang环境时写的博客:golang开发环境配置

2.3 克隆并编译geth

  • 从github上clone下来go-ethereum(简称geth)
git clone https://github.com/ethereum/go-ethereum.git
  • 进入geth根目录并编译
cd go-ethereum && make all
  • 配置环境变量
# 打开环境配置文件
vim ~/.profile

# 新增path
export PATH=$PATH:$GOPATH/bin:$HOME/go-ethereum/build/bin

# 让更改生效
source ~/.profile
  • 验证是否配置成功
    在终端输入geth -h有一堆帮助信息出来即可

2.4 安装及配置cmake环境

智能合约编译需要cmake,之前yum install cmake的版本只有2.8不够,这里需要去官网下载其独立安装包。这里我用的是最新版本3.12.3。

  • 下载到用户根目录$HOME
cd && wget https://cmake.org/files/v3.12/cmake-3.12.3.tar.gz
  • 解压
tar -xzvf cmake-3.12.3.tar.gz
  • 进入cmake根目录并编译安装
cd cmake-3.12.3
./bootstrap && make && make install
  • 配置环境变量
# 打开环境配置文件
vim ~/.profile

# 新增path
export PATH=$PATH:$GOPATH/bin:$HOME/go-ethereum/build/bin:$HOME/cmake/bin

# 让更改生效
source ~/.profile
  • 验证是否配置成功
    在终端输入cmake -h或cmake -version有一堆帮助信息出来或者有版本信息出来均可

2.5 防火墙

因为之前在配置golang私有云桌面的时候把防火墙关闭了,我在这里就不需要考虑。

  • 你可以选择关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
  • 也可以只添加防火墙规则,允许某几个geth使用一些默认端口。
firewall-cmd --zone=public --add-port=8087/tcp --permanent
firewall-cmd --zone=public --add-port=30303/tcp --permanent
  • mark一下:启用防火墙的命令
systemctl enable firewalld
systemctl start firewalld

2.6 时钟同步

因为区块链需要同步网络时间,所以需要启用网络时间同步

systemctl enable ntpd
systemctl start ntpd

3. 私有链创世区块搭建

  • 编写创世区块的配置文件genesis.json。里面可以设置一些挖矿难度,默认的挖矿账户和最大手续费等。
{
      
    "nonce": "0x0000000000000042",  
    "timestamp": "0x00",  
    "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",  
    "extraData": "0x00",  
    "gasLimit": "0x80000000",  
    "difficulty": "0x400",  
    "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",  
    "coinbase": "0x2D356ee3F5b8718d8690AFCD31Fe2CB5E602677e",  
    "alloc": {
    },  
    "config": {
      
        "chainId": 15,  
        "homesteadBlock": 0,  
        "eip155Block": 0,  
        "eip158Block": 0  
    }  
}  
  • 初始化创世区块节点信息
geth --datadir node0 init genesis.json
  • 得到如下信息,说明初始化成功
    在这里插入图片描述
  • 执行geth的控制台,将输出重定向到geth.log中
geth --datadir node0 console 2>> geth.log
  • 一些基本的操作
    • 新建账户、查看账户及账户余额
      在这里插入图片描述
    • 查看挖矿的账户、当前区块数等
      在这里插入图片描述
    • 发起交易、查看交易池状态等
      在这里插入图片描述
    • 挖矿使交易池中待处理的交易完成(可以看见pending从1变成0,目标账户的余额从0变成15个以太币——注意单位换算fromWei),sleepBlocks(n)可以监控挖矿过程,指定在挖到第n个新块的时候的时候执行下条命令,如stop()。
      在这里插入图片描述
    • 注:只要实际上有在挖矿,start返回null或true都无所谓。
  • 另开一个终端查看log内容
tail -f geth.log

4. 私有链节点加入

  • 新建一个节点并初始化
geth --datadir node1 init genesis.json
  • 启动上一个节点的rpc服务,并把网络id设置成2018,其他不设置(默认rpc端口8545,监听端口30303,ip地址127.0.0.1)
geth --datadir node0 -networkid 2018 -rpc console 2>> geth.log
  • 启动新节点的rpc服务,把网络id设置成2018(要相同),rpc的端口默认8545,节点监听的端口默认是30303,这两个端口都被上一个节点占用了(因为我是在一台机器上),因此这个新节点的这两个端口都要手动设置(我这里分别设成8546和30304),ip地址仍是默认
geth --datadir node1 -networkid 2018 -rpc -rpcport 8546 -port 30304 console 2>> geth.log
  • 查看新节点的enode,并拷贝
admin.nodeInfo.enode
  • 在另一个终端上,进入上一个节点的console,加入新节点
admin.addPeer(上面拷贝的enode)
  • 连接成功
    在这里插入图片描述

  • 有个小小的疑惑。当时忘记设置网络id,默认是1,网上说networkid是1的时候是接入公网?然后确实是会莫名其妙偶尔连到一些外国的节点,第一个193开头的是乌克兰,第一个52开头的是爱尔兰的。但是为何私链可以接到公网上面?后面发现加上-nodiscover就可以不被别的节点发现。
    在这里插入图片描述
    在这里插入图片描述

5. 区块字段解释

5.1 最新区块

在这里插入图片描述

5.2 各个字段解释

字段 意义
difficulty 挖到当前区块的难度
extraData 额外数据
gasLimit 该区块允许使用的手续费上限
gasUsed 该区块实际包含的所有手续费
hash 该区块的唯一哈希值标识,32字节
logsBloom 区块日志的过滤器,256字节
miner 打包该区块的矿工地址,20字节
mixHash 混合哈希
nonce 一个符合PoW难题的随机值,8字节
number 当前区块的序号
parentHash 父区块的哈希值,32字节
receiptRoot 收据的唯一哈希值标识
sha3Uncles 叔区块的SHA3哈希值
size 区块的大小,单位是字节
stateRoot 该区块最终前缀树的根哈希
timeStamp 该区块被发现时的Unix时间戳
totalDifficulty 挖到当前区块前所有区块的难度总和
transactions 所有交易的哈希值集合
transactionsRoot 该区块交易前缀树的根哈希
uncles 叔哈希

5.3 补充

  • difficulty由父区块的难度及其时间戳与本区块时间戳的差计算得到。
  • mixHash是一个与随机数 (nonce)相关的 256 位哈希计算,用于证明针对当前区块已经完成了足够的计算,相当于签名。
  • 创世区块的parentHash为0
  • 当前区块的哈希由区块头唯一决定。意味着,区块头不同,产生的哈希才会不同。区块头里面所要记录的信息都是确定的、不变的,为了让区块头产生变化,引入了一个随机值 Nonce,作为一个唯一的变量,在密码学中Nonce是一个只被使用一次的任意或非重复的随机数值。矿工的作用其实就是不断重复 变化 Nonce,计算哈希的过程,直到碰到一个 Nonce 的值,使得区块头的哈希可以小于目标值,从而能够写入区块链。
  • 一个块奖励5个以太币

6. 日志输出解释(默认日志等级)

6.1 挖矿

可以观察到每次挖到一个新的区块都需要经历四个阶段。分别是commit new mining work => successfully sealed new block => block reached canonical chain => mined potential block,即先提交要打包某个区块的任务,然后成功打包该区块,该区块接到主链上(主链最后一个区块号一般会比当前的目标区块小一些,因为有分叉),成功完成挖矿。每条记录后面的number指明的是当前挖矿的目标区块序号。
在这里插入图片描述

6.2 节点记录

  • new local node record这条记录在网上搜了很久没有发现对应的解释,我猜测可能是因为我关闭了防火墙,同时没有设置nodiscover,也许会从各个udp端口或30303tcp端口收到来自公网的包,从而进行同步,猜测这也是之前为什么会连接到公网节点的原因之一,当然可能也不仅仅只包含公网的同步,应该也还有私链上的多节点同步。每条该类记录后面会有一个seq标识当前记录是第几个的记录。
  • 然后每经过一定量的日志记录后,会出现regenerated local transaction journal,这个是日志记录的回转,为了防止日志记录过多。日志轮换是系统管理中使用的自动过程,对过时的日志文件进行存档。运行大型应用程序的服务器通常会记录每个请求,面对庞大的日志,日志轮换提供了一种限制保留日志总大小的方法,同时仍允许分析最近的事件。
    在这里插入图片描述

6.3 添加节点

  • 这段日志里的各种closed和stopped是因为退出了某个节点的控制台,需要关闭所有服务,当然包括数据库及各种协议。
  • 其他的像添加节点后便会出现从starting peer-to-peer node到started P2P networking等一系列节点连接的过程,包括ipc服务的open,存储空间的确认,第一次连接的话可能中间还会有一些初始化同一创世区块的步骤。

在这里插入图片描述

7. 部署智能合约

7.1 编写合约

通过remix,写了一个最简单的合约,有读操作也有写操作。

pragma solidity ^0.4.18;

contract Test{
    
    
    string public name;
    
    function Test() public {
    
        name = "xietao";
    }
    
    function getName() public view returns (string) {
    
        return name;
    }
    
    function setName(string _name) public {
    
        name = _name;
    }
}

7.2 编译并部署合约

  • 右侧点击start compile之后点击detail可以看见合约详情。
  • 找到WEB3DEPLOY一项,拷贝里面的代码
var testContract = web3.eth.contract([{
    "constant":true,"inputs":[],"name":"name","outputs":[{
    "name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{
    "constant":true,"inputs":[],"name":"getName","outputs":[{
    "name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{
    "constant":false,"inputs":[{
    "name":"_name","type":"string"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{
    "inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]);
var test = testContract.new(
   {
    
     from: web3.eth.accounts[0], 
     data: '0x6060604052341561000f57600080fd5b60408051908101604052600681527f78696574616f00000000000000000000000000000000000000000000000000006020820152600090805161005692916020019061005c565b506100f7565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061009d57805160ff19168380011785556100ca565b828001600101855582156100ca579182015b828111156100ca5782518255916020019190600101906100af565b506100d69291506100da565b5090565b6100f491905b808211156100d657600081556001016100e0565b90565b61037f806101066000396000f3006060604052600436106100565763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde03811461005b57806317d7de7c146100e5578063c47f0027146100f8575b600080fd5b341561006657600080fd5b61006e61014b565b60405160208082528190810183818151815260200191508051906020019080838360005b838110156100aa578082015183820152602001610092565b50505050905090810190601f1680156100d75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156100f057600080fd5b61006e6101e9565b341561010357600080fd5b61014960046024813581810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965061029295505050505050565b005b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101e15780601f106101b6576101008083540402835291602001916101e1565b820191906000526020600020905b8154815290600101906020018083116101c457829003601f168201915b505050505081565b6101f16102a9565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102875780601f1061025c57610100808354040283529160200191610287565b820191906000526020600020905b81548152906001019060200180831161026a57829003601f168201915b505050505090505b90565b60008180516102a59291602001906102bb565b5050565b60206040519081016040526000815290565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106102fc57805160ff1916838001178555610329565b82800160010185558215610329579182015b8281111561032957825182559160200191906001019061030e565b50610335929150610339565b5090565b61028f91905b80821115610335576000815560010161033f5600a165627a7a72305820ca52a32829a7341ced9c589fa39647854f065bcec7bf1d7b4a2aed5185b579900029', 
     gas: '4700000'
   }, function (e, contract){
    
    console.log(e, contract);
    if (typeof contract.address !== 'undefined') {
    
         console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
    }
 })
  • 将上述代码直接黏贴进console,或者可以保存在某个js文件中再load进来(比较优雅)。如果报错说没有unlock则需要先解锁一个账户。
personal.unlockAccount(eth.accounts[0])
  • 然后开始挖矿进行部署,否则合约实例的address是undefined。
miner.start();admin.sleepBlocks(1);miner.stop()
  • 提示contract mined则正式部署成功!
Contract mined! address: 0x7e8109387a54814fcc61a48729b7a891f24cd291 transactionHash: 0x656fc28b69415dab65edbb5e62f2034daa6f2402f5db052aaba72da3269a1707
  • 再次查看合约实例——test的address字段,已经不再是undefined了。

7.3 调用合约

  • 读操作。读操作很简单,直接调用就行,不消耗gas。
    在这里插入图片描述
  • 写操作。写操作遇到一点坑,一开始总是set没有更改到。虽然知道要扣费,但一开始不知道没搞懂怎么扣除指定账户的gas。后面发现其实合约也是一个账户,和交易类似。有两种设置付款账户的方法。
    • 为该节点设置默认账户,这样在调用set函数时就会自动让你付款。这种方法适用于只有一个账户的节点或者只有一个账户经常使用的多账户节点。
    eth.defaultAccount = eth.accouts[0]
    
    • 每次调用set时顺便指定付款账户。这种方法适用于每个账户都经常使用的多账户节点。
    test.setName("xietao1", {
          from:eth.accounts[0]}");
    
    • 其他操作和交易时是一样的,交易前记得解锁账户,产生交易后记得挖矿。
      在这里插入图片描述
  • 注意到读操作和写操作是有区别的——写操作要消耗gas,读操作不用。因为写操作需要通过矿工对链做出更改和同步,有代价。

8. 交易字段解释

8.1 交易信息

在这里插入图片描述

8.2 各个字段解释

字段 意义
blockHash 该交易所在区块的哈希,32字节
blockNumber 该交易所在的区块序号
from 该交易转账方的地址
gas 转账方愿意支付的手续费
gasPrice gas的单价,单位是Wei
hash 该交易的哈希
input 交易过程中用到的数据
nonce 转账方在该交易前发起的交易数
r,s 交易的ECDSA签名
to 该交易收款方的地址
v 公钥恢复id
transactionIndex 该交易在所在区块交易集合的下标
value 交易的金额,单位是Wei

8.3 补充

  • 区别(结合例子解释)
    • gas。在交易信息中的gas字段,表示交易发起方(转账方)愿意支付的gas上限。本例是发起方愿意支付90000单位的gas。
    • gas price。由交易发起方(转账方)设置,表示gas的单位价格,单价越高交易越优先处理。本例中发起方愿意以每单位1000000000Wei的价格支付gas。gas * gas price = 发起方最多愿意支付的手续费。
    • gas limit。在交易信息中和gas是一个意思,但是在区块信息中表示该区块最多可以消耗的gas上限。也就是该区块中所有交易实际消耗gas(gas used)的总和不能超过该区块的gas limit。
    • gas used。表示一个交易实际上消耗的gas。看看该交易对应的区块,可以发现gas实际上只消耗了21000,没有超过交易发起方愿意发起的gas上限(交易发起方的gas limit,即90000),交易正常进行。gas used * gas price = 发起方实际支付的手续费,多余的gas会被返还。
      在这里插入图片描述
    • 注意如果发起方愿意支付的gas小于实际消耗的gas,那么交易会被撤销,但仍然会扣除发起方支付的gas,因为不管交易是否可以顺利进行,矿工都已经进行了工作。
  • 区别
    • 交易的nonce。交易信息中的nonce字段是通过计数来用来唯一标识每一个交易。nonce越小会越先被处理。可以用来防止一些双花等恶意消费动机。
    • 挖矿的nonce。即区块中的nonce,这个前面解释过,是用来调整大小以产生符合难题的哈希的一个无意义的值。
  • Wei是以太币的最小计量单位, 一 个 以 太 币 = 1 0 18 W e i 一个以太币=10^{18}Wei =1018Wei
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Wonderful_sky/article/details/83898257

智能推荐

解决Sublime中Package Control无法下载的问题_chromacontrolasus-程序员宅基地

文章浏览阅读2k次。解决Sublime中Package Control无法下载的问题今天想下载sublime插件时,发现提示了Package Control并不能正常下载。报错 There are no packages available for installation.问题分析打开Sublime-> Preferences-> Package settings-> Packag..._chromacontrolasus

JAVA内写一个javascript语句内,实现按逗号和正斜杠/两种分隔符分割的功能_java正则表达式逗号横杠分隔-程序员宅基地

文章浏览阅读818次。刚开始要求按逗号分割,直接写:StringBuffer sbuf = new StringBuffer();sbuf.append( "var strArr = val.split(",")" );后来要求既要按逗号分割,又要按斜杠/分割。于是上网查了下,js涉及多种分隔符时可以用正则表达式形式。但注意正斜杠/在正则表达式里的写法是:\/val.split("/[,\/]/")";但是上面的js语句直接放在java里面会报错,原因是:反斜杠\ 在java里需要转义\\,应当写成:Strin_java正则表达式逗号横杠分隔

【量化笔记】随机指标交易策略 KDJ_gspc_index-程序员宅基地

文章浏览阅读1.7k次。KDJ指标的计算公式未成熟随机指标RSVRSV=第n天的收盘价−最近n天内的最低价最近n天内的最高价−最近n天内的最低价RSV=\frac{第n天的收盘价-最近n天内的最低价}{最近n天内的最高价-最近n天内的最低价}RSV=最近n天内的最高价−最近n天内的最低价第n天的收盘价−最近n天内的最低价​K指标:K值=23∗前一日K值+13∗当日RSVK值=\frac{2}{3}*前一日K值+\..._gspc_index

37 GIL 线程池 同步异步 阻塞非阻塞-程序员宅基地

文章浏览阅读78次。GIL锁GIL全局解释器锁,是一个互斥锁. 是为了防止多个本地线程同一时间执行python代码,,Cpython的内存管理是非线程安全的非线程安全 即 多个线程访问同一个资源,会 有问题 线程安全 即 多个线程访问同一个资源,不会有问题该锁只存在Cpython中,这并不是Python这门语言的 除了Cpython之外 Jpython, pypy,解释器 之所以使用Cpy..._手动管理gil

selenium启动chrome时弹出设置页面:Windows Defender 防病毒要重置您的设置_找不到triggeredreset-程序员宅基地

文章浏览阅读7k次,点赞5次,收藏4次。解决方案win+r 输入 regedit打开注册表查找指定目录:计算机\HKEY_CURRENT_USER\Software\Google\Chrome 注意是HKEY_CURRENT_USER文件加不是HKEY_USERS文件夹删除TriggeredReset文件夹即可注意事项:这里如果使用ctrl + f直接输入TriggeredReset可能会定位到HKEY_USERS文件夹下,该文件夹下也有一个TriggeredReset文件,删除这个文件是无效的..._找不到triggeredreset

【论文导读】- Cluster-driven Graph Federated Learning over Multiple Domains(聚类驱动的图联邦学习)-程序员宅基地

文章浏览阅读8.4k次,点赞54次,收藏39次。文章目录论文信息摘要主要贡献聚类驱动的图联邦学习问题定义联邦聚类聚类模型聚类模型的联系FedCG框架论文信息Cluster-driven Graph Federated Learning over Multiple Domains原文链接:Cluster-driven Graph Federated Learning over Multiple Domains:https://openaccess.thecvf.com/content/CVPR2021W/LLID/papers/Caldarola_cluster-driven graph federated learning over multiple domains

随便推点

spring定时任务详解spring schedule和spring-quartz-程序员宅基地

文章浏览阅读3.1w次,点赞26次,收藏115次。从实现的技术上来分类,java定时任务目前主要有三种:Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行;而且作业类需要集成java.util.TimerTask,一般用的较少。 Quartz,这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照..._spring schedule

PyQt5——打包资源文件_py qt import logo_rc-程序员宅基地

文章浏览阅读2.8k次。打包资源文件使用PyQt5生成的应用程序引用图片资源的主要方法有两种第一种方法:将资源转换为Python文件,然后引用Python文件第二种方法:在程序中通过相对路径引用外部图片资源使用Qt Designer 加载资源文件在Qt Designer中设计界面时是不能直接加入图片或图标资源的,而是需要在PyQt5开发目录下编辑.qrc文件新建一个资源文件apprcc.qrc&..._py qt import logo_rc

Java学习笔记(IO流 二)_commit('setaccess', data.permissions);可以用什么代替-程序员宅基地

文章浏览阅读96次。知识点总结于毕向东Java基础视频教程25天版本,侵权请联系删除。IO流(二)IO流(二)FIle类概述常用方法递归IO流(二)FIle类概述用来将文件或者文件夹封装成对象。File 是文件和目录路径名的抽象表示形式,即File类的对象代表一个文件或一个目录(文件夹)的路径,而不是文件本身。方便对文件与文件夹的属性信息进行操作。File对象可以作为参数传递给流的构造函数。构造方法:File(String pathname):通过将给定的字符串类型的路径名称转换为抽象路径名来创建新的F_commit('setaccess', data.permissions);可以用什么代替

布隆过滤器用Redisson实现的例子_jrebloom-程序员宅基地

文章浏览阅读2.9k次,点赞2次,收藏10次。背景介绍之前这边有写过关于布隆过滤器过滤器的安装使用,但是这次使用使用jrebloom来实现,jrebloom目前只提供一个maven依赖供我们使用。考虑到后续代码维护,bug的修复。使用redisson来做布隆过滤器是更加安全做法。下面是关于布隆过滤器的使用:布隆过滤器的原理、redis布隆过滤器的安装和使用代码实现下面使用Redisson对布隆过滤器进行的封装。1、引入maven依赖<!-- https://mvnrepository.com/artifact/org.red_jrebloom

VR+基础教育_vr+数学-程序员宅基地

文章浏览阅读390次。传统课堂教学受时空、设备、场地和安全等的限制,教学内容枯燥,形式单一,学生被动接受,缺乏自主性和积极性。而传统教学实验室设备设施大量购置费用高昂,损耗大,后期维护与更新困难,加上设备操作安全和场地等方面因素,都在很大程度上制约了教育信息化的发展。VR+基础教育VR技术在基础教育领域的应用,成功打破了时空的限制,学生可以在虚拟场景中开展沉浸性交互式学习,自主探索各种学习内容。同时可以突破场地、设..._vr+数学

springboot145基于java的在线问卷调查系统的设计与实现_用java实现校友问卷调查管理系统-程序员宅基地

文章浏览阅读43次。MySQL的数据存放形式从大向小的说是数据库最大,然后是表,每个表里面存放数据是有一定的规则的,数据存放是表格形式的,也就是说有横也有竖,横着的为行,一般表示一条数据,每个表都有字段,而字段是以列的形式存在,这样能保证一条数据每一个字段对应的是相同数据类型的数据。传统处理数据,必须是一张张纸,然后处理完毕又是统计在一张张纸上面,不断的重复处理,最终有个结果给最高层作为参考,这个模式在互联网没有出现之前,是一种常见的事情,信息管理的效率提不上去,那就用人才,人多力量大,是一个以前人们的常识。_用java实现校友问卷调查管理系统

推荐文章

热门文章

相关标签