智能合约从入门到精通:完整范例

简介:前几篇文章我们一直在讨论Solidity语言的相关语法,从本文开始,我们将介绍智能合约开发。今天我们将介绍一个完整范例。

 

此章节将介绍一个完整案例来帮助开发者快速了解合约的开发规范及流程。

 

注意:

 

在进行案例编写前,请先前往JUICE开放服务平台,完成用户注册,JUICE区块链账户创建;并下载、安装、配置好JUICE客户端。https://open.juzix.net/

 

场景描述

 

在案例实践前请确保已拥有可用的JUICE区块链平台环境!!!

 

现假设一个场景,编写一个顾客管理合约。主要实现以下功能:

 

  • 提供增加顾客信息功能,手机号作为唯一KEY
  • 提供根据手机号删除顾客信息的功能;
  • 提供输出所有顾客信息的功能;

接口定义

说明:此接口定义了顾客管理合约的基本操作,接口的定义可以开放给三方进行调用而不暴露源码;

文件目录:${workspace}/contracts/interfaces 用于存放抽象合约目录

pragma solidity ^0.4.2;contract IConsumerManager {function add(string _mobile, string _name, string _account, string _remark) public returns(uint);function deleteByMobile(string _mobile) public returns(uint);function listAll() constant public returns (string _json);}
  • add(string _mobile, string _name, string _account, string _remark) 新增一个顾客信息
  • deleteByMobile(string_mobile) 根据手机号删除顾客信息
  • listAll() 输出所有顾客信息,此方法不影响变量状态,因此使用constant修饰;

数据结构定义

说明:当接口中的输入输出数据项比较多,或者存储在链上的数据项比较多时,开发者可以定义一个结构化数据,来简化数据项的声明。并且在这个结构化数据,还可以封装对数据的序列化操作,主要包括通过将json格式转为结构化数据 反序列化为json格式。

可以把结构化数据,看成面向对象编程中的对象。

文件目录:${workspace}/contracts/librarys 用于存放数据结构的定义

pragma solidity ^0.4.2;import "../utillib/LibInt.sol";
import "../utillib/LibString.sol";
import "../utillib/LibStack.sol";
import "../utillib/LibJson.sol";library LibConsumer {using LibInt for *;using LibString for *;using LibJson for *;using LibConsumer for *;struct Consumer {string mobile;string name;string account;string remark;}/***@desc fromJson for Consumer*      Generated by juzhen SolidityStructTool automatically.*      Not to edit this code manually.*/function fromJson(Consumer storage _self, string _json) internal returns(bool succ) {_self.reset();if (!_json.isJson())return false;_self.mobile = _json.jsonRead("mobile");_self.name = _json.jsonRead("name");_self.account = _json.jsonRead("account");_self.remark = _json.jsonRead("remark");return true;}/***@desc toJson for Consumer*      Generated by juzhen SolidityStructTool automatically.*      Not to edit this code manually.*/function toJson(Consumer storage _self) internal constant returns (string _json) {LibStack.push("{");LibStack.appendKeyValue("mobile", _self.mobile);LibStack.appendKeyValue("name", _self.name);LibStack.appendKeyValue("account", _self.account);LibStack.appendKeyValue("remark", _self.remark);LibStack.append("}");_json = LibStack.pop();}/***@desc fromJsonArray for Consumer*      Generated by juzhen SolidityStructTool automatically.*      Not to edit this code manually.*/function fromJsonArray(Consumer[] storage _self, string _json) internal returns(bool succ) {_self.length = 0;if (!_json.isJson())return false;while (true) {string memory key = "[".concat(_self.length.toString(), "]");if (!_json.jsonKeyExists(key))break;_self.length++;_self[_self.length-1].fromJson(_json.jsonRead(key));}return true;}/***@desc toJsonArray for Consumer*      Generated by juzhen SolidityStructTool automatically.*      Not to edit this code manually.*/function toJsonArray(Consumer[] storage _self) internal constant returns(string _json) {_json = _json.concat("[");for (uint i=0; i<_self.length; ++i) {if (i == 0)_json = _json.concat(_self[i].toJson());else_json = _json.concat(",", _self[i].toJson());}_json = _json.concat("]");}/***@desc update for Consumer*      Generated by juzhen SolidityStructTool automatically.*      Not to edit this code manually.*/function update(Consumer storage _self, string _json) internal returns(bool succ) {if (!_json.isJson())return false;if (_json.jsonKeyExists("mobile"))_self.mobile = _json.jsonRead("mobile");if (_json.jsonKeyExists("name"))_self.name = _json.jsonRead("name");if (_json.jsonKeyExists("account"))_self.account = _json.jsonRead("account");if (_json.jsonKeyExists("remark"))_self.remark = _json.jsonRead("remark");return true;}/***@desc reset for Consumer*      Generated by juzhen SolidityStructTool automatically.*      Not to edit this code manually.*/function reset(Consumer storage _self) internal {delete _self.mobile;delete _self.name;delete _self.account;delete _self.remark;}}
  • toJson(Consumer storage _self) struct结构序列化为JSON格式:{"mobile":"xxx",...}.
  • fromJson(Consumer storage _self, string _json) 将一个JSON串反序列为struct结构.
  • fromJsonArray(Consumer[] storage _self, string _json),将一个数组形式的JSON串转为数据struct结构
  • toJsonArray(Consumer[] storage _self) 数组结构反序列化,eg.[{"mobile":"xxx",...},...]
  • reset(Consumer _self) 重置struct中为默认值.

