腾讯云通讯IM使用方法之独立模式

官方文档:https://cloud.tencent.com/document/product/269/3794

1、独立模式

独立模式是指用户注册和身份验证由开发者负责,开发者和腾讯之间通过签名验证建立信任关系。开发者在申请接入时,直接 下载公私钥 用于开发,而后用私钥加密指定数据生成签名交由腾讯服务器验证合法性。


上述说明是腾讯的官方说明。这里补一张更直观的图,了解独立模式登录方法,实际只要登录方法明白了,后续的也很简单。


屏幕快照 2018-07-12 下午10.01.23.png


首先用户登录一般都会提交:用户名和密码。提交到我们的服务器,然后判断是否正确。正确则设置缓存或者session,返回json正确信息。错误直接返回json错误信息。一般的逻辑是这样。但是腾讯云通讯独立模式多出来一步,就是当我们所有的验证都正确以后,我们要访问一个服务器。这个服务器是自己搭建的。功能主要是用来生成sig。腾讯也提供了这个服务器的搭建方法。地址:https://cloud.tencent.com/document/product/269/1510 各种平台都有,可以自行斟酌使用。实际提供的都是一些后台代码。还是要部署到我们服务器上的。搞好以后,我们需要利用搭建的sig服务器来进行生成sig。

生成的参数有几个:

1、私钥文件

2、sdkappid

3、用户id

这三个东西去哪里获取,实际很简单,前两个直接通过注册腾讯云通讯的账号就可以获取。

地址如下:https://cloud.tencent.com/document/product/269/1504

按照上述步骤进行操作,就可以获取,而且其中会让你设置一个管理员用户。记住这个非常关键。

第3个用户id就更简单了,你可以设置任意的id,例如纯字符串、字符串加数字等等一些用户id。为了和你的注册用户相关联,建议你用用户提交的用户名来做这个用户id参数。这样通过你的服务器生成sig就可以了。现在我们有了如下文件:

1、用户账户名

2、用户密码

3、sdkappid

4、用户账户名对于的sig文件(实际是一串字符串)

有了这么多文件我们就可以尝试登陆了,在云通讯后台,我们新建一个群组。

1531404796756.jpg现在我们来总结下我们已有的工件:

1、用户账户名

2、用户密码

3、sdkappid

4、用户账户名对于的sig文件(实际是一串字符串)

5、群ID

6、管理员ID

7、群主ID(实际你可以设置成你的管理员id)


然后来打开官方给的demo

//帐号模式,0-表示独立模式,1-表示托管模式
        var accountMode = 0;
        //官方 demo appid,需要开发者自己修改(托管模式)
        var sdkAppID = 云通讯后台查看;
        var accountType = 云通讯后台查看;
        //当前用户身份
        var loginInfo = {
            'sdkAppID': sdkAppID, //云通讯后台查看
            'identifier': '用户id', //当前用户ID,必须是否字符串类型,必填
            'accountType': accountType, //云通讯后台查看
            'userSig': '这个就是你用户id对应的sig文件', //当前用户身份凭证,必须是字符串类型,必填
            'identifierNick': null, //当前用户昵称,不用填写,登录接口会返回用户的昵称,如果没有设置,则返回用户的id
            'headurl': 'img/me.jpg' //当前用户默认头像,选填,如果设置过头像,则可以通过拉取个人资料接口来得到头像信息
        };


这样修改一下,我们在运行会发现出现一个登录框。

屏幕快照 2018-07-12 下午10.17.56.png

第一个就是你的用户id   第二个就是你的用户id对应的sig文件里的字符串。输入后直接确定,就可以进入聊天窗口了。

WX20180712-221958.png


当你这个逻辑明白以后,你就可以去看各种回调了。因为所有的东西都离不开这几个工件。

实际独立模式下用户的账户注册、登录、获取sig都跟腾讯不沾边。只有最后你生成好了用户id和sig你才可以去访问腾讯的服务器,其他的操作都是在你自己的服务器上操作的。

ThinkApiAdmin 回调机制实现

ThinkApiAdmin 是糅合了thinkadmin和apiadmin的一个合体版本。均采用上述两种框架的v2版本来进行整合的。其中有一个比较重要的父类:BasicAdmin

此时仅来说一下它其中的一个父类方法:_list

/**
 * 列表集成处理方法
 * @param Query $dbQuery 数据库查询对象
 * @param bool $isPage 是启用分页
 * @param bool $isDisplay 是否直接输出显示
 * @param bool $total 总记录数
 * @param array $result
 * @return array|mixed
 * @throws \think\db\exception\DataNotFoundException
 * @throws \think\db\exception\ModelNotFoundException
 * @throws \think\exception\DbException
 */
