guowei 4 months ago
commit
b972b67f81
100 changed files with 9629 additions and 0 deletions
  1. 37 0
      .gitignore
  2. 8 0
      LICENSE
  3. 30 0
      README.MD
  4. 114 0
      common/components/BaseCommonController.php
  5. 104 0
      common/components/DataHelper.php
  6. 39 0
      common/components/GlobalUrlService.php
  7. 174 0
      common/components/HttpClient.php
  8. 235 0
      common/components/RedisCache.php
  9. 117 0
      common/components/RedisConnection.php
  10. 40 0
      common/components/StaticService.php
  11. 117 0
      common/components/UtilHelper.php
  12. 1 0
      common/config/.gitignore
  13. 4 0
      common/config/bootstrap.php
  14. 12 0
      common/config/main-local.php
  15. 8 0
      common/config/main.php
  16. 9 0
      common/config/params-local.php
  17. 13 0
      common/config/params.php
  18. 77 0
      common/models/ChatHistory.php
  19. 85 0
      common/models/GuestTrace.php
  20. 69 0
      common/models/guest/Guest.php
  21. 64 0
      common/models/guest/GuestQueue.php
  22. 74 0
      common/models/guest/GuestServiceLog.php
  23. 74 0
      common/models/guest/GuestServicing.php
  24. 69 0
      common/models/log/AppErrorLogs.php
  25. 88 0
      common/models/merchant/Merchant.php
  26. 98 0
      common/models/merchant/MerchantStaff.php
  27. 62 0
      common/models/stat/StatDailyAccessSource.php
  28. 62 0
      common/models/stat/StatDailyBrowser.php
  29. 62 0
      common/models/stat/StatDailyDevice.php
  30. 62 0
      common/models/stat/StatDailyOs.php
  31. 62 0
      common/models/stat/StatDailyUuid.php
  32. 25 0
      common/services/AppLogService.php
  33. 26 0
      common/services/BaseService.php
  34. 38 0
      common/services/Constants.php
  35. 217 0
      common/services/EventsDispatch.php
  36. 148 0
      common/services/GuestDistribution.php
  37. 36 0
      common/services/MerchantService.php
  38. 24 0
      common/services/QiniuUploadService.php
  39. 73 0
      common/services/captcha/ValidateCode.php
  40. 94 0
      common/services/chat/ChatService.php
  41. BIN
      common/services/ip/17monipdb.dat
  42. 99 0
      common/services/ip/IPService.php
  43. 43 0
      common/services/lvb/LVBTecentService.php
  44. 1 0
      common/services/lvb/readme.md
  45. 139 0
      common/services/sms/SmsService.php
  46. 21 0
      common/services/wechat/CSMessageService.php
  47. 447 0
      common/services/wechat/MsgcryService.php
  48. 52 0
      common/services/wechat/WXRequestHelper.php
  49. 46 0
      composer.json
  50. 3831 0
      composer.lock
  51. BIN
      composer.phar
  52. 1 0
      console/config/.gitignore
  53. 1 0
      console/config/bootstrap.php
  54. 7 0
      console/config/main-local.php
  55. 38 0
      console/config/main.php
  56. 3 0
      console/config/params-local.php
  57. 4 0
      console/config/params.php
  58. 11 0
      console/config/router.php
  59. 49 0
      console/console.php
  60. 11 0
      console/controllers/BaseController.php
  61. 12 0
      console/controllers/DefaultController.php
  62. 52 0
      console/controllers/ErrorController.php
  63. 24 0
      console/modules/merchant/MerchantModule.php
  64. 126 0
      console/modules/merchant/controllers/StatDailyController.php
  65. 2 0
      console/runtime/.gitignore
  66. 93 0
      console/server_jobs/BusiHandler.php
  67. 38 0
      console/server_jobs/busiwork.php
  68. 46 0
      console/server_jobs/gateway.php
  69. 13 0
      console/server_jobs/register.php
  70. 34 0
      console/server_jobs/start.php
  71. 24 0
      console/server_jobs/yii_console.php
  72. 37 0
      console/shell/start.php
  73. BIN
      docs/images/help.jpg
  74. BIN
      docs/images/home.png
  75. BIN
      docs/images/kf.jpg
  76. BIN
      docs/images/stat.jpg
  77. 35 0
      docs/nginx/kefu.conf
  78. 466 0
      docs/saas_stkf_v1.sql
  79. 300 0
      init
  80. 132 0
      requirements.php
  81. 28 0
      www/assets/AppAsset.php
  82. 31 0
      www/assets/CSAsset.php
  83. 40 0
      www/assets/MerchantAsset.php
  84. 1 0
      www/config/.gitignore
  85. 1 0
      www/config/bootstrap.php
  86. 33 0
      www/config/main-local.php
  87. 45 0
      www/config/main.php
  88. 3 0
      www/config/params-local.php
  89. 4 0
      www/config/params.php
  90. 12 0
      www/config/router.php
  91. 48 0
      www/controllers/CodeController.php
  92. 57 0
      www/controllers/ErrorController.php
  93. 11 0
      www/controllers/IndexController.php
  94. 49 0
      www/controllers/UserController.php
  95. 42 0
      www/controllers/common/BaseController.php
  96. 24 0
      www/modules/customservice/CustomServiceModule.php
  97. 70 0
      www/modules/customservice/controllers/DefaultController.php
  98. 64 0
      www/modules/customservice/controllers/UserController.php
  99. 77 0
      www/modules/customservice/controllers/common/AuthController.php
  100. 0 0
      www/modules/customservice/views/default/index.php

+ 37 - 0
.gitignore

@@ -0,0 +1,37 @@
+# yii console commands
+/yii
+/yii_test
+/yii_test.bat
+
+# phpstorm project files
+.idea
+
+# netbeans project files
+nbproject
+
+# zend studio for eclipse project files
+.buildpath
+.project
+.settings
+
+# windows thumbnail cache
+Thumbs.db
+
+# composer vendor dir
+/vendor
+
+# composer itself is not needed
+#composer.phar
+
+# Mac DS_Store Files
+.DS_Store
+
+# phpunit itself is not needed
+phpunit.phar
+# local phpunit config
+/phpunit.xml
+
+# vagrant runtime
+/.vagrant
+
+# ignore generated files

+ 8 - 0
LICENSE

@@ -0,0 +1,8 @@
+MIT License
+Copyright (c) <year> <copyright holders>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 30 - 0
README.MD

