Yii2.0 – Word上传下载和预览功能

我们这篇来说下怎么上传Word文档以及预览功能,下载功能之前有所提到,这里我先单独将下载功能单独提出来。

下载

/**
 * 下载单独文件接口
 * @param file_name 需要下载的文件
 * @param ext 文件扩展名
 * @example http://127.0.0.1/basic/web/index.php?r=my/downfile&file_name=1.doc
 */
public function actionDownfile($file_name, $ext = "doc") {
    // 
    if (file_exists($file_name)) {
        // 文件存在
        ob_start();
        $download_filename = date("YmdHis") . "." . $ext;
        header("Content-type:application/octet-stream"); 
        header("Accept-Ranges:bytes");
        header("Content-Disposition:attachment;filename=" . $download_filename);
        $size = readfile($file_name);
        header("Accept-Length:" . $size);
    }
}

效果如下图所示

上传功能

在前面php文件上传中,我们使用ajax完成了文件上传功能。这里我们使用Yii2.0来完成文件上传功能。
由于前端使用ajax来传文件,所以我们这里依旧展示服务器的代码即可

/**
 * 上传文件接口
 * 前端Ajax调用
 */
public function actionUploadfile() {
    if (isset($_FILES) && isset($_FILES['myfile'])) {
        $err = "";
        $picname = $_FILES['myfile']['name'];   // 这里和提交的file表单名字相同, < input id="fileupload" type="file" name="myfile" >
        $picsize = $_FILES['myfile']['size'];
        if ($picname != "") { 
            if ($picsize > 512000 * 2) { // 限制上传大小 
                $err = '文件不能超过1M'; 
            } 
            $type = strstr($picname, '.'); //限制上传格式 
            if ($type != ".doc") { 
                $err = '文件格式不对!'; 
            }
            if ($err == "") {
                $rand = rand(100, 999); 
                $pics = date("YmdHis") . $rand . $type; //命名图片名称 
                
                //上传路径 
                $pic_path = "DocDir/" . $pics; 
                move_uploaded_file($_FILES['myfile']['tmp_name'], $pic_path);
            }
        } 
        $size = round($picsize / 1024, 2); //转换成kb 
        $arr = array( 
            'name' => $picname, 
            'filename' => $pics, 
            'size' => $size 
        ); 
        echo json_encode($arr); //输出json数据 
    }
}

在线预览功能

我们来看看在线预览怎么做
我们使用flexPaper + swftool的方式来做
首先我们先下载flexPaper,然后安装swftool
swftool参考:http://blog.csdn.net/zhizaibide1987/article/details/28901511
最终展示效果:http://47.94.156.112/FlexPaper
同时参考:http://blog.chinaunix.net/uid-25512152-id-3077202.html

Yii2.0 – 文件打包接口

这篇文章我们通过Yii2.0框架来完成文件打包下载功能,相信这个功能会在很多的地方有所使用。

Yii 2.0

开篇我们简单说下Yii框架,这个框架以MVC框架为基础,主要我们知道Controller、Model和View在哪里写就可以,我这里以basic为基础,同样的这篇文章不处理其他事情,比如路由什么的,只关注多文件打包zip以及下载功能。
比如这样的URL:http://127.0.0.1/basic/web/index.php?r=my/index
表示MyController的index方法
Controller文件写在controllers中
MyController.php

request->isPost) {
            $user_name = Yii::$app->request->post("name", "");
            echo $user_name;
        }
    }
}

这里我们新建一个action来作为多个文件打包下载的接口

/**
 * 多文件打包下载
 * @param file_list 表示下载文件的列表,数据格式可以多种,这里以json为例
 * @example file_list [1.txt, 2.txt, 3.txt]
 */
public function actionMutifiledown($file_list) {
    // TODO
    var_dump(json_decode($file_list, true));
}


现在我们的web目录下存在1.doc、2.doc、3.doc、4.doc几个文件,我们怎么将他们打包下载呢?

打包Zip

我们使用PHP的ZipArchive扩展,直接上代码

/**
 * 多文件打包下载
 * @param file_list 表示下载文件的列表,数据格式可以多种,这里以json为例
 * @example file_list [1.txt, 2.txt, 3.txt]
 */