业务合约编写

说明:顾客管理合约的主要业务逻辑,即合约接口的实现类. ConsumerManager.sol,该合约继承了基础合约OwnerNamed以及抽象合约IConsumerManager

  • OwnerNamed 主要提供一些基础操作,主要包含模块注册、合约注册、数据写入DB等操作,所有业务合约需按规定继承该合约。

文件目录:${workspace}/contracts 用于存放业务合约主体逻辑

pragma solidity ^0.4.2;import "./library/LibConsumer.sol";
import "./sysbase/OwnerNamed.sol";
import "./interfaces/IConsumerManager.sol";
import "./interfaces/IUserManager.sol";
import "./utillib/LibLog.sol";contract ConsumerManager is OwnerNamed, IConsumerManager {using LibConsumerfor * ;using LibStringfor * ;using LibIntfor * ;using LibLogfor * ;event Notify(uint _errno, string _info);LibConsumer.Consumer[] consumerList;mapping(string => uint) keyMap;//定义错误信息enum ErrorNo {NO_ERROR,BAD_PARAMETER,MOBILE_EMPTY,USER_NOT_EXISTS,MOBILE_ALREADY_EXISTS,ACCOUNT_ALREDY_EXISTS,NO_PERMISSION}// 构造函数,在合约发布时会被触发调用function ConsumerManager() {LibLog.log("deploy ConsumerModule....");//把合约注册到JUICE链上, 参数必须和ConsumerModule.sol中的保持一致register("ConsumerModule", "0.0.1.0", "ConsumerManager", "0.0.1.0");//或者注册到特殊的模块"juzix.io.debugModule",这样用户就不需要编写模块合约了//register("juzix.io.debugModule", "0.0.1.0", "ConsumerManager", "0.0.1.0");}function add(string _mobile, string _name, string _account, string _remark) public returns(uint) {LibLog.log("into add..", "ConsumerManager");LibLog.log("ConsumerManager into add..");if (_mobile.equals("")) {LibLog.log("Invalid mobile.", "ConsumerManager");errno = 15200 + uint(ErrorNo.MOBILE_EMPTY);Notify(errno, "顾客手机号为空,插入失败.");return errno;}if (keyMap[_mobile] == 0) {if (consumerList.length > 0) {if (_mobile.equals(consumerList[0].mobile)) {LibLog.log("mobile aready exists", "ConsumerManager");errno = 15200 + uint(ErrorNo.MOBILE_ALREADY_EXISTS);Notify(errno, "顾客手机号已存在,插入失败.");return errno;}}} else {LibLog.log("mobile aready exists", "ConsumerManager");errno = 15200 + uint(ErrorNo.MOBILE_ALREADY_EXISTS);Notify(errno, "顾客手机号已存在,插入失败.");return errno;}uint idx = consumerList.length;consumerList.push(LibConsumer.Consumer(_mobile, _name, _account, _remark));keyMap[_mobile] = idx;errno = uint(ErrorNo.NO_ERROR);LibLog.log("add a consumer success", "ConsumerManager");Notify(errno, "add a consumer success");return errno;}function deleteByMobile(string _mobile) public returns(uint) {LibLog.log("into delete..", "ConsumerManager");//合约拥有者,才能删除顾客信息if (tx.origin != owner) {LibLog.log("msg.sender is not owner", "ConsumerManager");LibLog.log("operator no permission");errno = 15200 + uint(ErrorNo.NO_PERMISSION);Notify(errno, "无操作权限,非管理员");return;}//顾客列表不为空if (consumerList.length > 0) {if (keyMap[_mobile] == 0) {//_mobile不存在,或者是数组第一个元素if (!_mobile.equals(consumerList[0].mobile)) {LibLog.log("consumer not exists: ", _mobile);errno = 15200 + uint(ErrorNo.USER_NOT_EXISTS);Notify(errno, "顾客手机号不存在,删除失败.");return;}}} else {LibLog.log("consumer list is empty: ", _mobile);errno = 15200 + uint(ErrorNo.USER_NOT_EXISTS);Notify(errno, "顾客列表为空,删除失败.");return;}//数组总长度uint len = consumerList.length;//此用户在数组中的序号uint idx = keyMap[_mobile];if (idx >= len) return;for (uint i = idx; i < len - 1; i++) {//从待删除的数组element开始,把后一个element移动到前一个位置consumerList[i] = consumerList[i + 1];//同时修改keyMap中,对应key的在数组中的序号keyMap[consumerList[i].mobile] = i;}//删除数组最后一个元素(和倒数第二个重复了)delete consumerList[len - 1];//删除mapping中元素,实际上是设置value为0delete keyMap[_mobile];//数组总长度-1consumerList.length--;LibLog.log("delete user success.", "ConsumerManager");errno = uint(ErrorNo.NO_ERROR);Notify(errno, "删除顾客成功.");}function listAll() constant public returns(string _json) {uint len = 0;uint counter = 0;len = LibStack.push("");for (uint i = 0; i < consumerList.length; i++) {if (counter > 0) {len = LibStack.append(",");}len = LibStack.append(consumerList[i].toJson());counter++;}len = itemsStackPush(LibStack.popex(len), counter);_json = LibStack.popex(len);}function itemsStackPush(string _items, uint _total) constant private returns(uint len) {len = 0;len = LibStack.push("{");len = LibStack.appendKeyValue("result", uint(0));len = LibStack.appendKeyValue("total", _total);len = LibStack.append(",\"data\":[");len = LibStack.append(_items);len = LibStack.append("]");len = LibStack.append("}");return len;}
}