@@ -0,0 +1,30 @@
+编程浪子开源云客服
+=========
+## 环境准备
+* nginx + mysql + php
+* [nginx的配置](./docs/nginx/kefu.conf)
+* [数据库文件:saas_stkf_v1](./docs/saas_stkf_v1.sql)
+* YII2 配置
+    * 数据库配置:common/config/main-local.php
+    * 访问系统域名配置:common/config/params-local.php
+* [课程使用vendor,切记文件夹名称是 vendor ](https://pan.baidu.com/s/1riMKPdSHLPdZK1-pMfFWvw)
+
+## 启动websocket
+* php console/server_jobs/start.php start
+
+## 账号说明
+* 管理员测试账户 用户名:54php.cn 密码:123456
+* 客服测试账户 用户名:54php.cn 密码:123456
+
+## 客服系统如何使用
+* 在websocket 服务启动之后,登录客服系统,点击 开始上班。 就可以进行沟通了
+
+## 展示图
+* ![统计首页](./docs/images/stat.jpg)
+* ![客服登录地址](./docs/images/help.jpg)
+* ![首页](./docs/images/home.png)
+* ![客服系统](./docs/images/kf.jpg)
+
+## 参考文档
+* [Workerman](https://www.workerman.net/)
+* [GatewayWorker](http://workerman.net/gatewaydoc/)

+ 114 - 0
common/components/BaseCommonController.php

@@ -0,0 +1,114 @@
+<?php
+
+namespace common\components;
+
+use Yii;
+
+class BaseCommonController extends \yii\web\Controller{
+
+    public $enableCsrfValidation = false;
+    protected  $auth_merchant_cookie = "sass_kf_merchant";
+    protected  $auth_cs_cookie = "sass_kf_cs";
+    protected  $salt = "usPkIqUBR5pdddsTY2";
+    public $page_size = 30;
+
+    protected  function createMerchantLoginStatus( $info ){
+        $params = [ $info['id'],$info['login_name'],$info['login_pwd'],$info['login_salt'],$info['sn'] ];
+        $auth_token = $this->createAuthToken(  $params );
+        $this->setCookie( $this->auth_merchant_cookie,$auth_token."#".$info['id']);
+    }
+
+    protected  function createCSLoginStatus( $info ){
+        $params = [ $info['id'],$info['login_name'],$info['login_pwd'],$info['login_salt'],$info['sn'] ];
+        $auth_token = $this->createAuthToken(  $params );
+        $this->setCookie( $this->auth_cs_cookie,$auth_token."#".$info['id']);
+    }
+
+    protected function createAuthToken( array $info ){
+        return md5($this->salt."-".implode("-",$info ) );
+    }
+
+    protected  function removeAuthToken( $cookie_name = "" ){
+        if( empty($cookie_name) ){
+            $cookie_name = $this->auth_merchant_cookie;
+        }
+        $this->removeCookie( $cookie_name );
+    }
+
+    protected function setCookie($name,$value,$expire = 0){
+        $cookies = Yii::$app->response->cookies;
+        $cookies->add(new \yii\web\Cookie([
+            'name' => $name,
+            'value' => $value,
+			'expire' => $expire
+        ]));
+    }
+
+    protected  function getCookie($name,$default_val=''){
+        $cookies = Yii::$app->request->cookies;
+        return $cookies->getValue($name, $default_val);
+    }
+
+
+    protected function removeCookie($name){
+        $cookies = Yii::$app->response->cookies;
+        $cookies->remove($name);
+    }
+
+
+    protected  function renderJS($msg,$url = "/"){
+        return $this->renderPartial("@www/views/layouts/js",['msg' => $msg,'location' => $url ]);
+    }
+
+    protected function renderJSON($data=[], $msg ="操作成功~~", $code = 200){
+
+        $response = Yii::$app->response;
+        $data = [
+            "code" => $code,
+            "msg"   =>  $msg,
+            "data"  =>  $data,
+            "req_id" =>  $this->geneReqId(),
+        ];
+        $response->format = \yii\web\Response::FORMAT_JSON;
+        $response->data= $data;
+        return $response;
+    }
+
+    protected function renderErrorJSON($data = [], $msg = "操作失败~~", $code = -1){
+        return $this->renderJSON($data,$msg,$code);
+    }
+
+    protected function renderJSONP($data=[], $msg ="ok", $code = 200) {
+
+        $func = $this->get("jsonp","jsonp_func");
+
+
+        echo $func."(".json_encode([
+                "code" => $code,
+                "msg"   =>  $msg,
+                "data"  =>  $data,
+                "req_id" =>  $this->geneReqId(),
+            ]).")";
+
+
+        return Yii::$app->end();
+    }
+
+    protected function renderNotFound() {
+        $this->renderJSON([],$msg = "ObjectNotFound", -1);
+    }
+
+
+    protected function geneReqId() {
+        return uniqid();
+    }
+
+    public function post($key, $default = "") {
+        return Yii::$app->request->post($key, $default);
+    }
+
+
+    public function get($key, $default = "") {
+        return Yii::$app->request->get($key, $default);
+    }
+} 

+ 104 - 0
common/components/DataHelper.php

@@ -0,0 +1,104 @@
+<?php
+
+namespace common\components;
+use  yii\helpers\Html;
+
+class DataHelper {
+
+
+
+    /**
+     * 转移特殊字符
+     */
+    public static function encode($dispaly_text){
+        return  Html::encode($dispaly_text);
+    }
+
+    /**
+     * 根据某个字段 in  查询
+     */
+    public static function getDicByRelateID($data,$relate_model,$id_column,$pk_column,$name_columns = [])
+    {
+        $_ids = [];
+        $_names = [];
+        foreach($data as $_row)
+        {
+            $_ids[] = $_row[$id_column];
+        }
+        $rel_data = $relate_model::findAll([$pk_column => array_unique($_ids)]);
+        foreach($rel_data as $_rel)
+        {
+            $map_item = [];
+            if($name_columns && is_array($name_columns)){
+                foreach($name_columns as $name_column){
+                    $map_item[$name_column] = $_rel->$name_column;
+                }
+            }
+            $_names[$_rel->$pk_column] = $map_item;
+        }
+        return $_names;
+    }
+
+    public static function getPkDict($model_class,$pk_column,$name_column,$condition=[])
+    {
+        $return = [];
+        $db_records = $model_class::find()->where($condition)->all();
+        foreach($db_records as $_tmp)
+        {
+            $return[$_tmp->$pk_column] = $_tmp->$name_column;
+        }
+        return $return;
+    }
+
+
+	/**
+	 *分页
+	 */
+	public static function ipagination($params)
+	{
+		$ret = [
+			'previous'  => true,
+			'next'      => true,
+			'from'      => 0,
+			'end'       => 0,
+			'totalPage' => 0,
+			'total_page' => 0,
+			'current'   => 0,
+			'page_size' => intval( $params['page_size'] )
+		];
+		$total     = (int)$params['total_count'];
+		$pageSize  = (int)$params['page_size'];
+		$page      = (int)$params['page'];
+		$display   = (int)$params['display'];
+		$totalPage = (int)ceil($total / $pageSize);
+		$totalPage = $totalPage?$totalPage:1;
+
+		if ($page <= 1) {
+			$ret['previous'] = false;
+		}
+		if ($page >= $totalPage) {
+			$ret['next'] = false;
+		}
+		$semi = (int)ceil($display / 2);
+		if ($page - $semi > 0) {
+			$ret['from'] = $page - $semi;
+		} else {
+			$ret['from'] = 1;
+		}
+		if ($page + $semi <= $totalPage) {
+			$ret['end'] = $page + $semi;
+		} else {
+			$ret['end'] = $totalPage;
+		}
+		$ret['total_count'] = $total;
+		$ret['totalPage'] = $totalPage;
+		$ret['total_page'] = $totalPage;
+		$ret['current']   = $page;
+		return $ret;
+	}
+
+    public static function getAuthorName(){
+        return \Yii::$app->params['author']['nickname'];
+    }
+
+}

+ 39 - 0
common/components/GlobalUrlService.php

@@ -0,0 +1,39 @@
+<?php
+namespace common\components;
+
+use yii\helpers\Url;
+
+class GlobalUrlService {
+
+	public static function buildWwwUrl(  $uri, $params = [] ){
+		$path = Url::toRoute(array_merge([ $uri ], $params));
+		$domain = \Yii::$app->params['domains']['www'];
+		return $domain.$path;
+	}
+
+    public static function buildMerchantUrl(  $uri, $params = [] ){
+        $path = Url::toRoute(array_merge([ $uri ], $params));
+        $domain = \Yii::$app->params['domains']['merchant'];
+        return $domain.$path;
+    }
+
+
+	public static function buildCsUrl($uri, $params = []){
+		$path = Url::toRoute(array_merge([ $uri ], $params));
+		$domain = \Yii::$app->params['domains']['cs'];
+		return $domain.$path;
+	}
+
+	public static function buildWwwStaticUrl(  $uri, $params = [] ){
+        $release_version = defined("RELEASE_VERSION") ? RELEASE_VERSION : time();
+		$params = $params + [ "ver" => $release_version ];
+		$path = Url::toRoute(array_merge([ $uri ], $params));
+		$domain = \Yii::$app->params['domains']['www'];
+		return $domain."/static".$path;
+	}
+
+
+    public static function buildNullUrl(){
+    	return "javascript:void(0);";
+	}
+} 

+ 174 - 0
common/components/HttpClient.php

@@ -0,0 +1,174 @@
+<?php
+
+namespace common\components;
+use common\service\applog\ApplogService;
+use Yii;
+
+class HttpClient
+{
+
+    private static $headers = [];
+
+
+    public static function get($url, $param =[] ) {
+
+        return self::curl($url, $param,"get");
+    }
+
+    public static function post($url, $param,$extra = []) {
+
+        return self::curl($url, $param,"post",$extra);
+    }
+
+
+    protected static function curl($url, $param, $method = 'post',$extra = [])
+    {
+        $calculate_time1 = microtime(true);
+        // 初始华
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_URL, $url);
+        curl_setopt($curl, CURLOPT_HEADER, 0);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($curl, CURLOPT_CERTINFO , true);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+		//curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
+		//curl_setopt($curl, CURLOPT_SSLVERSION, 3);
+		curl_setopt($curl,CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+
+        if( isset( Yii::$app->params['curl'] ) && isset(Yii::$app->params['curl']['timeout']) ){
+            curl_setopt($curl, CURLOPT_TIMEOUT, Yii::$app->params['curl']['timeout']);
+        }else{
+            curl_setopt($curl, CURLOPT_TIMEOUT, 5);
+        }
+
+        if(array_key_exists("HTTP_USER_AGENT",$_SERVER)){
+            curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
+        }
+
+        /**增加证书认证*/
+        if( isset( $extra['SSLCERTTYPE'] ) && $extra['SSLCERTTYPE'] ){
+            curl_setopt($curl,CURLOPT_SSLCERTTYPE,'PEM');
+            curl_setopt($curl,CURLOPT_SSLCERT, $extra['SSLCERTTYPE']);
+        }
+
+        if( isset( $extra['CURLOPT_SSLKEYTYPE'] ) && $extra['CURLOPT_SSLKEYTYPE'] ){
+            curl_setopt($curl,CURLOPT_SSLKEY,'PEM');
+            curl_setopt($curl,CURLOPT_SSLKEY, $extra['CURLOPT_SSLKEYTYPE']);
+        }
+
+
+        if( !empty(self::$headers) ){
+            $headerArr = [];
+            foreach( self::$headers as $n => $v ) {
+                $headerArr[] = $n .':' . $v;
+            }
+            curl_setopt ($curl, CURLOPT_HTTPHEADER , $headerArr );  //构造IP
+        }
+
+
+        // post处理
+        if ($method == 'post')
+        {
+            curl_setopt($curl, CURLOPT_POST, TRUE);
+            if( is_array($param) ){
+				if( isset( $param['forbidden_build_query'] ) ){//有的要禁止build_query,例如上传文件
+
+				}else{
+					$param = http_build_query($param);
+				}
+            }
+
+            curl_setopt($curl, CURLOPT_POSTFIELDS, $param);
+        }else{
+            curl_setopt($curl, CURLOPT_POST, FALSE);
+        }
+
+        // 执行输出
+        $info = curl_exec($curl);
+        
+        //log
+        $_errno = curl_errno($curl);
+        $_error = '';
+        if($_errno)
+        {
+            $_error = curl_error($curl);
+        }
+        curl_close($curl);
+        $calculate_time_span = microtime(true) - $calculate_time1;
+        $log = \Yii::$app->getRuntimePath().DIRECTORY_SEPARATOR.'curl_'.date("Y-m-d").'.log';
+		if( strlen( $info) < 10000 ){
+			file_put_contents($log,date('Y-m-d H:i:s')." [ time:{$calculate_time_span} ] url: {$url} \nmethod: {$method} \ndata: ".json_encode($param)." \nresult: {$info} \nerrorno: {$_errno} error: {$_error} \n",FILE_APPEND);
+		}else{
+			file_put_contents($log,date('Y-m-d H:i:s')." [ time:{$calculate_time_span} ] url: {$url} \nmethod: {$method} \ndata: ".json_encode($param)." \nresult: too large  \nerrorno: {$_errno} error: {$_error} \n",FILE_APPEND);
+		}
+
+        return $info;
+    }
+
+
+    public static function setHeader($header){
+         self::$headers = $header;
+    }
+
+
+    protected static function getProxy() {
+        $proxy = array(
+            '0' => '60.16.210.118:80',
+            '1' => '183.62.60.100:80',
+            '2' => '58.215.185.46:82',
+            '3' => '223.4.21.184:80',
+            '4' => '61.53.143.179:80',
+            '5' => '42.121.105.155:8888',
+            '6' => '115.29.184.17:82',
+            '7' => '183.131.144.204:443',
+            '8' => '121.199.30.110:82',
+            '9' => '113.207.130.166:80',
+            '10' => '124.202.181.226:8118',
+            '11' => '116.236.216.116:8080',
+            '12' => '114.255.183.173:8080',
+            '13' => '202.108.50.75:80',
+            '14' => '122.96.59.106:82',
+            '15' => '122.96.59.106:83',
+            '16' => '1.202.74.121:8118',
+            '17' => '114.255.183.164:8080',
+            '18' => '111.13.136.59:843',
+            '19' => '122.96.59.106:843',
+            '20' => '101.71.27.120:80',
+            '21' => '122.96.59.106:81',
+            '22' => '111.1.36.6:80',
+            '23' => '114.255.183.174:8080',
+            '24' => '120.198.243.111:80',
+            '25' => '218.240.156.82:80',
+            '26' => '61.184.192.42:80',
+            '27' => '119.6.144.74:83',
+            '28' => '119.6.144.74:843',
+            '29' => '124.202.217.134:8118',
+            '30' => '221.10.102.203:83',
+            '31' => '119.6.144.74:82',
+            '32' => '119.6.144.74:80',
+            '33' => '58.252.72.179:3128',
+            '34' => '60.24.122.236:8118',
+            '35' => '203.192.10.66:80',
+            '36' => '221.10.102.203:81',
+            '37' => '211.141.130.96:8118',
+            '38' => '124.88.67.13:843',
+            '39' => '119.6.144.74:81',
+            '40' => '222.33.41.228:80',
+            '41' => '221.10.102.203:843',
+            '42' => '111.7.129.133:80',
+            '43' => '124.88.67.13:83',
+            '44' => '61.156.3.166:80',
+            '45' => '218.204.140.212:8001',
+            '46' => '116.236.203.238:8080',
+            '47' => '122.96.59.106:80',
+            '48' => '182.118.23.7:8081',
+            '49' => '222.45.194.122:8118',
+            '50' => '123.171.119.52:80'
+        );
+
+        $rand = rand(0,50);
+        return $proxy[$rand];
+    }
+
+
+}

+ 235 - 0
common/components/RedisCache.php

@@ -0,0 +1,235 @@
+<?php
+namespace common\components;
+
+use Yii;
+use yii\base\InvalidConfigException;
+
+/**
+ * To use redis Cache as the cache application component, configure the application as follows,
+ *
+ * ~~~
+ * [
+ *     'components' => [
+ *         'cache' => [
+ *             'class' => 'common\components\RedisCache',
+ *             'redis' => [
+ *                 'host' => 'localhost',
+ *                 'port' => 6379,
+ *                 'database' => 0,
+ *             ]
+ *         ],
+ *     ],
+ * ]
+ * ~~~
+ *
+ * Or if you have configured the redis [[Connection]] as an application component, the following is sufficient:
+ *
+ * ~~~
+ * [
+ *     'components' => [
+ *         'cache' => [
+ *             'class' => 'common\components\RedisCache',
+ *             // 'redis' => 'redis' // id of the connection application component
+ *         ],
+ *     ],
+ * ]
+ * ~~~
+ */
+class RedisCache extends \yii\caching\Cache
+{
+    /**
+     * @var Connection|string|array the Redis [[Connection]] object or the application component ID of the Redis [[Connection]].
+     * This can also be an array that is used to create a redis [[Connection]] instance in case you do not want do configure
+     * redis connection as an application component.
+     * After the Cache object is created, if you want to change this property, you should only assign it
+     * with a Redis [[Connection]] object.
+     */
+    public $redis = 'redis';
+
+
+    /**
+     * Initializes the redis Cache component.
+     * This method will initialize the [[redis]] property to make sure it refers to a valid redis connection.
+     * @throws InvalidConfigException if [[redis]] is invalid.
+     */
+    public function init()
+    {
+        parent::init();
+        if (is_string($this->redis)) {
+            $this->redis = Yii::$app->get($this->redis);
+        } elseif (is_array($this->redis)) {
+            if (!isset($this->redis['class'])) {
+                $this->redis['class'] = RedisConnection::className();
+            }
+            $this->redis = Yii::createObject($this->redis);
+        }
+        if (!$this->redis instanceof RedisConnection) {
+            throw new InvalidConfigException("Cache::redis must be either a RedisConnection instance or the application component ID of a RedisConnection.");
+        }
+    }
+
+    /**
+     * Checks whether a specified key exists in the cache.
+     * This can be faster than getting the value from the cache if the data is big.
+     * Note that this method does not check whether the dependency associated
+     * with the cached data, if there is any, has changed. So a call to [[get]]
+     * may return false while exists returns true.
+     * @param mixed $key a key identifying the cached value. This can be a simple string or
+     * a complex data structure consisting of factors representing the key.
+     * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
+     */
+    public function exists($key)
+    {
+        // return (bool) $this->redis->exists([$this->buildKey($key)]);
+        return (bool) $this->redis->exists($this->buildKey($key));
+    }
+
+
+    /**
+     * @inheritdoc
+     */
+    protected function getValue($key)
+    {
+        return $this->redis->get($key);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    protected function getValues($keys)
+    {
+        $response = $this->redis->mGet($keys);
+        $result = [];
+        $i = 0;
+        foreach ($keys as $key) {
+            $result[$key] = $response[$i++];
+        }
+        return $result;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    protected function setValue($key, $value, $expire)
+    {
+        if ($expire == 0) {
+            return (bool) $this->redis->set($key, $value);
+        } else {
+            $expire = (int) ($expire * 1000);
+            return (bool) $this->redis->set($key, $value, ['px' => $expire]);
+        }
+    }
+
+    /**
+     * @inheritdoc
+     */
+    protected function setValues($data, $expire)
+    {
+        $failedKeys = [];
+        if ($expire == 0) {
+            $this->redis->mSet($data);
+        } else {
+            $expire = (int) ($expire * 1000);
+            $trans = $this->redis->multi();
+            $trans->mSet('MSET', $data);
+            $index = [];
+            foreach ($data as $key => $value) {
+                $this->redis->pexpire($key, $expire);
+                $index[] = $key;
+            }
+            $result = $this->redis->exec();
+            array_shift($result);
+            foreach ($result as $i => $r) {
+                if ($r != 1) {
+                    $failedKeys[] = $index[$i];
+                }
+            }
+        }
+        return $failedKeys;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    protected function addValue($key, $value, $expire)
+    {
+        if ($expire == 0) {
+            return (bool) $this->redis->setnx($key, $value);
+        } else {
+            return (bool) $this->redis->set($key, $value, ['nx','px'=>intval($expire * 1000)]);
+        }
+    }
+
+    /**
+     * @inheritdoc
+     */
+    protected function deleteValue($key)
+    {
+        return (bool) $this->redis->delete($key);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    protected function flushValues()
+    {
+        return true;
+        //return $this->redis->flushdb();
+    }
+
+    public function buildKey($key)
+    {
+        if (!is_string($key)) {
+            $key = json_encode($key);
+        }
+        // return $this->keyPrefix . $key;
+        return $key;
+    }
+
+    /**
+     * 这里默认所有score都为0
+     */
+    public function zAdd($key, $value_or_list, $score=1)
+    {
+        if (is_array($value_or_list))
+        {
+            $params = [$key];
+            foreach ($value_or_list as $key => $value) {
+                $params[] = $key;
+                $params[] = $value;
+            }
+            call_user_func_array([$this->redis, 'zAdd'], $params);
+        }
+        else
+        {
+            $this->redis->zAdd($key, $score, $value_or_list);
+        }
+    }
+
+    public function lPush($key, $value_or_list)
+    {
+        if (is_array($value_or_list))
+        {
+            array_unshift($value_or_list, $key);
+            $result = call_user_func_array([$this->redis, 'lPush'], $value_or_list);
+        }
+        else
+        {
+            $this->redis->lPush($key, $value_or_list);
+        }
+    }
+
+    /**
+     *
+     * @param string $name
+     * @param array $params
+     * @return mixed
+     */
+    public function __call($name, $params)
+    {
+        if (is_callable([$this->redis, $name], true))
+        {
+            return call_user_func_array([$this->redis, $name], $params);
+        }
+    }
+}

+ 117 - 0
common/components/RedisConnection.php

@@ -0,0 +1,117 @@
+<?php
+namespace common\components;
+
+/**
+ *  host port 等参数都与Redis扩展一致。
+ * TODO unit test
+ */
+class RedisConnection extends \yii\base\Component
+{
+    const EVENT_AFTER_OPEN = 'afterOpen';
+
+    public $host = 'localhost';
+    public $port = 6379;
+    public $timeout =  0.5;
+    public $database = 0;
+    public $unixSocket;
+    public $retry_interval;
+    public $prefix = 'REDIS_CACHE_KF_';
+
+    private $_redisconn_instance = null;
+
+    public function __sleep()
+    {
+        $this->close();
+        return array_keys(get_object_vars($this));
+    }
+
+    /**
+     * Returns a value indicating whether the DB connection is established.
+     * @return boolean whether the DB connection is established
+     */
+    public function getIsActive()
+    {
+        return $this->_redisconn_instance !== null;
+    }
+
+    /**
+     * Establishes a DB connection.
+     * It does nothing if a DB connection has already been established.
+     * @throws \Exception if connection fails
+     */
+    public function open()
+    {
+        if ($this->_redisconn_instance !== null) {
+            return;
+        }
+        $this->_redisconn_instance = new \Redis();
+        $success = false;
+        if($this->unixSocket)
+        {
+            $success = $this->_redisconn_instance->connect($this->unixSocket);
+        }
+        else
+        {
+            $success = $this->_redisconn_instance->connect($this->host,$this->port,$this->timeout,null,$this->retry_interval);
+        }
+        if($success)
+        {
+            $this->select($this->database);
+            $this->initConnection();
+        }
+        else
+        {
+             \Yii::error("Failed to open redis DB connection ", __CLASS__);
+             throw new \Exception('Failed to open redis DB connection ', -1);
+        }
+    }
+
+    /**
+     * Closes the currently active DB connection.
+     * It does nothing if the connection is already closed.
+     */
+    public function close()
+    {
+        if ($this->_redisconn_instance !== null) {
+            $this->_redisconn_instance->close();
+            $this->_redisconn_instance = null;
+        }
+    }
+
+    /**
+     * Initializes the DB connection.
+     * This method is invoked right after the DB connection is established.
+     * The default implementation triggers an [[EVENT_AFTER_OPEN]] event.
+     */
+    protected function initConnection()
+    {
+        $this->trigger(self::EVENT_AFTER_OPEN);
+        $this->setOption(\Redis::OPT_PREFIX, $this->prefix);
+    }
+
+    /**
+     * Returns the name of the DB driver for the current [[dsn]].
+     * @return string name of the DB driver
+     */
+    public function getDriverName()
+    {
+        return 'redis';
+    }
+
+    /**
+     *
+     * @param string $name
+     * @param array $params
+     * @return mixed
+     */
+    public function __call($name, $params)
+    {
+        $this->open();
+        //TODO 可能要禁止使用者调用connect之类的方法。
+        if (is_callable([$this->_redisconn_instance,$name],true)) {
+            return call_user_func_array([$this->_redisconn_instance,$name],$params);
+        } else {
+            return parent::__call($name, $params);
+        }
+    }
+}

+ 40 - 0
common/components/StaticService.php

@@ -0,0 +1,40 @@
+<?php
+namespace common\components;
+
+use common\components\GlobalUrlService;
+use Yii;
+
+class StaticService {
+
+    public static function includeAppStatic($type, $path, $depend){
+        $release_version = defined("RELEASE_VERSION") ? RELEASE_VERSION : time();
+        if (stripos($path, "?") !== false) {
+            $path = $path . "&version={$release_version}";
+        } else {
+            $path = $path . "?version={$release_version}";
+        }
+
+        if ($type == "css") {
+            Yii::$app->getView()->registerCssFile($path, ['depends' => $depend]);
+        } else {
+            Yii::$app->getView()->registerJsFile($path, ['depends' => $depend]);
+        }
+    }
+
+    public static function includeAppJsStatic($path, $depend)
+    {
+        self::includeAppStatic("js", $path, $depend);
+    }
+
+    public static function includeAppCssStatic($path, $depend)
+    {
+        self::includeAppStatic("css", $path, $depend);
+    }
+
+    public static function buildStaticUrl($path)
+    {
+        $release_version = defined("RELEASE_VERSION") ? RELEASE_VERSION : time();
+        return "/" . $path . "?version={$release_version}";
+    }
+
+} 

File diff suppressed because it is too large
+ 117 - 0
common/components/UtilHelper.php


+ 1 - 0
common/config/.gitignore

@@ -0,0 +1 @@
+

+ 4 - 0
common/config/bootstrap.php

@@ -0,0 +1,4 @@
+<?php
+Yii::setAlias('@common', dirname(__DIR__));
+Yii::setAlias('@www', dirname(dirname(__DIR__)) . '/www');
+Yii::setAlias('@console', dirname(dirname(__DIR__)) . '/console');

+ 12 - 0
common/config/main-local.php

@@ -0,0 +1,12 @@
+<?php
+return [
+	'components' => [
+		'db_kf'       => [
+			'class'    => 'yii\db\Connection',
+			'dsn'      => 'mysql:host=localhost;dbname=saas_stkf_v1',
+			'username' => 'root',
+			'password' => '',
+			'charset'  => 'utf8mb4',
+		]
+	],
+];

+ 8 - 0
common/config/main.php

@@ -0,0 +1,8 @@
+<?php
+return [
+    'aliases' => [
+        '@bower' => '@vendor/bower-asset',
+    ],
+    'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
+    'components' => [],
+];

+ 9 - 0
common/config/params-local.php

@@ -0,0 +1,9 @@
+<?php
+return [
+	"domains" => [
+		"www" => "http://v1.stkf.test",
+		"merchant" => "http://v1.stkf.test/merchant",
+		"cs" => "http://v1.stkf.test/cs"
+	],
+    "websocket" => "ws://v1.stkf.test/ws/"
+];

+ 13 - 0
common/config/params.php

@@ -0,0 +1,13 @@
+<?php
+return [
+    'adminEmail' => 'admin@example.com',
+    'supportEmail' => 'support@example.com',
+    'user.passwordResetTokenExpire' => 3600,
+	'company' => [
+		"copyright" => '版权所有 © 编程浪子开源云客服V1 <a target="_blank" href="http://www.54php.cn">www.54php.cn</a> 沪ICP备13003569号-2',
+		"title" => "编程浪子开源云客服",
+        "phone" => '商务微信 apanly'
+	],
+    "auth_domain" => "http://www.54php.cn",
+    "auth_busi" => "商业咨询QQ:1586538192、微信:apanly"
+];

+ 77 - 0
common/models/ChatHistory.php

@@ -0,0 +1,77 @@
+<?php
+
+namespace common\models;
+
+use Yii;
+
+/**
+ * This is the model class for table "chat_history".
+ *
+ * @property int $id
+ * @property string $cmd 指令
+ * @property string $f_name 发送方昵称
+ * @property string $f_code 发送方code
+ * @property string $f_avatar 发送方头像
+ * @property string $to_name 接收方昵称
+ * @property string $to_code 接收方code
+ * @property string $to_avatar 接收方头像
+ * @property string $content 发送内容
+ * @property int $source 来源 1:访客 2:客服 3:系统
+ * @property string $updated_time 更新时间
+ * @property string $created_time 创建时间
+ */
+class ChatHistory extends \yii\db\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return 'chat_history';
+    }
+
+    /**
+     * @return \yii\db\Connection the database connection used by this AR class.
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('db_kf');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['cmd'], 'required'],
+            [['source'], 'integer'],
+            [['updated_time', 'created_time'], 'safe'],
+            [['cmd'], 'string', 'max' => 20],
+            [['f_name', 'f_code', 'to_name', 'to_code'], 'string', 'max' => 32],
+            [['f_avatar', 'to_avatar'], 'string', 'max' => 250],
+            [['content'], 'string', 'max' => 1000],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'id' => 'ID',
+            'cmd' => 'Cmd',
+            'f_name' => 'F Name',
+            'f_code' => 'F Code',
+            'f_avatar' => 'F Avatar',
+            'to_name' => 'To Name',
+            'to_code' => 'To Code',
+            'to_avatar' => 'To Avatar',
+            'content' => 'Content',
+            'source' => 'Source',
+            'updated_time' => 'Updated Time',
+            'created_time' => 'Created Time',
+        ];
+    }
+}

+ 85 - 0
common/models/GuestTrace.php

@@ -0,0 +1,85 @@
+<?php
+
+namespace common\models;
+
+use Yii;
+
+/**
+ * This is the model class for table "guest_trace".
+ *
+ * @property int $id
+ * @property string $cmd 动作
+ * @property string $f_code 游客的uuid
+ * @property string $f_clientid 游客ws的clientid
+ * @property string $ua 浏览器UA
+ * @property string $ip 游客ip
+ * @property string $referer 游客referer
+ * @property string $talk_url 浏览页面
+ * @property string $source 来源域名
+ * @property string $client_browser 浏览器
+ * @property string $client_browser_version 浏览器版本号
+ * @property string $client_os 客户端操作系统
+ * @property string $client_os_version 操作系统版本号
+ * @property string $client_device 客户端设备
+ * @property string $created_time 创建时间
+ */
+class GuestTrace extends \yii\db\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return 'guest_trace';
+    }
+
+    /**
+     * @return \yii\db\Connection the database connection used by this AR class.
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('db_kf');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['created_time'], 'safe'],
+            [['cmd', 'client_os', 'client_device'], 'string', 'max' => 20],
+            [['f_code'], 'string', 'max' => 64],
+            [['f_clientid', 'ip'], 'string', 'max' => 32],
+            [['ua', 'talk_url'], 'string', 'max' => 500],
+            [['referer'], 'string', 'max' => 1500],
+            [['source'], 'string', 'max' => 100],
+            [['client_browser'], 'string', 'max' => 50],
+            [['client_browser_version', 'client_os_version'], 'string', 'max' => 40],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'id' => 'ID',
+            'cmd' => 'Cmd',
+            'f_code' => 'F Code',
+            'f_clientid' => 'F Clientid',
+            'ua' => 'Ua',
+            'ip' => 'Ip',
+            'referer' => 'Referer',
+            'talk_url' => 'Talk Url',
+            'source' => 'Source',
+            'client_browser' => 'Client Browser',
+            'client_browser_version' => 'Client Browser Version',
+            'client_os' => 'Client Os',
+            'client_os_version' => 'Client Os Version',
+            'client_device' => 'Client Device',
+            'created_time' => 'Created Time',
+        ];
+    }
+}

+ 69 - 0
common/models/guest/Guest.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace common\models\guest;
+
+use Yii;
+
+/**
+ * This is the model class for table "guest".
+ *
+ * @property int $id
+ * @property string $f_name 发送方昵称
+ * @property string $f_code 发送方code
+ * @property string $f_avatar 发送方头像
+ * @property string $f_clientid ws服务分配的客户端id
+ * @property string $kf_code 服务的客服sn
+ * @property int $online_status 在线状态 1:在线 0:不在线
+ * @property string $updated_time 更新时间
+ * @property string $created_time 创建时间
+ */
+class Guest extends \yii\db\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return 'guest';
+    }
+
+    /**
+     * @return \yii\db\Connection the database connection used by this AR class.
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('db_kf');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['online_status'], 'integer'],
+            [['updated_time', 'created_time'], 'safe'],
+            [['f_name', 'f_code', 'f_clientid', 'kf_code'], 'string', 'max' => 32],
+            [['f_avatar'], 'string', 'max' => 250],
+            [['f_code'], 'unique'],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'id' => 'ID',
+            'f_name' => 'F Name',
+            'f_code' => 'F Code',
+            'f_avatar' => 'F Avatar',
+            'f_clientid' => 'F Clientid',
+            'kf_code' => 'Kf Code',
+            'online_status' => 'Online Status',
+            'updated_time' => 'Updated Time',
+            'created_time' => 'Created Time',
+        ];
+    }
+}

+ 64 - 0
common/models/guest/GuestQueue.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace common\models\guest;
+
+use Yii;
+
+/**
+ * This is the model class for table "guest_queue".
+ *
+ * @property int $id
+ * @property string $f_name 发送方昵称
+ * @property string $f_code 发送方code
+ * @property string $f_avatar 发送方头像
+ * @property string $f_clientid ws服务分配的客户端id
+ * @property string $updated_time 更新时间
+ * @property string $created_time 创建时间
+ */
+class GuestQueue extends \yii\db\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return 'guest_queue';
+    }
+
+    /**
+     * @return \yii\db\Connection the database connection used by this AR class.
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('db_kf');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['updated_time', 'created_time'], 'safe'],
+            [['f_name', 'f_code', 'f_clientid'], 'string', 'max' => 32],
+            [['f_avatar'], 'string', 'max' => 250],
+            [['f_code'], 'unique'],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'id' => 'ID',
+            'f_name' => 'F Name',
+            'f_code' => 'F Code',
+            'f_avatar' => 'F Avatar',
+            'f_clientid' => 'F Clientid',
+            'updated_time' => 'Updated Time',
+            'created_time' => 'Created Time',
+        ];
+    }
+}

+ 74 - 0
common/models/guest/GuestServiceLog.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace common\models\guest;
+
+use Yii;
+
+/**
+ * This is the model class for table "guest_service_log".
+ *
+ * @property int $id
+ * @property string $guest_code 游客code
+ * @property string $guest_name 游客昵称
+ * @property string $guest_avatar 游客头像
+ * @property string $guest_client_id 游客客服系统分配的客户端id
+ * @property string $guest_ip 游客的ip
+ * @property string $kf_code 客服code
+ * @property string $begin_time 服务开始时间
+ * @property string $end_time 服务结束时间
+ * @property string $updated_time 更新时间
+ * @property string $created_time 创建时间
+ */
+class GuestServiceLog extends \yii\db\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return 'guest_service_log';
+    }
+
+    /**
+     * @return \yii\db\Connection the database connection used by this AR class.
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('db_kf');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['guest_name'], 'required'],
+            [['begin_time', 'end_time', 'updated_time', 'created_time'], 'safe'],
+            [['guest_code', 'guest_name', 'kf_code'], 'string', 'max' => 32],
+            [['guest_avatar'], 'string', 'max' => 255],
+            [['guest_client_id'], 'string', 'max' => 64],
+            [['guest_ip'], 'string', 'max' => 20],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'id' => 'ID',
+            'guest_code' => 'Guest Code',
+            'guest_name' => 'Guest Name',
+            'guest_avatar' => 'Guest Avatar',
+            'guest_client_id' => 'Guest Client ID',
+            'guest_ip' => 'Guest Ip',
+            'kf_code' => 'Kf Code',
+            'begin_time' => 'Begin Time',
+            'end_time' => 'End Time',
+            'updated_time' => 'Updated Time',
+            'created_time' => 'Created Time',
+        ];
+    }
+}

+ 74 - 0
common/models/guest/GuestServicing.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace common\models\guest;
+
+use Yii;
+
+/**
+ * This is the model class for table "guest_servicing".
+ *
+ * @property int $id
+ * @property string $guest_code 游客code
+ * @property string $guest_name 游客昵称
+ * @property string $guest_ip 游客ip
+ * @property string $guest_avatar 游客头像
+ * @property string $guest_client_id 游客客服系统分配的客户端id
+ * @property string $kf_code 客服code
+ * @property int $service_log_id 服务日志id
+ * @property string $updated_time 更新时间
+ * @property string $created_time 创建时间
+ */
+class GuestServicing extends \yii\db\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return 'guest_servicing';
+    }
+
+    /**
+     * @return \yii\db\Connection the database connection used by this AR class.
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('db_kf');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['guest_name', 'guest_ip'], 'required'],
+            [['service_log_id'], 'integer'],
+            [['updated_time', 'created_time'], 'safe'],
+            [['guest_code', 'guest_name', 'kf_code'], 'string', 'max' => 32],
+            [['guest_ip'], 'string', 'max' => 20],
+            [['guest_avatar'], 'string', 'max' => 255],
+            [['guest_client_id'], 'string', 'max' => 64],
+            [['guest_code'], 'unique'],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'id' => 'ID',
+            'guest_code' => 'Guest Code',
+            'guest_name' => 'Guest Name',
+            'guest_ip' => 'Guest Ip',
+            'guest_avatar' => 'Guest Avatar',
+            'guest_client_id' => 'Guest Client ID',
+            'kf_code' => 'Kf Code',
+            'service_log_id' => 'Service Log ID',
+            'updated_time' => 'Updated Time',
+            'created_time' => 'Created Time',
+        ];
+    }
+}

+ 69 - 0
common/models/log/AppErrorLogs.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace common\models\log;
+
+use Yii;
+
+/**
+ * This is the model class for table "app_error_logs".
+ *
+ * @property int $id
+ * @property string $app_name app 名字
+ * @property string $request_uri 请求uri
+ * @property string $content 日志内容
+ * @property string $ip ip
+ * @property string $ua ua信息
+ * @property string $cookies cookie信息。如果有的话
+ * @property string $created_time 插入时间
+ */
+class AppErrorLogs extends \yii\db\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return 'app_error_logs';
+    }
+
+    /**
+     * @return \yii\db\Connection the database connection used by this AR class.
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('db_kf');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['content'], 'required'],
+            [['content'], 'string'],
+            [['created_time'], 'safe'],
+            [['app_name'], 'string', 'max' => 30],
+            [['request_uri'], 'string', 'max' => 255],
+            [['ip'], 'string', 'max' => 500],
+            [['ua', 'cookies'], 'string', 'max' => 1000],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'id' => 'ID',
+            'app_name' => 'App Name',
+            'request_uri' => 'Request Uri',
+            'content' => 'Content',
+            'ip' => 'Ip',
+            'ua' => 'Ua',
+            'cookies' => 'Cookies',
+            'created_time' => 'Created Time',
+        ];
+    }
+}

+ 88 - 0
common/models/merchant/Merchant.php

@@ -0,0 +1,88 @@
+<?php
+
+namespace common\models\merchant;
+
+use Yii;
+
+/**
+ * This is the model class for table "merchant".
+ *
+ * @property int $id
+ * @property string $login_name 登录用户名
+ * @property string $login_pwd 登录密码
+ * @property string $login_salt 随机加密字符串
+ * @property string $sn 会员唯一编号
+ * @property int $status 状态 1:有效 0:无效
+ * @property string $updated_time 更新时间
+ * @property string $created_time 创建时间
+ */
+class Merchant extends \yii\db\ActiveRecord {
+    public function setPassword( $password ) {
+        $this->login_pwd = $this->getSaltPassword($password);
+    }
+
+    public function setSalt( $length = 16 ){
+        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
+        $salt = '';
+        for ( $i = 0; $i < $length; $i++ ){
+            $salt .= $chars[ mt_rand(0, strlen($chars) - 1) ];
+        }
+        $this->login_salt = $salt;
+    }
+
+    public function getSaltPassword($password) {
+        return md5( $password.md5($this->login_salt) );
+    }
+
+
+    public function verifyPassword($password) {
+        return $this->login_pwd === $this->getSaltPassword($password);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return 'merchant';
+    }
+
+    /**
+     * @return \yii\db\Connection the database connection used by this AR class.
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('db_kf');
+    }
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['status'], 'integer'],
+            [['updated_time', 'created_time'], 'safe'],
+            [['login_name'], 'string', 'max' => 30],
+            [['login_pwd', 'login_salt'], 'string', 'max' => 32],
+            [['sn'], 'string', 'max' => 10],
+            [['sn'], 'unique'],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'id' => 'ID',
+            'login_name' => 'Login Name',
+            'login_pwd' => 'Login Pwd',
+            'login_salt' => 'Login Salt',
+            'sn' => 'Sn',
+            'status' => 'Status',
+            'updated_time' => 'Updated Time',
+            'created_time' => 'Created Time',
+        ];
+    }
+}

+ 98 - 0
common/models/merchant/MerchantStaff.php

@@ -0,0 +1,98 @@
+<?php
+
+namespace common\models\merchant;
+
+use Yii;
+
+/**
+ * This is the model class for table "merchant_staff".
+ *
+ * @property int $id
+ * @property string $nickname 昵称
+ * @property string $sn 标识
+ * @property string $avatar 头像
+ * @property string $login_name 登录用户名
+ * @property string $login_pwd 登录密码
+ * @property string $login_salt 登录密码随机码
+ * @property int $status 状态
+ * @property int $online_status 在线状态
+ * @property string $last_active_time 最后活跃时间
+ * @property string $updated_time 最后更新时间
+ * @property string $created_time 插入时间
+ */
+class MerchantStaff extends \yii\db\ActiveRecord
+{
+    public function setPassword( $password ) {
+        $this->login_pwd = $this->getSaltPassword($password);
+    }
+
+    public function setSalt( $length = 16 ){
+        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
+        $salt = '';
+        for ( $i = 0; $i < $length; $i++ ){
+            $salt .= $chars[ mt_rand(0, strlen($chars) - 1) ];
+        }
+        $this->login_salt = $salt;
+    }
+
+    public function getSaltPassword($password) {
+        return md5( $password.md5($this->login_salt) );
+    }
+
+
+    public function verifyPassword($password) {
+        return $this->login_pwd === $this->getSaltPassword($password);
+    }
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return 'merchant_staff';
+    }
+
+    /**
+     * @return \yii\db\Connection the database connection used by this AR class.
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('db_kf');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['status', 'online_status'], 'integer'],
+            [['last_active_time', 'updated_time', 'created_time'], 'safe'],
+            [['nickname'], 'string', 'max' => 30],
+            [['sn', 'login_pwd', 'login_salt'], 'string', 'max' => 32],
+            [['avatar'], 'string', 'max' => 255],
+            [['login_name'], 'string', 'max' => 20],
+            [['login_name'], 'unique'],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'id' => 'ID',
+            'nickname' => 'Nickname',
+            'sn' => 'Sn',
+            'avatar' => 'Avatar',
+            'login_name' => 'Login Name',
+            'login_pwd' => 'Login Pwd',
+            'login_salt' => 'Login Salt',
+            'status' => 'Status',
+            'online_status' => 'Online Status',
+            'last_active_time' => 'Last Active Time',
+            'updated_time' => 'Updated Time',
+            'created_time' => 'Created Time',
+        ];
+    }
+}

+ 62 - 0
common/models/stat/StatDailyAccessSource.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace common\models\stat;
+
+use Yii;
+
+/**
+ * This is the model class for table "stat_daily_access_source".
+ *
+ * @property int $id
+ * @property string $date 日期
+ * @property string $source 来源
+ * @property int $total_number 来源总次数
+ * @property string $updated_time 最后一次更新时间
+ * @property string $created_time 创建时间
+ */
+class StatDailyAccessSource extends \yii\db\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return 'stat_daily_access_source';
+    }
+
+    /**
+     * @return \yii\db\Connection the database connection used by this AR class.
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('db_kf');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['date', 'source'], 'required'],
+            [['date', 'updated_time', 'created_time'], 'safe'],
+            [['total_number'], 'integer'],
+            [['source'], 'string', 'max' => 50],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'id' => 'ID',
+            'date' => 'Date',
+            'source' => 'Source',
+            'total_number' => 'Total Number',
+            'updated_time' => 'Updated Time',
+            'created_time' => 'Created Time',
+        ];
+    }
+}

+ 62 - 0
common/models/stat/StatDailyBrowser.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace common\models\stat;
+
+use Yii;
+
+/**
+ * This is the model class for table "stat_daily_browser".
+ *
+ * @property int $id
+ * @property string $date 日期
+ * @property string $client_browser 浏览器名称
+ * @property int $total_number 总次数
+ * @property string $updated_time 最后一次更新时间
+ * @property string $created_time 插入时间
+ */
+class StatDailyBrowser extends \yii\db\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return 'stat_daily_browser';
+    }
+
+    /**
+     * @return \yii\db\Connection the database connection used by this AR class.
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('db_kf');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['date'], 'required'],
+            [['date', 'updated_time', 'created_time'], 'safe'],
+            [['total_number'], 'integer'],
+            [['client_browser'], 'string', 'max' => 50],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'id' => 'ID',
+            'date' => 'Date',
+            'client_browser' => 'Client Browser',
+            'total_number' => 'Total Number',
+            'updated_time' => 'Updated Time',
+            'created_time' => 'Created Time',
+        ];
+    }
+}

+ 62 - 0
common/models/stat/StatDailyDevice.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace common\models\stat;
+
+use Yii;
+
+/**
+ * This is the model class for table "stat_daily_device".
+ *
+ * @property int $id
+ * @property string $date 日期
+ * @property string $client_device 设备名称
+ * @property int $total_number 当日总次数
+ * @property string $updated_time 最后一次更新时间
+ * @property string $created_time 插入时间
+ */
+class StatDailyDevice extends \yii\db\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return 'stat_daily_device';
+    }
+
+    /**
+     * @return \yii\db\Connection the database connection used by this AR class.
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('db_kf');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['date', 'client_device'], 'required'],
+            [['date', 'updated_time', 'created_time'], 'safe'],
+            [['total_number'], 'integer'],
+            [['client_device'], 'string', 'max' => 50],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'id' => 'ID',
+            'date' => 'Date',
+            'client_device' => 'Client Device',
+            'total_number' => 'Total Number',
+            'updated_time' => 'Updated Time',
+            'created_time' => 'Created Time',
+        ];
+    }
+}

+ 62 - 0
common/models/stat/StatDailyOs.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace common\models\stat;
+
+use Yii;
+
+/**
+ * This is the model class for table "stat_daily_os".
+ *
+ * @property int $id
+ * @property string $date 日期
+ * @property string $client_os 操作系统名称
+ * @property int $total_number 当日总次数
+ * @property string $updated_time 最后一次更新时间
+ * @property string $created_time 插入时间
+ */
+class StatDailyOs extends \yii\db\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return 'stat_daily_os';
+    }
+
+    /**
+     * @return \yii\db\Connection the database connection used by this AR class.
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('db_kf');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['date', 'client_os'], 'required'],
+            [['date', 'updated_time', 'created_time'], 'safe'],
+            [['total_number'], 'integer'],
+            [['client_os'], 'string', 'max' => 50],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'id' => 'ID',
+            'date' => 'Date',
+            'client_os' => 'Client Os',
+            'total_number' => 'Total Number',
+            'updated_time' => 'Updated Time',
+            'created_time' => 'Created Time',
+        ];
+    }
+}

+ 62 - 0
common/models/stat/StatDailyUuid.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace common\models\stat;
+
+use Yii;
+
+/**
+ * This is the model class for table "stat_daily_uuid".
+ *
+ * @property int $id
+ * @property string $date 日期 20161016
+ * @property string $uuid 唯一标示
+ * @property int $total_number 总次数
+ * @property string $updated_time 最后一次更新时间
+ * @property string $created_time 创建时间
+ */
+class StatDailyUuid extends \yii\db\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return 'stat_daily_uuid';
+    }
+
+    /**
+     * @return \yii\db\Connection the database connection used by this AR class.
+     */
+    public static function getDb()
+    {
+        return Yii::$app->get('db_kf');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['date', 'uuid'], 'required'],
+            [['date', 'updated_time', 'created_time'], 'safe'],
+            [['total_number'], 'integer'],
+            [['uuid'], 'string', 'max' => 100],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'id' => 'ID',
+            'date' => 'Date',
+            'uuid' => 'Uuid',
+            'total_number' => 'Total Number',
+            'updated_time' => 'Updated Time',
+            'created_time' => 'Created Time',
+        ];
+    }
+}