public function actionMutifiledown($file_list) {
    // 现在存在1.doc;2.doc;3.doc;4.doc几个文件
    $zip = new \ZipArchive;    // 存在Zip扩展
    if ($zip->open("1.zip", \ZIPARCHIVE::CREATE)!==TRUE) {
        // 创建1.zip 确保存在权限
        exit();
    }
    $zip->addFile("1.doc");
    $zip->addFile("2.doc");
    $zip->addFile("3.doc");
    $zip->addFile("4.doc");
    $zip->close();
    // 这样我们就打包好了
}

Zip下载

我们知道已经生成了zip文件,下面直接下载到本地,代码如下:

/**
 * 多文件打包下载
 * @param file_list 表示下载文件的列表,数据格式可以多种,这里以json为例
 * @example file_list [1.txt, 2.txt, 3.txt]
 */
public function actionMutifiledown($file_list) {
    // 现在存在1.doc;2.doc;3.doc;4.doc几个文件
    $zip = new \ZipArchive;    // 存在Zip扩展
    if ($zip->open("1.zip", \ZIPARCHIVE::CREATE)!==TRUE) {
        // 创建1.zip 确保存在权限
        exit();
    }
    $zip->addFile("1.doc");
    $zip->addFile("2.doc");
    $zip->addFile("3.doc");
    $zip->addFile("4.doc");
    $zip->close();
    // 这样我们就打包好了
    ob_start();
    $filename = "1.zip";
    header("Content-type:application/octet-stream"); 
    header("Accept-Ranges:bytes");
    header("Content-Disposition:attachment;filename=1.zip");
    $size = readfile($filename);
    header("Accept-Length:" . $size);
}

完善接口

我们上面只是完成了功能,这里还有一些内容需要完善。
接口:http://127.0.0.1/basic/web/index.php?r=my/mutifiledown&file_list=[“1.doc”,”2.doc”,”3.doc”]

/**
 * 多文件打包下载
 * @param file_list 表示下载文件的列表,数据格式可以多种,这里以json为例
 * @example file_list [1.txt, 2.txt, 3.txt]
 * @param $ext 文件可打包扩展名,默认doc
 */
public function actionMutifiledown($file_list, $ext = "doc") {
    //
    $file_arr = json_decode($file_list, true);
    if (!empty($file_arr)) {
        $zipComp = new \ZipArchive;    // Zip扩展
        $zipName = strtotime(date("Y-m-d H:i:s")) . ceil(rand() % 100) . "zip";
        if ($zipComp->open($zipName, \ZIPARCHIVE::CREATE) !== TRUE) {
            exit();
        }
        foreach ($file_arr as $file) {
            $fileArr = explode(".", $file);
            $fileExt = $fileArr[count($fileArr) - 1];
            if ($fileExt == $ext) {
                if (file_exists($file)) {
                    $zipComp->addFile($file);
                }
            }
        }
        $zipComp->close();
        // 开始下载
        if (file_exists($zipName)) {
            ob_start();
            $download_filename = date("YmdHis") . "_archive.zip";
            header("Content-type:application/octet-stream"); 
            header("Accept-Ranges:bytes");
            header("Content-Disposition:attachment;filename=" . $download_filename);
            $size = readfile($zipName);
            header("Accept-Length:" . $size);
        }
    }
}

PHP – Solr查询方法

Solr

Solr采用Lucene搜索库为核心,提供全文索引和搜索开源企业平台,提供REST的HTTP/XML和JSON的API。
下载Solr的jar包
执行java -jar start.jar
访问http://localhost:8983/solr/
可以通过java -jar post.jar solr.xml monitor.xml添加文档。
solr.xml



 SOLR1000
 Solr, the Enterprise Search Server
 Apache Software Foundation
 software
 search
 Advanced Full-Text Search Capabilities using Lucene
 Optimized for High Volume Web Traffic
 Standards Based Open Interfaces - XML and HTTP
 Comprehensive HTML Administration Interfaces
 Scalability - Efficient Replication to other Solr Search Servers
 Flexible and Adaptable with XML configuration and Schema
 Good unicode support: h&#xE9;llo (hello with an accent over the e)
 0
 10
 true
 2006-01-17T00:00:00.000Z

