博客

  • PhalApi数据库操作:基于NotORM的使用及优化

    (1) 基本CURD

    //查询
    $row = DI()->notorm->user->where('id', 1)->fetch();
    
    //更新
    $data = array('name' => 'test', 'update_time' => time());
    DI()->notorm->user->where('id', 1)->update($data);
    
    //插入(须是同一个对象才能正确获取插入的ID)
    $data = array('name' => 'phalapi');
    $userORM = DI()->notorm->user;
    $userORM->insert($data);
    $id = $userORM->insert_id();
    
    //删除
    DI()->notorm->user->where('id', 1)->delete();

    (2)update相同的数据的判断

    在使用update操作时,如果更新的数据和原来的一样,则会返回0(影响0行)。这时,会和更新失败(同样影响0行)混淆。

    但NotORM是一个优秀的类库,所以提供了优秀的解决文案。我们在使用update时,只须了解这两者返回的结果的微妙区别即可。
    因为失败异常时,返回false;而相同数据更新会返回0。即:

    • 1、update相同的数据时,返回0,严格来说是:int(0)
    • 2、update失败时,如更新一个不存在的字段,返回false,即:bool(false)

    用代码表示,就是:

    $rs = DI()->notorm->user->where('id', $userId)->update($data);
    
    if ($rs >= 1) {
        //成功
    } else if ($rs === 0) {
        //相同数据,无更新
    } else if ($rs === false) {
        //更新失败
    }

    以下单元测试代码,可以验证上面的判断:

        public function testUpdateOk()
        {
            $userId = 87;
    
            $rs = DI()->notorm->user->where('id', $userId)->update(array('reg_time' => time()));
    
            $this->assertSame(1, $rs);
        }
    
        public function testUpdateZero()
        {
            $userId = 1;
    
            $rs = DI()->notorm->user->where('id', $userId)->update(array('username' => 'aevit'));
    
            $this->assertSame(0, $rs);
        }
    
        public function testUpdateFail()
        {
            $userId = 1;
    
            $rs = DI()->notorm->user->where('id', $userId)->update(array('wrong_username' => 'aevit'));
    
            $this->assertSame(FALSE, $rs);
        }

    (3)简单的关联查询

    如果是简单的关联查询,可以使用NotORM支持的写法,这样的好处在于我们使用了一致的开发,并且能让PhalApi框架保持分布式的操作方式(注意,关联的表仍然需要在同一个数据库)。

    以下是一个简单的示例。

    假设我们有这样的数据:

    INSERT INTO `phalapi_user` VALUES ('1', 'wx_edebc877070133c65161d00799e00544', 'weixinName', '******', '4CHqOhe1Jxi3X9HmRfPOXygDnU267eCA', '1431790647', 'phpunit.png');
    INSERT INTO `phalapi_user_session_0` VALUES ('1', '1', 'ABC', '', '0', '0', '0', null);

    那么对应关联查询的代码如下面:

        public function testLeftJoin()
        {
            $rs = DI()->notorm->user_session_0
                ->select('expires_time, user.username, user.nickname')
                ->where('token', 'ABC')
                ->fetchRow();
    
            var_dump($rs);
        }
    

    运行一下,我们可以看到这样的输出:

    SELECT expires_time, user.username, user.nickname FROM phalapi_user_session_0 LEFT JOIN phalapi_user AS user ON phalapi_user_session_0.user_id = user.id WHERE (token = 'ABC') LIMIT 1;
    
    .[1 - 0.06318s]SELECT expires_time, user.username, user.nickname FROM phalapi_user_session_0 LEFT JOIN phalapi_user AS user ON phalapi_user_session_0.user_id = user.id WHERE (token = 'ABC') LIMIT 1;<br>
    array(3) {
      ["expires_time"]=>
      string(1) "0"
      ["username"]=>
      string(35) "wx_edebc877070133c65161d00799e00544"
      ["nickname"]=>
      string(10) "weixinName"
    }

    这样,我们就可以实现关联查询的操作。按照NotORM官网的说法,则是:

    If the dot notation is used for a column anywhere in the query ("$table.$column") then NotORM automatically creates left join to the referenced table. Even references across several tables are possible ("$table1.$table2.$column"). Referencing tables can be accessed by colon: $applications->select("COUNT(application_tag:tag_id)").

    ->select('expires_time, user.username, user.nickname')这一行调用将会【自动产生关联操作】,而ON 的字段,则是这个字段关联你配置的【表结构】,外键默认为: 表名_id 。

    (4)加1操作

    NotORM已提供了NotORM_Literal,其用法如下:

    DI()->notorm->user->where('id', 1)->update(array('age' => new NotORM_Literal("age + 1")));

    当需要更新为当前时间,可以:

    $array = array(
        "title" => "NotORM",
        "author_id" => null,
        "created" => new NotORM_Literal("NOW()"),
    );

    1.15.2 NotORM的优化

    但为了更符合项目的开发,这里对NotORM的底层作了升级修改,以下为主要修改点和新的使用:

    (1)将原来返回的结果全部从对象改成数组

    对原来的大部分使用无特别影响,可按原来的方式开发。主要目的是为了更方面处理返回的数据,以及简化对结果的再解析,简单明了。
    如:

    DI()->notorm->user->where('username = ?', 'dogstar')->fetch();

    返回的将是一个数组:

    array(7) {
      ["id"]=>
      string(3) "180"
      ["username"]=>
      string(17) "dogstar"
      ["regtime"]=>
      string(10) "1414811954"
      //...
    }

    (2)提供获取全部结果的接口 – fetchAll() / fetchRows()

    如:

    $rows = DI()->notorm->event_picurl->where('eid', $eids)->fetchAll();

    或:

    $rows = DI()->notorm->event_picurl->where('eid', $eids)->fetchRows();

    即可获取全部的数据,不再受限于分页。
    这里提供了fetchAll()和fetchRows()两种等效的操作,是为了减少记忆的痛苦,下同。

    (3)提供更灵活的查询方式 – queryAll() / queryRows()

    当需要进行复杂的SQL查询时,可以使用此接口,如:
    (注意:limit替换值:start和:num必须使用int类型)

    $sql = 'select * from example AS ep LEFT JOIN user AS u ON ep.ui
    d = u.id  where ep.touid = :userId ORDER BY dateline desc LIMIT :start,:num';
    $params = array(':userId' => $userId, ':start' => $start, ':num' => $num);
    $rs= DI()->notorm->example->queryAll($sql, $params);

    或:

    $rs= DI()->notorm->example->queryRows($sql, $params);

    (4)limit 操作的调整

    取消了NotORM中对OFFSET关键字的使用,改用逗号的写法,修改后正确的使用方法应该是:

    $table->limit(10);  // limit 10   # 查询前10个
    
    $table->limit(5, 10); // limit 5,10   # 从第5个位置开始,查询前10个

    (5)禁止全表删除,防止误删

    出于对数据的保护,当执行删除操作却又没有任何where条件时,将会禁止进行全表操作。如:

        public function testDeleteAll()
        {
            DI()->notorm->user->delete();
        }

    可以看到:

    $ phpunit --filter testDeleteAll ./Api/Api_User_Test.php 
    PHPUnit 4.3.4 by Sebastian Bergmann.
    
    E
    
    Time: 315 ms, Memory: 6.25Mb
    
    There was 1 error:
    
    1) PhpUnderControl_ApiUser_Test::testDeleteAll
    Exception: sorry, you can not delete the whole table --dogstar
    

    (6)添加& __sql__ =1请求参数,可开启HTTP调试模式

    当处于debug模式时,可以输入执行的全部SQL语句,以便调试。

    如:

    SELECT times FROM tpl_user_session_10 WHERE (user_id = ?); -- '74110'
    {"ret":0,"data":{"code":0},"msg":""}

    (7)关于NotORM中fetch()操作没有limit 1的处理方案 – fetchOne() / fetchRow()

    之前,有开发同学提及到,为什么notorm的基类fetch为啥没用limit(1)呢。后来,我去发了下NotORM的写法,确实做得很微妙。
    其实NotORM之所以没有在fetch()里面自动limit 1是因为,你可以循环地获取数据,如:

    $user = DI()->notorm->user->select('id, username, nickname')->where('id > ?', 0)->limit(3);
    while(($row = $user->fetch())) {
        var_dump($row);
     }

    但是,更多情况下,我们只需要获取某一行的数据,上面的做法会造成不必要的SQL查询。为了保留原来的写法,我特意添加扩展了一个新的操作:fetchRow(),用法同fetch(),但只会取第一条。
    以下是使用示例:

    $rs = DI()->notorm->user->select('id, username, nickname')->where('id > ?', 0)->fetchRow());
    
    var_dump($rs);
    
    //结果输出类如:
    array(3) {
      ["id"]=>
      string(1) "1"
      ["username"]=>
      string(5) "aevit"
      ["nickname"]=>
      string(4) "test"
    }
    
    //对应执行的SQL语句:
    [2 - 0.06544s]SELECT id, username, nickname FROM fami_user WHERE (id > ?) LIMIT 1; -- 0<br>

    如果,我们只需要获取这一行的某个字段,也可以像fetch()那样使用,即:

    $rs = DI()->notorm->user->select('id, username, nickname')->where('id > ?', 0)->fetchRow('nickname'));
    
    var_dump($rs);
    
    //结果输出类如:
    string(4) "test"
    
    //纪录不存在时,返回 bool(false)

    (8)显式的SQL语法异常提示

    很多时候,在开发时,我们对数据库操作一开始会存在一些SQL语法的问题,PDO会返回false,且原来NotORM也是使用 静默方式 来处理这类错误,从而使得开发人员有时难以发现这些问题,除非将调试的SQL手动放到数据库执行才能发现问题所在。

    为了能给开发同学更早、更直观的方式查看问题的所在,这里对NotORM底层进行了调整,使用了 显式方式 的策略来处理,即:直接抛出PDO异常。

    如:

    $userId = 1;
    
    //OK
    $rs = DI()->notorm->user->select('username')->where('id', $userId)->fetchOne();
    
    //WRONG
    $rs = DI()->notorm->user->select('wrong_username')->where('id', $userId)->fetchOne();
    

    将会看到:

    [1 - 0.06437s]SELECT username FROM fami_user WHERE (id = 1) LIMIT 1;<br>
    [2 - 0.06496s]SELECT wrong_username FROM fami_user WHERE (id = 1) LIMIT 1;<br>
    
    PDOException: Unknown column 'wrong_username' in 'field list'

    (9)复杂的关联查询

    如果是复杂的关联查询,则是建议使用原生态的SQL语句,但我们仍然可以保持很好的写法,如这样一个示例:

            $sql = 'SELECT t.id, t.team_name, v.vote_num '
                . 'FROM phalapi_team AS t LEFT JOIN phalapi_vote AS v '
                . 'ON t.id = v.team_id '
                . 'ORDER BY v.vote_num DESC';
            $rows = $this->getORM()->queryAll($sql, array());
    

    注意,此时的表需要使用全名,即自带前缀。这样也可以实现更自由的关联查询。

    (10)事务操作

    关于事务操作,可以参考 NotORM官网 的说明:

    $db->transaction = $command Assign 'BEGIN', 'COMMIT' or 'ROLLBACK' to start or stop transaction 

    即:

    //第一步:先指定待进行事务的数据库(通过获取一个notorm表实例来指定;否则会提示:PDO There is no active transaction)
    $user = DI()->notorm->user;
    
    //第二步:开启事务开关(此开关会将当前全部打开的数据库都进行此设置)
    DI()->notorm->transaction = 'BEGIN';
    
    //第三步:进行数据库操作
    $user->insert(array('name' => 'test1',));
    $user->insert(array('name' => 'test2',));
    
    //第四:提交/回滚
    DI()->notorm->transaction = 'COMMIT';
    //DI()->notorm->transaction = 'ROLLBACK';

    推荐使用PhalApi的事务操作方式

    PhalApi一开始对事务这块考虑不周,后来发现很多同学、很多项目都需要用到数据库事务操作。
    基于此,在不破坏原来的代码基础上,我们决定在PhalApi_DB_NotORM上添加对数据库维度的事务操作支持。

    示例简单如下:

        public function testTransactionCommit()
        {
            //Step 1: 开启事务
            DI()->notorm->beginTransaction('db_demo');
    
            //Step 2: 数据库操作
            DI()->notorm->user->insert(array('name' => 'test1'));
            DI()->notorm->user->insert(array('name' => 'test2'));
    
            //Step 3: 提交事务
            DI()->notorm->commit('db_demo');
            // DI()->notorm->rollback('db_demo'); // 回滚
    
        }

    温馨提示: 以上操作,须PhalApi 1.3.1 及以上版本才能支持。

    (11)扩展对非MySQL数据库的支持

    PhalApi使用的是NotORM来进行数据库操作,而NotORM底层则是采用了PDO。目前,NotORM支持: MySQL, SQLite, PostgreSQL, MS SQL, Oracle (Dibi support is obsolete)。

    但需要注意的是,PhalApi本身对NotORM进行了修改,需要调整一下代码才能更好地支持除MySQL外的数据库。 即使NotORM不支持的数据库,你也可以轻松通过添加扩展的方式来支持。如:

    首先,定制自己的数据库连接的PDO。

    class Common_MyDB extends PhalApi_DB_NotORM {
    
        protected function createPDOBy($dbCfg) {
            /* Connect to an ODBC database using driver invocation */
        $dsn = 'uri:file:///usr/local/dbconnect';
        return new PDO($dsn, $dbCfg['user'], $dbCfg['password']);
        }
    }

    随后,在初始化文件init.php中重新注册DI()->notorm即可,如:

    //数据操作 - 基于NotORM,$_GET['__sql__']可自行改名
    DI()->notorm = function() {
        $debug = !empty($_GET['__sql__']) ? true : false;
        return new Common_MyDB(DI()->config->get('dbs'), $debug);
    };

    1.15.3 可选的Model基类

    (1)表数据入口模式

    我们一直在考虑,是否应该提供数据库的基本操作支持,以减少开发人员重复手工编写基本的数据操作。

    最后,我们认为是需要的。然后就引发了新的问题:是以继承还是以委托来支持?

    委托有助于降低继承的层级,但仍然需要编写同类的操作然后再次委托。所以,这里提供了基于NotORM的Model基类:PhalApi_Model_NotORM。

    然而提供这个基类还是会遇到一些问题,例如:如何界定基本操作?如何处理分表存储?如何支持定制化?

    由于我们这里的Model使用了 “表数据入口” 模式,而不是“行数据入口”,也不是“活动纪录”,也不是复杂的“数据映射器”。所以在使用时可以考虑是否需要此基类。即使这样,你也可以很轻松转换到“行数据入口”和“活动纪录”模式。这里,PhalApi中的Model是更广义上的数据源层(后面会有更多说明),因此对应地PhalApi_Model_NotORM基类充当了数据库表访问入口的对象,处理表中所有的行。

    (2)规约层的CURD

    在明白了Model基类的背景后,再来了解其具体的操作和如何继承会更有意义。

    而具体的操作则与数据表的结构相关,在“约定编程”下:即每一个表都有一个主键(通常为id,也可以自由配置)以及一个序列化LOB字段ext_data。我们很容易想到Model接口的定义(注释已移除,感兴趣的同学可查看源码):

    interface PhalApi_Model {
    
        public function get($id, $fields = '*');
    
        public function insert($data, $id = NULL);
    
        public function update($id, $data);
    
        public function delete($id);
    }
    

    上面的接口在规约层上提供了基于表主键的CURD基本操作,在具体实现时,需要注意两点:一是分表的处理;另一点则是LOB字段的序列化。

    (3)不使用Model基类的写法

    由于我们使用了NotORM进行数据库的操作,所以这里也提供了基于NotORM的基类:PhalApi_Model_NotORM。下面以我们熟悉的获取用户的基本信息为例,说明此基类的使用。

    为唤醒记忆,下面贴上Model_User类原来的代码:

    // $ vim ./Demo/Model/User.php
    
    <?php
    
    class Model_User {
    
        public function getByUserId($userId) {
            return DI()->notorm->user->select('*')->where('id = ?', $userId)->fetch();
        }
    }

    对应的调用:

    $model = new Model_User();
    $rs = $model->getByUserId($userId);

    (4)继承Model基类的写法

    若继承于PhalApi_Model_NotORM,则是:

    // $ vim ./Demo/Model/User.php
    
    <?php
    
    class Model_User extends PhalApi_Model_NotORM {
    
        protected function getTableName($id) {
            return 'user';
        }
    }

    从上面的代码可以看出,基类已经提供了基于主键的CURD操作,但我们需要钩子函数以返回对应的表名。相应地,外部调用则调整为:

    $model = new Model_User();
    $rs = $model->get($userId);

    再进一步,我们可以得到其他的基本操作:

    $model = new Model_User();
    
    //查询
    $row = $model->get(1);
    $row = $model->get(1, 'id, name'); //取指定的字段
    $row = $model->get(1, array('id', 'name')); //可以数组取指定要获取的字段
    
    //更新
    $data = array('name' => 'test', 'update_time' => time());
    $model->update(1, $data); //基于主键的快速更新
    
    //插入
    $data = array('name' => 'phalapi');
    $id = $model->insert($data);
    //$id = $model->insert($data, 5); //如果是分表,可以这样指定
    
    //删除
    $model->delete(1);

    1.15.4 定制化你的Model基类

    正如上面提及到的两个问题:LOB序列化和分表处理。所以,如果PhalApi现有就此两问题的解决方案不能满足项目的需求,可作定制化处理。

    (1)LOB序列化

    先是LOB序列化,考虑到有分表的存在,当发生数据库变更时(特别在线上环境)会有一定的难度和风险,因此引入了扩展字段ext_data。当然,此字段也应对数据库变更的同时,也可以作为简单明了的值对象的大对象。序列化LOB首先要考虑的问题是使用二进制(BLOB)还是文本(CLOB),出于通用性、易读性和测试性,我们目前使用了json格式的文本序列化。所以,如果考虑到空间或性能问题(在少量数据下我认为问题不大,如果数据量大,应该及时重新调整数据库表结构),可以重写formatExtData() & parseExtData()。

    如改成serialize序列化:

    abstract class Common_Model_NotORM extends PhalApi_Model_NotORM {
    
        /**
         * 对LOB的ext_data字段进行格式化(序列化)
         */
        protected function formatExtData(&$data) {
            if (isset($data['ext_data'])) {
                $data['ext_data'] = serialize($data['ext_data']);
            }
        }
    
        /**
         * 对LOB的ext_data字段进行解析(反序列化)
         */
        protected function parseExtData(&$data) {
            if (isset($data['ext_data'])) {
                $data['ext_data'] = unserialize($data['ext_data'], true);
            }
        }
    
        // ...
    }

    将Model类继承于Common_Model_NotORM后,

    // $ vim ./Demo/Model/User.php
    
    <?php
    class Model_User extends Common_Model_NotORM {
       //...
    }

    就可以轻松切换到序列化,如:

    $model = new Model_User();
    
    //带有ext_data的更新
    $extData = array('level' => 3, 'coins' => 256);
    $data = array('name' => 'test', 'update_time' => time(), 'ext_data' => $extData);
    $model->update(1, $data); //基于主键的快速更新
    

    (2)分表处理

    其次是分表处理,同样考虑到分表的情况,以及不同的表可能配置不同的主键表,而基于主键的CURD又必须要先知道表的主键名才能进行SQL查询。所以,问题就演变成了如何找到表的主键名。这里可以自动匹配,也可以手工指定。自动匹配是智能的,因为当我们更改表的主键时,可以自动同步更新而不需要担心遗漏(虽然这种情况很少发生)。手工指定可以大大减少系统不必要的匹配操作,因为我们开发人员也知道数据库的主键名是什么,但需要手工编写一些代码。在这里,提供了可选的手工指定,即可重写getTableKey($table)来指定你的主键名。

    如,当我们的表的主键都固定为id时:

    abstract class Common_Model_NotORM extends PhalApi_Model_NotORM {
    
        protected function getTableKey($table) {
            return 'id';
        }
    }
  • JS控制键盘录入 和 window.event.keycode对照

    一、只允许录入整数

     1.不允许录入非数字(按下字母键就会提示并清空)
      function intOnly() {
        if (!(window.event.keyCode >= 48 && window.event.keyCode <= 57
          ||window.event.keyCode==13 //回车符
        )) {
          window.event.keyCode = 0;
          alert("请输入整数数字!");
        }
      }


     2.和intOnly()区别:输入完成焦点离开后判断数字是否合法
      function intOnly2(param) {
        var reg=/^([0-9]*)$/;
        var flag = reg.test(param.value);
        if(!flag){
          alert("请输入整数数字!");
          param.value="";
        }
      }

     

    二、只允许数字键录入小数和整数

     1、录入的同时进行判断
      function numberOnly() {
        if (!(((window.event.keyCode >= 48) && (window.event.keyCode <= 57)) || (window.event.keyCode == 13) || (window.event.keyCode == 46))) {
          window.event.keyCode = 0;
          alert("请输入数字!");
        }
      }
     2.和numberOnly()区别:输入完成焦点离开后判断数字是否合法
      function numberOnly2(param) {
        var reg=/^([0-9]+(\.?[0-9]+)?)$/;
        var flag = reg.test(param.value);
        if(!flag){
          if(param.value!=""){
            alert("请输入数字!");
            param.value="";
          }
        }
      }

     

    三、控制键盘输入时只能输入数字和'-',用于输入负数

     1.录入的同时进行判断
      function fushuOnly() {
        if (!(((window.event.keyCode >= 48) && (window.event.keyCode <= 57)) || (window.event.keyCode == 13) || (window.event.keyCode == 46))) {
          window.event.keyCode = 0;
          alert("输入有误,请输入数字!");
        }
      }


     2.和phoneOnly()区别:输入完成焦点离开后判断数字是否合法
      function fushuOnly2(param) {
        var reg=/^-?([0-9]+(\.?[0-9]+)?)$/;
        var flag = reg.test(param.value);
        if(!flag){
          if(param.value!=""){
            alert("输入有误,请输入数字或者负数!");
            param.value="";
          }
        }
      }

    四、控制键盘输入时只能输入数字和'-',用于限制电话号码的输入

     1.录入的同时进行判断
      function phoneOnly() {
        if (!(window.event.keyCode >= 48 && window.event.keyCode <= 57
          || window.event.keyCode == 45
          || window.event.keyCode == "\uff0d"
          ||window.event.keyCode==13 //回车符
        )) {
          alert("电话号码输入有误,请输入数字!");
          window.event.keyCode = 0;
        }
      }

     2.和phoneOnly()区别:输入完成焦点离开后判断数字是否合法
      function phoneOnly2(param) {
        var reg=/^([0-9]+(\-?[0-9]+)?)$/;
        var flag = reg.test(param.value);
        if(!flag){
          if(param.value!=""){
            alert("电话号码输入有误,请输入数字!");
            param.value="";
          }
        }
      }

     

    五、检查<html:file><input type="file">中地址所指文件是否是图片类型

      function isImage(obj) {
        var str = obj.value;
        var temp = str.indexOf(".");
        if (temp > 0) {
          str = str.substring(temp + 1).toLowerCase();
          var myTypes = new Array("gif", "jpg", "jpeg", "png");
          for (i = 0; i < myTypes.length; i++) {
            if (str == myTypes[i]) {
              return;
            }
          }

          //请选择格式为gif,jpg,jpeg,png的图片
          alert("\u8bf7\u9009\u62e9\u683c\u5f0f\u4e3agif,jpg,jpeg,png\u7684\u56fe\u7247");
          obj.select();
        }
      }

     

    来源:https://www.cnblogs.com/zhaoyhBlog/p/6255147.html

     

    =======================================华丽的分割线===================================================

     window.event.keyCode 代码对照大全

      firefox2.0中不支持 window.event.keyCode,

        但是我们可以用event.which代替。但是为了使其能更具有普遍的兼容性,最好用event.keyCode|| event.which.

      Keycode对照表(转载自地址
    字母和数字键的键码值(keyCode)
    按键 键码 按键 键码 按键 键码 按键 键码
    A 65 J 74 S 83 1 49
    B 66 K 75 T 84 2 50
    C 67 L 76 U 85 3 51
    D 68 M 77 V 86 4 52
    E 69 N 78 W 87 5 53
    F 70 O 79 X 88 6 54
    G 71 P 80 Y 89 7 55
    H 72 Q 81 Z 90 8 56
    I 73 R 82 0 48 9 57

      

     

    数字键盘上的键的键码值(keyCode) 功能键键码值(keyCode)
    按键 键码 按键 键码 按键 键码 按键 键码
    0 96 8 104 F1 112 F7 118
    1 97 9 105 F2 113 F8 119
    2 98 * 106 F3 114 F9 120
    3 99 + 107 F4 115 F10 121
    4 100 Enter 108 F5 116 F11 122
    5 101 109 F6 117 F12 123
    6 102 . 110        
    7 103 / 111        

      

     

    控制键键码值(keyCode)
    按键 键码 按键 键码 按键 键码 按键 键码
    BackSpace 8 Esc 27 Right Arrow 39 -_ 189
    Tab 9 Spacebar 32 Dw Arrow 40 .> 190
    Clear 12 Page Up 33 Insert 45 /? 191
    Enter 13 Page Down 34 Delete 46 `~ 192
    Shift 16 End 35 Num Lock 144 [{ 219
    Control 17 Home 36 ;: 186 \| 220
    Alt 18 Left Arrow 37 =+ 187 ]} 221
    Cape Lock 20 Up Arrow 38 ,< 188 '" 222

     

    多媒体键码值(keyCode)
    按键 键码 按键 键码 按键 键码 按键 键码
    音量加 175            
    音量减 174            
    停止 179            
    静音 173            
    浏览器 172            
    邮件 180            
    搜索 170            
    收藏 171  
     
     

  • Linux下编辑文件并保存

    第一步:cd到该文件的目录下

    第二步:vi  要编辑的文件名,进入普通模式,(可以查看文件内容)

    第三步:输入 i  进入编辑模式,开始编辑文本

    第四步:编辑之后,按ESC退出到普通模式。

    第五步:在普通模式下,输入 : 进入命令模式

    第六步:在命令模式下输入wq, 即可保存并退出

    来源:https://blog.csdn.net/ezreal_pan/article/details/91556805

  • SourceTree这是一个无效源路径/URL的解决方法

    很多新使用SourceTree的用户发现自己clone的时候不成功,提示这是一个无效源路径/URL

    如果确定自己的url没有问题的话,那么就是SourceTree设置的问题,很容易搞定,不需要什么ssh.

    只需要在菜单栏选择工具—-选项—-Git。把点击下图红色所示的Embedded按钮即可


     

     

    来源:https://blog.csdn.net/bbbzhuzhu/article/details/84643391

     

     

  • SQL 中 只查出日期最新记录

    示例表:重复的liveid只筛选日期最近的一条。

    语句:SELECT id,uid,liveid,max(FROM_UNIXTIME(starttime,'%Y-%m-%d')) as stime
      FROM cmf_XXXXX 
     WHERE uid = 62 GROUP BY liveid ORDER BY starttime DESC ;

    结果:

  • sql语句中直接将时间戳转化为时间格式

    例如将:1532311689  =》2018-07-23 10:8:9

    语句如下:select *,FROM_UNIXTIME(addtime,'%Y-%m-%d %H:%i:%s') as addTime from user

    重点是:FROM_UNIXTIME(addtime,'%Y-%m-%d %H:%i:%s')

    来源:https://blog.csdn.net/nuc_badaomen/article/details/81976243

  • SQL去重distinct方法解析

    一 distinct

    含义:distinct用来查询不重复记录的条数,distinct来返回不重复字段的条数(count(distinct id),其原因是distinct只能返回他的目标字段,而无法返回其他字段

    用法注意:

    1.distinct 【查询字段】,必须放在要查询字段的开头,即放在第一个参数;

    2.只能在SELECT 语句中使用,不能在 INSERT, DELETE, UPDATE 中使用;

    3.DISTINCT 表示对后面的所有参数的拼接取 不重复的记录,即查出的参数拼接每行记录都是唯一的

    4.不能与all同时使用,默认情况下,查询时返回的就是所有的结果。

    1.1只对一个字段查重

    对一个字段查重,表示选取该字段一列不重复的数据。

    示例表: psur_list

    PLAN_NUMBER字段去重,语句:SELECT DISTINCT PLAN_NUMBER  FROM psur_list;

    结果如下:

    1.2多个字段去重

    对多个字段去重,表示选取多个字段拼接的一条记录,不重复的所有记录

    示例表: psur_list

    PLAN_NUMBER和PRODUCT_NAME字段去重,语句:SELECT DISTINCT PLAN_NUMBER,PRODUCT_NAME FROM psur_list;

    结果如下:

     期望结果:只对第一个参数PLAN_NUMBER取唯一值

    解决办法一: 使用 group_concat 函数

    语句:SELECT GROUP_CONCAT(DISTINCT PLAN_NUMBER) AS PLAN_NUMBER,PRODUCT_NAME FROM psur_list GROUP BY PLAN_NUMBER

    解决办法二:使用group by

    语句:SELECT PLAN_NUMBER,PRODUCT_NAME FROM psur_list GROUP BY PLAN_NUMBER

    结果如下:

    1.3针对null处理

    distinct不会过滤掉null值,返回结果包含null值

    表psur_list如下:

    对COUNTRY字段去重,语句:SELECT DISTINCT COUNTRY FROM psur_list

    结果如下:

    1.4与distinctrow同义

    语句:SELECT  DISTINCTROW COUNTRY FROM psur_list 

    结果如下:

     二 聚合函数中使用distinct

    在聚合函数中DISTINCT 一般跟 COUNT 结合使用。count()会过滤掉null项

    语句:SELECT COUNT(DISTINCT COUNTRY) FROM psur_list

    结果如下:【实际包含null项有4个记录,执行语句后过滤null项,计算为3】

     

    来源:https://www.cnblogs.com/lixuefang69/p/10420186.html

  • 什么是框架?

    什么是框架?

    框架(framework)是一个框子——指其约束性,也是一个架子——指其支撑性。是一个基本概念上的结构,用于去解决或者处理复杂的问题。

    框架这个广泛的定义使用的十分流行,尤其在软件概念。框架也能用于机械结构。

    在软件工程中的框架:

    框架( Framework )是构成一类特定软件可复用设计的一组相互协作的类。框架规定了你的应用的体系结构。它定义了整体结构,类和对象的分割,各部分的主要责任,类和对象怎么协作,以及控制流程。框架预定义了这些设计参数,以便于应用设计者或实现者能集中精力于应用本身的特定细节。

    为什么要用框架:

    因为软件系统发展到今天已经很复杂了,特别是服务器端软件,涉及到的知识,内容,问题太多。在某些方面使用别人成熟的框架,就相当于让别人帮你完成一些基础工作,你只需要集中精力完成系统的业务逻辑设计。而且框架一般是成熟,稳健的,他可以处理系统很多细节问题,比如,事务处理,安全性,数据流控制等问题。还有框架一般都经过很多人使用,所以结构很好,所以扩展性也很好,而且它是不断升级的,你可以直接享受别人升级代码带来的好处。

    框架一般处在低层应用平台(如J2EE)和高层业务逻辑之间的中间层。

    软件为什么要分层? 为了实现“高内聚、低耦合”。把问题划分开来各个解决,易于控制,易于延展,易于分配资源…总之好处很多啦。

    框架开发:

    框架的最大好处就是重用。面向对象系统获得的最大的复用方式就是框架,一个大的应用系统往往可能由多层互相协作的框架组成。

    由于框架能重用代码,因此从一已有构件库中建立应用变得非常容易,因为构件都采用框架统一定义的接口,从而使构件间的通信简单。

    框架能重用设计。它提供可重用的抽象算法及高层设计,并能将大系统分解成更小的构件,而且能描述构件间的内部接口。这些标准接口使在已有的构件基础上通过组装建立各种各样的系统成为可能。只要符合接口定义,新的构件就能插入框架中,构件设计者就能重用构架的设计。

    框架还能重用分析。所有的人员若按照框架的思想来分析事务,那么就能将它划分为同样的构件,采用相似的解决方法,从而使采用同一框架的分析人员之间能进行沟通。

    主要特点:

    1、领域内的软件结构一致性好; 建立更加开放的系统;

    2、重用代码大大增加,软件生产效率和质量也得到了提高;

    3、软件设计人员要专注于对领域的了解,使需求分析更充分;

    4、存储了经验,可以让那些经验丰富的人员去设计框架和领域构件,而不必限于低层编程;

    5、允许采用快速原型技术;

    6、有利于在一个项目内多人协同工作;

    7、大力度的重用使得平均开发费用降低,开发速度加快,开发人员减少,维护费用降低,而参数化框架使得适应性、灵活性增强。

    ———————
    本文著作权归作者所有。
    商业转载请联系作者获得授权,非商业转载请注明出处。
    来源地址:https://www.php.cn/faq/451857.html
    来源:php中文网(www.php.cn)
    © 版权声明:转载请附上原文链接!

  • 给Cmder添加到右键菜单

    Cmder 是一个在Windows下很好的终端工具,但是如果要进入目标文件夹需要一层一层的进入,比较麻烦。于是给Cmder添加右键菜单就很好的解决这个问题。

    1、首先把 Cmder 安装所在目录 加到系统环境变量 。
    添加完之后,如果你运行 WIN+ R, 输入 Cmder 回车,就可以打开该软件。

    2、为了添加 Cmder 可以在任意文件夹中打开,且路径在是在当前目录路径, 在管理员权限的终端 输入以下语句即可:

    Cmder.exe /REGISTER ALL
    

    3、这时候可以在任意文件夹内,右键打开 Cmder, 即可终端显示路径在当前文件夹。

    作者:2010jing
    链接:https://www.jianshu.com/p/60410db85e10
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • composer install过慢解决方法

    在phper开发过程中,有时候会遇到composer拉取安装镜像过慢的问题,这个时候只要让composer配置使用国内镜像即可:

    阿里云的composer镜像源

    composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

    腾讯云的composer镜像源

    composer config -g repo.packagist composer https://mirrors.cloud.tencent.com/composer/

    作者:Allen_Go
    链接:https://www.jianshu.com/p/8d0b8a921d50
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。