+ 25 - 0
common/services/AppLogService.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace common\services;
+
+
+use common\components\UtilHelper;
+use common\models\log\AppErrorLogs;
+
+class AppLogService extends BaseService {
+
+
+    public static function addErrorLog($appid,$uri,$err_msg){
+
+        $model_app_logs = new AppErrorLogs();
+        $model_app_logs->app_name = $appid;
+        $model_app_logs->request_uri = $uri;
+        $model_app_logs->content = $err_msg;
+        $model_app_logs->ip = UtilHelper::getClientIP();
+        $model_app_logs->ua = isset($_SERVER['HTTP_USER_AGENT'])?$_SERVER['HTTP_USER_AGENT']:"";
+        $model_app_logs->cookies = var_export($_COOKIE,true);
+        $model_app_logs->created_time = date("Y-m-d H:i:s");
+        $model_app_logs->save(0);
+        return true;
+    }
+} 

+ 26 - 0
common/services/BaseService.php

@@ -0,0 +1,26 @@
+<?php
+namespace common\services;
+
+
+class BaseService {
+    protected static $_error_msg = null;
+    protected static $_error_code = null;
+    public static function _err($msg='',$code = -1){
+        if($msg){
+            self::$_error_msg = $msg;
+        }else{
+            self::$_error_msg = '操作失败';
+        }
+        self::$_error_code = $code;
+        return false;
+    }
+
+    public static function getLastErrorMsg(){
+        return self::$_error_msg?self::$_error_msg:"";
+    }
+
+
+    public static function getLastErrorCode(){
+        return self::$_error_code?self::$_error_code:0;
+    }
+}