protected function _list($dbQuery = null, $isPage = true, $isDisplay = true, $total = false, $result = [])
{
    $db = is_null($dbQuery) ? Db::name($this->table) : (is_string($dbQuery) ? Db::name($dbQuery) : $dbQuery);
    // 列表排序默认处理
    if ($this->request->isPost() && $this->request->post('action') === 'resort') {
        $data = $this->request->post();
        unset($data['action']);
        foreach ($data as $key => &$value) {
            if (false === $db->where('id', intval(ltrim($key, '_')))->setField('sort', $value)) {
                $this->error('列表排序失败, 请稍候再试');
            }
        }
        $this->success('列表排序成功, 正在刷新列表', '');
    }
    // 列表数据查询与显示
    if (null === $db->getOptions('order')) {
        $fields = $db->getTableFields($db->getTable());
        in_array('sort', $fields) && $db->order('sort asc');
    }
    if ($isPage) {
        $rows = intval($this->request->get('rows', cookie('rows')));
        cookie('rows', $rows >= 10 ? $rows : 20);
        $page = $db->paginate($rows, $total, ['query' => $this->request->get('', '', 'urlencode')]);
        list($pattern, $replacement) = [['|href="(.*?)"|', '|pagination|'], ['data-open="$1"', 'pagination pull-right']];
        list($result['list'], $result['page']) = [$page->all(), preg_replace($pattern, $replacement, $page->render())];
    } else {
        $result['list'] = $db->select();
    }
    if (false !== $this->_callback('_data_filter', $result['list']) && $isDisplay) {
        !empty($this->title) && $this->assign('title', $this->title);
        return $this->fetch('', $result);
    }
    return $result;
}

前面基本是都是数据的刷新,排序等一些方法。注意最后面几行

if (false !== $this->_callback('_data_filter', $result['list']) && $isDisplay) {
    !empty($this->title) && $this->assign('title', $this->title);
    return $this->fetch('', $result);
}

这几行代码就是实现回调机制的,回调如果用C/C++来理解的话应该是指针传值,通过指针访问函数。例如下面的代码:

void callBack(l callback)
{
    /** NULL */
}
void subCallback(char *s) 
{
    /** NULL */
} 
callBack(&subCallback);

上述代码是一段回调,如果可以理解,那么就可以继续下去。如果没有学过指针,那就不要在意了。只要知道回调函数是指的是将函数已参数的形式传递并调用就好了。

可以看到它是首先调用了callback这个函数传递了一个字符串和一个关联数组的内容,掉用完后判断一下是否等于true 并且 判断是否可以显示。那么关键的就是看看这个callback函数了。

    /**
     * 当前对象回调成员方法
     * @param string $method
     * @param array|bool $data
     * @return bool
     */
    protected function _callback($method, &$data)
    {
        foreach ([$method, "_" . $this->request->action() . "{$method}"] as $_method) {
            if (method_exists($this, $_method) && false === $this->$_method($data)) {
                return false;
            }
        }
        return true;
    }

这一句话:[$method, "_" . $this->request->action() . "{$method}"]  实际是一个索引数组。

array(2) {
  [0] => string(12) "_data_filter"
  [1] => string(18) "_index_data_filter"
}

当第二次循环到_index_data_filter的时候就可以进行调用了

 /**
     * 列表数据处理
     * @param $list
     */
    protected function _index_data_filter(&$list)
    {
        dump("www");die();
        $alert = [
            'type' => 'danger',
            'title' => '操作安全警告(谨慎刷新接口路由)',
            'content' => '新增或编辑接口后必须刷新接口路由才能正常访问!请根据实际情况刷新路由!'
        ];
        $tags = Db::name($this->table_api_group)->column('id,name');
        $handlers = Db::name($this->table_admin)->column('id,username');
        foreach ($list as &$vo) {
            $vo['tags_list'] = [];
            $vo['handler_name'] = Db::name($this->table_admin)->where('id', $vo['handler'])->value('username');
            foreach (explode(',', $vo['gid']) as $tag) {
                if ($tag !== '' && isset($tags[$tag])) {
                    $vo['tags_list'][$tag] = $tags[$tag];
                } elseif ($tag !== '') {
                    $vo['tags_list'][$tag] = $tag;
                }
            }
        }
        $this->assign(['handlers' => $handlers, 'alert' => $alert, 'tags' => $tags]);
    }

实际就是执行了上述代码。具体的执行指令就是: $this->$_method($data)php5+这种写法是允许的即直接指向一个变量,这个变量如果是函数名的话,则会直接运行这个函数。虽然运行这个函数的上下文句柄是Row.php文件,但是其实你是基于BasicAdmin这个类的。而这个函数定义在BasicAdmin类中,所以你可以进行引用。