模块合约

说明:模块合约是JUICE区块链中,为了管理用户的业务合约,以及为了管理DAPP和业务的关系而引入的。开发者在实现业务合约后,必须编写一个或多个模块合约,并在模块合约中说明本模块中用到的业务合约。从DAPP的角度来理解,就是一个DAPP必须对应一个模块,一个DAPP能调用的业务合约,必须在DAPP对应的模块合约中说明。

模块合约继承了基础模块合约BaseModule

  • BaseModule 主要提供一些基础操作,主要包含:模块新增、合约新增、角色新增等操作.

文件目录:${workspace}/contracts 用于存放业务模块合约主体逻辑

/*** @file      ConsumerModule.sol* @author    JUZIX.IO* @time      2017-12-11* @desc      给用户展示如何编写一个自己的模块。*            ConsumerModule本身也是一个合约,它需要部署到链上;同时,它又负责管理用户的合约。只有添加到模块中的用户合约,用户才能在dapp中调用这些合约*/
pragma solidity ^ 0.4 .2;//juice的管理库,必须引入
import "./sysbase/OwnerNamed.sol";
import "./sysbase/BaseModule.sol";//juice提供的模块库,必须引入
import "./library/LibModule.sol";//juice提供的合约库,必须引入
import "./library/LibContract.sol";//juice提供的string库
import "./utillib/LibString.sol";//juice提供的log库
import "./utillib/LibLog.sol";contract ConsumerModule is BaseModule {using LibModulefor * ;using LibContractfor * ;using LibStringfor * ;using LibIntfor * ;using LibLogfor * ;LibModule.Module tmpModule;LibContract.Contract tmpContract;//定义Demo模块中的错误信息enum MODULE_ERROR {NO_ERROR}//定义Demo模块中用的事件,可以用于返回错误信息,也可以返回其他信息event Notify(uint _code, string _info);// module : predefined datafunction ConsumerModule() {//定义模块合约名称string memory moduleName = "ConsumerModule";//定义模块合约名称string memory moduleDesc = "顾客模块";//定义模块合约版本号string memory moduleVersion = "0.0.1.0";//指定模块合约ID//moduleId = moduleName.concat("_", moduleVersion);string memory moduleId = moduleName.concat("_", moduleVersion);//把合约注册到JUICE链上LibLog.log("register DemoModule");register(moduleName, moduleVersion);//模块名称,只是JUICE区块链内部管理模块使用,和moduleText有区别tmpModule.moduleName = moduleName;tmpModule.moduleVersion = moduleVersion;tmpModule.moduleEnable = 0;tmpModule.moduleDescription = moduleDesc;//显示JUICE开放平台,我的应用列表中的DAPP名字tmpModule.moduleText = moduleDesc;uint nowTime = now * 1000;tmpModule.moduleCreateTime = nowTime;tmpModule.moduleUpdateTime = nowTime;tmpModule.moduleCreator = msg.sender;//这里设置用户DAPP的连接地址(目前DAPP需要有用户自己发布、部署到公网上)tmpModule.moduleUrl = "http://host.domain.com/youDapp/";tmpModule.icon = "";tmpModule.publishTime = nowTime;//把模块合约本身添加到系统的模块管理合约中。这一步是必须的,只有这样,用户的dapp才能调用添加到此模块合约的相关合约。//并在用户的“我的应用”中展示出来LibLog.log("add ConsumerModule to SysModule");uint ret = addModule(tmpModule.toJson());if (ret != 0) {LibLog.log("add ConsumerModule to SysModule failed");return;}//添加用户合约到模块合约中LibLog.log("add ConsumerManager to ConsumerModule");ret = initContract(moduleName, moduleVersion, "ConsumerManager", "顾客管理合约", "0.0.1.0");if (ret != 0) {LibLog.log("add ConsumerManager to ConsumerModule failed");return;}//返回消息,以便控制台能看到是否部署成功Notify(1, "deploy ConsumerModule success");}/*** 初始化用户自定义合约。* 如果用户有多个合约文件,则需要多次调用此方法。* @param moduleName        约合所属模块名* @param moduleVersion     约合所属模块版本* @param contractName      约合名* @param contractDesc      约合描述* @param contractVersion   约合版本* @return return 0 if success;*/function initContract(string moduleName, string moduleVersion, string contractName, string contractDesc, string contractVersion) private returns(uint) {tmpContract.moduleName = moduleName;tmpContract.moduleVersion = moduleVersion;//合约名称tmpContract.cctName = contractName;//合约描述tmpContract.description = contractDesc;//合约版本tmpContract.cctVersion = contractVersion;//保持falsetmpContract.deleted = false;//保持0tmpContract.enable = 0;uint nowTime = now * 1000;//合约创建时间tmpContract.createTime = nowTime;//合约修改时间tmpContract.updateTime = nowTime;//合约创建人tmpContract.creator = msg.sender;//预约块高tmpContract.blockNum = block.number;uint ret = addContract(tmpContract.toJson());return ret;}}
  • 模块合约作用:当进行一个新的DAPP开发时会伴随着一些合约的业务服务的编写,即,合约为DAPP应用提供业务逻辑的服务,我们将这一类(或一组)合约统一归属到一个模块中(eg:HelloWorldModuleMgr)。在JUICE区块链平台上有一套鉴权体系,一个合约要被成功调用需要经过多层鉴权:
  • 校验模块开关,开:继续鉴权,关:直接通过
  • 校验合约开关,开:继续鉴权,关:直接通过
  • 检验函数开关,开:继续鉴权,关:直接通过
  • 校验用户是否存在,存在则访问通过,不存在则鉴权失败

注意:如果是合约发布者owner(超级管理员)则不需要鉴权可直接通过。

  • HelloWorldModuleMgr该合约的主要功能就是做数据的初始化操作,当合约被发布时触发构造函数的调用。
  • 添加一个新的模块到角色过滤器(默认过滤器)
  • 添加绑定合约与模块的关系
  • 添加菜单(新的DAPP如果需要菜单-如:用户管理)
  • 添加权限,合约中的每个函数操作都是一个Action,如果需要访问就需要进行配置;
  • 添加角色,初始化某些角色到模块中,并绑定对应的权限到角色上;

编译部署、测试

编译部署

业务合约,模块合约编写完成后

  • 首先,处理业务合约

1.编译业务合约,编译成功后,在控制台分别复制出ABIBIN,并分别保存到contracts/ConsumerManager.abicontracts/ConsumerManager.bin文本文件中。这两个文件,可以用web3j生成调用业务合约的JAVA代理类,这个在编写DAPP时有用,因此在编译阶段就先保存这两个文件。(注:JUICE客户端的后续版本中,将在编译业务合约时,直接生成JAVA代理类,开发者不用再手工保存bin/abi,再手工生成JAVA代理类)

2.部署业务合约

  • 然后,处理模块合约

1.编译模块合约。编译成功后的的bin/abi,不需要保存。

2.部署模块合约

测试

JUICE客户端中,选择需要测试的业务合约,以及相应的业务方法,然后填写输入参数,即可运行。用户可观察控制台的日志输出,来判断业务方法是否执行成功。

参考内容:https://open.juzix.net/doc

智能合约开发教程视频:http://edu.51cto.com/course/13403.html

 

转载于:https://www.cnblogs.com/shjuzhen/p/9273994.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/266734.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高考英语口语测试软件,练习英语口语的app有哪些

随着国际化的发展&#xff0c;英语越来越普遍。英语中口语也是很重要的成分之一。也是跟老外交流或者英语考试的重要考点之一。我们有很多方法锻炼口语。什么方法最快捷最有效方便&#xff1f;下面我们看看几款最受欢迎的英语口语练习app。练习口语的app1、口语侠一款非常实用的…

chkconfig命令会立即生效吗_UG绘制波纹管,整体变形命令你会用吗?

最近又有小伙伴问我波纹管怎么画了&#xff0c;这个以前教过大家&#xff0c;文章名字叫饮料吸管和排水管&#xff0c;看来这个名字不够专业&#xff0c;大家找不到啊&#xff0c;今天就来重新绘制一个&#xff0c;用一个新的方法&#xff0c;大家来额外学习一下&#xff0c;了…

Windows Phone 8 开发资源汇总

1) 提前下载安装包&#xff0c;离线安装https://dev.windowsphone.com/en-us/downloadsdk 2&#xff09;在线安装地址http://www.microsoft.com/en-us/download/details.aspx?id354713) 案例代码&#xff1a;Windows Phone Samples: learn through code http://code.msdn.micr…