+ 38 - 0
common/services/Constants.php

@@ -0,0 +1,38 @@
+<?php
+/**
+ * Class Constants
+ */
+
+namespace common\services;
+
+
+class Constants {
+	public static $queue_map = [
+		"chat" => "list_chat_message",
+		"trace" => "list_trace_guest",
+        "guest" => "list_guest_ops",
+        "sms" => "list_sms_notices",
+        "wechat" => "list_wechat_message"
+	];
+
+	public static $ignore_cmds = [ "ping","pong"  ];
+	public static $chat_cmds = [ "chat" ];
+	public static $wechat_cmds = [ "wechat_ask","wechat_reply" ];
+	public static $guest_trace_ignore_cmds = [ "kf_connect","kf_close","guest_close" ];
+	public static $guest_trace_cmds = [ "guest_in" ];
+
+	public static $default_kf_avatar = "kf_default_avatar";
+	public static $default_login_pwd = "••••••";
+
+	public static $login_status_map = [
+	    1 => "有效",
+        0 => "无效"
+    ];
+
+    public static $online_status_map = [
+        1 => "在线",
+        0 => "下线"
+    ];
+
+    public static $kf_pop_name = "咨询客服";
+}

+ 217 - 0
common/services/EventsDispatch.php

@@ -0,0 +1,217 @@
+<?php
+
+namespace common\services;
+
+use common\services\chat\ChatService;
+use GatewayWorker\Lib\Gateway;
+
+class EventsDispatch{
+
+    public static function guestIn( $client_id,$message ){
+        self::addChatHistory( $client_id,$message );
+        $data = isset( $message['data'] )?$message['data']:[];
+        if( !$data  ){
+            Gateway::closeClient($client_id);
+            return;
+        }
+
+        if( !GuestDistribution::guestIn( $client_id,$message ) ){
+            $err_data = [
+                "cmd" => "error",
+                "data" => [
+                    "content" => "请重新刷新页面进行客服沟通~~"
+                ]
+            ];
+            Gateway::sendToClient( $client_id, json_encode( $err_data ) );
+            return ;
+        }
+        //将系统生成的$client_id 和  用户uuid绑定起来
+        Gateway::bindUid(  $client_id ,$data['f_code'] );
+        //getUidByClientId 由于目前么有这个方法,我们自己用$_SESSION 来存储
+        $_SESSION['uid'] = $data['f_code'];
+
+        //给游客一个编号,一天有效。
+        $guest_num = MerchantService::getGuestNumer($data['f_code'] );
+
+        $rep_guest_in = [
+            "cmd" => "rep_in",
+            "data" => [
+                "guest_num" => $guest_num
+            ]
+        ];
+        Gateway::sendToClient( $client_id, json_encode( $rep_guest_in ) );
+    }
+	/**
+	 * 访客初始化
+	 * @param $message
+	 * @param $client_id
+	 */
+	public static function guestConnect($client_id,$message){
+		//写入客户表,表示来了一个客户
+		self::addChatHistory( $client_id,$message );
+
+		//尝试分配一个客服
+		$kf_info = GuestDistribution::customerDistribution( $client_id,$message );
+		if( $kf_info['code'] != 200 ){
+            Gateway::sendToClient($client_id, json_encode([
+                'cmd' => $kf_info['cmd'],
+                'data' => [
+                    'content' => $kf_info['msg'],
+                    'date' => date("Y-m-d H:i:s")
+                ]
+            ]));
+            return;
+        }
+
+        //发给游客 安排了 客服
+        Gateway::sendToClient($client_id, json_encode([
+            'cmd' => "kf_link",
+            'data' => $kf_info['data']
+        ]));
+
+        //发给客服 准备接客啦
+		$kf_code = $kf_info['data']['kf_code'];
+        $kf_clients = Gateway::getClientIdByUid( $kf_code );
+        $kf_client_id = $kf_clients?$kf_clients[0]:'';
+        if( $kf_client_id ){
+            Gateway::sendToClient( $kf_client_id , json_encode([
+                'cmd' => "guest_link",
+                'data' => [
+                    "msg" => "有客户来了,接活了~~"
+                ]
+            ]));
+        }
+
+        //更新相关表,例如guest,guest_queue
+        $queue_data = $message + [ "client_id" => $client_id ] ;
+        $queue_data['cmd'] = "kf_link";
+        $queue_data['data']['to_code'] = $kf_info['data']['kf_code'];
+        $queue_data['data']['to_name'] = $kf_info['data']['kf_name'];
+        $queue_data['data']['to_avatar'] = $kf_info['data']['kf_avatar'];
+
+        GuestDistribution::guestKfLink( $queue_data );
+
+        return;
+	}
+
+	public static function guestClose($client_id,$message){
+		//self::addChatHistory( $client_id,$message );
+		Gateway::closeClient( $client_id );
+		//要给客服发评价
+        self::chatMessage( $client_id ,$message );
+	}
+
+    public static function kfCloseGuest($client_id,$message){
+	    //如果通过code还可以找到游客还在线就发给他
+        if( isset( $message['data']['to_code'] ) ) {
+            $clients = Gateway::getClientIdByUid($message['data']['to_code']);
+            if( $clients ){
+                self::chatMessage( $client_id ,$message );
+                return;
+            }
+        }
+        //模拟游客关闭
+        self::simGuestClose( $message );
+    }
+
+    //模拟游客关闭
+    public static function simGuestClose( $message ){
+	    $client_id = null;
+        $chat_msg = $message;
+        $chat_msg['cmd'] = "sim_guest_close";
+        $chat_msg['data']['f_code'] = $message['data']['to_code'];
+        $chat_msg['data']['f_name'] = $message['data']['to_name'];
+        $chat_msg['data']['f_avatar'] = $message['data']['to_avatar'];
+        $chat_msg['data']['to_code'] = $message['data']['f_code'];
+        $chat_msg['data']['to_name'] = $message['data']['f_name'];
+        $chat_msg['data']['to_avatar'] = $message['data']['f_avatar'];
+        $chat_msg['data']['content'] = "游客已断线,强制关闭";
+        //处理收尾工作
+        GuestDistribution::guestClose( $client_id,$chat_msg );
+        //记录聊天历史中
+        self::chatMessage( $client_id ,$chat_msg );
+    }
+
+
+	public static function kfConnect($client_id,$message){
+		self::addChatHistory( $client_id,$message );
+		$data = isset( $message['data'] )?$message['data']:[];
+		//写入客户表,表示来了一个客户
+		//将客服登录系统的sn 和 系统生成的$client_id 绑定
+		Gateway::bindUid(  $client_id ,$data['f_code'] );
+		$_SESSION['uid'] = $data['f_code'];
+	}
+
+	public static function kfClose($client_id,$message){
+		self::addChatHistory( $client_id,$message );
+		Gateway::closeClient( $client_id );
+	}
+	/**
+	 * 聊天事件
+	 * @param $message
+	 */
+	public static function chatMessage( $client_id,$message ){
+
+		//转发给对应的客服或者游客
+        if( isset( $message['data'] ) &&  isset( $message['data']['to_code'] ) ){
+            $clients = Gateway::getClientIdByUid( $message['data']['to_code'] );
+            $client_id = $clients?$clients[0]:'';
+            if( $client_id ){
+
+                if( isset( $message['data']['f_source'] ) &&  $message['data']['f_source'] == "guest" ){
+                    $message['data']['f_ip'] = isset( $message['REMOTE_ADDR'] )?$message['REMOTE_ADDR']:'';
+                }
+
+                $chat_msg = $message;
+                $chat_msg['data']['date'] = date("Y-m-d H:i:s");
+
+                unset( $chat_msg['REMOTE_ADDR']);
+                unset( $chat_msg['REMOTE_PORT']);
+                unset( $chat_msg['GATEWAY_ADDR']);
+                unset( $chat_msg['GATEWAY_PORT']);
+                unset( $chat_msg['GATEWAY_CLIENT_ID']);
+                Gateway::sendToClient( $client_id, json_encode($chat_msg) );
+            }
+        }else{
+            $chat_msg = $message;
+            $chat_msg['data']['date'] = date("Y-m-d H:i:s");
+            $chat_msg['data']['f_code'] = '';
+            $chat_msg['data']['f_avatar'] = '';
+            $chat_msg['data']['f_name'] = '';
+            $chat_msg['data']['to_code'] = $message['data']['f_code'];
+            $chat_msg['data']['to_avatar'] = $message['data']['f_avatar'];
+            $chat_msg['data']['to_name'] = $message['data']['f_name'];
+            $chat_msg['data']['content'] = "留言成功,我们会尽快联系您的~~";
+            unset( $chat_msg['REMOTE_ADDR']);
+            unset( $chat_msg['REMOTE_PORT']);
+            unset( $chat_msg['GATEWAY_ADDR']);
+            unset( $chat_msg['GATEWAY_PORT']);
+            unset( $chat_msg['GATEWAY_CLIENT_ID']);
+            //回复留言,如果是来源小程序或者微信消息,那么就不能回复,因为此次的client_id = f_code
+            $reply_flag = !( isset( $message['data']['f_name'] ) && mb_stripos( $message['data']['f_name'],"微信" ) !== false );
+            if( $reply_flag ){
+                Gateway::sendToClient( $client_id, json_encode($chat_msg) );
+            }
+        }
+
+        //写入队列,队列慢慢入库
+        self::addChatHistory( $client_id,$message );
+	}
+
+	public static function addChatHistory( $client_id,$message ){
+		echo "client_id:{$client_id},message:".json_encode( $message )."\r\n";
+		if( in_array( $message['cmd'],[ "ping","pong" ] ) ){
+			return true;
+		}
+
+		if( in_array( $message['cmd'] ,Constants::$chat_cmds ) ){
+            ChatService::addHistory( $message );
+        }
+
+        if( in_array( $message['cmd'] , Constants::$guest_trace_cmds ) ){
+            ChatService::addTrace( $message + [ "client_id" => $client_id ] );
+        }
+
+        return true;
+	}
+}