流程:parent::_list(ROW模块) -> _callback(BasicAdmin模块 -> _index_data_filter (ROW模块)


php5+的版本中你还可以直接 new $var 。这样来初始化类,即使这个变量只存了一个类名,但是你依然可以引用。

ThinkPHP5 Mac 安装 mkdir 错误

安装的时候提示:mkdir(): Permission denied 。大致看了一下说是没有权限进行文件读写,如果知道ThinkPHP5的目录结构的话,就会了解有一个runtime的文件夹,这个文件夹主要是运行时产生的一些临时文件的存放位置。于是给这个文件配置777的权限。

进入:cd /Applications/XAMPP/xamppfiles/htdocs/thinkphp5

输入:chmod -R 777 runtime




ThinkPHP5 Extend 扩展写法

首先ThinkPHP是基于PHP来编写的一套框架,所以它的扩展自动加载的方法也必须依托于PHP,而PHP对自动加载进行了PSR-4规则限定。下面两个是中英文说明。

英文:https://www.php-fig.org/psr/psr-4/

中文:https://www.jianshu.com/p/e0a33214688b


了解了规则后编写就相对简单,ThinkPHP扩展的编写过程,实际就是一个类文件的编写过程。只不过不能避免的要加上命名空间等等内容,这些内容上述说明也有写。这里主要说一下父类或者超类的问题。


父类是可以继承的类,在其他语言中可以存在多继承,而PHP中只能单继承,即只继承一个父类。


类的构造方法:__construct 这个方法是你在New一个类的时候,由解释器进行类初始化的时候第一个执行的页面函数。这里可以进行一些初始化的行为。


注意的是:如果在子类中定义与父类中相同的函数或者方法。那么父类中相同的函数或方法会被覆盖,即不会执行。哪怕他是构造函数等一些魔术方法。所以如果你在子类中定义了__construct那么父类不会被执行。


例子:

/** 父类 */
class TopParent
{
    public function __construct()
    {
        echo "TopParent";
    }
}
/** 子类 */
class SubClass extends TopParent
{
     public function __construct(Request $request = null)
     {
         echo "SubClass";
     }
}
/** Tp引用 */
class Index extends SubClass
{
    public function index()
    {
        echo "";
    }
}

结果输出:

*~:SubClass


这里定义了一个SubClass类,并且是基于Controller的子类。基于ThinkPHP5的运行机制,会将要给Controller的初始化参数,即__construct参数所需要的值传递给SubClass子类。在你新建这个类的时候IDE会自动帮你加上一句话:parent::__construct($request);这句话就是手动运行父类的构造方法。因为父类的构造方法不会再自己执行。当然你也可以不执行父类的构造方法。但是如果不执行的话,如果你的派生类用到了父类的方法。而这个方法恰恰需要父类自己进行初始化。那么很有可能会报错。所以在执行一遍父类的构造方法很有必要。


对于为什么控制器可以有一个默认的$request参数。这里可以参考如下代码:

文件路径:thinkphp\library\think\Loader.php

/**
     * 实例化(分层)控制器 格式:[模块名/]控制器名
     * @access public
     * @param  string $name         资源地址
     * @param  string $layer        控制层名称
     * @param  bool   $appendSuffix 是否添加类名后缀
     * @param  string $empty        空控制器名称
     * @return object
     * @throws ClassNotFoundException
     */
    public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
    {
        list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);
        if (class_exists($class)) {
            return App::invokeClass($class);
        }
        if ($empty) {
            $emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix);
            if (class_exists($emptyClass)) {
                return new $emptyClass(Request::instance());
            }
        }
        throw new ClassNotFoundException('class not exists:' . $class, $class);
    }


php cookie 与 session

一:cookie(需要浏览器支持cookie功能)

存储在客户端 由服务器返回 http响应头信息发送给客户端 下次访问自动携带。

设置函数:setCookie(由于http协议头的限定,必须在其他输出信息到浏览器前调用)多次设置相同字段的值会进行覆盖操作。

客户端发给服务器的信息中如果包含cookie信息, 则会被保存在$_COOKIE全局数组中(数组为关联数组)。

//设置多维数组的方法:
setCookie("user[username]","name");
setCookie("user[password]","pw");
//获取方法
$_COOKIE["user"]["username"]
...

删除方法:

1、键值覆盖为空

2、时间设置为过期



二:session(会话) 存储在服务端 由客户端提供

客户端提交信息,服务器将信息存储进session,但是会提供一个32位的十六进制字符串(永不重复)。然后将32位的字符串传递给客户端,客户端存储进cookie下次登录的时候会提供这个32位的十六进制字串。这样服务器在通过字串进行session信息提取。注意的是这个字串在客户端cookie里储存的期限是通过配置文件确定的即php.ini配置文件里的session.cookie_lifetime字段。


使用方法:

启动session:session_start(void); //函数是为了php将一些与session相关的内建环境变量预先载入到内存。


三:cache

存在于浏览器中。

ThinkAdmin echo var_dump print_r 错误

360截图16850825519483.png


ThinkAdmin在进行代码调试的时候会出现输出错误,导致前台显示一个错误弹窗。这里可以更改如下位置达到解决无法调试的问题。

文件位置:thinkadmin\extend\controller\BasicAdmin.php

操作名称:_list

