目录

  • 1. 元宇宙核心技术
  • 2. 元宇宙实例及应用实例
  • 3. 以太坊里的智能合约开发
    • 3.1. World Wide Web的访问能力
    • 3.2. 初始化以太坊钱包
    • 3.3. 开发4方合约
    • 3.4. 手动运行DMall智能合约
    • 3.5. Python调用DMall智能合约
  • 4. Decentraland里的Smart Item开发

1. 元宇宙核心技术

腾讯最近发布了一个全真互联白皮书,虽然他们强调全真互联元宇宙不同,但怎么看都像是无奈之下的牵强附会。从核心技术上来看,其实元宇宙Web3.0和这个全真互联都是一回事儿,都是前端和后端两方面技术发展的产物:

  • 随着前端交互技术(既包括软件渲染技术,也包括硬件交互设备)的发展,互联网从只能在PC上看PGC的Web1.0,发展到还可以在手机上看UGC的Web2.0。到了今天,发展出了又能在各种智能穿戴设备上,用不同的人类感官,去交互三维内容的Web3.0,也就是元宇宙。
  • 内容的种类和规模的增加,必然要求后端计算、存储、网络的处理能力的增强。云计算全栈软硬件技术的不断演进支持了Web1.0、2.0到3.0的变化,只是到了3.0,也就是元宇宙,后端又增加了另一项配合社会形态转变的智能合约技术(比如NFT-非同质数字化资产、数字货币等),智能合约当然不见得要跟区块链连在一起,但区块链确实是实现它的一种有效方式。

腾讯的全真互联确实刻意的回避了智能合约,这也不奇怪,乖巧如腾讯者深谙个中道理,不可能碰这种危险的东西,这也是为啥国内元宇宙初创公司大多数都集中在前端技术的原因,而且腾讯自己是Web2.0的既得利益者,商业上,显然也不愿意去掉包括自己在内的这些中心互联网平台。

为了从技术上实操完整的元宇宙应用开发,本文选择Decentraland作为做实验的平台。

2. 元宇宙实例及应用实例

关于本实例完整的业务模式描述,请参见前面的文章。本文是从技术角度记录下验证的实际开发过程。

Decentraland是一个基于以太坊区块链实现的分布式虚拟现实平台。它是一个高度符合元宇宙定义的虚拟世界,这个世界的成员可以在它的土地上创建内容应用体验他人的内容和应用,用自己的内容和应用赚钱。Decentraland中的土地是用以太坊智能合约维护的一种NFT,里面的空间是3D的,用户可以在里面游逛,土地总数有限。土地被分为Parcel,Parcel用坐标来标识,这些Parcel被这个世界的成员们永久持有,可以用MANA(马那币)交易,MANA是Decentraland的官方加密数字货币。用户可以全权控制他们的土地,在上面用现成或自定义的3D前端组件,创建静态3D场景或可交互的游戏和应用。

本文实现的就是一个自定义的3D可交互前端组件(在Decentraland叫做Smart Item),组件里包含由一个以太坊智能合约维护的、可交互的广告内容。这个合约是个4方合约,参与方有组件运营者土地所有者广告发布者消费者。整个合约发生作用的过程如下:

  • 组件运营者把组件发布到Decentraland Builder
  • 广告发布者将广告发布到组件上
  • 土地所有者在Decentraland Builder里把组件装配在自己土地上的建筑上
  • 消费者来这个建筑里,点击广告,广告发布者按约定的金额,支付MANA给土地所有者和组件运营者
  • 消费者打开广告后,发生了购买行为,广告发布者按约定的金额,支付MANA给消费者

3. 以太坊里的智能合约开发

3.1. World Wide Web的访问能力

首先,使用代理或其他方式,获得World Wide网络访问能力。