vue中mixin 感觉很牛逼(父子组件融合成一个新组件)

vue中提供了一种混合机制--mixins&#xff0c;用来更高效的实现组件内容的复用。最开始我一度认为这个和组件好像没啥区别。。后来发现错了。下面我们来看看mixins和普通情况下引入组件有什么区别&#xff1f; 组件在引用之后相当于在父组件内开辟了一块单独的空间&#xff0c;…

C语言入门日记

参考&#xff1a;C语言入门日记 作者&#xff1a;9art0 发布时间&#xff1a;2020-08-30 16:37:46 网址&#xff1a;https://blog.csdn.net/GatoWong/article/details/108307915?spm1001.2014.3001.5501 C语言入门日记1.1.C语言搭建及system函数1.2.C语言的基础框架解释1.3.C程…

测试电视是不是4k的软件,怎么判断4K电视真假?教你快速检测的方法!

原标题&#xff1a;怎么判断4K电视真假?教你快速检测的方法!4K电视从进入市场之后一直都受到企业的力捧&#xff0c;随着电视企业对4K电视的大力度宣传和消费环境的逐渐成熟&#xff0c;越来越多的消费者开始认可4K电视&#xff0c;并在购机时表明首选4K电视。4K电视顾名思义就…

等压线上怎么画风向_战场上骑兵应该怎么拔刀?从清人佩刀为何总是刀柄向后说起...