/**
     * 列表集成处理方法
     * @param Query $dbQuery 数据库查询对象
     * @param bool $isPage 是启用分页
     * @param bool $isDisplay 是否直接输出显示
     * @param bool $total 总记录数
     * @param array $result 结果集
     * @return array|string
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     * @throws \think\Exception
     */
    protected function _list($dbQuery = null, $isPage = true, $isDisplay = true, $total = false, $result = [])
    {
        $db = is_null($dbQuery) ? Db::name($this->table) : (is_string($dbQuery) ? Db::name($dbQuery) : $dbQuery);
        // 列表排序默认处理
        if ($this->request->isPost() && $this->request->post('action') === 'resort') {
            foreach ($this->request->post() as $key => $value) {
                if (preg_match('/^_\d{1,}$/', $key) && preg_match('/^\d{1,}$/', $value)) {
                    list($where, $update) = [['id' => trim($key, '_')], ['sort' => $value]];
                    if (false === Db::table($db->getTable())->where($where)->update($update)) {
                        $this->error('列表排序失败, 请稍候再试');
                    }
                }
            }
            $this->success('列表排序成功, 正在刷新列表', '');
        }
        // 列表数据查询与显示
        if (null === $db->getOptions('order')) {
            in_array('sort', $db->getTableFields($db->getTable())) && $db->order('sort asc');
        }
        if ($isPage) {
            $rows = intval($this->request->get('rows', cookie('page-rows')));
            cookie('page-rows', $rows = $rows >= 10 ? $rows : 20);
            // 分页数据处理
            $query = $this->request->get();
            $page = $db->paginate($rows, $total, ['query' => $query]);
            if (($totalNum = $page->total()) > 0) {
                list($rowHTML, $curPage, $maxNum) = [[], $page->currentPage(), $page->lastPage()];
                foreach ([10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200] as $num) {
                    list($query['rows'], $query['page']) = [$num, '1'];
                    $url = url('@admin') . '#' . $this->request->baseUrl() . '?' . http_build_query($query);
                    $rowHTML[] = "<option data-url='{$url}' " . ($rows === $num ? 'selected' : '') . " value='{$num}'>{$num}</option>";
                }
                list($pattern, $replacement) = [['|href="(.*?)"|', '|pagination|'], ['data-open="$1"', 'pagination pull-right']];
                $html = "<span class='pagination-trigger nowrap'>共 {$totalNum} 条记录,每页显示 <select data-auto-none>" . join('', $rowHTML) . "</select> 条,共 {$maxNum} 页当前显示第 {$curPage} 页。</span>";
                list($result['total'], $result['list'], $result['page']) = [$totalNum, $page->all(), $html . preg_replace($pattern, $replacement, $page->render())];
            } else {
                list($result['total'], $result['list'], $result['page']) = [$totalNum, $page->all(), $page->render()];
            }
        } else {
            $result['list'] = $db->select();
        }
        if (false !== $this->_callback('_data_filter', $result['list'], []) && $isDisplay) {
            !empty($this->title) && $this->assign('title', $this->title);
            if (\think\facade\Config::get("app_debug")) {
                return view("", $result);
            } else {
                return $this->fetch("", $result);
            }
        }
        return $result;
    }


造成的原因不清楚,如果有清楚的欢迎告诉我。

ApiAdmin user-token

360截图170811067710199.png


首先来看公共请求部分,是作者加在http协议头里面的一些固定的字段参数,对应了一些限制类功能。首先来看user-token实现部分。作者在文档里没有提到。起码是在今天以前没有提到。所以我们就要去看源代码。

/**
     * TODO::需要根据实际情况另外改写
     * 检测用户登录情况  检测通过请赋予USER_INFO值
     */
    private function checkLogin() {
        $userToken = $this->request->header('user-token', '');
        if ($this->apiInfo['needLogin']) {
            if (!$userToken) {
                return json(['code' => ReturnCode::AUTH_ERROR, 'msg' => '缺少user-token', 'data' => []]);
            }
        }
        if ($userToken) {
            $userInfo = cache('wx:openId:' . $userToken);
            var_dump(cache('wx:openId:' . $userToken));
            if (!is_array($userInfo) || !isset($userInfo['openId'])) {
                return json(['code' => ReturnCode::AUTH_ERROR, 'msg' => 'user-token不匹配', 'data' => []]);
            }
            ApiLog::setUserInfo($userInfo);
        }
    }

这里作者的意见也是自己实现。这里只是演示一下流程,剩余的自由发挥。

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/7/2/002
 * Time: 11:52
 */
namespace app\api\controller;
use think\Cache;
class User extends Base
{
    /**
     * 验证user_token
     */
    public function getVar(){
       echo "验证了token";
    }
    /**
     * 设置缓存
     */
    public function setVar(){
        cache('wx:openId:' . "aaaaaaaaaaa",['openId'=>'ccc','aa'=>'cc']);
        echo "缓存已经设置";
    }
}

这里写了两个接口,第一个是验证user_token,第二个是设置缓存。首先访问第二个函数。

360截图17571119204919.png

可以看到缓存已经设置。这个时候在来访问验证token的函数。

360截图17001020315824.png


你要先设置好head头才可以访问正确。这里我也验证了access_token所有都填写了一下。如果不填aaaaaaaaaaa而填写其他的。可以看一下效果。

360截图18180715819089.png可以看到为不匹配。这里要说一下他的验证方式。 

1、获取协议头里的user-token数据。

2、判断是否需要判断user-token

3、获取缓存:(格式):wx:openId:xxxxxxxxxx 这里的xxxxxxxxxx是你要预先设置好的缓存名字。

4、判断缓存是否是数组格式

5、判断缓存是否存在openId字段


如果上述条件都满足的话就会user-token验证通过。

ApiAdmin 搭建过程

QQ截图20180630180348.png

官网安装步骤:http://apiadmin.gitee.io/apiadmin-wiki/#/zh-cn/3.0/quick-start