3.2. 初始化以太坊钱包

  • chrome应用商店中安装chrome插件形式的加密货币钱包MetaMask,并依提示创建默认账户。
  • 在chrome右上角的MetaMask插件界面中,将默认账户的名字改为“组件运营者”。
  • 继续创建三个账户,分别命名为“土地所有者”、“广告发布者”、“消费者”。
  • 将当前所在链从以太坊Ethereum主网络,切换至Ropsten测试网络。注意:这个测试网络只能用到22年Q4末,随着以太坊完成了“Merge”,此测试网络将下线,测试需要切换至新网络,建议使用Sepolia test network,不过,现在Decentraland还不支持这个新测试网络。
  • 打开Ropsten测试网络的代币免费获取页面,将账户依次切换至“组件运营者”、“广告发布者”、“消费者”,为这三个账户分别充值1个以太币,转账需要在MetaMask面板上点击确定。这些以太币是用于支付在以太坊上执行交易所需支付的成本。如果使用“Sepolia test network”,则需要使用Sepolia的代币免费获取页面,不过它的代币到账时间需要大概2个小时。
  • 打开Decentraland在Ropster上的MANA测试代币免费获取页面,为“广告发布者”账户充值MANA代币,转账需要在MetaMask面板上点击确定。这些MANA是4方合约在执行交易时使用的货币。

3.3. 开发4方合约

以太坊智能合约开发使用的语言是Solidity,使用Solidity让人想起用c++写系统软件,对内存精细规划,对代码极致雕琢。这是因为合约要被存储到链上并在链上执行,代码体积和执行内存越大,消耗的资源越多,要付的钱就越多。它必须很干净,能删除掉的都要删掉,注释不要有,甚至变量名都应该越短越好。本文中代码注释是为了说明逻辑,实际生产运行时一定要删掉注释,缩短变量名。

合约的开发工具为Remix。Remix可进行本地部署,但验证过程就直接使用SaaS版本了。打开Remix,越过欢迎界面,进入开发界面,在左侧选择File explorer,然后在contracts文件夹下,创建一个新的Solidity程序文件,命名为dmall.sol(此实例中的合约名字定为DMall),并将以下代码复制进文件中:

//SPDX-License-Identifier: UNLICENSEDpragma solidity >=0.7.0 <0.9.0;// 声明MANAToken的接口,用于DMall合约中调用
interface MANAToken {// 广告所有者必须在发布广告之前,调用MANAToken的这个接口,允许DMall从他的MANA账户里划转代币到其他账户,其中_sender参数为DMall部署后的地址,_value参数为允许划转的MANA代币数量的上限function approve(address _sender, uint256 _value) external returns (bool);// DMall合约会调用这个接口,将MANA代币从广告所有者转账到其他三个账户,_from是广告所有者,_to是其他三个账户,_value是转账金额function transferFrom(address _from, address _to, uint256 _value) external returns (bool);
}contract DMall {// 广告的状态枚举,广告所有者首次调用uptAd接口创建接口后为Created,组件运营者调用apprAd接口同意后变为Approved,广告所有者再次调用uptAd接口后为Updated,此时还需要组件运营者调用apprAd接口。只有Approved状态的广告可以被看到和购买。Undefined是初始状态,无意义,Invalid是广告因为其他原因(如广告发布者没给MANA)失效。enum AdState { Undefined, Created, Approved, Updated, Invalid } // 广告点击记录的状态,Undefined是初始状态,无意义,Clicked表示消费者点击过了(即调用clickAd),Bought表示消费者买过了(即调用buyAd)。enum ClickState { Undefined, Clicked, Bought } // 在链上存储的一条广告的数据结构struct Ad {// 广告点击时,广告发布者给土地所有者的MANA数量uint m2L;// 广告点击时,广告发布者给组件运营者的MANA数量uint m2O;// 广告被购买时,广告发布者给消费者的MANA数量uint m2C;// 广告的状态AdState state;// 广告的点击记录数据集合,第一个地址是土地所有者的地址,第二个地址是消费者的地址,最终里面存的数据是一条点击数据的状态mapping(address => mapping(address => ClickState)) clicks;}// MANAToken合约的地址MANAToken manaToken;// 广告数据集合,第一个地址是广告发布者的地址,第二个整数是广告ID,数据是上面的Ad结构体mapping(address => mapping(uint => Ad)) public ads;// 合约运营者,就是组件运营者的地址address public op;// 合约构造函数,用调用者的地址初始化合约运营者地址,用输入的MANAToken合约地址初始化对应数据,由所在的链是生产链还是测试链决定constructor(address _manaToken) {op = msg.sender;manaToken = MANAToken(_manaToken);}// 因为链上的操作都是异步的,需要存在链上才算成功,以下事件是对应接口调用成功后的回调,可在前端应用程序中实现// 广告创建成功后的事件(uptAd接口首次调用后的事件)event AdCrted(address merchant, uint adId, uint m2L, uint m2O, uint m2C);// 广告更新成功后的事件(uptAd接口再次调用后的事件)event AdUpted(address merchant, uint adId, uint oM2L, uint oM2O, uint oM2C, uint m2L, uint m2O, uint m2C);// 广告批准成功后的事件(apprAd接口调用后的事件)event AdAppred(address merchant, uint adId);// 广告点击后的事件(clickAd接口调用后的事件)event AdClicked(address merchant, uint adId, address landowner, address consumer);// 广告失效事件(clickAd接口或buyAd接口中发生失败后的事件)event AdInvalid(address merchant, uint adId);// 广告被购买事件(buyAd接口调用后的事件)event AdBought(address merchant, uint adId, address landowner, address consumer);// 创建或更新广告,由广告发布者调用,_adId是广告的ID,_m2L是广告发布者愿意给土地所有者的MANA数量,_m2O是广告发布者愿意给组件运营者的MANA数量,_m2C是广告发布者愿意给消费者的MANA数量function uptAd(uint _adId, uint _m2L, uint _m2O, uint _m2C) public {// 初始化一个Ad对象Ad storage ad = ads[msg.sender][_adId];if (ad.state == AdState.Undefined) {// 如果这是个新广告,则设置参数,并触发AdCrtedad.state = AdState.Created;ad.m2L = _m2L;ad.m2O = _m2O;ad.m2C = _m2C;emit AdCrted(msg.sender, _adId, _m2L, _m2O, _m2C);}else {// 如果不是新广告,则记录当前参数,设置新参数,并触发AdUptedad.state = AdState.Updated;uint oM2L = ad.m2L;uint oM2O = ad.m2O;uint oM2C = ad.m2C;ad.m2L = _m2L;ad.m2O = _m2O;ad.m2C = _m2C;emit AdUpted(msg.sender, _adId, oM2L, oM2O, oM2C, _m2L, _m2O, _m2C);}}// 批准广告,由组件运营者调用, _merchant是广告发布者地址,_adId是广告的IDfunction apprAd(address _merchant, uint _adId) public {// 要求只能是组件所有者调用require(msg.sender == op, "Only DMall operator can approve the deployment of Ad");// 要求广告不是初始状态和批准过的状态require(ads[_merchant][_adId].state != AdState.Undefined && ads[_merchant][_adId].state != AdState.Approved , "The ad of merchant with the ID does not exist or has been approved");// 设置广告状态ads[_merchant][_adId].state = AdState.Approved;// 触发AdAppred事件emit AdAppred(_merchant, _adId);}// 点击广告,由消费者调用,_merchant是广告发布者地址,_adId是广告的ID,_landowner是广告所在土地的土地所有者function clickAd(address _merchant, uint _adId, address _landowner) public {// 需要广告是Approved的require(ads[_merchant][_adId].state == AdState.Approved, "The ad of merchant with the ID has not been approved");// 需要广告不是已被此消费者点击状态require(ads[_merchant][_adId].clicks[_landowner][msg.sender] != ClickState.Clicked, "The clicking had been paid but does not lead to buying yet");// 广告发布者按约定给土地所有者和组件运营者转账if (manaToken.transferFrom(_merchant, _landowner, ads[_merchant][_adId].m2L) && manaToken.transferFrom(_merchant, op, ads[_merchant][_adId].m2O) ) {// 如果转账成功,则标记点击状态,触发AdClickedads[_merchant][_adId].clicks[_landowner][msg.sender] = ClickState.Clicked;emit AdClicked(_merchant, _adId, _landowner, msg.sender);}else {// 转账失败,则标记广告失效,触发AdInvalidads[_merchant][_adId].state = AdState.Invalid;emit AdInvalid(_merchant, _adId);}}// 购买广告商品,由消费者调用,_merchant是广告发布者地址,_adId是广告的ID,_landowner是广告所在土地的土地所有者function buyAd(address _merchant, uint _adId, address _landowner) public {// 需要消费者点击过此广告require(ads[_merchant][_adId].clicks[_landowner][msg.sender] == ClickState.Clicked, "The consumer does not click the ad or has bought");// 广告发布者转账给消费者if (manaToken.transferFrom(_merchant, msg.sender, ads[_merchant][_adId].m2C)) {// 转账成功,则将广告点击记录标记为Bought,并触发AdBoughtads[_merchant][_adId].clicks[_landowner][msg.sender] = ClickState.Bought;emit AdBought(_merchant, _adId, _landowner, msg.sender);}else {// 转账失败,则标记广告失效,触发AdInvalidads[_merchant][_adId].state = AdState.Invalid;emit AdInvalid(_merchant, _adId);}        }
}