数据的处理无外乎增加、删除、修改、查找。
我们来看看怎么操作?

  • 数据导入

可以使用DIH(DataImportHandler)从数据库导入数据
支持CSV文件导入,因此Excel数据也能轻松导入
支持JSON格式文档
二进制文档比如:Word、PDF
还能以编程的方式来自定义导入

  • 更新数据

如果同一份文档solr.xml重复导入会出现什么情况呢?实际上solr会根据文档的字段id来唯一标识文档,如果导入的文档的id已经存在solr中,那么这份文档就被最新导入的同id的文档自动替换。

  • 删除数据

java -Ddata=args -jar post.jar “SOLR1000”
java -Ddata=args -jar post.jar “name:DDR”

  • 查询数据

查询数据都是通过HTTP的GET请求获取的,搜索关键字用参数q指定,另外还可以指定很多可选的参数来控制信息的返回
http://localhost:8983/solr/collection1/select?q=solr&fl=name&wt=json&indent=true
排序:q=video&sort=price desc

全文检索基本原理

首先我们需要知道什么是索引。
想想字典,查一个字,我们用过拼音,比如“翘楚”这个词语,如果我们使用字典顺序扫描的话,需要很久很久,但是如果我们先找到Q对应的位置,然后依次查找,就会很快,这个步骤就是利用了索引的规则,那么字典中的A-Z就是索引,同理,我们处理文本将结果也利用索引的方式存储起来,更加的方便查找。
对于全文检索也是类似的原理,它可以归结为两个过程:1.索引创建(Indexing)2. 搜索索引(Search)。那么索引到底是如何创建的呢?索引里面存放的又是什么东西呢?搜索的的时候又是如何去查找索引的呢?带着这一系列问题继续往下看。

Solr/Lucene采用的是一种反向索引,所谓反向索引:就是从关键字到文档的映射过程,保存这种映射这种信息的索引称为反向索引。

左边保存的是字符串序列
右边是字符串的文档(Document)编号链表,称为倒排表(Posting List)
字段串列表和文档编号链表两者构成了一个字典。现在想搜索”lucene”,那么索引直接告诉我们,包含有”lucene”的文档有:2,3,10,35,92,而无需在整个文档库中逐个查找。如果是想搜既包含”lucene”又包含”solr”的文档,那么与之对应的两个倒排表去交集即可获得:3、10、35、92。

  • 索引创建

假设有如下两个原始文档:
文档一:Students should be allowed to go out with their friends, but not allowed to drink beer.
文档二:My friend Jerry went to school to see his students but found them drunk which is not allowed.

一:把原始文档交给分词组件(Tokenizer)
将文档分成一个一个单独的单词
去除标点符号
去除停词(stop word),所谓停词(Stop word)就是一种语言中没有具体含义,因而大多数情况下不会作为搜索的关键词,这样一来创建索引时能减少索引的大小。英语中停词(Stop word)如:”the”、”a”、”this”,中文有:”的,得”等。不同语种的分词组件(Tokenizer),都有自己的停词(stop word)集合。经过分词(Tokenizer)后得到的结果称为词汇单元(Token)。上例子中,便得到以下词汇单元(Token):
“Students”,”allowed”,”go”,”their”,”friends”,”allowed”,”drink”,”beer”,”My”,”friend”,”Jerry”,”went”,”school”,”see”,”his”,”students”,”found”,”them”,”drunk”,”allowed”

二:词汇单元(Token)传给语言处理组件(Linguistic Processor)
变为小写(Lowercase)
将单词缩减为词根形式,如”cars”到”car”等。这种操作称为:stemming
将单词转变为词根形式,如”drove”到”drive”等。这种操作称为:lemmatization

三:得到的词(Term)传递给索引组件(Indexer)
创建字典

Term    Document ID
student     1
allow       1
go          1
their       1
friend      1
allow       1
drink       1
beer        1
my          2
friend      2
jerry       2
go          2
school      2
see         2
his         2
student     2
find        2
them        2
drink       2
allow       2

对字典按字母顺序排序