注意如果你下载的时候是3.0.8版本,并且提交历史上最新版本是2018-06-28那千万不要下载,因为miss路由有点问题。你可以下载a9fa3c901版本。如下图

QQ截图20180630175932.png

然后你用phpstudy新建两个本地的域名:

前台域名:q.com 这个随便一个文件夹比如xxxxxx文件夹

后台域名:h.com 指向ApiAdmin/public文件夹

然后下载phpstudy的redis扩展安装。这样再按官网的文档部署就可以了。这里我也把需要的文件整个打包了。地址如下:

阅读全文»

PHP-CPP 使用 PCRE 正则表达式

PCRE官网:http://www.pcre.org

PCRE下载:https://ftp.pcre.org/pub/pcre/

----------------------------

MAC系统安装

首先我们点开电脑(shift+command+c)。进入以后按下(shift+command+g)输入:/usr/local 回车。如下图

屏幕快照 2018-06-22 下午6.37.19.png

进入以后会发现很多文件夹,这个时候需要你新建一个src文件夹,或者你已经有了这个文件夹,那么直接进入就好。路径:/usr/local/src 然后将你下载好的pcre源码放入进去。

屏幕快照 2018-06-22 下午6.39.28.png

进入源码文件夹,我们点击齿轮符号拷贝下路径:/usr/local/src/pcre2-10.31 进入终端。

输入:cd /usr/local/src/pcre2-10.31 回车

进入源码文件夹后,依次输入如下命令:

./configure --prefix=/usr/local 
make 
sudo make install

就可以了,如果你想检查是否安装成功可以输入:make -k check 然后看提示就好了。

使用

注意你的makefile描述文件。加入如下链接描述符 LINKER_FLAGS=-shared -lcurl-lpcre


PHP-CPP 调用PHP原生函数

最近一直忙于面试,就没有多少时间写博客。今天继续写关于PHP-CPP相关的内容。

官网文档地址:http://www.php-cpp.com/documentation/calling-functions-and-methods


调用php函数格式

Php::Value data = Php::call("some_function", "some_parameter");

可以看到是通过一个Php:call的函数实现的,这个函数的定义如下:

/**
 *  Call a function in PHP
 *  @param  name        Name of the function to call
 *  @param  params      Variable number of parameters
 *  @return Value
 */
template <typename ...Params>
Value call(const char *name, Params&&... params)
{
    // the name can be turned into a Php::Value object, which implements
    // the operator () method to call it
    Value function(name);
    // invoke the operator ()
    return function(std::forward<Params>(params)...);
}


第一个参数:调用函数的名称

第二个参数:调用函数的参数

函数返回值:返回一个Value类型的数值


调用函数例子

Php::Value cpp_print_r(Php::Parameters &params)
    {
        Php::Value data = Php::call("print_r",params[0]);
        return data;
    }


调用对象/类(情况1)

// create an object (this will also call __construct())   
Php::Object time("DateTime", "now");      // call a method on the datetime object  
Php::out << time.call("format", "Y-m-d H:i:s") << std::endl;


通过Php:object对象。格式:Php:object 对象名(“类名”,construct默认参数)。这里为什么传入一个now可能大家没有注意,可以看php手册。

public __construct ([ string $time = "now" [, DateTimeZone $timezone = NULL ]] )

从这里可以看出实际第一句话的object对象是construct的实现。而construct实际是返回你的对象上下文的。所以第一条是通过construct创建一个datetime类返回给了time。然后time可以继续用call方法调用datatime类里面的方法。

调用对象/类(情况2)

// in PHP it is possible to create an array with two parameters, the first    
// parameter being an object, and the second parameter should be the name    
// of the method, we can do that in PHP-CPP too     
Php::Array time_format({time, "format"});      // call the method that is stored in the array     
Php::out << time_format("Y-m-d H:i:s") << std::endl;

上述这种方法实际与第一种方法类似,只不过是把object对象和要调用的函数全部放入数组了。

PHP-CPP Php::ByVal和Php::ByRef详解

理解Php::ByRefPhp::ByVal之前我们先来补充几个知识点(关于C语言):

指针:指针的基本概念 在计算机中,所有的数据都是存放在存储器中的。 一般把存储器中的一个字节称为一个内存单元, 不同的数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元等, 在第二章中已有详细的介绍。为了正确地访问这些内存单元, 必须为每个内存单元编上号。 根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。 既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。 

下面这一张图很好的解释了这个指针:

32.png

参数调用:传值:传值调用是指方法在调用参数时,不是对原参数进行操作,而是创建参数的拷贝并对进行操作,这种调用有利于保护数据。简单理解代码:

int i=1;
int m(int a){
    return a;
}

调用:

int c=m(i);

上述代码相当于一个简单的赋值语句,这种函数调用参数的方法实际就是传值。这里传入的是实际的数值:1。


参数调用:引用:传值调用是指引函数传递参数的一种方式,使用引用调用,可以在子函数中对形参所做的更改对主函数中的实参有效。:

int i=1; //变量 也叫做实参
int m(int a){ //int a 这里的a就是形参
    return a; //返回形参
}
int c=m(i);