3.4. 手动运行DMall智能合约

  • 在chrome右上角MetaMask插件的面板里,确保切换到“Ropsten测试网络”链和“组件运营者”账号
  • 在Remix左侧,选择Solidity Compiler,点击Compile dmall.sol按钮
  • ENEIRONMENT选择Injected Provide - MetamaskCONTRACT选择DMall
  • 在Decentraland中的智能合约地址页面,找到Ropsten网络的MANAToken地址,即0x2a8fd99c19271f4f04b1b7b9c4f7cf264b626edb
  • 将上述地址填写进Deploy按钮后的输入框,然后点击Deploy按钮,等待MetaMask面板上弹出确认并点击,完成DMall合约部署
  • 重选选择CONTRACT为MANAToken,将前面的MANAToken地址填入At Address按钮后的输入框,然后点击At Address按钮,等待MetaMask面板上弹出确认并点击,完成MANAToken合约在Remix上的显示,这个不是我们部署的,只是为了能调用接口
  • 在MetaMask面板上,切换至广告发布者账号
  • Deployed Contracts下,找到并复制DMALL合约的地址,然后打开MANATOKEN合约,打开approve接口,输入_sender为上面复制的DMALL地址,_value输入个大数,如100000。点击transact按钮,等待MetaMask面板上弹出确认并点击,完成广告发布者对DMALL合约的授权
  • 打开DMALL合约的uptAd接口,输入_adId=1,_m2L=11,_m2O=12,_m2C=13,点击transact按钮,等待MetaMask面板上弹出确认并点击,完成广告创建
  • 在MetaMask面板上,复制广告发布者的地址,然后切换至组件运营者账号
  • 打开DMALL合约的apprAd接口,输入_merchant为刚才复制的地址,_adId为1,点击transact按钮,等待MetaMask面板上弹出确认并点击,完成广告批准
  • 在MetaMask面板上,切换至消费者账号
  • 打开DMALL合约的clickAd接口,输入_merchant为广告发布者地址,_adId为1,_landowner为土地所有者的地址,点击transact按钮,等待MetaMask面板上弹出确认并点击,完成广告点击
  • 此时,可以通过MetaMask面板上的账户详情页面,查看以太币和MANA币的各账户变化情况
  • 打开DMALL合约的buyAd接口,输入_merchant为广告发布者地址,_adId为1,_landowner为土地所有者的地址,点击transact按钮,等待MetaMask面板上弹出确认并点击,完成广告购买
  • 此时,可以再通过MetaMask面板上的账户详情页面,查看以太币和MANA币的各账户变化情况