在以前的文章《兵器谱|挂错地方死得快&#xff01;图说骑兵马刀的佩带与悬挂》&#xff0c;有读者问&#xff1a;清代的武将大多数都是刀柄朝右边&#xff0c;挂在屁股后面&#xff0c;算不算双附耳式悬挂法的一个变种呢&#xff1f;这样到底是不是真的有助于马上拔刀&#xff…

整合axis2到web项目中

1.将axis2.war发布到%TOMCAT_HOME%\webapps中&#xff0c;发布后的目录为%TOMCAT_HOME%\webapps\axis2 &#xff0c;该目录定义为%AXIS2_HOME%. 2.拷贝%AXIS2_HOME%\WEB-INF目录下所有内容到%项目App%\WEB-INF目录下。 3.修改%项目APP%\WEB-INF目录下的web.xml文件&#xff0c…

win10计算机跑分,鲁大师如何跑分_鲁大师跑分详细教程

很多小伙伴都知道鲁大师可以测试电脑的性能&#xff0c;就是大家称为的跑分&#xff0c;跑分的大小跟电脑的性能成正比&#xff0c;跑的越高性能越好。那么该如何在鲁大师里面进行跑分测试呢?其实只要打开性能测试就可以了&#xff0c;具体的教程下面一起来看看吧。鲁大师跑分…

