博客

  • WordPress出现“Error Establishing a Database Connection”错误解决方法

    WordPress绝对是当下功能比较强大的内容管理系统,但它也会出现相当一部分的错误。其中大家经常遇到的错误就是“Error Establishing a Database Connection(建立数据库连接错误)”。那么WordPress出现“Error Establishing a Database Connection”错误如何解决呢,本文就带大家简单了解下。

    1、损坏的数据库

    首先检查错误是否是数据库损坏的结果。如果数据库已损坏,尝试所有其他解决方案将是徒劳的。

    如何检查您的WordPress数据库是否已损坏很容易。导航到 yousite.com/wp-admin/,不要忘记用你的网站域名替换 yoursite.com。如果收到“建立数据库连接错误”错误,则表明数据库运行状况良好。但是,如果遇到其他错误,例如“一个或多个数据库表不可用……”,则数据库已损坏。如果yoursite.com/ wp-admin显示其他错误,则可能是数据库损坏。

    2、检查您的数据库凭据

    要与数据库建立连接,WordPress网站必须通过wp-config.php文件提供准确的登录详细信息 。如果这没有发生,您将看到“Error establishing a database connection”。

    3、检查数据库用户名,密码和主机名

    对于以下部分,创建一个.PHP文件,该文件将保存在WordPress根目录中(在wpconfig.php文件所在的文件夹中)。

    打开你喜欢的代码编辑器,然后将以下代码添加到新文件中:

    <?php
    $test = mysqli_connect('localhost', 'db_user', 'db_password');
    if (!$test) {
    die('MySQL Error: ' . mysqli_error());
    }
    echo 'Database connection is working properly!';
    mysqli_close($testConnection);

    请将文件另存为check.php或任何您想要的文件;确保它是一个PHP文件。此外,确保替换localhost, DB_USER和DB_PASSWORD的相应值。

    4、修复损坏的WordPress文件

    由于WordPress文件损坏,许多用户收到“Error establishing a database connection”错误。如果你在网站上添加了一些文件并出现错误,请删除这些有问题的文件,那一切就好了。

    但是,很难确切地知道哪些文件已损坏,尤其是对于初学者而言。别担心,因为替换损坏的WordPress文件很简单。

    同时,需要注意的是,因为此处的一个小错误可能会破坏您的整个网站。因此,在继续操作之前,请确保备份整个网站,即文件和数据库。

    备份好之后,从WordPress.org下载WordPress的新副本。然后解压缩计算机上的WordPress软件包,并删除wp-content文件夹以及wp-config-sample.php文件。这样可以确保您不会覆盖主题,插件和现有的wp-config.php文件。

    接下来,使用FileZilla等FTP程序将其余文件上传到WordPress根目录。这样做将替换所有有问题的WordPress核心文件。

    之后,清除浏览器缓存,然后尝试重新加载网站。如果一切顺利,您将再也不会遇到“Error establishing a database connection”的错误。

  • One or more database tables are unavailable. The database may need to be repaired. 怎么处理

    当你的wordpress网站出现

    One or more database tables are unavailable. The database may need to be repaired.

    这个问的时候,最大的原因是 wp_options 卡住了

    处理办法,进到数据库管理,然后选中 wp_options 选择“修复表” 就🆗啦 。

  • 密码保护:115道MySQL面试题(含答案),从简单到深入!

    此内容受密码保护。如需查阅,请在下方输入密码。

  • 密码保护:PHP面试题

    此内容受密码保护。如需查阅,请在下方输入密码。

  • Solidity语法大致总结

     

    一、数据类型

    1.1、值类型

    1.1.1、布尔

    pragma solidity ^0.4.25;
    
    contract TestBool {
        bool flag;
        int num1 = 100;
        int num2 = 200;
        // default false
        function getFlag() public view returns(bool) {
            return flag;  // false
        }
        // 非
        function getFlag2() public view returns(bool) {
            return !flag;  // true
        }
        // 与
        function getFlagAnd() public view returns(bool) {
            return (num1 != num2) && !flag;  // true
        }
        // 或
        function getFlagOr() public view returns(bool) {
            return (num1 == num2) || !flag;  // true
        }
    }
    
    1.1.2、整数

    加减乘除、取余、幂运算,

    pragma solidity ^0.4.25;
    
    // 整型特性与运算
    contract TestInteger {
      int num1; // 有符号整型 int256
      uint num2; // 无符号整型 uint256
    
      function add(uint _a, uint _b) public pure returns(uint) {
          return _a + _b;
      }
      function sub(uint _a, uint _b) public pure returns(uint) {
          return _a - _b;
      }
      function mul(uint _a, uint _b) public pure returns(uint) {
          return _a * _b;
      }
      function div(uint _a, uint _b) public pure returns(uint) {
          return _a / _b;  // 在solidity中,除法是做整除,没有小数点
      }
      function rem(uint _a, uint _b) public pure returns(uint) {
          return _a % _b;
      }
      function square(uint _a, uint _b) public pure returns(uint) {
          return _a ** _b;  // 幂运算在0.8.0之后,变为右优先,即a ** b ** c => a ** (b ** c)
      }
      function max() public view returns(uint) {
          return uint(-1);
          // return type(uint).max;  // 0.8不再允许uint(-1)
      }
    }

    位运算,

    pragma solidity ^0.4.25;
    
    // 位运算
    contract TestBitwise {
      uint8 num1 = 3;
      uint8 num2 = 4;
    
      function bitAdd() public view returns(uint) {
          return num1 & num2;
      }
      function bitOr() public view returns(uint) {
          return num1 | num2;
      }
      function unBit() public view returns(uint) {
          return ~num1;
      }
      function bitXor() public view returns(uint) {
          return num1 ^ num2;
      }
      function bitRight() public view returns(uint) {
          return num1 >> 1;
      }
      function bitLeft() public view returns(uint) {
          return num1 << 1;
      }
    }

    1.1.3、定长浮点型

    目前只支持定义,不支持赋值使用,

    fixed num; // 有符号
    ufixed num; // 无符号

    1.1.4、地址类型

    address addr = msg.sender;
    address addr = 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF;

    1.1.5、合约类型

    在合约TestType中使用TestBitwise合约,

    TestBitwise t = TestBitwise(addr);

    1.1.6、枚举类型

    enum ActionChoices { Up, Down, Left, Right }

    1.1.7、定长字节数组

    pragma solidity ^0.4.25;
    
    // 固定长度的字节数组(静态),以及转换为string类型
    contract TestBytesFixed {
        // public 自动生成同名的get方法
        bytes1 public num1 = 0x7a;  // 1 byte = 8 bit
        bytes1 public num2 = 0x68;
        bytes2 public num3 = 0x128b;  // 2 byte = 16 bit
        // 获取字节数组长度
        function getLength() public view returns(uint) {
            return num3.length; // 2
        }
        // 字节数组比较
        function compare() public view returns(bool) {
            return num1 < num2;
        }
        // 字节数组位运算
        function bitwise() public view returns(bytes1,bytes1,bytes1) {  // 多返回值
            return ((num1 & num2), (num1 | num2), (~num1));
        }
    
        // 先转为bytes动态数组,再通过string构造  0x7a7a -> zz
        function toString(bytes2 _val) public pure returns(string) {
            bytes memory buf = new bytes(_val.length);
            for (uint i = 0; i < _val.length; i++) {
                buf[i] = _val[i];
            }
            return string(buf);
        }
    }
    
    固定长度字节数组的扩充和压缩,
    pragma solidity ^0.4.25;
    
    // 固定长度的字节数组(静态)扩容和压缩
    contract TestBytesExpander {
        // public 自动生成同名的get方法
        bytes6 name = 0x796f7269636b;
    
        function changeTo1() public view returns(bytes1) {
            return bytes1(name); // 0x79
        }
        function changeTo2() public view returns(bytes2) {
            return bytes2(name); // 0x796f
        }
        function changeTo16() public view returns(bytes16) {
            return bytes16(name); // 0x796f7269636b00000000000000000000
        }
    }
    
    1.1.8、函数类型
    function (<parameter types>) {internal|external|public|private} [pure|constant|view|payable] [returns ()]

    1.2、引用类型

    1.2.1、字符串

    pragma solidity ^0.4.25;
    
    // 修改string类型的数据
    contract TestString {
        string name = 'yorick';  // 字符串可以使用单引号或者双引号
        string name2 = "!@#$%^&";  // 特殊字符占1个byte
        string name3 = "张三";  // 中文在string中使用utf8的编码方式存储,占用3个byte
    
        function getLength() view public returns(uint) {
            // 不可以直接获取string的length
            return bytes(name).length; // 6
        }
        function getLength2() view public returns(uint) {
            return bytes(name2).length; // 7
        }
        function getLength3() view public returns(uint) {
            return bytes(name3).length; // 6
        }
    
        function getElmName() view public returns(bytes1) {
            // 不可以直接通过数组下标name[0]获取
            return bytes(name)[0]; // 0x79
        }
        function changeElmName() public {
            bytes(name)[0] = "h";
        }
        function getName() view public returns(bytes) {
            return bytes(name);
        }
    }

    1.2.2、变长字节数组

    pragma solidity ^0.4.25;
    
    // 动态的字节数组,以及转换为string类型
    contract TestBytesDynamic {
        bytes public dynamicBytes;
    
        function setDynamicBytes(string memory val) public {
            dynamicBytes = bytes(val);
        }
        function getVal() public view returns(string){
            return string(dynamicBytes);
        }
    }
    
    1.2.3、数组

    定长数组,

    pragma solidity ^0.4.25;
    
    // 定长数组
    contract TestArrFixed {
    
        uint[5] arr = [1,2,3,4,5];
    
        // 修改数组元素内容
        function modifyElements() public {
            arr[0] = 12;
            arr[1] = 14;
        }
        // 查看数组
        function watchArr() public view returns(uint[5]) {
            return arr;
        }
        // 数组元素求和计算
        function sumArr() public view returns(uint) {
            uint sum = 0;
            for (uint i = 0; i < arr.length; i++) {
                sum += arr[i];
            }
            return sum;
        }
    
        // 数组长度
        function getLength() public view returns(uint) {
            return arr.length;
        }
    
        // delete重置数组某下标的元素值,不会真正删除该元素
        function deleteElm(uint idx) public {
            delete arr[idx];
        }
        // delete重置整个数组
        function deleteArr() public {
            delete arr;
        }
    
        /**  定长数组不允许改变长度和push
        // 压缩数组后,右侧多余元素被丢弃
        function changeLengthTo1() public {
            arr.length = 1;
        }
        // 扩容数组后,右侧元素补0
        function changeLengthTo10() public {
            arr.length = 10;
        }
        // 追加新元素
        function pushElm(uint _elm) public {
            arr.push(_elm);
        }
        */
    }

    变长数组,

    pragma solidity ^0.4.25;
    
    // 变长数组
    contract TestArrDynamic {
    
        uint[] arr = [1,2,3,4,5];
    
        // 查看数组
        function watchArr() public view returns(uint[]) {
            return arr;
        }
        // 数组长度
        function getLength() public view returns(uint) {
            return arr.length;
        }
        // 压缩数组后,右侧多余元素被丢弃
        function changeLengthTo1() public {
            arr.length = 1;
        }
        // 扩容数组后,右侧元素补0
        function changeLengthTo10() public {
            arr.length = 10;
        }
        // 追加新元素
        function pushElm(uint _elm) public {
            arr.push(_elm);
        }
        // delete重置数组某下标的元素值,不会真正删除该元素
        function deleteElm(uint idx) public {
            delete arr[idx];
        }
        // delete重置整个数组
        function deleteArr() public {
            delete arr;
        }
    }

    二维数组,

    pragma solidity ^0.4.25;
    
    /**
    二维数组
    solidity的二维数组与其他语言不同,[2] [3]表示3行2列,而其他语言为2行3列;
    二维动态数组与一维数组类似,可以改变其数组长度;
    */
    contract TestArr2Dimensional {
        uint[2][3] arr = [[1,2],[3,4],[5,6]];
        function getRowSize() public view returns(uint) {
            return arr.length; // 3
        }
        function getColSize() public view returns(uint) {
            return arr[0].length; // 2
        }
        function watchArr() public view returns(uint[2][3]) {
            return arr;  // 1,2,3,4,5,6
        }
        function sumArr() public view returns(uint) {
            uint sum = 0;
            for (uint i = 0; i < getRowSize(); i++) {
                for (uint j = 0; j < getColSize(); j++) {
                    sum += arr[i][j];
                }
            }
            return sum;
        }
        function modifyArr() public {
            arr[0][0] = 99;
        }
    }

    数组字面值,

    pragma solidity ^0.4.25;
    
    // 数组字面值
    contract TestArrLiteral {
        // 最小存储匹配,未超过255,所以使用uint8存储
        function getLiteral8() pure public returns(uint8[3]) {
            return [1,2,3];
        }
        // 超过255,所以使用uint16存储
        function getLiteral16() pure public returns(uint16[3]) {
            return [256,2,3];  // [255,2,3] 不被uint16允许
        }
        // 强制转换为uint256
        function getLiteral256() pure public returns(uint[3]) {
            return [uint(1),2,3];  // 给任意元素强转即可,否则不被允许
        }
        // 计算外界传入的内容
        function addLiterals(uint[3] arr) pure external returns(uint) {
            uint sum = 0;
            for (uint i = 0; i < arr.length; i++) {
                sum += arr[i];
            }
            return sum;
        }
    }

    1.2.4、结构体

    pragma solidity ^0.4.25;
    
    // 结构体初始化的两种方法
    contract TestStruct {
        struct Student {
            uint id;
            string name;
            mapping(uint=>string) map;
        }
    
        // 默认为storage类型,只能通过storage类型操作结构体中的mapping类型数据
        Student storageStu;
    
        // mapping类型可以不用在定义的时候赋值,而其他类型不赋值则会报错
        function init() public pure returns(uint, string) {
            Student memory stu = Student(100, "Jay");
            return (stu.id, stu.name);
        }
        function init2() public pure returns(uint, string) {
            Student memory stu = Student({name: "Jay", id: 100});
            return (stu.id, stu.name);
        }
        function init3() public returns(uint, string, string) {
            Student memory stu = Student({name: "Jay", id: 100});
            // 直接操作结构体中的mapping不被允许: Student memory out of storage
            // stu.map[1] = "artist";
            // 通过storage类型的变量操作结构体中的mapping
            storageStu = stu;
            storageStu.map[1] = "artist";
            return (storageStu.id, storageStu.name, storageStu.map[1]);
        }
    
        // 结构体作为入参时,为memory类型,并且不能使用public或external修饰函数:Internal or recursive type is not allowed for public or external functions.
        // 赋值时也要指定为memory,否则报错
        function testIn(Student stu) internal returns(uint) {
            return stu.id;
        }
        // 结构体作为出参,同样只能private或internal声明内部使用
        function testOut(Student stu) private returns(Student) {
            return stu;
        }
    }
    
    1.3、映射
    contract TestMapping {
        mapping(address => uint) private scores;  // <学生,分数>的单层映射
        mapping(address => mapping(bytes32 => uint8)) private _scores;  // <学生,<科目,分数>>的两层映射
        function getScore() public view returns(address, uint) {
            address addr = msg.sender;
            return (addr, scores[addr]);
        }
        function setScore() public {
            scores[msg.sender] = 100;
        }
    }

    二、作用域(访问修饰符)

    contract TestAccessCtrl {
        constructor () public {}
        uint public num1 = 1;  // 自动为public生成同名的get函数,但在编码时不可直接调用num1()
        uint private num2 = 2;
        uint num3 = 3;  // 不写则默认private
        function funcPublic() public returns(string) {
            return "public func";
        }
        function funcPrivate() private returns(string) {
            return "private func";
        }
        function funcInternal() internal returns(string) {
            return "internal func";
        }
        function funcExternal() external returns(string) {
            return "external func";
        }
        function test1(uint choice) public returns(string) {
            if (choice == 1) return funcPublic();
            if (choice == 2) return funcPrivate();
            if (choice == 3) return funcInternal();
            //if (choice == 4) return funcExternal();  // external不允许直接在内部用
            if (choice == 4) return this.funcExternal();  // 间接通过this才可以调用external
        }
    }
    contract TestAccessCtrlSon is TestAccessCtrl {
        function test2(uint choice) public returns(string) {
            if (choice == 1) return funcPublic();  // public允许派生合约使用
            //if (choice == 2) return funcPrivate();  // private不允许派生合约使用
            if (choice == 3) return funcInternal();  // internal允许派生合约使用
            //if (choice == 4) return funcExternal();  // external也不允许派生合约直接使用
        }
    }
    contract TestAccessCtrl2 {
        function test2(uint choice) public returns(string) {
            TestAccessCtrl obj = new TestAccessCtrl();
            return obj.funcExternal();  // external只允许在外部合约中这样间接调用
        }
    }

    2.1、private

    仅在当前合约使用,且不可被继承,私有状态变量只能从当前合约内部访问,派生合约内不能访问。

    2.2、public

    同时支持内部和外部调用。修饰状态变量时,自动生成同名get函数,

    在这里插入图片描述

    但在编码时不可直接调用num1()

    2.3、internal

    只支持内部调用,也包括其派生合约内访问。

    2.4、external

    外部才可调用,内部想要调用可以用this

    三、函数修饰符

    contract TestFuncDecorator {
        uint public num = 1;
        /// pure
        function testPure(uint _num) public pure {
            //uint num1 = num;  // pure不允许读状态变量
            //num = _num;  // pure不允许修改状态变量
        }
        /// view
        function testView(uint _num) public view {
            uint num1 = num;  // 允许读状态变量
            num = _num;  // 0.4语法上允许修改状态变量,但实际不会修改,所以num还是1
            // 0.5及之后不允许在view中这样修改,否则编译不通过
        }
        /// payable
        function () public payable {}
        function getBalance() public view returns(uint) {  // balance获取合约地址下的以太币余额
            return address(this).balance;
        }
        // 充值函数payable,只有添加这个关键字,才能在执行这个函数时,给这个合约充以太币,否则该函数自动拒绝所有发送给它的以太币
        function testPayable() payable public {  // transfer转账
            address(this).transfer(msg.value);
        }
    }

    3.1、pure

    表明该函数是纯函数,连状态变量都不用读,函数的运行仅仅依赖于参数。不消耗gas。承诺不读取或修改状态,否则编译出错。

    3.2、view

    设置了view修饰符,就是一次调用,不需要执行共识、进入EVM,而是直接查询本地节点数据,因此性能会得到很大提升。不消耗gas。不会发起交易,所以不能实际改变状态变量。

    3.3、payable

    允许函数被调用的时候,让合约接收以太币。如果未指定,该函数将自动拒绝所有发送给它的以太币。

    四、构造函数

    唯一,不可重载。也可以接收入参。

    pragma solidity ^0.4.25;
    
    contract Test1 {
        address private _owner;
        constructor() public {
            _owner = msg.sender;
        }
        /**constructor(int num) public {  重载构造->编译错误
            _owner = msg.sender;
        }*/
    }
    contract Test2 {
        uint public num;
        constructor(uint x) public {  // 带参构造,在deploy时传入
            num = x;
        }
    }

    五、修饰器modifier

    方法修饰器modifier,类似AOP处理。

    pragma solidity ^0.4.25;
    
    contract TestModifier {
        address private _owner;
        bool public endFlag;  // 执行完test后的endFlag仍是true
        constructor() public {
            _owner = msg.sender;
        }
        modifier onlyOwner {  // 权限拦截器,非合约部署账号执行test()则被拦截
            require(_owner == msg.sender, "Auth: only owner is authorized.");
            _;  // 类似被代理的test()方法调用
            endFlag = true;
        }
        function test() public onlyOwner {
            endFlag = false;
        }
    }

    六、数据位置

    6.1、memory

    其生命周期只存在于函数调用期间,局部变量默认存储在内存,不能用于外部调用。内存位置是临时数据,比存储位置便宜。它只能在函数中访问。

    通常,内存数据用于保存临时变量,以便在函数执行期间进行计算。一旦函数执行完毕,它的内容就会被丢弃。你可以把它想象成每个单独函数的内存(RAM)。

    6.2、storage

    状态变量保存的位置,只要合约存在就一直保存在区块链中。该存储位置存储永久数据,这意味着该数据可以被合约中的所有函数访问。可以把它视为计算机的硬盘数据,所有数据都永久存储。

    保存在存储区(Storage)中的变量,以智能合约的状态存储,并且在函数调用之间保持持久性。与其他数据位置相比,存储区数据位置的成本较高。

    6.3、calldata

    calldata是不可修改的只读的非持久性数据位置,所有传递给函数的值,都存储在这里。此外,calldata是外部函数的参数(而不是返回参数)的默认位置。

    0.4的external入参声明calldata则报错,0.5及之后的external入参必须声明calldata否则报错。

    从 Solidity 0.6.9 版本开始,之前仅用于外部函数的calldata位置,现在可以在内部函数使用了。

    6.4、stack

    堆栈是由EVM (Ethereum虚拟机)维护的非持久性数据。EVM使用堆栈数据位置在执行期间加载变量。堆栈位置最多有1024个级别的限制。

    【总结】

    花费gas:storage > memory(calldata) > stack

    状态变量总是存储在存储区storage中。(隐式地标记状态变量的位置)

    函数参数(值类型)包括返回参数(值类型)都存储在内存memory中

    值类型的局部变量:栈(stack)

    值类型的局部变量存储在内存中。但是,对于引用类型,需要显式地指定数据位置

    不能显式声明具有值类型的局部变量为memory还是storage

    七、事件event

    定义使用event,类似一个函数声明,调用试图emit

    contract Test {
        event testEvent(uint a, uint b, uint c, uint result);
        function calc(uint a, uint b, uint c) public returns(uint) {
            uint result = a ** b ** c;
            emit testEvent(a, b, c, result);
            return result;
        }
    }

    事件会输出在logs中,

    [
    	{
    		"from": "0x19a0870a66B305BE9917c0F14811C970De18E6fC",
    		"topic": "0x271cb5fa8dca917938dbd3f2522ef54cf70092ead9e1871225a2b3b407f9a81a",
    		"event": "testEvent",
    		"args": {
    			"0": "2",
    			"1": "1",
    			"2": "3",
    			"3": "8",
    			"a": "2",
    			"b": "1",
    			"c": "3",
    			"result": "8"
    		}
    	}
    ]

    通过给event形参添加indexed,便于事件条件的筛选,indexed不能超过三个,否则编译出错,

    event testEvent(uint indexed a, uint b, uint c, uint result);

    八、单位和全局变量

    时间单位不加默认s,以太币默认wei

    function testUnit() pure public {
        require(1 == 1 seconds);
        require(1 minutes == 60 seconds);
        require(1 hours == 60 minutes);
        require(1 days == 24 hours);
        require(1 weeks == 7 days);
        require(1 years == 365 days);  // years 从 0.5.0 版本开始不再支持
    
        require(1 ether == 1000 finney);
        require(1 finney == 1000 szabo);    // 从0.7.0开始 finney 和 szabo 被移除了
        require(1 szabo == 1e12 wei);
        //require(1 gwei == 1e9);  // 0.7.0开始加入gwei
    }

    全局变量,

    pragma solidity ^0.8.0;
    contract TestGlobalVariable {
        function test1() public view returns(/**bytes32,*/ uint, uint, address, uint, uint, uint, uint) {
            return (
                // blockhash(block.number - 1),  // 指定区块的区块哈希,仅可用于最新的 256 个区块且不包括当前区块,否则返回0。0.5移除了block.blockhash
                block.basefee,  // 当前区块的基础费用
                block.chainid,  // 当前链 id
                block.coinbase,  // 挖出当前区块的矿工地址
                block.difficulty,  // 当前区块难度
                block.gaslimit,  // 当前区块 gas 限额
                block.number,  // 当前区块号
                block.timestamp  // 自 unix epoch 起始当前区块以秒计的时间戳,0.7.0移除了now
            );
        }
        function test2() public payable returns(bytes memory, address, bytes4, uint, uint256, uint, address) {
            return (
                msg.data,  // 完整的 calldata
                msg.sender,  // 消息发送者(当前调用)
                msg.sig,  // calldata 的前 4 字节(也就是函数标识符)
                msg.value,  // 随消息发送的 wei 的数量
                gasleft(),  // 剩余的 gas,0.5移除了msg.gas
                tx.gasprice,  // 交易的 gas 价格
                tx.origin  // 交易发起者(完全的调用链)
            );
        }
    }

    九、异常处理

    9.1、assert

    assert(1 == 1 seconds);

    9.2、require

    require(1 == 1 seconds);
    require(1 == 1 seconds, "err");

    9.3、revert

    if (x != y) revert("x should equal to b");

    9.4、try/catch

    Solidity0.6版本之后加入。try/catch仅适用于外部调用,另外,try大括号内的代码是不能被catch捕获的。

    pragma solidity ^0.6.10;
    
    contract TestTryCatch {
        function execute (uint256 amount) external returns(bool){
            try this.onlyEvent(amount){
                return true;
            } catch {
                return false;
            }
        }
        function onlyEvent (uint256 a) public {
            //code that can revert
            require(a % 2 == 0, "Ups! Reverting");
        }
    }

    【assert和require的选择】

    在EVM里,处理assert和require两种异常的方式是不一样的,虽然他们都会回退状态,不同点表现在:

    1. gas消耗不同。assert类型的异常会消耗掉所有剩余的gas,而require不会消耗掉剩余的gas(剩余的gas会返还给调用者)
    2. 操作符不同。当assert发生异常时,Solidity会执行一个无效操作(无效指令0xfe)。当发生require类型的异常时,Solidity会执行一个回退操作(REVERT指令0xfd)
    • 优先使用require()

       

      • 用于检查用户输入。
      • 用于检查合约调用返回值,如require(external.send(amount))。
      • 用于检查状态,如msg.send == owner。
      • 通常用于函数开头。
      • 不知道使用哪一个的时候,就使用require。
    • 优先使用assert()

       

      • 用于检查溢出错误,如z = x + y; assert(z >= x);
      • 用于检查不应该发生的异常情况。
      • 用于在状态改变之后,检查合约状态。
      • 尽量少使用assert。
      • 通常用于函数中间或者尾部。

    十、重载

    // 重载
    function addNums(uint x, uint y) public pure returns(uint) {
        return x + y;
    }
    function addNums(uint x, uint y, uint z) public pure returns(uint) {
        return x + y + z;
    }

    十一、继承

    子合约is父合约的格式,

    pragma solidity ^0.4.25;
    
    contract TestExtendA { // 父类要写在子类之前
        uint public a;
        constructor() public {
            a = 1;
        }
    }
    contract TestExtend is TestExtendA {
        uint public b;
        constructor() public {
            b = 2;
        }
    }

    直接在继承列表中指定基类的构造参数,

    contract A { // 父类要写在子类之前
        uint public x;
        constructor(uint _a) public {  // 带参构造
            x = _a;
        }
    }
    contract B is A(1) {  // 指定父类构造参数
        uint public y;
        constructor() public {
            y = 2;
        }
    }

    通过派生合约(子类)的构造函数中使用修饰符方式调用基类合约,

    contract A { // 父类要写在子类之前
        uint public x;
        constructor(uint _a) public {  // 带参构造
            x = _a;
        }
    }
    // 方式一:
    contract B1 is A {
        uint public b;
        constructor() A(1) public {  // 子类构造使用父类带参修饰符A(1)
            b = 2;
        }
    }
    // 方式二:
    contract B2 is A {
        uint public b;
        constructor(uint _b) A(_b / 2) public {  // 子类带参构造使用父类带参修饰符A(_b / 2)
            b = _b;
        }
    }

    连续继承、多重继承,

    /// 连续继承,Z继承Y,Y又继承X
    contract X {
        uint public x;
        constructor() public{
            x = 1;
        }
    }
    contract Y is X {
        uint public y;
        constructor() public{
            y = 1;
        }
    }
    // 如果是多个基类合约之间也有继承关系,那么is后面的合约书写顺序就很重要。顺序应该是,基类合约在前面,派生合约在后面,否则无法编译。
    // 实际上Z只需要继承Y就行
    contract Z is X,Y {  // 所以必须是X,Y而不是Y,X
    }
    /// 多重继承,子类可以拥有多个基类的属性
    contract Father {
        uint public x = 180;
    }
    contract Mother {
        uint public y = 170;
    }
    contract Son is Father, Mother {
    }

    十二、抽象合约

    0.6开始支持。

    如果一个合约有构造函数,且是内部(internal)函数,或者合约包含没有实现的函数,这个合约将被标记为抽象合约,使用关键字abstract,抽象合约无法成功部署,他们通常用作基类合约。

    抽象合约可以声明一个virtual纯虚函数,纯虚函数没有具体实现代码的函数。其函数声明用;结尾,而不是用{}结尾。

    如果合约继承自抽象合约,并且没有通过重写(override)来实现所有未实现的函数,那么他本身就是抽象合约的,隐含了一个抽象合约的设计思路,即要求任何继承都必须实现其方法。

    //pragma solidity ^0.4.25;  // 0.4/0.5不兼容
    pragma solidity ^0.6.10;
    
    abstract contract TestAbstractContract {
        uint public a;
    	constructor(uint _a) internal {
    		a = _a;
    	}
        function get () virtual public;
    }

    十三、重写

    0.8以下不支持。

    合约中的虚函数(函数使用了virtual修饰的函数)可以在子合约重写该函数,以更改他们在父合约中的行为。重写的函数需要使用关键字override修饰。

    pragma solidity ^0.8.0;
    
    contract TestOverride {
        function get() virtual public{}
    }
    contract Middle is TestOverride {
    }
    contract Inherited is Middle{
        function get() public override{
        }
    }

    对于多重继承,如果有多个父合约有相同定义的函数,override关键字后必须指定所有的父合约名称,

    contract Base1 {
        function get () virtual public{}
    }
    contract Base2 {
        function get () virtual public{}
    }
    contract Middle2 is Base1, Base2{  // 指定所有父合约名称
        function get() public override( Base1, Base2){
        }
    }

    注意:如果函数没有标记为virtual(除接口外,因为接口里面所有的函数会自动标记为virtual),那么派生合约是不能重写来更改函数行为的。另外,private的函数是不可标记为virtual的。

    十四、接口

    0.8以下不支持。

    接口和抽象合约类似,与之不同的是,接口不实现任何函数,同时还有以下限制:

    1. 无法继承其他合约或者接口

    2. 无法定义构造函数

    3. 无法定义变量

    4. 无法定义结构体

    5. 无法定义枚举

    pragma solidity ^0.8.0;
    interface TestInterface {
        function transfer (address recipient, uint amount) external;
    }
    contract TestInterfaceSon {
        function transfer(address recipient, uint amount) public {
        }
    }

    就像继承其他合约一样,合约可以继承接口,接口中的函数会隐式地标记为virtual,意味着他们会被重写。

    十五、库

    开发合约的时候,总是会有一些函数经常被多个合约调用,这个时候可以把这些函数封装为一个库,库的关键字用library来定义。

    如果合约引用的库函数都是内部(internal)函数,那么编译器在编译合约时,会把库函数的代码嵌入到合约里,就像合约自己实现了这些函数,这时并不会单独部署。

    pragma solidity >=0.4.0 <0.7.0;
    //pragma solidity ^0.4.25;
    
    library TestLibrary{
    	function add (uint a,uint b) internal pure returns (uint){
    		uint c = a + b;
    		require(c > a, "SafeMath: addition overflow");
    		return c;
    	}
    }

    库的调用,

    contract Test {
        function add (uint x, uint y) public pure returns(uint){
            return TestLibrary.add(x, y);  // 调用库
        }
    }

    除了使用上面的TestLibrary.add(x, y)这种方式来调用库函数,还有一个是使用using LibA for B这种附着库的方式。

    它表示把所有LibA的库函数关联到数据类型B,这样就可以在B类型直接调用库函数。

    contract Test {
        using TestLibrary for uint;
        //using TestLibrary for *;
        function add2 (uint x,uint y) public pure returns (uint){
            return x.add(y);  // uint的数据x就可以直接调用add(y)
        }
    }

    0x1、示例代码

    https://remix.ethereum.org/下,基于不同版本语法的差异,分别用三个合约文件基本覆盖到了以上语法,

    • Test04.sol => ^0.4.25
    • Test06.sol => ^0.6.10
    • Test08.sol => ^0.8.0
    1. Test04.sol,
    pragma solidity ^0.4.25;
    //pragma solidity ^0.8.0;
    
    /** 1.1.1 */
    contract TestBool {
        bool flag;
        int num1 = 100;
        int num2 = 200;
        // default false
        function getFlag() public view returns(bool) {
            return flag;  // false
        }
        // 非
        function getFlag2() public view returns(bool) {
            return !flag;  // true
        }
        // 与
        function getFlagAnd() public view returns(bool) {
            return (num1 != num2) && !flag;  // true
        }
        // 或
        function getFlagOr() public view returns(bool) {
            return (num1 == num2) || !flag;  // true
        }
    }
    /** 1.1.2 */
    // 整型特性与运算
    contract TestInteger {
        int num1; // 有符号整型 int256
        uint num2; // 无符号整型 uint256
    
        function add(uint _a, uint _b) public pure returns(uint) {
            return _a + _b;
        }
        function sub(uint _a, uint _b) public pure returns(uint) {
            return _a - _b;
        }
        function mul(uint _a, uint _b) public pure returns(uint) {
            return _a * _b;
        }
        function div(uint _a, uint _b) public pure returns(uint) {
            return _a / _b;  // 在solidity中,除法是做整除,没有小数点
        }
        function rem(uint _a, uint _b) public pure returns(uint) {
            return _a % _b;
        }
        function square(uint _a, uint _b) public pure returns(uint) {
            return _a ** _b;  // 幂运算在0.8.0之后,变为右优先,即a ** b ** c => a ** (b ** c)
        }
        function max() public view returns(uint) {
            return uint(-1);
            // return type(uint).max;  // 0.8不再允许uint(-1)
        }
    }
    // 位运算
    contract TestBitwise {
        uint8 num1 = 3;
        uint8 num2 = 4;
    
        function bitAdd() public view returns(uint) {
            return num1 & num2;
        }
        function bitOr() public view returns(uint) {
            return num1 | num2;
        }
        function unBit() public view returns(uint) {
            return ~num1;
        }
        function bitXor() public view returns(uint) {
            return num1 ^ num2;
        }
        function bitRight() public view returns(uint) {
            return num1 >> 1;
        }
        function bitLeft() public view returns(uint) {
            return num1 << 1;
        }
    }
    /** 1.1.3 - 1.1.6 */
    contract TestType {
        fixed num;
        ufixed num2;
        fixed8x8 decimal;  // fixedMxN, M表示位宽,必须位8的整数倍,N表示十进制小数部分的位数
        address addr = msg.sender;
        address addr2 = 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF;
        TestBitwise t = TestBitwise(addr);
        enum ActionChoices { Up, Down, Left, Right }
    }
    /** 1.1.7 */
    // 固定长度的字节数组(静态),以及转换为string类型
    contract TestBytesFixed {
        // public 自动生成同名的get方法
        bytes1 public num1 = 0x7a;  // 1 byte = 8 bit
        bytes1 public num2 = 0x68;
        bytes2 public num3 = 0x128b;  // 2 byte = 16 bit
        // 获取字节数组长度
        function getLength() public view returns(uint) {
            return num3.length; // 2
        }
        // 字节数组比较
        function compare() public view returns(bool) {
            return num1 < num2;
        }
        // 字节数组位运算
        function bitwise() public view returns(bytes1,bytes1,bytes1) {  // 多返回值
            return ((num1 & num2), (num1 | num2), (~num1));
        }
    
        // 先转为bytes动态数组,再通过string构造  0x7a7a -> zz
        function toString(bytes2 _val) public pure returns(string) {
            bytes memory buf = new bytes(_val.length);
            for (uint i = 0; i < _val.length; i++) {
                buf[i] = _val[i];
            }
            return string(buf);
        }
    }
    // 固定长度的字节数组(静态)扩容和压缩
    contract TestBytesExpander {
        // public 自动生成同名的get方法
        bytes6 name = 0x796f7269636b;
    
        function changeTo1() public view returns(bytes1) {
            return bytes1(name); // 0x79
        }
        function changeTo2() public view returns(bytes2) {
            return bytes2(name); // 0x796f
        }
        function changeTo16() public view returns(bytes16) {
            return bytes16(name); // 0x796f7269636b00000000000000000000
        }
    }
    /** 1.2.1 */
    // 修改string类型的数据
    contract TestString {
        string name = 'yorick';  // 字符串可以使用单引号或者双引号
        string name2 = "!@#$%^&";  // 特殊字符占1个byte
        string name3 = "张三";  // 中文在string中使用utf8的编码方式存储,占用3个byte  ->  切换到0.8则中文字符报错
        // string memory str = unicode"Hello 😃";  // 0.7.0支持Unicode字符串
        // string memory str2 = unicode"\u20ac";  // 0.7.0支持Unicode字符串
    
        function getLength() view public returns(uint) {
            // 不可以直接获取string的length
            return bytes(name).length; // 6
        }
        function getLength2() view public returns(uint) {
            return bytes(name2).length; // 7
        }
        function getLength3() view public returns(uint) {
            return bytes(name3).length; // 6
        }
    
        function getElmName() view public returns(bytes1) {
            // 不可以直接通过数组下标name[0]获取
            return bytes(name)[0]; // 0x79
        }
        function changeElmName() public {
            bytes(name)[0] = "h";
        }
        function getName() view public returns(bytes) {
            return bytes(name);
        }
    }
    /** 1.2.2 */
    // 动态的字节数组,以及转换为string类型
    contract TestBytesDynamic {
        bytes public dynamicBytes;
    
        function setDynamicBytes(string memory val) public {
            dynamicBytes = bytes(val);
        }
        function getVal() public view returns(string){
            return string(dynamicBytes);
        }
    }
    /** 1.2.3 */
    // 定长数组
    contract TestArrFixed {
    
        uint[5] arr = [1,2,3,4,5];
    
        // 修改数组元素内容
        function modifyElements() public {
            arr[0] = 12;
            arr[1] = 14;
        }
        // 查看数组
        function watchArr() public view returns(uint[5]) {
            return arr;
        }
        // 数组元素求和计算
        function sumArr() public view returns(uint) {
            uint sum = 0;
            for (uint i = 0; i < arr.length; i++) {
                sum += arr[i];
            }
            return sum;
        }
    
        // 数组长度
        function getLength() public view returns(uint) {
            return arr.length;
        }
    
        // delete重置数组某下标的元素值,不会真正删除该元素
        function deleteElm(uint idx) public {
            delete arr[idx];
        }
        // delete重置整个数组
        function deleteArr() public {
            delete arr;
        }
    
        /**  定长数组不允许改变长度和push
        // 压缩数组后,右侧多余元素被丢弃
        function changeLengthTo1() public {
            arr.length = 1;
        }
        // 扩容数组后,右侧元素补0
        function changeLengthTo10() public {
            arr.length = 10;
        }
        // 追加新元素
        function pushElm(uint _elm) public {
            arr.push(_elm);
        }
        */
    }
    // 变长数组
    contract TestArrDynamic {
    
        uint[] arr = [1,2,3,4,5];
    
        // 查看数组
        function watchArr() public view returns(uint[] memory) {
            return arr;
        }
        // 数组长度
        function getLength() public view returns(uint) {
            return arr.length;
        }
        // 压缩数组后,右侧多余元素被丢弃
        function changeLengthTo1() public {
            arr.length = 1;
        }
        // 扩容数组后,右侧元素补0
        function changeLengthTo10() public {
            arr.length = 10;
        }
        // 追加新元素
        function pushElm(uint _elm) public {
            arr.push(_elm);
        }
        /**
        // 弹出元素
        function popElm(uint _elm) public {
            arr.pop();  // 0.4不支持pop
        }
        */
        // delete重置数组某下标的元素值,不会真正删除该元素
        function deleteElm(uint idx) public {
            delete arr[idx];
        }
        // delete重置整个数组
        function deleteArr() public {
            delete arr;
        }
    }
    /**
    二维数组
    solidity的二维数组与其他语言不同,[2] [3]表示3行2列,而其他语言为2行3列;
    二维动态数组与一维数组类似,可以改变其数组长度;
    */
    contract TestArr2Dimensional {
        uint[2][3] arr = [[1,2],[3,4],[5,6]];
        function getRowSize() public view returns(uint) {
            return arr.length; // 3
        }
        function getColSize() public view returns(uint) {
            return arr[0].length; // 2
        }
        function watchArr() public view returns(uint[2][3]) {
            return arr;  // 1,2,3,4,5,6
        }
        function sumArr() public view returns(uint) {
            uint sum = 0;
            for (uint i = 0; i < getRowSize(); i++) {
                for (uint j = 0; j < getColSize(); j++) {
                    sum += arr[i][j];
                }
            }
            return sum;
        }
        function modifyArr() public {
            arr[0][0] = 99;
        }
    }
    // 数组字面值
    contract TestArrLiteral {
        // 最小存储匹配,未超过255,所以使用uint8存储
        function getLiteral8() pure public returns(uint8[3]) {
            return [1,2,3];
        }
        // 超过255,所以使用uint16存储
        function getLiteral16() pure public returns(uint16[3]) {
            return [256,2,3];  // [255,2,3] 不被uint16允许
        }
        // 强制转换为uint256
        function getLiteral256() pure public returns(uint[3]) {
            return [uint(1),2,3];  // 给任意元素强转即可,否则不被允许
        }
        // 计算外界传入的内容
        function addLiterals(uint[3] arr) pure external returns(uint) {
            uint sum = 0;
            for (uint i = 0; i < arr.length; i++) {
                sum += arr[i];
            }
            return sum;
        }
    }
    /** 1.2.4 */
    // 结构体初始化的两种方法
    contract TestStruct {
        struct Student {
            uint id;
            string name;
            mapping(uint=>string) map;
        }
    
        // 默认为storage类型,只能通过storage类型操作结构体中的mapping类型数据
        Student storageStu;
    
        // mapping类型可以不用在定义的时候赋值,而其他类型不赋值则会报错
        function init() public pure returns(uint, string) {
            Student memory stu = Student(100, "Jay");
            return (stu.id, stu.name);
        }
        function init2() public pure returns(uint, string) {
            Student memory stu = Student({name: "Jay", id: 100});
            return (stu.id, stu.name);
        }
        function init3() public returns(uint, string, string) {
            Student memory stu = Student({name: "Jay", id: 100});
            // 直接操作结构体中的mapping不被允许: Student memory out of storage
            // stu.map[1] = "artist";
            // 通过storage类型的变量操作结构体中的mapping
            storageStu = stu;
            storageStu.map[1] = "artist";
            return (storageStu.id, storageStu.name, storageStu.map[1]);
        }
    
        // 结构体作为入参时,为memory类型,并且不能使用public或external修饰函数:Internal or recursive type is not allowed for public or external functions.
        // 赋值时也要指定为memory,否则报错
        function testIn(Student stu) internal returns(uint) {
            return stu.id;
        }
        // 结构体作为出参,同样只能private或internal声明内部使用
        function testOut(Student stu) private returns(Student) {
            return stu;
        }
    }
    /** 1.3 */
    contract TestMapping {
        mapping(address => uint) private scores;  // <学生,分数>的单层映射
        mapping(address => mapping(bytes32 => uint8)) private _scores;  // <学生,<科目,分数>>的两层映射
        function getScore() public view returns(address, uint) {
            address addr = msg.sender;
            return (addr, scores[addr]);
        }
        function setScore() public {
            scores[msg.sender] = 100;
        }
    }
    /** 二 */
    contract TestAccessCtrl {
        constructor () public {}
        uint public num1 = 1;  // 自动为public生成同名的get函数,但在编码时不可直接调用num1()
        uint private num2 = 2;
        uint num3 = 3;  // 不写则默认private
        function funcPublic() public returns(string) {
            return "public func";
        }
        function funcPrivate() private returns(string) {
            return "private func";
        }
        function funcInternal() internal returns(string) {
            return "internal func";
        }
        function funcExternal() external returns(string) {
            return "external func";
        }
        function test1(uint choice) public returns(string) {
            if (choice == 1) return funcPublic();
            if (choice == 2) return funcPrivate();
            if (choice == 3) return funcInternal();
            //if (choice == 4) return funcExternal();  // external不允许直接在内部用
            if (choice == 4) return this.funcExternal();  // 间接通过this才可以调用external
        }
    }
    contract TestAccessCtrlSon is TestAccessCtrl {
        function test2(uint choice) public returns(string) {
            if (choice == 1) return funcPublic();  // public允许派生合约使用
            //if (choice == 2) return funcPrivate();  // private不允许派生合约使用
            if (choice == 3) return funcInternal();  // internal允许派生合约使用
            //if (choice == 4) return funcExternal();  // external也不允许派生合约直接使用
        }
    }
    contract TestAccessCtrl2 {
        function test2(uint choice) public returns(string) {
            TestAccessCtrl obj = new TestAccessCtrl();
            if (choice == 4) {
                return obj.funcExternal();  // external只允许在外部合约中这样间接调用
            } else return "0x0";
        }
    }
    /** 三 */
    contract TestFuncDecorator {
        uint public num = 1;
        /// pure
        function testPure(uint _num) public pure {
            //uint num1 = num;  // pure不允许读状态变量
            //num = _num;  // pure不允许修改状态变量
        }
        /// view
        function testView(uint _num) public view {
            uint num1 = num;  // 允许读状态变量
            num = _num;  // 0.4语法上允许修改状态变量,但实际不会修改,所以num还是1
            // 0.5及之后不允许在view中这样修改,否则编译不通过
        }
        /// payable
        function () public payable {}
        function getBalance() public view returns(uint) {  // balance获取合约地址下的以太币余额
            return address(this).balance;
        }
        // 充值函数payable,只有添加这个关键字,才能在执行这个函数时,给这个合约充以太币,否则该函数自动拒绝所有发送给它的以太币
        function testPayable() payable public {  // transfer转账
            address(this).transfer(msg.value);
        }
    }
    /** 四 */
    contract TestConstruct1 {
        address private _owner;
        constructor() public {
            _owner = msg.sender;
        }
        /**constructor(int num) public {  // 重载构造->编译错误
            _owner = msg.sender;
        }*/
        /**
        function TestFuncDecorator(uint x) {}  // 0.5之前还可以用同名函数定义
        */
    }
    contract TestConstruct2 {
        uint public num;
        constructor(uint x) public {  // 带参构造,在deploy时传入
            num = x;
        }
    }
    /** 五 */
    contract TestModifier {
        address private _owner;
        bool public endFlag;  // 执行完test后的endFlag仍是true
        constructor() public {
            _owner = msg.sender;
        }
        modifier onlyOwner {  // 权限拦截器,非合约部署账号执行test()则被拦截
            require(_owner == msg.sender, "Auth: only owner is authorized.");
            _;  // 类似被代理的test()方法调用
            endFlag = true;
        }
        function test() public onlyOwner {
            endFlag = false;
        }
    }
    /** 七 */
    contract TestEvent {
        event testEvent(uint indexed a, uint indexed b, uint indexed c, uint result); // indexed不能超过三个
        function calc(uint a, uint b, uint c) public returns(uint) {
            uint result = a ** b ** c;
            emit testEvent(a, b, c, result);  // 事件会输出在logs中
            return result;
        }
    }
    /** 八 */
    contract TestUnit {
        function testUnit() pure public {
            require(1 == 1 seconds);
            require(1 minutes == 60 seconds);
            require(1 hours == 60 minutes);
            require(1 days == 24 hours);
            require(1 weeks == 7 days);
            require(1 years == 365 days); // years 从 0.5.0 版本开始不再支持
    
            require(1 ether == 1000 finney);
            require(1 finney == 1000 szabo);
            require(1 szabo == 1e12 wei);
            //require(1 gwei == 1e9);  // 0.7.0开始加入gwei
        }
    }
    /** 九 */
    contract TestException {
        function testAssert(int x) public pure {
            assert(x >= 0);
        }
        function testRequire(int x) public pure {
            require(x >= 0);
            //require(x >= 0, "x < 0");
        }
        function testRevert(int x, int y) public pure {
            if (x != y) revert("x should equal to b");
        }
    }
    /** 十 */
    contract TestOverload {  // 重载
        function addNums(uint x, uint y) public pure returns(uint) {
            return x + y;
        }
        function addNums(uint x, uint y, uint z) public pure returns(uint) {
            return x + y + z;
        }
    }
    /** 十一 */
    contract TestExtendA { // 父类TestExtendA要写在子类TestExtend之前
        uint public a;
        constructor() public {
            a = 1;
        }
    }
    contract TestExtend is TestExtendA {
        uint public b;
        constructor() public {
            b = 2;
        }
    }
    /// 直接在继承列表中指定基类的构造参数
    contract A { // 父类要写在子类之前
        uint public x;
        constructor(uint _a) public {  // 带参构造
            x = _a;
        }
    }
    contract B is A(1) {  // 指定父类构造参数
        uint public y;
        constructor() public {
            y = 2;
        }
    }
    /// 通过派生合约(子类)的构造函数中使用修饰符方式调用基类合约
    // 方式一:
    contract B1 is A {
        uint public b;
        constructor() A(1) public {  // 子类构造使用父类带参修饰符A(1)
            b = 2;
        }
    }
    // 方式二:
    contract B2 is A {
        uint public b;
        constructor(uint _b) A(_b / 2) public {  // 子类带参构造使用父类带参修饰符A(_b / 2)
            b = _b;
        }
    }
    /// 连续继承,Z继承Y,Y又继承X
    contract X {
        uint public x;
        constructor() public{
            x = 1;
        }
    }
    contract Y is X {
        uint public y;
        constructor() public{
            y = 1;
        }
    }
    // 如果是多个基类合约之间也有继承关系,那么is后面的合约书写顺序就很重要。顺序应该是,基类合约在前面,派生合约在后面,否则无法编译。
    // 实际上Z只需要继承Y就行
    contract Z is X,Y {  // 所以必须是X,Y而不是Y,X
    }
    /// 多重继承,子类可以拥有多个基类的属性
    contract Father {
        uint public x = 180;
    }
    contract Mother {
        uint public y = 170;
    }
    contract Son is Father, Mother {
    }
    /** 十五 */
    library TestLibrary{
    	function add (uint a,uint b) internal pure returns (uint){
    		uint c = a + b;
    		require(c > a, "Math: addition overflow");
    		return c;
    	}
    }
    // 库的调用
    contract TestLibraryCall {
        function add(uint x, uint y) public pure returns(uint){
            return TestLibrary.add(x, y);  // 调用库
        }
    }
    // 除了使用上面的TestLibrary.add(x, y)这种方式来调用库函数,还有一个是使用using LibA for B这种附着库的方式。
    // 它表示把所有LibA的库函数关联到数据类型B,这样就可以在B类型直接调用库函数。
    contract TestLibraryUsing {
        using TestLibrary for uint;
        //using TestLibrary for *;
        function add2(uint x,uint y) public pure returns (uint){
            return x.add(y);  // uint的数据x就可以直接调用add(y)
        }
    }
    1. Test06.sol,
    pragma solidity ^0.6.10;
    
    /** 1.2.3 */
    contract TestArrDynamic {
        uint[] arr = [1,2,3,4,5];
        // 弹出元素
        function popElm() public {
            arr.pop();
        }
        function watchArr() public view returns(uint[] memory) {
            return arr;
        }
        /**
        0.6开始不再可以通过修改length改变数组长度,需要通过push(),push(value),pop的方式,或者赋值一个完整的数组
        */
        /**function changeLengthTo1() public {
            arr.length = 1;
        }*/
    }
    /** 九 */
    // Solidity0.6版本之后加入。try/catch仅适用于外部调用,另外,try大括号内的代码是不能被catch捕获的。
    contract TestTryCatch {
        function execute (uint256 amount) external returns(bool) {
            try this.run(amount) {  // 这里的函数异常会被捕获
                return true;  // 这里的异常不再会被捕获
            } catch {
                return false;
            }
        }
        function run(uint256 a) public {
            //code that can revert
            require(a % 2 == 0, "Ups! Reverting");
        }
    }
    /** 十二 */
    // 0.6后支持。抽象合约不能使用new创建
    abstract contract TestAbstractContract {
        uint public a;
    	constructor(uint _a) internal {
    		a = _a;
    	}
        function get () virtual public;
    }
    1. Test08.sol,
    pragma solidity ^0.8.0;
    
    /** 1.2.1 */
    contract TestString {
        function test() public view returns(string memory, string memory, string memory) {
            string memory str = unicode"Hello 😃";  // Hello 😃
            string memory str2 = unicode"\u20ac";  // €
            string memory str3 = hex"414243444546474849";  // ABCDEFGHI
            // string memory name3 = "张三";  // 0.8不允许中文字符,必须改为unicode
            return (str, str2, str3);
        }
    }
    /** 八 */
    contract TestGlobalVariable {
        function test1() public view returns(/**bytes32,*/ uint, uint, address, uint, uint, uint, uint) {
            return (
                // blockhash(block.number - 1),  // 指定区块的区块哈希,仅可用于最新的 256 个区块且不包括当前区块,否则返回0。0.5移除了block.blockhash
                block.basefee,  // 当前区块的基础费用
                block.chainid,  // 当前链 id
                block.coinbase,  // 挖出当前区块的矿工地址
                block.difficulty,  // 当前区块难度
                block.gaslimit,  // 当前区块 gas 限额
                block.number,  // 当前区块号
                block.timestamp  // 自 unix epoch 起始当前区块以秒计的时间戳,0.7.0移除了now
            );
        }
        function test2() public payable returns(bytes memory, address, bytes4, uint, uint256, uint, address) {
            return (
                msg.data,  // 完整的 calldata
                msg.sender,  // 消息发送者(当前调用)
                msg.sig,  // calldata 的前 4 字节(也就是函数标识符)
                msg.value,  // 随消息发送的 wei 的数量
                gasleft(),  // 剩余的 gas,0.5移除了msg.gas
                tx.gasprice,  // 交易的 gas 价格
                tx.origin  // 交易发起者(完全的调用链)
            );
        }
    }
    /** 十三 */
    // 合约中的虚函数(函数使用了virtual修饰的函数)可以在子合约重写该函数,以更改他们在父合约中的行为。重写的函数需要使用关键字override修饰。
    // 0.8以下不支持。
    contract TestOverride {
        function get() virtual public{}
    }
    contract Middle is TestOverride {
    }
    contract Inherited is Middle {
        function get() public override {
        }
    }
    // 对于多重继承,如果有多个父合约有相同定义的函数,override关键字后必须指定所有的父合约名称
    contract Base1 {
        function get() virtual public {}
    }
    contract Base2 {
        function get() virtual public {}
    }
    contract Middle2 is Base1, Base2 {  // 指定所有父合约名称
        function get() public override (Base1, Base2){
        }
    }
    /** 十四 */
    // 0.8以下不支持。
    interface TestInterface {
        function transfer(address recipient, uint amount) external;
    }
    contract TestInterfaceSon {
        function transfer(address recipient, uint amount) public {
        }
    }

    0x2、各版本主要变化

    0.5.0

    • sha3改用keccak256, keccak256只允许接收一个参数,使用abi.encodePacked等组合params
    • 构造函数由同名空参方法变成constructor

    0.6.0

    • 仅标记virtual的接口才可以被覆盖,覆盖时需要使用新关键字override,如果多个基类同方法名时,需要像这样列出 override(Base1, Base2)
    • 不能通过修改length来修改数组长度,需要通过push(),push(value),pop的方式,或者赋值一个完整的数组
    • 使用abstract标识抽象合约,抽象合约不能使用new创建
    • 回调函数由function()拆分为fallback()和receive()
    • 新增try/catch,可对调用失败做一定处理
    • 数组切片,例如: abi.decode(msg.data[4:], (uint, uint)) 是一个对函数调用payload进行解码底层方法
    • payable(x) 把 address 转换为 address payable

    0.7.0

    • call方式调用方法由x.f.gas(1000).value(1 ether)(arg1,arg2)改成 x.f{gas:1000,value:1 ether}(arg1,arg2)
    • now 不推荐使用,改用block.timestamp
    • gwei增加为关键字
    • 字符串支持ASCII字符,Unicode字符串
    • 构造函数不在需要 public修饰符,如需防止创建,可定义成abstract
    • 不允许在同一继承层次结构中具有同名同参数类型的多个事件
    • using A for B,只在当前合约有效, 以前是会继承的,现在需要使用的地方,都得声明一次

    0.8.0

    • 弃用safeMath,默认加了溢出检查,如需不要检查使用 unchecked { ... } , 可以节省丢丢手续费
    • 默认支持ABIEncoderV2,不再需要声明
    • 求幂是右结合的,即表达式a**b**c被解析为a**(b**c)。在 0.8.0 之前,它被解析为(a**b)**c
    • assert 不在消耗完 完整的gas,功能和require基本一致,但是try/catch错误里面体现不一样,还有一定作用…
    • 不再允许使用uint(-1),改用type(uint).max
     
  • Laravel mews-captcha 扩展-api调用安全性提高

    新建一个服务类

    <?php
    namespace App\Services;
    use Cache;
    
    class CaptchaCode
    {
        public static $captcha_expire = 60 * 3; // 过期时间 3分钟
    
        /**
         * 生成验证码
         *
         * @param string $config
         * @param int $captcha_expire 过期时间
         * @return  array
         *  $captchaParams =  [
         *      "sensitive" => true,
         *      "key" => "$2y$10$4UTAMBN0hd1V6wP3bVmbhu/PQf/y9Mz6FhFJ/VtU8CkwmRkBF8/cy",// 验证码的hash值
         *      "img" => "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAAkCAYAAABCKP5eAAAA",// base64后的图片
         *  ];
         */
        public static function createCodeAPI(string $config = 'default', $captcha_expire = 180)
        {
            if (!is_numeric($captcha_expire) || $captcha_expire <= 0) {
                $captcha_expire = static::$captcha_expire;
            }
    
            $captchaParams = app('captcha')->create($config, true);
            $captcha_key = $captchaParams['key'];
            // 缓存起来 这里的键值前缀要和扩展里验证函数captcha_api_check中的验证前缀一致
            Cache::put("captcha_" . md5($captcha_key), 1, $captcha_expire);
            return $captchaParams;
        }
    
        /**
         * 校验验证码
         * 验证码验证一次即销毁 
         * @param string $captcha_code 验证码
         * @param string $captcha_key 缓存key
         * @return  mixed sting 具体错误(验证不通过) ;
         */
        public static function captchaCheckAPI($captcha_code = '', $captcha_key = '')
        {
    
            if (!captcha_api_check($captcha_code, $captcha_key)) {
                return ['code' => 1, 'msg' => '验证码错误'];
            }
    
            return ['code' => 0, 'msg' => ''];
        }
    }
    

     

     

    创建图片验证码及验证

    <?php
    namespace App\Http\Controllers;
    
    use App\Http\Controllers\Controller;
    use App\Services\CaptchaCode;
    
    class IndexController extends Controller
    {
    
        public function getCaptcha()
        {
            $data = CaptchaCode::createCodeAPI('resetPassword', 300);
            return ['code' => 0, 'message' => '', 'data' => $data];
        }
    
        public function captchaCheck(Request $request)
        {
            
            //$this->validate($request, [ 'captcha' => 'required|captcha',]); //通过validate方式验证captcha,不用写逻辑
            $key = $request->input('imgkey', '');
            $captcha = $request->input('captcha', '');
            $check = CaptchaCode::captchaCheckAPI($captcha, $key);
            if ($check["code"] != 0) {
                return ['code' => 1, 'message' => $check["msg"]];
            }
            //处理其他逻辑
            //......
        }
    
    }
    

     

     

  • laravel实现验证码功能

     

    一、创建验证码

    ①打开vscode控制台输入composer require mews/captcha载入验证码库



    ②在config文件->app.php中注册验证码服务 

    在config/app.php的providers节点下追加

     

    Mews\Captcha\CaptchaServiceProvider::class,


    ③在config文件->app.php中注册别名

    在config/app.php的aliases节点下追加:

     

    'Captcha' => Mews\Captcha\Facades\Captcha::class,


    ④控制器->创建TestController.php测试类


    ⑤编写验证方法


    ⑥编写captcha.blade.php视图


    ⑦编辑路由器


    ⑧在TestController.php中编写验证规则


    ⑨浏览器运行(验证码过长,需要修改验证码配置)


    二、发布验证并修改验证码设置

    ①控制台->输入命令: php artisan vendor:publish ->选择Mews\Captcha\CaptchaServiceProvider所对应的序号10->输入10->回车

    注意:版本不同对应的序号不同,比如我的版本对应的是10


     

    ②config文件->captcha.php->修改验证码设置


    ③再次浏览器运行



     

  • PHP-Laravel缓存Cache

    Laravel中的cache为我们提供了三种缓存机制。

    Redis,memcache,以及框架的文件缓存。

    这里主要看的是cache中的文件缓存。

    一:访问多个缓存存储

    使用 Cache 门面,你可以使用 store 方法访问不同的缓存存储器,传入 store 方法的键就是 cache 配置文件中 stores 配置数组里列出的相应的存储器:

    $value = Cache::store('file')->get('foo');
    Cache::store('redis')->put('bar', 'baz', 600);  // 10分钟
    

    二:从缓存中获取数据

    1:获取数据并设置默认值

    (1):正常取值

    $value = Cache::get('key');
    

    (2):如果不存在,附默认值

    $value = Cache::get('key', 'default');
    

    (3):使用闭包操作,附默认值

    $value = Cache::get('key', function() {
        return DB::table(...)->get();
    });
    

    2:检查缓存项是否存在

    has 方法用于判断缓存项是否存在,如果值为 null 或 false 该方法会返回 false:

    if (Cache::has('key')) {
        //
    }
    

    3:数值增加/减少

    increment 和 decrement 方法可用于调整缓存中的整型数值。这两个方法都可以接收第二个参数来指明缓存项数值增加和减少的数目:

    Cache::increment('key');
    Cache::increment('key', $amount);
    Cache::decrement('key');
    Cache::decrement('key', $amount);
    

    4:获取 & 存储

    有时候你可能想要获取缓存项,但如果请求的缓存项不存在时给它存储一个默认值。例如,你可能想要从缓存中获取所有用户,或者如果它们不存在的话,从数据库获取它们并将其添加到缓存中,你可以通过使用 Cache::remember 方法实现:

    $value = Cache::remember('users', $seconds, function() {
        return DB::table('users')->get();
    });
    

    如果缓存项不存在,传递给 remember 方法的闭包被执行并且将结果存放到缓存中。

    你还可以使用 rememberForever 方法从缓存中获取数据或者将其永久存储起来:

    $value = Cache::rememberForever('users', function() {
        return DB::table('users')->get();
    });
    

    5:获取 & 删除

    如果你需要从缓存中获取缓存项然后删除,你可以使用 pull 方法,和 get 方法一样,如果缓存项不存在的话返回 null:

    $value = Cache::pull('key');
    

    三:缓存中存储数据

    1:获取存储数据

    你可以使用 Cache 门面上的 put 方法在缓存中存储数据。当你在缓存中存储数据的时候,需要指定数据被缓存的时间(秒数):

    Cache::put('key', 'value', $seconds);
    

    如果没有传递缓存时间到 put 方法,则缓存项永久有效:

    Cache::put('key', 'value');
    

    除了传递缓存项失效时间,你还可以传递一个代表缓存项有效时间的 PHP Datetime 实例:

    $expiresAt = Carbon::now()->addMinutes(10);
    Cache::put('key', 'value', $expiresAt);
    

    2:缓存不存在时存储数据

    add 方法只会在缓存项不存在的情况下添加数据到缓存,如果数据被成功添加到缓存返回 true,否则,返回 false:

    Cache::add('key', 'value', $seconds);
    

    3:永久存储数据

    forever 方法用于持久化存储数据到缓存,这些值必须通过 forget 方法手动从缓存中移除:

    Cache::forever('key', 'value');
    

    四:从缓存中移除数据

    可以使用 Cache 门面上的 forget 方法从缓存中移除缓存项数据:

    Cache::forget('key');
    

    还可以通过设置缓存有效期为 0 或负数来移除缓存项:

    Cache::put('key', 'value', 0);
      
    Cache::put('key', 'value', -5);
    

    如果要清除所有缓存,可以通过 flush 方法:

    Cache::flush();
    

    链接:https://www.jianshu.com/p/48d44d91ec93/

  • Redis应用场景-统计文章浏览数(Laravel+redis)

     

    需求和背景

    一篇文章或者帖子的浏览次数的统计
    如果只是每次增加一个浏览量 就到数据库新增/修改一个数据
    请求频繁 用户量一多就出问题了

    解决方案

    1.每次增加一个访问量就在缓存中去进行更改
    2.达到一定数量后刷新改变Mysql数据库
    这样数据也是准确的 效率也比直接每次刷新数据库要高出许多

    实践

    创建对应的控制器

    php artisan make:controller PostController

    具体代码如下

    <?php
    
    namespace App\Http\Controllers;
    
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Redis;
    use App\Post;
    use App\Events\PostViewEvent;
    use Illuminate\Support\Facades\Cache;
    class PostController extends Controller
    {
        private $cacheExpires = 60;//post文章数据缓存时间 s
        public function showPost(Request $request,$id){
            //存入缓存中,该键值key='post:cache'.$id生命时间60分钟
            $post = Cache::remember('post:cache:'.$id, $this->cacheExpires, function () use ($id) {
                return Post::whereId($id)->first();
            });
    
            //获取客户端请求的IP
            $ip = $request->ip();
    //        $ip = "127.0.1.1";//手动更改ip 以不同ip访问,计数
            //触发浏览次数统计时间
            event(new PostViewEvent($post, $ip));
            dump("当前预览文章的名称:".$post->title);
            dump("当前预览数:".$post->view_count);
        }
    }
    

    创建对应Model

    php artisan make:model Post

    创建对应表

    CREATE TABLE `bs_posts` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
      `title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
      `content` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
      `view_count` int(10) unsigned NOT NULL,
      `created_at` timestamp NULL DEFAULT NULL,
      `updated_at` timestamp NULL DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

    使用seeder或手动随便插入些数据

    创建路由

    Route::get('/post/{id}', 'PostController@showPost');

    使用laravel的事件监听

    app/providers/EventServiceProvider 中$listen加入

    protected $listen = [
            'App\Events\PostViewEvent' => [
                'App\Listeners\PostEventListener',
            ],
        ];

    生成事件监听

    php artisan event:generate

    事件PostViewEvent实现

    <?php
    
    namespace App\Events;
    
    use App\Post;
    use Illuminate\Broadcasting\Channel;
    use Illuminate\Queue\SerializesModels;
    use Illuminate\Broadcasting\PrivateChannel;
    use Illuminate\Broadcasting\PresenceChannel;
    use Illuminate\Foundation\Events\Dispatchable;
    use Illuminate\Broadcasting\InteractsWithSockets;
    use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
    
    class PostViewEvent
    {
        use Dispatchable, InteractsWithSockets, SerializesModels;
    
        public $ip;
        public $post;
    
    
        /**
         * PostViewEvent constructor.
         * @param Post $post
         * @param $ip
         */
        public function __construct(Post $post, $ip)
        {
            $this->post = $post;
            $this->ip = $ip;
        }
    
        /**
         * Get the channels the event should broadcast on.
         *
         * @return Channel|array
         */
        public function broadcastOn()
        {
            return new PrivateChannel('channel-name');
        }
    }

    监听事件实现

    <?php
    
    namespace App\Listeners;
    
    use App\Events\PostViewEvent;
    use App\Post;
    use Illuminate\Support\Facades\Redis;
    
    class PostEventListener
    {
        /**
         * 一个帖子的最大访问数
         */
        const postViewLimit = 2;
        /**
         * 同一用户浏览同一个帖子的过期时间
         */
        const ipExpireSec = 200;
        /**
         * Create the event listener.
         */
        public function __construct()
        {
        }
    
        /**
         * @param PostViewEvent $event
         */
        public function handle(PostViewEvent $event)
        {
            $post = $event->post;
            $ip = $event->ip;
    //        $ip = "127.2.1.31";
            $id = $post->id;
            //首先判断下ipExpireSec = 200秒时间内,同一IP访问多次,仅仅作为1次访问量
            if($this->ipViewLimit($id, $ip)){
                //一个IP在300秒时间内访问第一次时,刷新下该篇post的浏览量
                $this->updateCacheViewCount($id, $ip);
            }
        }
        /**
         * 限制同一IP一段时间内得访问,防止增加无效浏览次数
         * @param $id
         * @param $ip
         * @return bool
         */
        public function ipViewLimit($id, $ip)
        {
            $ipPostViewKey = 'post:ip:limit:'.$id;
            //Redis命令SISMEMBER检查集合类型Set中有没有该键,Set集合类型中值都是唯一
            $existsInRedisSet = Redis::command('SISMEMBER', [$ipPostViewKey, $ip]);
            //如果集合中不存在这个建 那么新建一个并设置过期时间
            if(!$existsInRedisSet){
                //SADD,集合类型指令,向ipPostViewKey键中加一个值ip
                Redis::command('SADD', [$ipPostViewKey, $ip]);
                //并给该键设置生命时间,这里设置300秒,300秒后同一IP访问就当做是新的浏览量了
                Redis::command('EXPIRE', [$ipPostViewKey, self::ipExpireSec]);
                return true;
            }
            return false;
        }
    
        /**
         * 达到要求更新数据库的浏览量
         * @param $id
         * @param $count
         */
        public function updateModelViewCount($id, $count)
        {
            //访问量达到300,再进行一次SQL更新
            $post = Post::find($id);
            $post->view_count += $count;
            $post->save();
        }
    
        /**
         * 不同用户访问,更新缓存中浏览次数
         * @param $id
         * @param $ip
         */
        public function updateCacheViewCount($id, $ip)
        {
            $cacheKey = 'post:view:'.$id;
            //这里以Redis哈希类型存储键,就和数组类似,$cacheKey就类似数组名 如果这个key存在
            if(Redis::command('HEXISTS', [$cacheKey, $ip])){
                //哈希类型指令HINCRBY,就是给$cacheKey[$ip]加上一个值,这里一次访问就是1
                $save_count = Redis::command('HINCRBY', [$cacheKey, $ip, 1]);
                //redis中这个存储浏览量的值达到30后,就去刷新一次数据库
                if($save_count == self::postViewLimit){
                    $this->updateModelViewCount($id, $save_count);
                    //本篇post,redis中浏览量刷进MySQL后,就把该篇post的浏览量清空,重新开始计数
                    Redis::command('HDEL', [$cacheKey, $ip]);
                    Redis::command('DEL', ['laravel:post:cache:'.$id]);
                }
            }else{
                //哈希类型指令HSET,和数组类似,就像$cacheKey[$ip] = 1;
                Redis::command('HSET', [$cacheKey, $ip, '1']);
            }
        }
    }
    

    结果

    在这里插入图片描述
    在这里插入图片描述

     
  • 配置nginx 支持php

    一、确保php-fpm已经启动:

    ps -A | grep php-fpm

    如果没有启动,则启动php-fpm:

    /usr/local/sbin/php-fpm

    查看是否启动成功:

     
    root@iZ25fm7iewtZ:/usr/local/etc# ps -ef | grep php-fpm
    root      3691     1  0 18:49 ?        00:00:00 php-fpm: master process (/usr/local/etc/php-fpm.conf)
    www-data  3692  3691  0 18:49 ?        00:00:00 php-fpm: pool www      
    www-data  3693  3691  0 18:49 ?        00:00:00 php-fpm: pool www      
    root      4982 29553  0 18:59 pts/1    00:00:00 grep --color=auto php-fpm
    
    
    root@iZ25fm7iewtZ:/usr/local/etc# netstat -tnl | grep 9000
    tcp        0      0 127.0.0.1:9000          0.0.0.0:*               LISTEN  
     

    修改nginx的配置文件,支持php文件的解析,找到location的添加位置,在后面添加下面这个location

     
    location ~ \.php$ {
                root           /usr/local/nginx/html;
                fastcgi_pass   127.0.0.1:9000;
                fastcgi_index  index.php;
                #fastcgi_param  SCRIPT_FILENAME  /usr/local/nginx/html/$fastcgi_script_name;
                #以下方式也可以
                fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
                include        fastcgi_params;
            }
     

    重启nginx

    service nginx restart

    进入web更目录,编辑index.php

    ?
    1
    2
    <?php
        echo "hello php !"

    浏览器中输入:localhost/index.php 即可