+ 148 - 0
common/services/GuestDistribution.php

@@ -0,0 +1,148 @@
+<?php
+
+namespace common\services;
+
+
+use common\components\GlobalUrlService;
+use common\models\guest\Guest;
+use common\models\guest\GuestQueue;
+use common\models\guest\GuestServiceLog;
+use common\models\guest\GuestServicing;
+use common\models\merchant\MerchantStaff;
+
+class GuestDistribution extends  BaseService {
+
+    /**
+     * 用户上线后触发分配客服
+     * @param $param
+     * @return array
+     */
+    public static function customerDistribution($client_id, $message){
+        // 随便找个客服给分配上去
+        $staff_list = MerchantStaff::find()
+            ->where([ "online_status" => 1] )
+            ->asArray()->all();
+        if( !$staff_list ){
+            return [
+                "code" => 201,
+                "msg" => "暂无客服在线,请稍后再联系~~" ,
+                "cmd" => "no_kf"
+            ];
+        }
+
+        $staff_info = $staff_list[ array_rand( $staff_list ) ];
+        $data = [
+            "code" => 200,
+            "data" => [
+                "kf_code" => $staff_info['sn'],
+                "kf_name" => $staff_info['nickname'],
+                "kf_avatar" => GlobalUrlService::buildWwwStaticUrl( "/images/".$staff_info['avatar'] )
+            ]
+        ];
+        return $data;
+    }
+
+    public static function guestIn($client_id, $message){
+        $data = isset( $message['data'] )?$message['data']:[];
+        $f_code = $data['f_code'];
+        $connection =  GuestQueue::getDb();
+        $transaction = $connection->beginTransaction();
+        try {
+            $guest_queue_info = GuestQueue::findOne([ 'f_code' => $f_code]);
+            if ($guest_queue_info) {
+                $model_guest_queue = $guest_queue_info;
+            } else {
+                $model_guest_queue = new GuestQueue();
+                $model_guest_queue->f_name = $data['f_name'];
+                $model_guest_queue->f_avatar = $data['f_avatar'];
+                $model_guest_queue->f_code = $data['f_code'];
+            }
+            $model_guest_queue->f_clientid = $client_id;
+            $flag = $model_guest_queue->save(0);
+            if( !$flag ){
+                throw new \Exception("保存游客失败~~");
+            }
+
+            $guest_info = Guest::findOne( [ 'f_code' => $f_code] );
+            if( $guest_info ){
+                $model_guest = $guest_info;
+            }else{
+                $model_guest = new Guest();
+                $model_guest->f_name = $data['f_name'];
+                $model_guest->f_avatar = $data['f_avatar'];
+                $model_guest->f_code = $data['f_code'];
+            }
+            $model_guest->online_status = 1;
+            $model_guest->f_clientid = $client_id;
+            $model_guest->save(0);
+
+            $transaction->commit();
+
+            return true;
+        }catch (\Exception $exception) {
+            $except_data = [
+                "code" => $exception->getCode(),
+                "msg" => $exception->getMessage(),
+                "line" => $exception->getLine(),
+                "file" => $exception->getFile()
+            ];
+            $transaction->rollBack();
+            return false;
+        }
+    }
+
+    public static function guestClose( $client_id,$message ){
+        $data = isset( $message['data'] )?$message['data']:[];
+        if( $client_id ){
+            GuestQueue::deleteAll(['f_clientid' => $client_id ]);
+        }
+        Guest::updateAll( [ "online_status" => 0 ],[ "f_code" => $data['f_code'] ] );
+        $guest_servicing_info = GuestServicing::findOne([ "guest_code" =>  $data['f_code'] ]);
+        if( $guest_servicing_info ){
+            $guest_servicing_info->delete();
+            GuestServiceLog::updateAll([ 'end_time' => date("Y-m-d H:i:s") ],[ "id" => $guest_servicing_info['service_log_id']  ]);
+        }
+    }
+
+    public static function guestKfLink( $message ){
+        $data = isset( $message['data'] )?$message['data']:[];
+        $connection =  GuestQueue::getDb();
+        $transaction = $connection->beginTransaction();
+        try {
+            GuestQueue::deleteAll([ "f_code" => $data["f_code"]  ]);
+            Guest::updateAll([ "kf_code" => $data['to_code'] ],[ "f_code" => $data["f_code"] ] );
+            //生产正在服务的客户
+            $model_service_log = new GuestServiceLog();
+            $model_service_log->guest_code = $data["f_code"];
+            $model_service_log->guest_name = $data["f_name"];
+            $model_service_log->guest_avatar = $data["f_avatar"];
+            $model_service_log->guest_client_id = $message["client_id"];
+            $model_service_log->guest_ip = $message["REMOTE_ADDR"];
+            $model_service_log->kf_code = $data['to_code'];
+            $model_service_log->begin_time = date("Y-m-d H:i:s");
+            $model_service_log->save( 0 );
+
+            $has_service = GuestServicing::findOne([ "guest_code" => $data["f_code"] ]);
+            if( !$has_service ){
+                $model_servicing = new GuestServicing();
+                $model_servicing->guest_code = $data["f_code"];
+                $model_servicing->guest_name = $data["f_name"];
+                $model_servicing->guest_avatar = $data["f_avatar"];
+                $model_servicing->guest_ip = $message["REMOTE_ADDR"];
+                $model_servicing->guest_client_id = $message["client_id"];
+                $model_servicing->kf_code = $data['to_code'];
+                $model_servicing->service_log_id = $model_service_log->id;
+                $model_servicing->save( 0 );
+            }
+            $transaction->commit();
+        }catch (\Exception $exception){
+            $except_data = [
+                "code" => $exception->getCode(),
+                "msg" => $exception->getMessage(),
+                "line" => $exception->getLine(),
+                "file" => $exception->getFile()
+            ];
+            $transaction->rollBack();
+        }
+    }
+}