3.5. Python调用DMall智能合约

  • 创建可供代码调用的以太坊API接口地址

    • 在infura页面上,注册用户并登录,选择右上角的Dashboard进入
    • 选择右上角的CREATE NEW KEY按钮
    • 网络选择Web3 API
    • Ethereum下,从MAINNET切换到Ropsten,不过现在Ropsten,已经消失了,大家可以按照以下格式,将key替换进去即可,或者直接用也行:https://ropsten.infura.io/v3/dd4cc999659f448d905400a4e8fb4e9d
  • 安装PyCharm
  • 创建合约接口文件
    • 在PyCharm中创建一个工作区,在里面增加一个contract_abi.py文件
    • 在Remix左侧,选择Solidity Compiler,CONTRACT选择DMALL,点击下方的ABI复制按钮
    • 在contract_abi.py中,输入dmall_abi = """""",在六个分号的正中间粘贴上面的复制内容
    • 在MANAToken的代码页面,点击Contract ABI部分右上角的Copy ABI to clipboard
    • 在contract_abi.py中,换行输入manatoken_abi = """""",在六个分号的正中间粘贴上面的复制内容
  • 在MetaMask面板中,导出广告发布者的Private Key
  • 在PyCharm中,为项目安装Web3包(pip install web3),创建dmalltest.py,粘贴以下代码,按注释调试验证合约的部分接口
import time
from web3 import Web3, HTTPProvider
from web3.logs import IGNORE
import contract_abi# DMall合约的地址,从Remix中获得,输入单引号之间
dmall_address = Web3.toChecksumAddress('')
# MANAToken合约的地址
manatoken_address = Web3.toChecksumAddress('0x2a8fd99c19271f4f04b1b7b9c4f7cf264b626edb')# 广告发布者的Private Key,将前文导出的值输入单引号之间
merchant_private_key = ''
# 广告发布者的账号地址,从MetaMask面板中获得,输入单引号之间
merchant_address = ''
# 土地所有者的账号地址,从MetaMask面板中获得,输入单引号之间
landowner_address = ''
# 消费者的账号地址,从MetaMask面板中获得,输入单引号之间
consumer_address = ''# 以太坊API地址,将前文infura获取的https地址输入单引号之间
w3 = Web3(HTTPProvider(''))# 初始化DMall和MANAToken合约的接口和事件
dmall_contract = w3.eth.contract(address=dmall_address, abi=contract_abi.dmall_abi)
manatoken_contract = w3.eth.contract(address=manatoken_address, abi=contract_abi.manatoken_abi)# 广告所有者在MANAToken合约上批准DMall合约对其账户的MANA进行操作
def m_appr_dmall(dmall, amount):nonce = w3.eth.getTransactionCount(merchant_address)# 创建MANAToken上的一个交易,内容就是执行approve接口txn_dict = manatoken_contract.functions.approve(dmall, amount).buildTransaction({'chainId': 3,'gas': 140000,'gasPrice': w3.toWei('40', 'gwei'),'nonce': nonce,})# 用广告所有者的Private Key给上述交易签名signed_txn = w3.eth.account.signTransaction(txn_dict, private_key=merchant_private_key)# 执行交易result = w3.eth.sendRawTransaction(signed_txn.rawTransaction)# 轮询等待交易成功tx_receipt = Nonecount = 0while tx_receipt is None and (count < 30):time.sleep(10)try:tx_receipt = w3.eth.get_transaction_receipt(result)print(tx_receipt)except Exception as e:print('error: ', e)if tx_receipt is None:return {'status': 'failed', 'error': 'timeout'}# 处理异步返回的事件processed_receipt = manatoken_contract.events.Approval().processReceipt(tx_receipt, errors=IGNORE)print(processed_receipt)return {'status': 'added', 'processed_receipt': processed_receipt}# 广告所有者发布广告
def m_pub_ad(ad_id, m_2_l, m_2_o, m_2_c):nonce = w3.eth.getTransactionCount(merchant_address)# 创建DMall上的一个交易,内容就是执行创建广告的接口txn_dict = dmall_contract.functions.uptAd(ad_id, m_2_l, m_2_o, m_2_c).buildTransaction({'chainId': 3,'gas': 140000,'gasPrice': w3.toWei('40', 'gwei'),'nonce': nonce,})# 用广告所有者的Private Key给上述交易签名signed_txn = w3.eth.account.signTransaction(txn_dict, private_key=merchant_private_key)# 执行交易result = w3.eth.sendRawTransaction(signed_txn.rawTransaction)# 轮询等待交易成功tx_receipt = Nonecount = 0while tx_receipt is None and (count < 30):time.sleep(10)try:tx_receipt = w3.eth.get_transaction_receipt(result)print(tx_receipt)except Exception as e:print('error: ', e)if tx_receipt is None:return {'status': 'failed', 'error': 'timeout'}# 处理异步返回的事件processed_receipt = dmall_contract.events.AdCrted().processReceipt(tx_receipt)print(processed_receipt)return {'status': 'added', 'processed_receipt': processed_receipt}if __name__ == "__main__":# 可分别打开以下两行注释符号,执行对应函数,验证合约执行情况m_appr_dmall(dmall_address, 100)#m_pub_ad(2, 21, 22, 23)