上述代码只是简单的解释一下形参 实参。下面这段代码是百度里面的一段解释引用的解释。

int i,j; //定义两个变量 i 和 j
int &ri=i; //新建int型的引用ri,并将其初始化为变量i的一个别名
j=10; //修改j变量的内容为10
ri=j; //相当于i=j 你修改ri实际就是修改i。因为你修改的ri实际是修改的i的内存当中的指针。
//因为ri存储的是i变量在内存当中的地址

上述代码就是对引用的定义以及解释总结一下:

1:声明一个引用时,必须同时对它进行初始化,使它指向一个已经存在的对象。

2:一旦一个引用被初始化后,就不能改为指向其它对象(也就是说,一个引用从它诞生之时起,就必须确定是哪个变量的别名,而且始终只能作为这个变量的别名不能另作他用):

3:引用也可以作为形参,作为形参时候,情况稍有不同,这是因为,形参的初始化不在类型说明时候进行,而是在执行主调函数的调用表达式时 ,才为形参分配内存空间,同时用实参来初始化形参。这样引用类型的形参就通过形实结合,成为实参的一个别名,对形参的任何操作也就会直接作用于实参。


浅拷贝:在有指针的情况下,浅拷贝只是增加了一个指针指向已经存在的内存。


深拷贝:增加一个指针并且申请一个新的内存,使这个增加的指针指向这个新的内存,采用深拷贝的情况下,释放内存的时候就不会出现在浅拷贝时重复释放同一内存的错误。


理解完上述的情况,我们来说一下Php:ByVal和Php:ByRef。


Php:ByVal:可以理解为传值,传入具体的数值。传入的变量,在函数体内修改不会影响函数体外的定义。

例如下面的代码片段

int i=10;
int m(int a){
    return a++;
}
m(i);
printf(i);

运行上述代码,实际i变量还是为10。你虽然在函数体内更改了它的传入参数。但是你并没有更改外部的定义。所以还是10。

Php:ByRef:可以理解为引用(网上还有叫:传地。千篇一律),而它的方式跟浅拷贝类似。就是你传入的变量,实际传入的不是具体的值,而是传入的这个变量在内存中的地址,由于这种特性所以你不能直接调用的时候填入具体的值,而必须填写变量。

例如下面的代码片段

/* 传入具体的值 */
m("www.iwonmo.com"); //错误 注:在Php:ByVal就是正确的,因为Php:ByVal是传值调用。可以传入变量,或者具体值
/* 传入变量 */
char *i="www.iwonmo.com";
m(i);//正确

再看下面这段代码:

int i=10;
int m(int &a){
    return a++;
}
m(i);
printf(i);


这个时候打印的应该就是11。注意引用调用形参的时候,形参变量名前面要加“&”。这是一个硬性规定,没有什么解释的。是编程语言的书写格式。

微信小程序发送模板消息

 
<?php
 
function Get_M($url){
    $curl = curl_init(); // 启动一个CURL会话
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_HEADER, 0);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
    $tmpInfo =   curl_exec($curl)  ;     //返回api的json对象
    curl_close($curl);
    if(strpos($tmpInfo,"access_token")>0){
        $de_json = json_decode('['.$tmpInfo.']',TRUE);
        return $de_json[0]['access_token'] ;
    }
    return "";
}
function Post_M($p_url,$p_data){
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $p_url);
    // 执行后不直接打印出来
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    // 设置请求方式为post
    curl_setopt($ch, CURLOPT_POST, true);
    // post的变量
    curl_setopt($ch, CURLOPT_POSTFIELDS, $p_data);
    // 跳过证书检查
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    // 不从证书中检查SSL加密算法是否存在
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    $output = curl_exec($ch);
    curl_close($ch);
    //echo $output;
    return $output;
}
function postTemplate($formid,$p_name,$p_nr,$p_sj){
    $access_token=Get_M("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=微信小程序的id&secret=secret的内容");
    $postTemplateUrl="https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=".$access_token;
$postdata='{
  "touser": "替换为你要发送的openid",
  "template_id": "模板的id",
  "form_id": "'.$formid.'",
  "data": {
      "keyword1": {
          "value": "'.$p_name.'"
      },
      "keyword2": {
          "value": "'.$p_nr.'"
      },
      "keyword3": {
          "value": "'.$p_sj.'"
      }  
  }
}';
 Post_M($postTemplateUrl,$postdata);
 return '1';
 }
?>



上面是我定义的一个函数,替换如下内容:

微信小程序的id

secret的内容

替换为你要发送的openid

模板的id

调用postTemplate就可以了。

PHP-CPP 参数的定义以及参数的转换

php传递一个字符串给php-cpp写的插件。从官方来看php-cpp基本都是向量(vector:编程语言的一种数据结构)来进行传递参数的。下面这个官方的例子就可以看出来。

#include <phpcpp.h>
void example(Php::Parameters &params)
{
}
extern "C" {
    PHPCPP_EXPORT void *get_module() {
        static Php::Extension myExtension("my_extension", "1.0");
        myExtension.add<example>("example", {
            Php::ByVal("a", Php::Type::Numeric),
            Php::ByVal("b", "ExampleClass"),
            Php::ByVal("c", "OtherClass")
        });
        return myExtension;
    }
}