+ 36 - 0
common/services/MerchantService.php

@@ -0,0 +1,36 @@
+<?php
+namespace common\services;
+
+use common\models\merchant\Merchant;
+use common\models\merchant\MerchantStaff;
+
+class MerchantService extends BaseService {
+
+    public static function getUniqueSn( $mobile ){
+        do{
+            $sn = md5("saas_merchant_{$mobile}".time());
+            $sn = mb_substr($sn,5,8);
+        }while( Merchant::findOne( ['sn'=>$sn] ) );
+        return $sn;
+    }
+
+    public static function getStaffUniqueSn( $login_name ){
+        do{
+            $sn = md5("saas_kf_{$login_name}".time());
+            $sn = mb_substr($sn,5,8);
+        }while( MerchantStaff::findOne( ['sn'=>$sn] ) );
+        return $sn;
+    }
+
+    public static function getMerchantInfoBySn( $sn ){
+        $merchant_info = Merchant::findOne([ 'sn' => $sn ]);
+        return $merchant_info;
+    }
+
+
+    public static function getGuestNumer( $f_code = '' ){
+        //给游客一个编号,一天有效。建议使用缓存 这样同一天的f_code 编号一样
+        return mt_rand(0,10000);
+    }
+
+}

+ 24 - 0
common/services/QiniuUploadService.php