4. Decentraland里的Smart Item开发

Decentraland使用Builder(需使用以太坊Ethereum主网络登录)搭建Scene,Scene被部署到实际的Land中,就成为建筑物。搭建Scene的组件有普通和Smart Item两种,Smart Item就是可以响应用户操作,执行代码的组件。可从github上获取现有Smart Item代码,修改代码,验证合约执行:

git clone git@github.com:decentraland/smart-items.git

可以选择一个现有的Smart Item,在其item.ts源代码的spawn函数中,增加如下代码,调用智能合约

    ent.addComponent(new OnPointerDown(async function () {const provider = await getProvider()const requestManager = new RequestManager(provider)const factory = new ContractFactory(requestManager, abi)// 需将从Remix中获取的DMall合约地址,输入双引号之间const contract = (await factory.at("")) as anyconst address = await getUserAccount()log(address)const res = await contract.clickAd(// 需将从MetaMask中获取的广告发布者账号地址,输入双引号之间"",0,// 需将从MetaMask中获取的土地所有者账号地址,输入双引号之间"",{from: address,})log(res)// 打开外部的商品购买链接openExternalURL("https://item.jd.com/10045659650093.html")},{button: ActionButton.PRIMARY,hoverText: locationString,}))

接下来可按如下步骤上传Smart Item到Builder进行验证使用:

  • 安装Node.js
  • 安装Decentraland
npm install -g decentraland
  • 到对应的Smart Item源代码文件夹下,在本地启动进行交互验证
dcl install
dcl start
  • 也可以打包上传到Builder使用
dcl pack

元宇宙应用开发实例——以太坊里的智能合约和Decentraland里的3D前端交互组件相关推荐

  1. 以太坊可更新智能合约研究与开发综述

    原文地址:https://ethfans.org/posts/ethereum-upgradeable-smart-contract-strategies 这篇文章是对以太坊可更新智能合约领域里的研究 ...

  2. 基于以太坊网络的智能合约开发、部署和测试(入门)

    为什么80%的码农都做不了架构师?>>>    基本概念: 以太坊是一个开放的.公开的区块链平台,允许用户构建自己的去中心化应用在上面运行 Solidity是一种语法类似JavaSc ...

  3. 3.25 以太坊:实战智能合约众筹1

    这次我们来看看怎么实现以太坊的众筹智能合约案例,首先我们了解一下下面的概念  1.什么是ICO?  ICO是以初始产生的数字加密货币作为投资回报的一种筹措资金的方式,它的概念源自证券界的Initial ...

  4. 以太坊—开放的智能合约完整解决方案

    什么是以太坊(Ethereum)? 通俗的讲,以太坊是一种新的法律.传统的合同合约依据法律订立,执行.违约处理依赖律师.法院:以太坊解决了这一系列问题,合约通过程序订立.执行,人工无法干预,所以几乎可 ...

  5. 区块链入门文章二《以太坊:下一代智能合约和去中心化应用平台》

    以太坊:下一代智能合约和去中心化应用平台 以太坊基金会 著 李志阔(网名:面神护法) 赵海涛 焦锋 译 中本聪2009年发明的比特币经常被视作货币和通货领域内一次激进的发展,这种激进首先表现为一种没有 ...

  6. 比特币开发者:BSC可能会超越以太坊成为顶级智能合约平台

    比特币开发者Udi Wertheimer发推称,我觉得这是以太坊作为顶级智能合约平台的最后一个周期了. 昨天币安智能链(BSC)结算500万ETH,以太坊本身结算700万ETH左右.这可能会很快结束. ...

  7. solidity开发以太坊代币智能合约

    智能合约开发是以太坊编程的核心之一,而代币是区块链应用的关键环节,下面我们来用solidity语言开发一个代币合约的实例,希望对大家有帮助. 以太坊的应用被称为去中心化应用(DApp),DApp的开发 ...

  8. 附录2 以太坊:下一代智能合约和去中心化应用平台(选译)

    以太坊基金会 著 李志阔(网名:面神护法) 赵海涛 焦锋 译 中本聪2009年发明的比特币经常被视作货币和通货领域内一次激进的发展,这种激进首先表现为一种没有资产担保或内生价值[1],也没有中央发行者 ...

  9. 以太坊:下一代智能合约和去中心化应用平台

    以太坊基金会 著 李志阔(网名:面神护法) 赵海涛 焦锋 译 中本聪2009年发明的比特币经常被视作货币和通货领域内一次激进的发展,这种激进首先表现为一种没有资产担保或内生价值[1],也没有中央发行者 ...

最新文章

  1. TiDB 源码阅读系列文章(六)Select 语句概览
  2. 初识java反射机制
  3. python 安装使用saltstack salt-api 简介
  4. 实现线程安全的单例模式
  5. python【蓝桥杯vip练习题库】ADV-234字符串跳步
  6. 财务人员学python有用吗-一个来自35岁职场高管的忠告:Python为什么不得不学?...
  7. datatable的数据进行组内排序_排序算法学习分享(四)希尔排序
  8. leetcode题解108-将有序数组转换为二叉排序树
  9. 零基础带你学习计算机网络复习—(五)
  10. linux shell脚本date命令 按照不同格式输出
  11. K-special Tables
  12. linux内核之同步
  13. ibm doors api java_IBM Rational DOORS通过DXL进行二次开发初试(1)
  14. ubuntu安装vasp_Ubuntu下p4vasp的安装
  15. matlab谐波分析fft,fft谐波分析_matlab进行fft谐波分析_交流电谐波?
  16. python实现简易聊天室程序
  17. c语言源代码下载TGAM,2018年江西理工大学C语言程序设计竞赛(初级组)一
  18. 《近匠》专访Ayla Networks云平台工程部主管——企业级物联网云平台的设计与部署...
  19. 解决Ubuntu22.04wps打不开的问题
  20. @Before有啥用

热门文章

  1. WEB基础之:CSS Margins,Padding 和 Borders, Outlines
  2. 2021年平凉市五中高考成绩查询,平凉五中举行2022届学生高考动员誓师大会
  3. 我的世界服务器公会系统指令,我的世界Factions 公会家族插件
  4. 华为18级工程师三年心血终成趣谈网络协议文档(附讲解)
  5. Java四大名著--effective java
  6. Qt 6.0正式版2020-12-08发布
  7. 最近看过的几本给我印象深刻的书
  8. ApiCloud 查看文件(通过第三方应用打开)
  9. 【漫事杂谈003】电脑弹出2345热点资讯的处理办法
  10. 数据流盘高速存储读取技术最新进展