第二十二篇 玩转数据结构——构建动态数组

1.. 数组基础数组就是把数据码成一排进行存放。Java中&#xff0c;数组的每个元素类型必须相同&#xff0c;可以都为int类型&#xff0c;string类型&#xff0c;甚至是自定义类型。数组的命名要语义化&#xff0c;例如&#xff0c;如果数组用来存放学生的成绩&#xff0c;那么命…

C语言函数日记

参考&#xff1a;C语言-函数-日记 作者&#xff1a;9art0 发布时间&#xff1a;2020-08-31 00:57:06 网址&#xff1a;https://blog.csdn.net/GatoWong/article/details/108314011?spm1001.2014.3001.5501 C语言-函数-日记2.1.函数概述一2.2.函数概述二2.3.函数无返回参数&…

python运行调出控制台_python.exe 和 pythonw.exe 的区别

(区分.py、.pyw、.pyc 文件)最近也在学习python&#xff0c;针对python中的一些问题做下记录&#xff0c;希望大家共同成长&#xff1b; Windows系统搭建好Python的环境后&#xff0c;进入Python的安装目录&#xff0c;大家会发现目录中有python.exe和pythonw.exe两个程序。 …

Vim的6种模式

Vim的6种模式|微刊 - 悦读喜欢Vim的6种模式大多数人都知道 Vim的两种模式, 正常模式和 插入模式,实际上 Vim有六种模式.- 正常模式 (Normal mode): 用户输入命令的模式- 可视化模式 (Visual mode): 可视化区块被高亮的时候- 选择模式 (Select mode): 类似可视模式 但是用用户输…

修复计算机u盘,u盘损坏怎么修复 u盘损坏修复方法

造成u盘损坏的原因有很多&#xff0c;比如我们在拔出u盘前&#xff0c;如果没有先将u盘弹出或者弹出的不彻底&#xff0c;就很可能造成u盘的损坏。u盘损坏就会导致计算机无法识别出u盘。那u盘损坏了能不能修复?如果能修复&#xff0c;那要怎么操作?下面就让我们一起来看看u盘…

.net 技术类网址

本人在工作学习中遇到的一些技术的网站&#xff0c;讲的也很详细&#xff0c;与各位共享&#xff0c;希望对您有帮助EF实体类&#xff1a; https://msdn.microsoft.com/zh-cn/library/gg696172(vvs.103).aspx数据库&#xff1a; https://docs.microsoft.com/zh-cn/sql/ssms/dow…

433M射频灯

参考&#xff1a;433M射频灯 作者&#xff1a;9art0 发布时间&#xff1a;2020-09-21 21:57:29 网址&#xff1a;https://blog.csdn.net/GatoWong/article/details/108700989?spm1001.2014.3001.5501 目录433M射频灯硬件接线1. KEIL建立工程2. 电子可编程开关继电器原理3. 上电…

2清空所有表_拉链表(二)

拉链表&#xff08;一&#xff09;拉链表&#xff08;二&#xff09;一、前言在上一节简单介绍了拉链表&#xff0c;本节主要讲解如何通过binlog采集MySQL的数据并且按月分区的方式实现拉链表。这里以上节介绍的用户表(user) 举例二、涉及到的表1. 原始表&#xff08;user&…

出生日期范围的Sql语句_【呕心总结】python如何与mysql实现交互及常用sql语句

9 月初&#xff0c;我对 python 爬虫 燃起兴趣&#xff0c;但爬取到的数据多通道实时同步读写用文件并不方便&#xff0c;于是开始用起mysql。这篇笔记&#xff0c;我将整理近一个月的实战中最常用到的 mysql 语句&#xff0c;同时也将涉及到如何在python3中与 mysql 实现数据交…

iphone的生命周期

ios应用的生命周期在开发过程中我们需要一些全局对象来将程序的各个部分连接起来&#xff0c;这些全局对象中最重要的就是UIApplication对象。但在实际编程中我们并不直接和UIApplication对象打交道&#xff0c;而是和其代理打交道。UIApplication 是iPhone应用程序的开始并且负…

vb发送邮箱连接服务器失败,VB:如何用需要身份验证的SMTP邮件服务器发信

Option Explicit需要引用 Microsoft CDO for Windows 2000 Library和 Microsoft ActiveX Data Objects 2.5 LibraryDim objConfig As CDO.ConfigurationDim objMessage As CDO.MessageDim Fields As ADODB.Fields Get a handle on the config object and its fieldsSe…