@@ -0,0 +1,24 @@
+<?php
+namespace common\services;
+
+
+use common\components\HttpClient;
+
+class QiniuUploadService extends BaseService{
+	public function getConfig(){
+		return \Yii::$app->params['upload']['qiniu_config'];
+	}
+
+	public function getUploadKey($key, $bucket = "vincentguo-pic2"){
+
+		$config = $this->getConfig();
+		$auth   = new \Qiniu\Auth($config['ak'], $config['sk']);
+
+		if (!empty($config['bucket'])) {
+			$bucket = $config['bucket'];
+		}
+		return $auth->uploadToken($bucket, $key);
+	}
+
+
+}

+ 73 - 0
common/services/captcha/ValidateCode.php

@@ -0,0 +1,73 @@
+<?php
+/** 验证码类* */
+
+namespace common\services\captcha;
+
+
+class ValidateCode {
+    private $charset = 'abcdefghkmnprstuvwxyzABCDEFGHKMNPRSTUVWXYZ23456789';//随机因子
+    private $code;//验证码
+    private $codelen = 4;//验证码长度
+    private $width = 100;//宽度
+    private $height = 37;//高度
+    private $img;//图形资源句柄
+    private $font;//指定的字体
+    private $fontsize = 28;//指定字体大小
+    private $fontcolor;//指定字体颜色
+    //构造方法初始化
+    public function __construct( $path ) {
+        $this->font = $path;//注意字体路径要写对,否则显示不了图片
+    }
+    //生成随机码
+    private function createCode() {
+        $_len = strlen($this->charset)-1;
+        for ($i=0;$i<$this->codelen;$i++) {
+            $this->code .= $this->charset[mt_rand(0,$_len)];
+        }
+    }
+    //生成背景
+    private function createBg() {
+        $this->img = imagecreatetruecolor($this->width, $this->height);
+        $color = imagecolorallocate($this->img, 255, 255,255);
+        imagefilledrectangle($this->img,0,$this->height,$this->width,0,$color);
+    }
+    //生成文字
+    private function createFont() {
+        $_x = $this->width / $this->codelen;
+        for ($i=0;$i<$this->codelen;$i++) {
+            $this->fontcolor = imagecolorallocate($this->img,mt_rand(0,156),mt_rand(0,156),mt_rand(0,156));
+            imagettftext($this->img,$this->fontsize,mt_rand(-30,30),$_x*$i+mt_rand(1,5),$this->height / 1.4,$this->fontcolor,$this->font,$this->code[$i]);
+        }
+    }
+    //生成线条、雪花
+    private function createLine() {
+        //线条
+        for ($i=0;$i<6;$i++) {
+            $color = imagecolorallocate($this->img,mt_rand(0,156),mt_rand(0,156),mt_rand(0,156));
+            imageline($this->img,mt_rand(0,$this->width),mt_rand(0,$this->height),mt_rand(0,$this->width),mt_rand(0,$this->height),$color);
+        }
+        //雪花
+        for ($i=0;$i<100;$i++) {
+            $color = imagecolorallocate($this->img,mt_rand(200,255),mt_rand(200,255),mt_rand(200,255));
+            imagestring($this->img,mt_rand(1,5),mt_rand(0,$this->width),mt_rand(0,$this->height),'*',$color);
+        }
+    }
+    //输出
+    private function outPut() {
+        header('Content-type:image/png');
+        imagepng($this->img);
+        imagedestroy($this->img);
+    }
+    //对外生成
+    public function doimg() {
+        $this->createBg();
+        $this->createCode();
+        $this->createLine();
+        $this->createFont();
+        $this->outPut();
+    }
+    //获取验证码
+    public function getCode() {
+        return strtolower($this->code);
+    }
+}