这个时候如果说我们想把params参数进行转换为char或者string应该怎么办?其实官方也给了答案:http://www.php-cpp.com/documentation/variables


/**
 *  Example function
 *  @param  params
 */
void myFunction(Parameters &params)
{
    // store the first parameter in a std::string (the entire string
    // buffer is copied from the Php::Value object to the std::string)
    std::string var1 = params[0];
    // it also is possible to cast the object into a const char *. This works
    // too, but the buffer is only valid for as long as the Php::Value object
    // stays in scope
    const char *var2 = params[0];
    size_t var2size = params[0].size();
}

上面代码中包含了各种字符串转换。官方对params的解释是一个向量结构体,里面全部都是php:Value结构,而php:Value又是对c++语言里的一些类型进行包装后的通用类型。所以上面的params[0]实际就是取出第一个向量单元,类似数组的第一个纸,这个数组的类型是php:Value。所以php:Value转字符串就很方便了。


不过我在转换的时候用的第二个方式直接const char *var2=params[0];。但是我打印出来的数据却是0。按理说是不应该的,应该是我代码写的问题。于是我先转换的string后转换的char *。下面是我修改后的代码,仅供参考。


        std::string str=params[0];
        
        const char *str_c = str.c_str();



mac apache 重启 关闭 开启

重启apache:sudo /usr/sbin/apachectl restart

关闭apache:sudo /usr/sbin/apachectl stop

开启apache:sudo /usr/sbin/apachectl start


Mac 编译&安装 PHP-CPP

What is PHP-CPP?

A C++ library for developing PHP extensions. It offers a collection of well documented and easy-to-use classes that can be used and extended to build native extensions for PHP.

Why PHP-CPP?

This C++ library makes building PHP extensions fun. Extensions built on top of PHP-CPP are easy to understand and simple to maintain, and your code looks great - and it gives a huge boost to your application!

官网

http://www.php-cpp.com


------------------


一:更新gcc

更新GCC是非常有必要的特别是你在安装了xcode的情况下,更新的原因是要支持C++11因为最新的PHPCPP里用了很多C++11的特性。更新方法非常简单打开终端运行如下命令:

brew search gcc #查找gcc源
brew install gcc@4.9 #更新对应版本

首先你可以先查找gcc的源,看看版本。我这里是更新的4.9。更新时间很长容易卡在make bootstrap。这里不用管,只需要等待就好。


更新完毕后的位置:

/usr/local/Cellar/gcc@4.9/4.9.4_1: 1,181 files, 214.5MB, built in 82 minutes 5 seconds

更新完后会提示所在位置,在这里管不管都行。


二:安装gcc

简单的来说,你更新好了以后并不代表可以用,因为存在一个多版本gcc共存这个问题,要想让mac用你最新的gcc你可以这样操作。

打开终端输入:vim ~/.bash_profile

没有用过vim的也不要怕,直接把下面的代码ctrl+v粘贴到终端。

alias gcc="gcc-4.9"
alias g++="g++-4.9"
alias cc="gcc-4.9"
alias c++="c++-4.9"

粘贴完毕之后按下esc键。这个时候是让你输入指令的,而并非编辑文本。按下之后你按下 shift 和 键。这个时候会在终端最底部出现一个 符号。然后就可以输入命令了。直接输入wq按下回车。就可以退出和保存你的代码了。

:wq   将缓冲区内的资料写入磁盘中,并离开vi。

然后退回终端后输入:source ~/.bash_profile 这条指令是让你刚才设置的环境变量生效(不用重启)。

终端输入:gcc -v 看看效果吧。


三:下载php源码

http://www.iwonmo.com/archives/1311.html


源码也可以去php的官方下载,上述地址里也有官方地址。但是建议你用我的百度网盘,因为官网速度很恐怖。


四:下载phpcpp源码

$ git clone https://github.com/CopernicaMarketingSoftware/PHP-CPP.git


上述这条指令就可以phpcpp代码下载下来了。位置在:电脑-macintosh hd -用户 - (你自己的登陆名)-php-cpp


这里的PHP-CPP就是源码文件夹,我们要修改几个地方。

1、删除zend这个文件夹。

2、还记得下载的php源码吧。源码包里有一个zend文件夹,拖过来放到php-cpp里。

3、注意你下载的php版本,我给出的是7.0以上的。所以你下载的phpcpp也要是兼容7.0以上的,也就是最新代码就行。

4、修改makefile文件,这里很简单,修改两处地方:

    4.1、官方给出的地方:

LINKER_FLAGS=-shared 

改为 

LINKER_FLAGS=-shared -undefined dynamic_lookup

    4.2、官方没有给出的地方:

-soname

改为

-install_name

    因为Mac OS下的编译器Clang不支持链接器选项-soname。


到此为止基本上都可以了。


五、make

打开你的终端直接输入:cd php-cpp 进入phpcpp源码目录。然后输入:make 编译。最后会出现如下的显示:

mkdir -p shared/common

mkdir -p shared/zend