Term    Document ID
allow       1
allow       1
allow       2
beer        1
drink       1
drink       2
find        2
friend      1
friend      2
go          1
go          2
his         2
jerry       2
my          2
school      2
see         2
student     1
student     2
their       1
them        2

合并相同的词(Term)成为文档倒排(Posting List)链表

  • 搜索步骤
  • 一:对查询内容进行词法分析、语法分析、语言处理
    词法分析:区分查询内容中单词和关键字,比如:english and janpan,”and”就是关键字,”english”和”janpan”是普通单词
    根据查询语法的语法规则形成一棵树

    语言处理,和创建索引时处理方式是一样的。比如:leaned–>lean,driven–>drive
    二:搜索索引,得到符合语法树的文档集合
    三:根据查询语句与文档的相关性,对结果进行排序
    我们把查询语句也看作是一个文档,对文档与文档之间的相关性(relevance)进行打分(scoring),分数高比较越相关,排名就越靠前。当然还可以人工影响打分,比如百度搜索,就不一定完全按照相关性来排名的。
    如何评判文档之间的相关性?一个文档由多个(或者一个)词(Term)组成,比如:”solr”, “toturial”,不同的词可能重要性不一样,比如solr就比toturial重要,如果一个文档出现了10次toturial,但只出现了一次solr,而另一文档solr出现了4次,toturial出现一次,那么后者很有可能就是我们想要的搜的结果。这就引申出权重(Term weight)的概念。
    权重表示该词在文档中的重要程度,越重要的词当然权重越高,因此在计算文档相关性时影响力就更大。通过词之间的权重得到文档相关性的过程叫做空间向量模型算法(Vector Space Model)
    影响一个词在文档中的重要性主要有两个方面:
    Term Frequencey(tf),Term在此文档中出现的频率,ft越大表示越重要
    Document Frequency(df),表示有多少文档中出现过这个Trem,df越大表示越不重要
    物以希为贵,大家都有的东西,自然就不那么贵重了,只有你专有的东西表示这个东西很珍贵,权重的公式:

    空间向量模型
    文档中词的权重看作一个向量
    Document = {term1, term2, …… ,term N}
    Document Vector = {weight1, weight2, …… ,weight N}
    把欲要查询的语句看作一个简单的文档,也用向量表示:
    Query = {term1, term 2, …… , term N}
    Query Vector = {weight1, weight2, …… , weight N}
    把搜索出的文档向量及查询向量放入N维度的空间中,每个词表示一维:

    夹角越小,表示越相似,相关性越大

    Solr查询串 PHP样例

    $requestArray = array();
    $requestArray['filterList'][0] = ['attribute' => 'webSiteId', "valueArray" => [1, 2, 3]];
    $requestArray['filterList'][1] = ['attribute' => 'sourceType', "valueArray" => [4]];
    $requestArray['minTime']       = $startStamp;
    $requestArray['maxTime']       = $endStamp;
    $keyword = '\"测试\"' OR '\"测试2\"';
    $requestArray['qValue'] = $keyword;
    $requestArray['limit'] = 1000;
    $requestStr = json_encode($requestArray);
    $ret = \SoapService::queryByJson('ARTICLE_URL', ["arg0" => $requestStr, "arg1" =>0, "arg2"=>0]);
    $content = $ret->return;
    

    PHP – 页面抓取与分析

    一般我们收集资料的时候,尤其在网站中发现比较好的内容,我们需要另存为或者复制粘贴的方式来进行。我们的效率就会变的很低下,这次我们就来自己编写爬虫和页面的抓取并慢慢的进行优化。
    比如我们拿CSDN来进行测试http://blog.csdn.net/mindfloating/article/details/71076570

    Curl和simple_html_dom

    我们通过PHP的Curl来进行远程页面内容的抓取。

    <?php
    #加载页面
    function curl_get($url){
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, 1);
        $result = curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if($code != '404' && $result){
            curl_close($ch);
            return $result;
        }
    }
    $URL = "http://blog.csdn.net/mindfloating/article/details/71076570";
    $content = curl_get($URL);
    

    查看源码我们知道文章的HTML格式。
    我们使用simple_html_dom来完成,Github下载地址

    <?php
    include('simple_html_dom.php');
    $URL = "http://blog.csdn.net/mindfloating/article/details/71076570";
    $html = file_get_html($URL);
    // 我们解析出来Title和正文内容
    // Title link_title
    // Content article_content
    $title_arr = $html->find('.link_title');
    $title_dom = new simple_html_dom();
    $title = "";
    foreach ($title_arr as $item) {
        $title_nodes = $title_dom->load($item->innertext);
        $title_link_arr = $title_nodes->find('a');
        if (count($title_link_arr) > 0) {
            $title = $title_link_arr[0]->innertext;
        }
    }
    echo "<h3>$title</h3>";
    $article_content = "";
    $article_content_arr = $html->find("#article_content");
    foreach ($article_content_arr as $item) {
        $article_content = $item->innertext;
    }
    echo $article_content;
    

    显示结果内容如下:

    更换一个网址再次进行访问也是OK的http://blog.csdn.net/qq_25827845/article/details/52932234

    页面外链获取与分析

    我们获取页面HTML之后,可以过滤取的我们所需要的内容,例如:图片、URL这两个应该是比较常用的。我们来用另外一个小说网站来进行说明。

    下图是这个网站的具体内容

    我们这里主要抓取一下排行榜

    通过分析页面我们知道百度小说月票榜单的id为monthTicketRankList。

    <?php
    include('simple_html_dom.php');
    $URL = "http://www.zongheng.com/";
    $html = file_get_html($URL);
    $articles = $html->find("#monthTicketRankList");
    foreach ($articles as $item) {
        $article_dom = new simple_html_dom();
        $article_dom->load($item->innertext);
        $urls = $article_dom->find("a");
        foreach ($urls as $url) {
            // 每一篇的文章
            $truely_url = $url->href;
            $article_html = file_get_html($truely_url);
            // 获取阅读按钮
            $start_url = $article_html->find("#readRecord");
            foreach ($start_url as $start_item) {
                $every_article_url = $start_item->href;
                $every_article_html = file_get_html($every_article_url);
                // 得到标题 (.tc txt)
                $arr_title = $every_article_html->find('.tc');
                // 得到内容 (.readtext)
                $arr_content = $every_article_html->find('.readtext');
                if (count($arr_title) > 0 && count($arr_content) > 0) {
                    echo $arr_title[0]->innertext;
                    echo $arr_content[0]->innertext;
                }
                $every_article_html->clear();
            }
            $article_html->clear();
        }
        $article_dom->clear();
    }
    $html->clear();
    

    PHP – 设计模式之单例模式

    < ?php
    class User {
        //静态变量保存全局实例
        private static $_instance = null;
        //私有构造函数,防止外界实例化对象
        private function __construct() {
        }
        //私有克隆函数,防止外办克隆对象
        private function __clone() {
        }
        //静态方法,单例统一访问入口
        static public function getInstance() {
            if (is_null ( self::$_instance ) || isset ( self::$_instance )) {
                self::$_instance = new self ();
            }
            return self::$_instance;
        }
        public function getName() {
            echo 'hello world!';
        }
    }
    

    PHP权限系统

    权限系统

    一个系统存在N个模块,我们称之为F(1),F(2),F(3),…,F(N),每个模块存在读权限和写权限。
    若现在系统用户表设计如下:

    
    -----------------------------------------------------------------------
     | id     | username  | password  | type  | write |  desc |  email |
    -----------------------------------------------------------------------
      1001      tomyuan      xxxxxx      0       null    null    null
    

    我们假设现在存在3个模块分别称之为N1,N2,N3,然后存在“读”和“写”权限,该怎么更改这个数据表让其支持权限系统呢?
    方法很多

  • 方法1 权限json化
  • 新增字段permissions表示权限,类别varchar(255)
    {“N1”:[0, 0], “N2”:[1, 0], “N3”:[1, 1]} 这样表示tomyuan对于N1模块没有读和写的权限,对于N2模块有读的权限,对于N3模块有读和写权限即可。

  • 方法2 进制判断法
  • 我们根据权限类别数量的平方作进制数,而模块类别作为位数。则上述问题可以转化成生成一个3位的4进制数,数据表中存放一个字段permissions表示权限,类别int(10)即可,1表示没有读和写权限,2表示只有读权限,3表示只有写权限,4表示有读和写权限,该字段是124表示和上面的json表示的结果一样。

    PHP中的解决方案

  • 方法1 权限json化
  • 
    $user_data = User::find()->where(
        ["username" => $username, "password" => $password]
    )->one();
    $user_exist = User::find()->where(
        ["username" => $username]
    )->one();
    if (isset($user_data)) {
        // 检测权限 (无论是那种方法都这样)
        $permissions = $user_data["permissions"];
        $session->set('permissions', $permissions);
        ....
    }
    // 权限判断
    function getWritablePermissions() {
    
        $session = \Yii::$app->session;
        if ($session->isActive) {
            // Error
            $session->open();
        }
    
        return $session->get("permissions");
    }
    //
    $permissions = $this->getWritablePermissions();
    $permissions_arr = json_decode($permissions, true);
    if ($permissions_arr) {
        if (!isset($permissions_arr[$this->_modId]) || $permissions_arr[$this->_modId][0] == 0) 
        {
            // 没有读权限
            echo "< script >window.location.href='/';< /script >";
            return FALSE;
        }
    }  
    
  • 方法2 进制判断法
  • 这里和json唯一的区别就是解析权限,json的方法我们使用json_encode和json_decode就可以进行分析,进制这里我们使用取余的方法即可。

    $Permissions = [
    	"没有读写权限",
    	"读权限",
    	"写权限",
    	"读写权限"
    ];
    $permission_number = 1234;
    $n = $permission_number;
    $index = 4;
    while ($n > 0) {
    	$e = $n % 10;
    	$n = floor($n / 10);
    	echo "第" . $index . "模块有" . $Permissions[$e - 1] . "\n";
    	$index = $index - 1;
    }
    

    运行结果如下所示:

    php文件上传

    jQuery+php实现ajax文件即时上传

    https://cdn.bootcss.com/jquery.form/4.2.1/jquery.form.min.js
    //cdn.bootcss.com/jquery/3.2.1/jquery.min.js

    添加附件

     < input id="fileupload" type="file" name="myfile" >

    我们用于上传文件的html中并没有出现form表单,而正常的上传功能是要依赖form表单的。我们的form表单是动态插入的,这样可以避免一个大表单中出现多个form。
    对该按钮进行样式修改

    .btn{position: relative;overflow: hidden;margin-right: 4px;display:inline-block; 
    *display:inline;padding:4px 10px 4px;font-size:14px;line-height:18px; 
    *line-height:20px;color:#fff; 
    text-align:center;vertical-align:middle;cursor:pointer;background:#5bb75b; 
    border:1px solid #cccccc;border-color:#e6e6e6 #e6e6e6 #bfbfbf; 
    border-bottom-color:#b3b3b3;-webkit-border-radius:4px; 
    -moz-border-radius:4px;border-radius:4px;} 
    .btn input{position: absolute;top: 0; right: 0;margin: 0;border:solid transparent; 
    opacity: 0;filter:alpha(opacity=0); cursor: pointer;} 
    

    首先定义各种变量,注意动态将表单元素form插入到上传按钮部位,并且form的属性enctype必须设置为:multipart/form-data。然后当点击“上传附件”按钮,选择要上传的文件后,调用jquery.form插件的ajaxSubmit方法,如下代码说明。

    $(function () { 
        var files = $(".files"); 
        var btn = $(".btn span"); 
        $("#fileupload").wrap("< form id='myupload' action='uploadfile.php'  
        method='post' enctype='multipart/form-data' >< /form >"); 
        $("#fileupload").change(function(){ //选择文件 
            $("#myupload").ajaxSubmit({ 
                dataType:  'json', //数据格式为json 
                beforeSend: function() { //开始上传 
                    btn.html("上传中..."); //上传按钮显示上传中 
                }, 
                uploadProgress: function(event, position, total, percentComplete) { 
                    // TODO
                }, 
                success: function(data) { //成功 
                    //获得后台返回的json数据,显示文件名,大小,以及删除按钮 
                    // file_name
                    btn.html("添加附件"); //上传按钮还原 
                }, 
                error:function(xhr){ //上传失败 
                    btn.html("上传失败"); 
                } 
            }); 
        }); 
    }); 
    


    需要通过PHP文件来完成文件上传。图片上传时需要验证格式和大小,然后通过move_uploaded_file()方法上传图片,最后返回json格式的数据。为了安全起见,这里文件上传就不做说明了!

    多维数组排序

  • 多维数组排序
  •     // 多维数组排序
    	public function multi_array_sort($multi_array, $sort_key, $sort = SORT_ASC){ 
    		if (is_array($multi_array)) { 
    			foreach ($multi_array as $row_array) { 
    				if(is_array($row_array)) { 
    					$key_array[] = $row_array[$sort_key]; 
    				} else { 
    					return false; 
    				} 
    			} 
    		} else { 
    			return false; 
    		} 
    		array_multisort($key_array,$sort,$multi_array); 
    		return $multi_array; 
    	} 
    

    composer

    1 安装方法
    参考:https://pkg.phpcomposer.com/#how-to-install-composer

    下载 Composer

    安装前请务必确保已经正确安装了 PHP。打开命令行窗口并执行 php -v 查看是否正确输出版本号。

    执行如下命令

    php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"  // 下载安装脚本 - composer-setup.php - 到当前目录。
    php composer-setup.php   // 执行安装过程。
    php -r "unlink('composer-setup.php');"  // 删除安装脚本。
    

    运行结果如下

    Do not run Composer as root/super user!

    这个是因为composer为了防止非法脚本在root下执行,解决办法随便切换到非root用户即可

    PHPMailer实现找回密码功能

    整体流程大致是:
    1 用户点击”密码找回”,跳转密码找回页面。
    2 密码找回页面需要输入”用户名”,然后根据用户名取得对应的邮箱地址。

    
    $user_exist = User::find()->where(["username" => $username]);
       ->one();
    

    3 当用户存在时,给对应邮箱发送邮件,邮件拼装方式如下图所示:

    主要包括设置Email相关参数,以及设置Email主题和内容,我们需要对内容进行Token加密,加密算法使用authcode,Token中主要包括email、username以及时间戳,用来后面的验证。具体的authcode如下:

    
        // 加密算法
        public function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {   
            $ckey_length = 4;   
              
            $key = md5($key ? $key : UC_KEY);   
            $keya = md5(substr($key, 0, 16));   
            $keyb = md5(substr($key, 16, 16));   
            $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';   
              
            $cryptkey = $keya.md5($keya.$keyc);   
            $key_length = strlen($cryptkey);   
              
            $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;   
            $string_length = strlen($string);   
              
            $result = '';   
            $box = range(0, 255);   
              
            $rndkey = array();   
            for($i = 0; $i <= 255; $i++) {   
                $rndkey[$i] = ord($cryptkey[$i % $key_length]);   
            }   
              
            for($j = $i = 0; $i < 256; $i++) {   
                $j = ($j + $box[$i] + $rndkey[$i]) % 256;   
                $tmp = $box[$i];   
                $box[$i] = $box[$j];   
                $box[$j] = $tmp;   
            }   
              
            for($a = $j = $i = 0; $i < $string_length; $i++) {   
                $a = ($a + 1) % 256;   
                $j = ($j + $box[$a]) % 256;   
                $tmp = $box[$a];   
                $box[$a] = $box[$j];   
                $box[$j] = $tmp;   
                $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));   
            }   
              
            if($operation == 'DECODE') {   
                if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {   
                    return substr($result, 26);   
                } else {   
                        return '';   
                    }   
            } else {   
                return $keyc.str_replace('=', '', base64_encode($result));   
            }     
        }
    

    4 发送邮件后,用户点击邮件链接后跳转链接到指定页面,这里我们需要对Token进行验证,验证项目包括username和email是否对应、时间戳是否在一个小时内,如果验证通过,设置session并显示密码重置页面,否则显示验证错误页面。
    5 在密码重置页面中,通过session得到需要重置密码的用户名,然后输入新密码并再次输入验证,然后通过接口设置工程,完成整个密码找回过程。