+ 94 - 0
common/services/chat/ChatService.php

@@ -0,0 +1,94 @@
+<?php
+namespace common\services\chat;
+
+use apanly\BrowserDetector\Browser;
+use apanly\BrowserDetector\Device;
+use apanly\BrowserDetector\Os;
+use common\components\UtilHelper;
+use common\models\ChatHistory;
+use common\models\GuestTrace;
+use common\services\BaseService;
+use common\services\Constants;
+
+class ChatService extends BaseService {
+	public static function addHistory( $data ){
+		$cmd = isset( $data['cmd'] )?$data['cmd']:'';
+		$f_data = isset( $data['data'] )?$data['data']:[];
+		if( !$f_data ){
+			return true;
+		}
+		if( !in_array( $cmd,Constants::$chat_cmds  )){
+			return true;
+		}
+
+		$tmp_source = 0;
+		if( isset( $f_data['f_source'] ) ){
+		    if( $f_data['f_source'] == "guest" ){
+                $tmp_source = 1;
+            }elseif ( $f_data['f_source'] == "kefu" ){
+                $tmp_source = 2;
+            }elseif ( $f_data['f_source'] == "system" ){
+                $tmp_source = 3;
+            }
+        }
+		$model_chat_history = new ChatHistory();
+		$model_chat_history->cmd = $cmd;
+		$model_chat_history->f_name = isset( $f_data['f_name'] )?$f_data['f_name']:'';
+		$model_chat_history->f_avatar = isset( $f_data['f_avatar'] )?$f_data['f_avatar']:'';
+		$model_chat_history->f_code = isset( $f_data['f_code'] )?$f_data['f_code']:'';
+		$model_chat_history->content = isset( $f_data['content'] )?$f_data['content']:'';
+		$model_chat_history->to_name = isset( $f_data['to_name'] )?$f_data['to_name']:'';
+		$model_chat_history->to_code = isset( $f_data['to_code'] )?$f_data['to_code']:'';
+		$model_chat_history->to_avatar = isset( $f_data['to_avatar'] )?$f_data['to_avatar']:'';
+        $model_chat_history->source = $tmp_source;
+		$model_chat_history->save( 0 );
+		return true;
+	}
+
+	public static function addTrace( $data ){
+		$cmd = isset( $data['cmd'] )?$data['cmd']:'';
+		$f_data = isset( $data['data'] )?$data['data']:[];
+		if( !$f_data ){
+			return true;
+		}
+        if( in_array( $cmd , Constants::$guest_trace_ignore_cmds + Constants::$ignore_cmds ) ){
+            return false;
+        }
+
+		$model_guest_trace = new GuestTrace();
+		$model_guest_trace->cmd = $cmd;
+
+		$model_guest_trace->f_clientid = isset( $f_data['client_id'] )?$f_data['client_id']:'';
+		$model_guest_trace->f_code = isset( $f_data['f_code'] )?$f_data['f_code']:'';
+		$model_guest_trace->ua = isset( $f_data['ua'] )?$f_data['ua']:'';
+		$model_guest_trace->ip = isset( $data['REMOTE_ADDR'] )?$data['REMOTE_ADDR']:'';
+		$model_guest_trace->referer = isset( $f_data['content'] )?$f_data['content']:'';
+		$model_guest_trace->talk_url = isset( $f_data['domain'] )?$f_data['domain']:'';
+		//增加几个来源统计
+        $tmp_source = 'direct';
+        if( $model_guest_trace->referer ){
+            $tmp_source = parse_url( $model_guest_trace->referer ,PHP_URL_HOST );
+            if( stripos($tmp_source,"www.google.") !== false ){
+                $tmp_source = "www.google.com";
+            }
+        }
+        $model_guest_trace->source = $tmp_source;
+        if( $model_guest_trace->ua ){
+            $tmp_browser = new Browser( $model_guest_trace->ua );
+            $tmp_os = new Os( $model_guest_trace->ua );
+            $tmp_device = new Device( $model_guest_trace->ua );
+
+            $model_guest_trace->client_browser = $tmp_browser->getName()?$tmp_browser->getName():'';
+            $model_guest_trace->client_browser_version = $tmp_browser->getVersion()?$tmp_browser->getVersion():'';
+            $model_guest_trace->client_os = $tmp_os->getName()?$tmp_os->getName():'';
+            $model_guest_trace->client_os_version = $tmp_os->getVersion()?$tmp_os->getVersion():'';
+            $model_guest_trace->client_device = $tmp_device->getName()?$tmp_device->getName():'';
+            if( $model_guest_trace->client_device == "unknown" && UtilHelper::isPC() ){
+                $model_guest_trace->client_device = "pc";
+            }
+        }
+		$model_guest_trace->save( 0 );
+		return true;
+	}
+
+}

BIN
common/services/ip/17monipdb.dat


+ 99 - 0
common/services/ip/IPService.php

@@ -0,0 +1,99 @@
+<?php
+namespace common\services\ip;
+
+
+class IPService {
+
+    private static $ip     = NULL;
+
+    private static $fp     = NULL;
+    private static $offset = NULL;
+    private static $index  = NULL;
+
+    private static $cached = array();
+
+    public static function find($ip)
+    {
+        if (empty($ip) === TRUE)
+        {
+            return 'N/A';
+        }
+
+        $nip   = gethostbyname($ip);
+        $ipdot = explode('.', $nip);
+
+        if ($ipdot[0] < 0 || $ipdot[0] > 255 || count($ipdot) !== 4)
+        {
+            return 'N/A';
+        }
+
+        if (isset(self::$cached[$nip]) === TRUE)
+        {
+            return self::$cached[$nip];
+        }
+
+        if (self::$fp === NULL)
+        {
+            self::init();
+        }
+
+        $nip2 = pack('N', ip2long($nip));
+
+        $tmp_offset = (int)$ipdot[0] * 4;
+        $start      = unpack('Vlen', self::$index[$tmp_offset] . self::$index[$tmp_offset + 1] . self::$index[$tmp_offset + 2] . self::$index[$tmp_offset + 3]);
+
+        $index_offset = $index_length = NULL;
+        $max_comp_len = self::$offset['len'] - 1024 - 4;
+        for ($start = $start['len'] * 8 + 1024; $start < $max_comp_len; $start += 8)
+        {
+            if (self::$index{$start} . self::$index{$start + 1} . self::$index{$start + 2} . self::$index{$start + 3} >= $nip2)
+            {
+                $index_offset = unpack('Vlen', self::$index{$start + 4} . self::$index{$start + 5} . self::$index{$start + 6} . "\x0");
+                $index_length = unpack('Clen', self::$index{$start + 7});
+
+                break;
+            }
+        }
+
+        if ($index_offset === NULL)
+        {
+            return 'N/A';
+        }
+
+        fseek(self::$fp, self::$offset['len'] + $index_offset['len'] - 1024);
+
+        self::$cached[$nip] = explode("\t", fread(self::$fp, $index_length['len']));
+
+        return self::$cached[$nip];
+    }
+
+    private static function init()
+    {
+        if (self::$fp === NULL)
+        {
+            self::$ip = new self();
+
+            self::$fp = fopen(__DIR__ . '/17monipdb.dat', 'rb');
+            if (self::$fp === FALSE)
+            {
+                throw new \Exception('Invalid 17monipdb.dat file!');
+            }
+
+            self::$offset = unpack('Nlen', fread(self::$fp, 4));
+            if (self::$offset['len'] < 4)
+            {
+                throw new \Exception('Invalid 17monipdb.dat file!');
+            }
+
+            self::$index = fread(self::$fp, self::$offset['len'] - 4);