c++ -Wall -c -std=c++11 -fvisibility=hidden -DBUILDING_PHPCPP -Wno-write-strings -MD -g -fpic -o shared/common/modifiers.o common/modifiers.cpp

c++ -Wall -c -std=c++11 -fvisibility=hidden -DBUILDING_PHPCPP -Wno-write-strings -MD -g -fpic -o shared/common/streambuf.o common/streambuf.cpp

c++ -shared -undefined dynamic_lookup -g `php-config --ldflags` -Wl,-install_name,libphpcpp.so.2.0 -o libphpcpp.so.2.0.0 shared/common/modifiers.o shared/common/streambuf.o 

ld: warning: directory not found for option '-L/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.Internal.sdk/usr/lib'

mkdir -p static/common

mkdir -p static/zend

c++ -Wall -c -std=c++11 -fvisibility=hidden -DBUILDING_PHPCPP -Wno-write-strings -MD -g  -o static/common/modifiers.o common/modifiers.cpp

c++ -Wall -c -std=c++11 -fvisibility=hidden -DBUILDING_PHPCPP -Wno-write-strings -MD -g  -o static/common/streambuf.o common/streambuf.cpp

ar rcs libphpcpp.a.2.0.0 static/common/modifiers.o static/common/streambuf.o 


Build complete.


可以看到组建完成。


六、安装

sudo make install

make指令输入完成,并且编译完成后。直接输入上述指令。


七、组建hello world

http://www.php-cpp.com/EmptyExtension.zip

上述地址是官方提供的一个基础的插件下载地址。如果你在编译也就是make的时候出现

thread_local thread-local storage is not supported for the curren 错误。那你可以更新你的mac版本就可以了,这句话的意思是TLS已经禁用。网上说更新下MAC版本就好了。我更新了一下mac osx 版本,奇迹的好了。


注意你git的php-cpp源码里有一个空的例子目录为下:php-cpp/examples/emptyextension。cd命令切换到进去,然后直接make也可以得到一个基础的插件。


ThinkPHP 手动连接数据库实现断线重连

首先要手动连接数据库,需要知道ThinkPHP连接数据库的函数:

/**
     * 连接数据库方法
     * @access public
     */
    public function connect($config='',$linkNum=0,$autoConnection=false) {
        if ( !isset($this->linkID[$linkNum]) ) {
            if(empty($config))  $config =   $this->config;
            try{
                if(empty($config['dsn'])) {
                    $config['dsn']  =   $this->parseDsn($config);
                }
                if(version_compare(PHP_VERSION,'5.3.6','<=')){ 
                    // 禁用模拟预处理语句
                    $this->options[PDO::ATTR_EMULATE_PREPARES]  =   false;
                }
                $this->linkID[$linkNum] = new PDO( $config['dsn'], $config['username'], $config['password'],$this->options);
            }catch (\PDOException $e) {
                if($autoConnection){
                    trace($e->getMessage(),'','ERR');
                    return $this->connect($autoConnection,$linkNum);
                }elseif($config['debug']){
                    E($e->getMessage());
                }
            }
        }
        return $this->linkID[$linkNum];
    }


我们要做的就是导出这个函数


这里先看ThinkPHP的调用方法:

    /**
     * 初始化数据库连接
     * @access protected
     * @param boolean $master 主服务器
     * @return void
     */
    protected function initConnect($master=true) {
        if(!empty($this->config['deploy']))
            // 采用分布式数据库
            $this->_linkID = $this->multiConnect($master);
        else
            // 默认单数据库
            if ( !$this->_linkID ) $this->_linkID = $this->connect();
    }

这个函数主要是判断一下数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)。如果是0的话就是单一服务器,那就默认linkNum为0。直接调用connect获取PDO对象的上下文返回。但是这个函数是protected定义方式。我们外部是没办法直接引用的。所以你可以在它的上面定义一个函数newInitConnect来进行连接,这里更改protected为public。这样外部就能引用了。

引用方法:$this->db->newInitConnect();


PDOMysql.Lib.class版本的initConnect

    /**
     * 初始化数据库连接
     * @access protected
     * @param boolean $master 主服务器
     * @return void
     */
    protected function initConnect($master=true) {
        //读写分离数据库配置
        if ($this->config['deploy_type'] == 1){
            $this->_linkID = $this->multiConnect($master);
        }else{
            $this->_linkID = $this->singleConnect();
        }
    }
protected function singleConnect($master=false){
    if(!$master){
        if (!$this->connected){
            $this->traceCfg = $this->config;
            $this->traceCfg['link_num'] = 0;
            $this->_linkID              = $this->connect();
        }
    }
}

注意一下这个$this->config。当你连接过一次以后这里的config已经被置为NULL。所以你需要更改为:

 $this->config = $this->traceCfg;


通过这样的操作可以实现CLI断线重连。

微信小程序navigator组件传递参数

QQ截图20180512103131.png



在做一个项目的时候发现URL传参的话会出现“乱码”。后来想想觉得实际挺有意思。首先这个传参的方法实际是http的get传参。所以url在分享的时候会变成URL编码。这样的话中文需要解码。


在JavaScript里面有这么一个函数:decodeURIComponent

decodeURIComponent(URIstring)


URIstring就是需要解码的文本。