root 3 rokov pred
commit
f5385eda57
100 zmenil súbory, kde vykonal 25820 pridanie a 0 odobranie
  1. 32 0
      LICENSE.md
  2. 65 0
      README.md
  3. 77 0
      Vagrantfile
  4. 9 0
      codeception.yml
  5. 45 0
      common/behaviors/PrimaryKeyBehavior.php
  6. 15 0
      common/codeception.yml
  7. 197 0
      common/components/ActiveQuery.php
  8. 447 0
      common/components/ActiveRecord.php
  9. 31 0
      common/components/AsyncFileTarget.php
  10. 53 0
      common/components/Controller.php
  11. 55 0
      common/components/Model.php
  12. 114 0
      common/components/MongoActiveRecord.php
  13. 30 0
      common/components/Redis.php
  14. 147 0
      common/components/Request.php
  15. 225 0
      common/components/SwooleAsyncTimer.php
  16. 5 0
      common/config/.gitignore
  17. 11 0
      common/config/bootstrap.php
  18. 122 0
      common/config/config-example.php
  19. 94 0
      common/config/config-oci.php
  20. 117 0
      common/config/config-product.php
  21. 103 0
      common/config/config-test.php
  22. 107 0
      common/config/config.php
  23. 89 0
      common/config/main.php
  24. 283 0
      common/config/params-nation.php
  25. 31 0
      common/config/params-rpc.php
  26. 40 0
      common/config/params-swoole.php
  27. 355 0
      common/config/params.php
  28. 14 0
      common/config/test.php
  29. 9 0
      common/fixtures/UserFixture.php
  30. 241 0
      common/helpers/Cache.php
  31. 513 0
      common/helpers/Date.php
  32. 899 0
      common/helpers/Excel.php
  33. 49 0
      common/helpers/Form.php
  34. 3481 0
      common/helpers/IDValidator/GB2260.php
  35. 187 0
      common/helpers/IDValidator/IDValidator.php
  36. 130 0
      common/helpers/IDValidator/util.php
  37. 16 0
      common/helpers/Level.php
  38. 146 0
      common/helpers/Log.php
  39. 55 0
      common/helpers/LoggerTool.php
  40. 142 0
      common/helpers/MongodbSearchFilter.php
  41. 72 0
      common/helpers/Schema.php
  42. 514 0
      common/helpers/Tool.php
  43. 139 0
      common/helpers/Validator.php
  44. 4063 0
      common/helpers/bonus/BonusCalc.php
  45. 1157 0
      common/helpers/bonus/BonusSend.php
  46. 1113 0
      common/helpers/bonus/CalcCache.php
  47. 1388 0
      common/helpers/bonus/CalcServeBonusCalc.php
  48. 640 0
      common/helpers/bonus/CalcServePerfCalc.php
  49. 1696 0
      common/helpers/bonus/PerfCalc.php
  50. 101 0
      common/helpers/http/BackendToFrontendApi.php
  51. 156 0
      common/helpers/http/LingYunGongApi.php
  52. 152 0
      common/helpers/http/RemoteUploadApi.php
  53. 208 0
      common/helpers/http/ShopApi.php
  54. 20 0
      common/helpers/parsedown/LICENSE.txt
  55. 1679 0
      common/helpers/parsedown/Parsedown.php
  56. 86 0
      common/helpers/parsedown/README.md
  57. 33 0
      common/helpers/parsedown/composer.json
  58. 40 0
      common/helpers/snowflake/PageSnowFake.php
  59. 40 0
      common/helpers/snowflake/SnowFake.php
  60. 90 0
      common/libs/LoginIpChecker.php
  61. 115 0
      common/libs/api/sms/SmsApi.php
  62. 79 0
      common/libs/api/sms/meilian/SmsSend.php
  63. 19 0
      common/libs/api/sms/meilian/errorCode.php
  64. 461 0
      common/libs/dataList/DataList.php
  65. 30 0
      common/libs/dataList/DataListInterface.php
  66. 24 0
      common/libs/dataList/column/AbstractColumn.php
  67. 37 0
      common/libs/dataList/column/Area.php
  68. 27 0
      common/libs/dataList/column/DateTime.php
  69. 12 0
      common/libs/dataList/column/InterfaceColumn.php
  70. 20 0
      common/libs/dataList/column/LongNumber.php
  71. 22 0
      common/libs/dataList/column/Price.php
  72. 20 0
      common/libs/dataList/column/Text.php
  73. 22 0
      common/libs/dataList/column/YesNo.php
  74. 1088 0
      common/libs/export/BaseExport.php
  75. 10 0
      common/libs/export/module/AtlasExport.php
  76. 10 0
      common/libs/export/module/BonusExport.php
  77. 10 0
      common/libs/export/module/FinanceExport.php
  78. 10 0
      common/libs/export/module/LogExport.php
  79. 10 0
      common/libs/export/module/OrderExport.php
  80. 10 0
      common/libs/export/module/ReconsumeExport.php
  81. 10 0
      common/libs/export/module/ShopExport.php
  82. 10 0
      common/libs/export/module/UserExport.php
  83. 120 0
      common/libs/lock/RedisLock.php
  84. 76 0
      common/libs/logging/login/AdminLogin.php
  85. 76 0
      common/libs/logging/login/UserLogin.php
  86. 589 0
      common/libs/logging/operate/AbstractOperate.php
  87. 65 0
      common/libs/logging/operate/AdminOperate.php
  88. 65 0
      common/libs/logging/operate/UserOperate.php
  89. 147 0
      common/libs/logging/operate/provider/AbstractProvider.php
  90. 89 0
      common/libs/logging/operate/provider/BatchProvider.php
  91. 89 0
      common/libs/logging/operate/provider/SingleProvider.php
  92. 23 0
      common/libs/logging/operate/valueType/ADMIN.php
  93. 59 0
      common/libs/logging/operate/valueType/AbstractValueType.php
  94. 40 0
      common/libs/logging/operate/valueType/Area.php
  95. 18 0
      common/libs/logging/operate/valueType/AuditStatus.php
  96. 40 0
      common/libs/logging/operate/valueType/Config.php
  97. 22 0
      common/libs/logging/operate/valueType/Date.php
  98. 22 0
      common/libs/logging/operate/valueType/DateTime.php
  99. 29 0
      common/libs/logging/operate/valueType/DecLv.php
  100. 27 0
      common/libs/logging/operate/valueType/DecRoleId.php

+ 32 - 0
LICENSE.md

@@ -0,0 +1,32 @@
+The Yii framework is free software. It is released under the terms of
+the following BSD License.
+
+Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+ * Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in
+   the documentation and/or other materials provided with the
+   distribution.
+ * Neither the name of Yii Software LLC nor the names of its
+   contributors may be used to endorse or promote products derived
+   from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.

+ 65 - 0
README.md

@@ -0,0 +1,65 @@
+<p align="center">
+    <a href="https://github.com/yiisoft" target="_blank">
+        <img src="https://avatars0.githubusercontent.com/u/993323" height="100px">
+    </a>
+    <h1 align="center">Yii 2 Advanced Project Template</h1>
+    <br>
+</p>
+
+Yii 2 Advanced Project Template is a skeleton [Yii 2](http://www.yiiframework.com/) application best for
+developing complex Web applications with multiple tiers.
+
+The template includes three tiers: front end, back end, and console, each of which
+is a separate Yii application.
+
+The template is designed to work in a team development environment. It supports
+deploying the application in different environments.
+
+Documentation is at [docs/guide/README.md](docs/guide/README.md).
+
+[![Latest Stable Version](https://img.shields.io/packagist/v/yiisoft/yii2-app-advanced.svg)](https://packagist.org/packages/yiisoft/yii2-app-advanced)
+[![Total Downloads](https://img.shields.io/packagist/dt/yiisoft/yii2-app-advanced.svg)](https://packagist.org/packages/yiisoft/yii2-app-advanced)
+[![Build Status](https://travis-ci.org/yiisoft/yii2-app-advanced.svg?branch=master)](https://travis-ci.org/yiisoft/yii2-app-advanced)
+
+DIRECTORY STRUCTURE
+-------------------
+
+```
+common
+    config/              contains shared configurations
+    mail/                contains view files for e-mails
+    models/              contains model classes used in both backend and frontend
+    tests/               contains tests for common classes    
+console
+    config/              contains console configurations
+    controllers/         contains console controllers (commands)
+    migrations/          contains database migrations
+    models/              contains console-specific model classes
+    runtime/             contains files generated during runtime
+backend
+    assets/              contains application assets such as JavaScript and CSS
+    config/              contains backend configurations
+    controllers/         contains Web controller classes
+    models/              contains backend-specific model classes
+    runtime/             contains files generated during runtime
+    tests/               contains tests for backend application    
+    views/               contains view files for the Web application
+    web/                 contains the entry script and Web resources
+frontend
+    assets/              contains application assets such as JavaScript and CSS
+    config/              contains frontend configurations
+    controllers/         contains Web controller classes
+    models/              contains frontend-specific model classes
+    runtime/             contains files generated during runtime
+    tests/               contains tests for frontend application
+    views/               contains view files for the Web application
+    web/                 contains the entry script and Web resources
+    widgets/             contains frontend widgets
+vendor/                  contains dependent 3rd-party packages
+environments/            contains environment-based overrides
+```
+
+注意事项
+-------------------
+1. 后台新增功能需考虑permission,有权限的才显示
+2. 增删字段要同时修改相应的导出功能

+ 77 - 0
Vagrantfile

@@ -0,0 +1,77 @@
+require 'yaml'
+require 'fileutils'
+
+required_plugins = %w( vagrant-hostmanager vagrant-vbguest )
+required_plugins.each do |plugin|
+    exec "vagrant plugin install #{plugin}" unless Vagrant.has_plugin? plugin
+end
+
+domains = {
+  frontend: 'y2aa-frontend.test',
+  backend:  'y2aa-backend.test'
+}
+
+config = {
+  local: './vagrant/config/vagrant-local.yml',
+  example: './vagrant/config/vagrant-local.example.yml'
+}
+
+# copy config from example if local config not exists
+FileUtils.cp config[:example], config[:local] unless File.exist?(config[:local])
+# read config
+options = YAML.load_file config[:local]
+
+# check github token
+if options['github_token'].nil? || options['github_token'].to_s.length != 40
+  puts "You must place REAL GitHub token into configuration:\n/yii2-app-advanced/vagrant/config/vagrant-local.yml"
+  exit
+end
+
+# vagrant configurate
+Vagrant.configure(2) do |config|
+  # select the box
+  config.vm.box = 'bento/ubuntu-16.04'
+
+  # should we ask about box updates?
+  config.vm.box_check_update = options['box_check_update']
+
+  config.vm.provider 'virtualbox' do |vb|
+    # machine cpus count
+    vb.cpus = options['cpus']
+    # machine memory size
+    vb.memory = options['memory']
+    # machine name (for VirtualBox UI)
+    vb.name = options['machine_name']
+  end
+
+  # machine name (for vagrant console)
+  config.vm.define options['machine_name']
+
+  # machine name (for guest machine console)
+  config.vm.hostname = options['machine_name']
+
+  # network settings
+  config.vm.network 'private_network', ip: options['ip']
+
+  # sync: folder 'yii2-app-advanced' (host machine) -> folder '/app' (guest machine)
+  config.vm.synced_folder './', '/app', owner: 'vagrant', group: 'vagrant'
+
+  # disable folder '/vagrant' (guest machine)
+  config.vm.synced_folder '.', '/vagrant', disabled: true
+
+  # hosts settings (host machine)
+  config.vm.provision :hostmanager
+  config.hostmanager.enabled            = true
+  config.hostmanager.manage_host        = true
+  config.hostmanager.ignore_private_ip  = false
+  config.hostmanager.include_offline    = true
+  config.hostmanager.aliases            = domains.values
+
+  # provisioners
+  config.vm.provision 'shell', path: './vagrant/provision/once-as-root.sh', args: [options['timezone']]
+  config.vm.provision 'shell', path: './vagrant/provision/once-as-vagrant.sh', args: [options['github_token']], privileged: false
+  config.vm.provision 'shell', path: './vagrant/provision/always-as-root.sh', run: 'always'
+
+  # post-install message (vagrant console)
+  config.vm.post_up_message = "Frontend URL: http://#{domains[:frontend]}\nBackend URL: http://#{domains[:backend]}"
+end

+ 9 - 0
codeception.yml

@@ -0,0 +1,9 @@
+# global codeception file to run tests from all apps
+include:
+    - common
+    - frontend
+    - backend
+paths:
+    log: console/runtime/logs
+settings:
+    colors: true

+ 45 - 0
common/behaviors/PrimaryKeyBehavior.php

@@ -0,0 +1,45 @@
+<?php
+namespace common\behaviors;
+use common\helpers\snowflake\SnowFake;
+use yii\behaviors\AttributeBehavior;
+use yii\db\Expression;
+
+/**
+ * Created by PhpStorm.
+ * User: congli
+ * Date: 2020/2/25
+ * Time: 5:11 PM
+ */
+class PrimaryKeyBehavior extends AttributeBehavior
+{
+    public $primaryKeyAttribute = 'ID';
+
+    public $value;
+
+    public function init()
+    {
+        parent::init();
+
+        if (empty($this->attributes)) {
+            $this->attributes = [
+                \yii\db\ActiveRecord::EVENT_BEFORE_INSERT => [$this->primaryKeyAttribute],
+            ];
+        }
+    }
+
+    /**
+     * @param \yii\base\Event $event
+     * @return mixed
+     */
+    protected function getValue($event)
+    {
+        //@todo 修正同一事务中取ID会出错的问题
+//        return new Expression("REPLACE(UUID(),'-','')");
+
+//        $uuidStr = \Yii::$app->db->createCommand('select UUID()')->queryScalar();
+//        return str_replace('-', '', $uuidStr);
+
+        return SnowFake::instance()->generateId();
+    }
+
+}

+ 15 - 0
common/codeception.yml

@@ -0,0 +1,15 @@
+namespace: common\tests
+actor_suffix: Tester
+paths:
+    tests: tests
+    output: tests/_output
+    data: tests/_data
+    support: tests/_support
+settings:
+    bootstrap: _bootstrap.php
+    colors: true
+    memory_limit: 1024M
+modules:
+    config:
+        Yii2:
+            configFile: 'config/test-local.php'

+ 197 - 0
common/components/ActiveQuery.php

@@ -0,0 +1,197 @@
+<?php
+namespace common\components;
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/3/31
+ * Time: 下午5:39
+ */
+class ActiveQuery extends \yii\db\ActiveQuery {
+
+    /**
+     * 原始是否启用从库的配置
+     * @var
+     */
+    public $oriEnableSlaves;
+
+    /**
+     * 从哪个数据库查询
+     * @var string
+     */
+    public $fromDb = '';
+
+    /**
+     * 查找一条数据
+     * @param null $db
+     * @return array|null|\yii\db\ActiveRecord
+     */
+    public function one($db = null)
+    {
+        $result = parent::one($db);
+        $this->_switchSlaves($db);
+        return $result;
+    }
+
+    /**
+     * 查询全部数据
+     * @param null $db
+     * @return array|\yii\db\ActiveRecord[]
+     */
+    public function all($db = null)
+    {
+        $result = parent::all($db);
+        $this->_switchSlaves($db);
+        return $result;
+    }
+
+    /**
+     * 批量查询逐条返回
+     * @param int $batchSize
+     * @param null $db
+     * @return \yii\db\BatchQueryResult
+     */
+    public function each($batchSize = 100, $db = null)
+    {
+        $result = parent::each($batchSize, $db);
+        $this->_switchSlaves($db);
+        return $result;
+    }
+
+    /**
+     * 批量查询,批量返回
+     * @param int $batchSize
+     * @param null $db
+     * @return \yii\db\BatchQueryResult
+     */
+    public function batch($batchSize = 100, $db = null)
+    {
+        $result = parent::batch($batchSize, $db);
+        $this->_switchSlaves($db);
+        return $result;
+    }
+
+    /**
+     * 增加可以从指定库查表的功能
+     * @param null $db
+     * @return \yii\db\Command
+     * @throws \yii\base\InvalidConfigException
+     */
+    public function createCommand($db = null)
+    {
+        /* @var $modelClass ActiveRecord */
+        $modelClass = $this->modelClass;
+        if ($db === null) {
+            if($this->fromDb === ''){
+                $db = $modelClass::getDb();
+            } else {
+                $db = \Yii::$app->get($this->fromDb);
+            }
+        }
+        return parent::createCommand($db);
+    }
+
+    /**
+     * 获取全部表分区
+     * @param null $db
+     * @return array
+     */
+    public function allPart($db = null){
+        if ($this->emulateExecution) {
+            return [];
+        }
+        if($db === null){
+            $db = $this->modelClass::getDb();
+        }
+        // 从主库去获取表分区
+        $db->enableSlaves = false;
+        $tableName = $this->modelClass::tableName();
+        $sql = "SELECT * FROM USER_TAB_PARTITIONS WHERE TABLE_NAME='$tableName'";
+        $rows = $db->createCommand($sql)->queryAll();
+        // 切换回原始设置
+        $db->enableSlaves = $this->oriEnableSlaves;
+        return $this->populate($rows);
+    }
+
+    /**
+     * 从分区表中查出年月所在分区的相关数据(如果有from()放在from()后面使用)
+     * @param $yearMonth
+     * @param string $format
+     * @return $this
+     */
+    public function yearMonth($yearMonth, $format='YYYYMM'){
+//        if(!$this->from){
+//            $tableName = $this->modelClass::tableName();
+//        } else {
+//            $tableName = $this->from[0];
+//        }
+//        $alias = '';
+//        $pregResult = null;
+//        if(preg_match('/^\s*(\S+)\s+(AS\s+|as\s+)?(\S+)\s*$/', $tableName, $pregResult)){
+//            $tableName = $pregResult[1];
+//            $alias = ' '.$pregResult[3];
+//        }
+//        if(!$yearMonth){
+//            $yearMonth = '201701';
+//        }
+//        $this->from = [$tableName." PARTITION FOR(TO_DATE('$yearMonth'".','."'$format'))$alias"];
+        return $this;
+    }
+
+    /**
+     * 从指定数据库查询
+     * @param string $fromDb
+     * @return $this
+     */
+    public function fromDb(string $fromDb){
+        $this->fromDb = $fromDb;
+        return $this;
+    }
+
+    /**
+     * 切换回原始的主从设置
+     * @param null $db
+     */
+    private function _switchSlaves($db = null){
+        if($db === null){
+            $db = $this->modelClass::getDb();
+        }
+        $db->enableSlaves = $this->oriEnableSlaves;
+    }
+
+    /**
+     * 要筛选的字段,不用*
+     * @param array $append
+     * @return $this
+     */
+    public function selectNoText($append = []){
+        $modeClass = $this->modelClass;
+        $columns = $modeClass::getTableSchema()->columns;
+        $result = array_filter($columns, function($col){
+            return in_array($col->dbType, ['text', 'longtext', 'tinytext', 'CLOB']);
+        });
+        if($result){
+            foreach($result as $k=>$v){
+                unset($columns[$k]);
+            }
+        }
+        $selects = array_map(function ($value) use($modeClass){
+            return  $modeClass::tableName().'.'.$value;
+        }, array_keys($columns));
+        if($append){
+            $selects = array_merge($selects, $append);
+        }
+        $this->select($selects);
+        return $this;
+    }
+
+    /**
+     * 以ID筛选
+     * @param $id
+     * @param string $key
+     * @return $this
+     */
+    public function is($id, $key = 'ID'){
+        $this->where($key .'=:' . $key, [$key=>$id]);
+        return $this;
+    }
+}

+ 447 - 0
common/components/ActiveRecord.php

@@ -0,0 +1,447 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Leo
+ * Date: 2017/9/3
+ * Time: 下午9:56
+ */
+
+namespace common\components;
+
+use common\helpers\snowflake\SnowFake;
+use Yii;
+use yii\base\ErrorException;
+use common\helpers\Date;
+use common\helpers\Tool;
+use common\models\PerfOrder;
+use yii\data\Pagination;
+use yii\db\Exception;
+use yii\db\Expression;
+use yii\db\Migration;
+use yii\helpers\ArrayHelper;
+
+/**
+ * Class ActiveRecord
+ * @package common\components
+ */
+class ActiveRecord extends \yii\db\ActiveRecord {
+
+    /**
+     * 最后一次执行的SQL
+     * @var null
+     */
+    public static $rawSql = null;
+    /**
+     * Query对象
+     * @var null
+     */
+    public static $query = null;
+    /**
+     * @var array
+     */
+    public static $argv = [];
+
+    public function behaviors()
+    {
+        $behaviors = parent::behaviors(); // TODO: Change the autogenerated stub
+        $behaviors[] = \common\behaviors\PrimaryKeyBehavior::class;
+
+        return $behaviors;
+    }
+
+    /**
+     * 重写find方法达到强制使用主库还是从库的问题
+     * @param bool $enableSlaves
+     * @param string $fromDb
+     * @return ActiveQuery
+     */
+    public static function find(bool $enableSlaves = false, string $fromDb = '')
+    {
+        $modelClass = get_called_class();
+        if(class_exists($modelClass) && method_exists($modelClass, 'getDb')){
+            $db = $modelClass::getDb();
+            $enableSlavesTemp = $db->enableSlaves;
+            $db->enableSlaves = $enableSlaves;
+            return \Yii::createObject(ActiveQuery::class, [get_called_class(), ['oriEnableSlaves' => $enableSlavesTemp, 'fromDb' => $fromDb]]);
+        } else {
+            return parent::find();
+        }
+    }
+
+    /**
+     * 使用主库查询数据
+     * @return ActiveQuery
+     */
+    public static function findUseMaster(){
+        return self::find(false);
+    }
+
+    /**
+     * 使用从库查询数据
+     * @return ActiveQuery
+     */
+    public static function findUseSlaves(){
+//        return self::find(true);
+        return self::find(false);
+    }
+
+    /**
+     * 从其他库查询数据
+     * @param string $fromDb
+     * @return ActiveQuery
+     */
+    public static function findUseOtherDb(string $fromDb){
+        return self::find(false, $fromDb);
+    }
+
+    /**
+     * 从奖金专属结算库查询数据
+     * @return ActiveQuery
+     */
+    public static function findUseDbCalc(){
+        return self::findUseOtherDb('dbCalc');
+    }
+
+    /**
+     * 以数组的方式获取一条数据
+     * @param string $condition
+     * @param array $params
+     * @param null $select
+     * @return array|null
+     */
+    public static function findOneAsArray($condition='', $params= [], $select=null){
+        $query = static::find()->where($condition, $params)->asArray();
+        if($select){
+            $query->select($select);
+        }
+        return $query->one();
+    }
+
+    /**
+     * 已数组形式获取所有数据
+     * @param string $condition
+     * @param array $params
+     * @param null $select
+     * @return array|null
+     */
+    public static function findAllAsArray($condition='', $params= [], $select=null){
+        $query = static::find()->where($condition, $params)->asArray();
+        if($select){
+            $query->select($select);
+        }
+        return $query->all();
+    }
+
+    /**
+     * 插入一条数据
+     * @param array $insertData
+     * @param null $tableName
+     * @throws Exception
+     */
+    public static function insertOne(array $insertData, $tableName = null){
+        $modelClass = get_called_class();
+        if($tableName === null){
+            $tableName = $modelClass::tableName();
+        }
+        $db = $modelClass::getDb();
+        if(!empty($insertData)){
+            if( !isset($insertData['ID']) ) $insertData['ID'] = SnowFake::instance()->generateId();
+            if(!$db->createCommand()->insert($tableName, $insertData)->execute()){
+                throw new Exception($tableName.'表添加一条数据失败');
+            }
+        }
+    }
+
+    /**
+     * 批量添加数据
+     * @param array $insertData
+     * @param null $tableName
+     * @param string $db
+     * @throws Exception
+     */
+    public static function batchInsert(array $insertData, $tableName = null, $db = 'db'){
+        $modelClass = get_called_class();
+        if($tableName === null){
+            $tableName = $modelClass::tableName();
+        }
+        if(!empty($insertData)){
+            $insertKey = [];
+            foreach($insertData[0] as $key=>$data){
+                $insertKey[] = $key;
+            }
+            if( !in_array('ID', $insertKey ) ) {
+                array_push($insertKey, 'ID');
+                foreach ($insertData as & $everyData) {
+                    $everyData['ID'] = SnowFake::instance()->generateId();
+                }
+            }
+            if(!\Yii::$app->$db->createCommand()->batchInsert($tableName, $insertKey, $insertData)->execute()){
+                throw new Exception($tableName.'表批量添加数据失败');
+            }
+        }
+    }
+
+    /**
+     * 更新全部带着表名
+     * @param $attributes
+     * @param string $condition
+     * @param array $params
+     * @param null $tableName
+     * @param string $db
+     * @return mixed
+     */
+    public static function batchUpdate($attributes, $condition = '', $params = [], $tableName = null, $db = 'db') {
+        $modelClass = get_called_class();
+        if($tableName === null){
+            $tableName = $modelClass::tableName();
+        }
+        $command = \Yii::$app->$db->createCommand();
+        $command->update($tableName, $attributes, $condition, $params);
+        return $command->execute();
+    }
+
+    /**
+     * @param string $condition
+     * @param array $params
+     * @param array $argv
+     * @param bool $isList
+     */
+    public static function prepare($condition = '', $params = [], $argv = [], $isList=false){
+        $default = [
+            'page' => null,
+            'pageSize'=>0, //10
+            'orderBy'=>'CREATED_AT DESC',
+            'with'=>null,
+            'select'=>null,
+            'asArray'=>true,
+            'joinWith'=>null,
+            'from'=>null,
+            'join'=>null,
+            'yearMonth'=>null,
+            'useSlaves' => false,
+            'count' => '*',
+        ];
+        self::$argv = Tool::deepParse($argv ,$default);
+
+        if (!self::$argv['pageSize'] && $isList) {
+            $pageSize = \Yii::$app->request->all('pageSize', \Yii::$app->params['pageSize']);
+            self::$argv['pageSize'] = $pageSize;
+        }
+        if ($condition == '') {
+            $condition = '1=1';
+            $params = [];
+        } else {
+            $condition = '1=1 ' . $condition;
+        }
+        if(self::$argv['useSlaves']){
+            self::$query = self::findUseSlaves();
+        } else {
+            self::$query = self::findUseMaster();
+        }
+        if(self::$argv['select']){
+            self::$query->select(self::$argv['select']);
+        }
+        if(self::$argv['with']){
+            self::$query->with(self::$argv['with']);
+        }
+        if(self::$argv['joinWith']){
+            self::$query->joinWith(self::$argv['joinWith']);
+        }
+        if(self::$argv['from']){
+            self::$query->from(self::$argv['from']);
+        }
+        if(self::$argv['yearMonth']){
+            if(is_array(self::$argv['yearMonth'])){
+                self::$query->yearMonth(self::$argv['yearMonth'][0], self::$argv['yearMonth'][1]);
+            } else {
+                self::$query->yearMonth(self::$argv['yearMonth']);
+            }
+        }
+        if(self::$argv['join']){
+            if(!is_array(self::$argv['join'][0])){
+                self::$query->join(self::$argv['join'][0], self::$argv['join'][1], self::$argv['join'][2]);
+            } else {
+                foreach(self::$argv['join'] as $value){
+                    self::$query->join($value[0], $value[1], $value[2]);
+                }
+            }
+        }
+        self::$query->where($condition, $params);
+        if(self::$argv['orderBy']){
+            self::$query->orderBy(self::$argv['orderBy']);
+        }
+        unset($condition, $params, $argv, $default);
+    }
+
+    /**
+     * 获取列表
+     * @param string $condition
+     * @param array $params
+     * @param array $argv
+     * @return array
+     */
+    public static function lists($condition = '', $params = [], $argv = []) {
+        self::prepare($condition, $params, $argv, true);
+        unset($condition, $params, $argv);
+        $countQuery = clone self::$query;
+        // self::$rawSql = self::$query->createCommand()->getRawSql();
+        // var_dump(self::$rawSql);exit;
+        $count = $countQuery->count(self::$argv['count']); // 得到总数
+        $pagination = new Pagination(['totalCount' => $count]);
+        $pagination->setPageSize(self::$argv['pageSize']);
+        if(self::$argv['page'] !== null){
+            $pagination->setPage(self::$argv['page']);
+        }
+        self::$query->offset($pagination->offset)->limit($pagination->limit);
+        if(self::$argv['asArray']){
+            self::$query->asArray();
+        }
+        $lists = self::$query->all();
+        
+        self::$query = null;
+        unset($countQuery);
+        $startNum = $pagination->page * $pagination->pageSize + 1;
+        return [
+            'list' => $lists ? $lists : [],
+            'pagination' => $pagination,
+            // 'sql'=self::$rawSql,
+            'currentPage'=>$pagination->page,
+            'totalPages'=>$pagination->pageCount,
+            'startNum' => $startNum,
+            'totalCount' => $pagination->totalCount,
+            'pageSize' => $pagination->pageSize,
+        ];
+    }
+
+    /**
+     * 是否存在某个表
+     * @param $tableName
+     * @param string $db
+     * @return mixed
+     */
+    public static function isExistsTable($tableName, $db='db'){
+        if(preg_match('/^\{\{\%(\w+)\}\}$/', $tableName)){
+            $tableName = preg_replace('/^\{\{\%(\w+)\}\}$/', \Yii::$app->$db->tablePrefix.'${1}', $tableName);
+        }
+        \Yii::$app->$db->getDriverName();
+//        $table=\Yii::$app->$db->createCommand("select count(*) AS EXI from User_Tables where table_name = '{$tableName}'")->queryOne();
+        $tableNames = \Yii::$app->$db->getSchema()->getTableNames();
+        return in_array($tableName, $tableNames);
+    }
+
+    /**
+     * 删除表
+     * @param $tableName
+     * @param string $db
+     * @return mixed
+     */
+    public static function deleteTable($tableName, $db='db'){
+        return \Yii::$app->$db->createCommand("DROP TABLE {$tableName}")->execute();
+    }
+
+    /**
+     * 获取表中的所有字段
+     * @param $modelClassName
+     * @return array
+     */
+    public static function getAllFields($modelClassName){
+        $table = $modelClassName::tableName();
+        $tableSchema = \Yii::$app->db->schema->getTableSchema($table);
+        return $fields = ArrayHelper::getColumn($tableSchema->columns, 'name', false);
+    }
+
+    /**
+     * 表中是否存在某字段
+     * @param $modelClassName
+     * @param $field
+     * @return bool
+     */
+    public static function isExistsField($modelClassName, $field){
+        $allFields = self::getAllFields($modelClassName);
+        return in_array($field, $allFields);
+    }
+
+    /**
+     * 分区表的按月的分区表名
+     * @param $yearMonth
+     * @param $format
+     * @return Expression
+     */
+    public static function yearMonthPartName($yearMonth, $format=Date::OCI_TIME_FORMAT_SHORT_MONTH){
+        $modelClass = get_called_class();
+        return new Expression($modelClass::tableName()." PARTITION FOR(TO_DATE('$yearMonth'".','."'$format'))");
+    }
+
+
+    /**
+     * 通过一张模板表创建一个新的表
+     * @param $createTableName
+     * @param $fromTableName
+     * @param array $indexes
+     * @param string $creatDb
+     * @param string $fromDb
+     */
+    public static function createTableFromTable($createTableName, $fromTableName, $indexes = [], $creatDb='db', $fromDb='db'){
+        $fromTableSchema = \Yii::$app->$fromDb->getTableSchema($fromTableName);
+        if($fromTableSchema){
+            $mir = new Migration();
+            $mir->db = \Yii::$app->$creatDb;
+            $mir->compact = true;
+            $newColumn = [];
+            foreach($fromTableSchema->columns as $column){
+                $tempStr = $column->dbType;
+                if($column->defaultValue !== null && strtoupper($column->dbType) == 'varchar'){
+                    $tempStr .= ' DEFAULT '."'".trim($column->defaultValue, "'")."'";
+                } elseif ($column->defaultValue !== null && strtoupper($column->dbType) == 'int') {
+                    $tempStr .= ' DEFAULT '.$column->defaultValue;
+                }elseif ($column->defaultValue !== null && strtoupper($column->dbType) == 'decimal') {
+                    $tempStr .= ' DEFAULT '.$column->defaultValue;
+                }
+                if($column->allowNull){
+                    $tempStr .= '';
+                } else {
+                    $tempStr .= ' NOT NULL';
+                }
+                if($column->isPrimaryKey){
+                    $tempStr .= ' PRIMARY KEY';
+                }
+                $newColumn[$column->name] = $tempStr;
+                unset($tempStr);
+            }
+            $mir->createTable($createTableName, $newColumn);
+            if(!empty($indexes)){
+                foreach($indexes as $index){
+                    [
+                        'name' => $name,
+                        'table' => $table,
+                        'columns' => $columns,
+                        'unique' => $unique,
+                    ] = $index;
+                    $unique = $unique ? $unique : false;
+                    $mir->createIndex($name, $table, $columns, $unique);
+                }
+            }
+        }
+
+
+    }
+
+
+    /**
+     * 删除数据
+     * @param string $where
+     * @return bool
+     */
+    public static function pageDeleteAll($where='') {
+        $limit = 10000;
+        $sql = sprintf('DELETE FROM %s WHERE %s LIMIT %d', strtoupper(self::tableName()), $where, $limit);
+        $affectRow = self::getDb()->createCommand($sql)->execute();
+        if( $affectRow == $limit ) {
+            unset($limit, $sql, $affectRow);
+            return self::pageDeleteAll($where);
+        }
+
+        unset($limit, $sql, $affectRow);
+        return true;
+    }
+}

+ 31 - 0
common/components/AsyncFileTarget.php

@@ -0,0 +1,31 @@
+<?php
+
+
+namespace common\components;
+
+
+use Yii;
+use yii\log\FileTarget;
+
+class AsyncFileTarget extends FileTarget
+{
+    /**
+     * 获取日志文件路径(异步专用能够自动获取当前日期)
+     * @return string
+     */
+    public function getLogFile(){
+        $logDir = dirname($this->logFile);
+        return Yii::getAlias($logDir).DIRECTORY_SEPARATOR.'async_'.date('Ymd', time()).'.log';
+    }
+
+    /**
+     * 重写输出函数,每次输出之前,重新获取一次当前日期的日志文件名
+     * @throws \yii\base\InvalidConfigException
+     * @throws \yii\log\LogRuntimeException
+     */
+    public function export()
+    {
+        $this->logFile = $this->getLogFile();
+        parent::export();
+    }
+}

+ 53 - 0
common/components/Controller.php

@@ -0,0 +1,53 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Leo
+ * Date: 2017/9/3
+ * Time: 下午3:05
+ */
+
+namespace common\components;
+
+
+use common\helpers\Tool;
+use yii\base\ActionEvent;
+use yii\helpers\Json;
+use yii\helpers\Url;
+
+class Controller extends \yii\web\Controller {
+
+    /**
+     * @return array
+     */
+    public function actions() {
+        return [
+            'captcha' =>  [
+                'class' => 'common\helpers\CaptchaAction',
+                'width' => 120,
+                'height' => 40,
+                'padding' => 0,
+                'minLength' => 4,
+                'maxLength' => 4,
+                'offset'=>8,        //设置字符偏移量 有效果
+                'testLimit'=>1,
+            ],
+        ];
+    }
+
+    /**
+     * 不允许直接访问ajax页面
+     * @param $action
+     * @return bool
+     * @throws \yii\web\BadRequestHttpException
+     */
+    protected function checkAjax(&$action){
+        $currentAction = $action->id;
+        if(strpos($currentAction ,'ajax-')===0){
+            if(!\Yii::$app->request->getIsAjax()){
+                throw new \yii\web\BadRequestHttpException('无法完成您的请求');
+            }
+        }
+        return true;
+    }
+
+}

+ 55 - 0
common/components/Model.php

@@ -0,0 +1,55 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Leo
+ * Date: 2017/11/12
+ * Time: 下午3:59
+ */
+
+namespace common\components;
+
+use common\helpers\Validator;
+use yii\base\InvalidConfigException;
+
+class Model extends \yii\base\Model {
+
+    use \common\traits\ModelTrait;
+
+    /**
+     * @var \common\libs\logging\operate\AdminOperate
+     */
+    protected $adminOperateLogger;
+    /**
+     * @var \common\libs\logging\operate\UserOperate
+     */
+    protected $userOperateLogger;
+    protected $systemLogger;
+
+    /**
+     * 扩展父类方法用于校验表单
+     * @return \ArrayObject
+     * @throws InvalidConfigException
+     */
+    public function createValidators()
+    {
+        $validators = new \ArrayObject();
+        foreach ($this->rules() as $rule) {
+            if ($rule instanceof \yii\validators\Validator) {
+                $validators->append($rule);
+            } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
+                if(array_key_exists($rule[1], Validator::$extensionRule)){
+                    // 启用扩展规则
+                    $otherParams = array_merge(['validateMethod'=>$rule[1]], array_slice($rule, 2));
+                    $validator = \yii\validators\Validator::createValidator('\common\helpers\Validator', $this, (array) $rule[0], $otherParams);
+                } else {
+                    $validator = \yii\validators\Validator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2));
+                }
+                $validators->append($validator);
+            } else {
+                throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
+            }
+        }
+        return $validators;
+    }
+
+}

+ 114 - 0
common/components/MongoActiveRecord.php

@@ -0,0 +1,114 @@
+<?php
+namespace common\components;
+
+use common\helpers\Tool;
+use Yii;
+use yii\data\Pagination;
+
+/**
+ * Base active record class for models
+ * @package common\components
+ */
+class MongoActiveRecord extends \yii\mongodb\ActiveRecord {
+    /**
+     * Query对象
+     * @var null
+     */
+    public static $query = null;
+    /**
+     * @var array
+     */
+    public static $argv = [];
+    /**
+     * 构造器
+     *
+     * @access public
+     */
+    public function init(){
+        parent::init();
+    }
+
+    /**
+     * 批量写入数据
+     * @param $field
+     * @param $data
+     * @return mixed
+     */
+    public static function batchInsert($field, $data){
+        $db = static::getDb();
+        $query = $db->createCommand()->batchInsert(static::collectionName(), $field, $data);
+        return $query->execute();
+    }
+
+    /**
+     * @param string $condition
+     * @param array $argv
+     * @throws \yii\base\InvalidConfigException
+     */
+    public static function prepare($condition = [], $argv = []){
+        $default = [
+            'page' => null,
+            'pageSize'=>0, //10
+            'orderBy'=>'_id desc',
+            'select'=>null,
+            'asArray'=>true,
+            'validatePage'=>true,
+            'filter' => null,
+            'indexBy' => null,
+            'count' => '*',
+        ];
+        self::$argv = Tool::deepParse($argv ,$default);
+        if (!self::$argv['pageSize']) {
+            $pageSize = \Yii::$app->request->all('pageSize', \Yii::$app->params['pageSize']);
+            self::$argv['pageSize'] = $pageSize;
+        }
+        self::$query = static::find();
+        if(self::$argv['select']){
+            self::$query->select(self::$argv['select']);
+        }
+        if(self::$argv['indexBy']){
+            self::$query->indexBy(self::$argv['indexBy']);
+        }
+        if(self::$argv['asArray']){
+            self::$query->asArray();
+        }
+        self::$query->orderBy(self::$argv['orderBy'])->where($condition);
+        unset($condition, $argv, $default);
+    }
+
+    /**
+     * 获取列表
+     * @param array $condition
+     * @param array $argv
+     * @return array
+     * @throws \yii\base\InvalidConfigException
+     */
+    public static function  lists($condition = [], $argv = []) {
+        self::prepare($condition, $argv);
+        $countQuery = clone self::$query;
+        $count = $countQuery->count(self::$argv['count']); // 得到总数
+        $pagination = new Pagination(['totalCount' => $count]);
+        $pagination->setPageSize(self::$argv['pageSize']);
+        if(self::$argv['page'] !== null){
+            $pagination->setPage(self::$argv['page']);
+        }
+        self::$query->offset($pagination->offset)->limit($pagination->limit);
+        if(self::$argv['filter']){
+            $lists = self::$query->filter(self::$argv['filter'])->all();
+        }else{
+            $lists = self::$query->all();
+        }
+        self::$query = null;
+        unset($pageParams, $countQuery);
+        $startNum = $pagination->page * $pagination->pageSize + 1;
+        return [
+            'list' => $lists ? $lists : [],
+            'pagination' => $pagination,
+            'currentPage'=>$pagination->page,
+            'totalPages'=>$pagination->pageCount,
+            'startNum' => $startNum,
+            'totalCount' => $pagination->totalCount,
+            'pageSize' => $pagination->pageSize,
+        ];
+    }
+}

+ 30 - 0
common/components/Redis.php

@@ -0,0 +1,30 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/2/28
+ * Time: 上午11:43
+ */
+
+namespace common\components;
+
+use yii\redis\Connection;
+
+class Redis extends Connection
+{
+
+    /**
+     * Redis constructor.
+     * @param array $config
+     * @throws \RedisException
+     */
+    public function __construct($config = [])
+    {
+        parent::__construct($config);
+        $this->open();
+    }
+
+    public static function key($key){
+        return md5($key);
+    }
+}

+ 147 - 0
common/components/Request.php

@@ -0,0 +1,147 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * Date: 2017/09/01
+ * Time: 15:06
+ */
+
+namespace common\components;
+
+use Yii;
+use yii\helpers\ArrayHelper;
+
+class Request extends \yii\web\Request {
+
+    const DEVICE_PC = 'pc';
+    const DEVICE_Mobile = 'mobile';
+    const DEVICE_APP = 'app';
+
+    private $_device = null;
+    private $_deviceInfo = null;
+    private $_suppressResponseCode = null;
+    /**
+     * 返回 post 和 get 的数据
+     * @param null $name
+     * @return array|mixed
+     */
+    public function request($name = null){
+        $result = $this->post($name);
+        if(!$result){
+            $result = $this->get($name);
+        }
+        return $result;
+    }
+
+    /**
+     * 合并获取参数
+     *
+     * @param $name
+     * @param null $default
+     * @return null
+     */
+    public function all($name = null ,$default = NULL){
+        if(is_null($name)){
+            return ArrayHelper::merge($this->getQueryParams(), $this->post());
+        }
+        $params = $this->getQueryParams();
+        if(isset($params[$name])){
+            return $params[$name];
+        }
+        $value = $this->post($name ,$default);
+        return $value ? : $default;
+    }
+
+    /**
+     * 校验完成Csrf之后清空csrf
+     * @param null $clientSuppliedToken
+     * @return bool
+     */
+    public function validateCsrfToken($clientSuppliedToken = null)
+    {
+        if(Yii::$app->controller->module->id == 'gii'){
+            return true;
+        }
+        return parent::validateCsrfToken($clientSuppliedToken);
+    }
+
+    /**
+     * 获取设备类型
+     * @return mixed
+     */
+    public function getDevice(){
+        if($this->_device === null){
+            $header = Yii::$app->request->getHeaders();
+            if(isset($header['device-type']) && ($header['device-type'] == self::DEVICE_PC || $header['device-type'] == self::DEVICE_APP)){
+                $this->_device = $header['device-type'];
+            } else {
+                $this->_device = self::DEVICE_PC;
+            }
+        }
+        return $this->_device;
+    }
+
+    /**
+     * 获取设备信息
+     * @return mixed|null|string
+     */
+    public function getDeviceInfo(){
+        if($this->_deviceInfo === null){
+            $header = Yii::$app->request->getHeaders();
+            if(isset($header['device-info']) && $header['device-info']){
+                // System,Version,NetworkType,UUID
+                $pattern = '/System\:([\w]+)\;\s+Version\:([\w\.]+)\;\s+NetworkType\:([\w\.]+)\;\s+UUID\:([\-\w]+)/i';
+                $infoArr = [];
+                if(preg_match($pattern, $header['device-info'], $infoArr)){
+                    $this->_deviceInfo = [
+                        'system' => $infoArr[1],
+                        'version' => $infoArr[2],
+                        'networkType' => $infoArr[3],
+                        'uuid' => $infoArr[4],
+                    ];
+                }
+            } else {
+                $this->_deviceInfo = [];
+            }
+        }
+        return $this->_deviceInfo;
+    }
+
+    /**
+     * 获取是否支持json响应代码
+     * @return bool
+     */
+    public function getSuppressResponseCode(){
+        if($this->_suppressResponseCode === null){
+            $header = Yii::$app->request->getHeaders();
+            if(isset($header['Suppress-Response-Code']) && $header['Suppress-Response-Code'] == 1){
+                $this->_suppressResponseCode = true;
+            }
+            elseif (Yii::$app->request->get('suppress_response_code')){
+                $this->_suppressResponseCode = true;
+            }
+            else {
+                $this->_suppressResponseCode = false;
+            }
+        }
+        return $this->_suppressResponseCode;
+    }
+
+    /**
+     * 向query中添加数据
+     * @param array $params
+     * @return array|bool
+     */
+    public function appendQueryParams(array $params = []){
+        if(!$params){
+            return false;
+        }
+        $queryParams = \Yii::$app->request->getQueryParams();
+        if($queryParams){
+            $queryParams = ArrayHelper::merge($queryParams, $params);
+        }else{
+            $queryParams = $params;
+        }
+        \Yii::$app->request->setQueryParams($queryParams);
+        return $queryParams;
+    }
+}

+ 225 - 0
common/components/SwooleAsyncTimer.php

@@ -0,0 +1,225 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/4/9
+ * Time: 下午5:15
+ */
+namespace common\components;
+
+use anlity\swooleAsyncTimer\SocketInterface;
+use anlity\swooleAsyncTimer\SwooleAsyncTimerComponent;
+use common\helpers\Cache;
+use common\libs\logging\system\AsyncSystem;
+use common\libs\taskQueue\Queue;
+use Yii;
+use common\helpers\bonus\CalcServeBonusCalc;
+use common\helpers\bonus\CalcServePerfCalc;
+use yii\base\Exception;
+use yii\helpers\Json;
+
+class SwooleAsyncTimer extends SwooleAsyncTimerComponent implements SocketInterface {
+
+    const HANDLE_ADMIN_ASYNC = 'adminAsync';
+    const HANDLE_ADMIN_PULL_MESSAGE = 'adminPullMsg';
+    const HANDLE_USER_ASYNC = 'userAsync';
+    const HANDLE_USER_PULL_MESSAGE = 'userPullMsg';
+    const HANDLE_ADMIN_ASYNC_PERCENT = 'adminAsyncPercent';
+
+    const COMMAND_PUSH_TO = 'pushTo';
+
+    /**
+     * 定时器自动执行任务
+     * @param $timerId
+     * @param $server
+     * @return bool|void
+     * @throws \yii\base\InvalidConfigException
+     * @throws \yii\base\InvalidRouteException
+     * @throws \yii\console\Exception
+     * @throws \yii\db\Exception
+     */
+    public function timerCallback($timerId, $server){
+        CalcServePerfCalc::instance()->calcStep(); // 累计业绩
+        CalcServeBonusCalc::instance()->calcStep();  // 计算奖金
+        // 自动执行任务队列中的任务
+        Queue::instance()->consumeTask();
+
+        return true;
+    }
+
+    /**
+     * 初始化队列
+     * @param $server
+     * @param $workerId
+     */
+    public function onWorkerStart($server, $workerId){
+        if($workerId == 1){
+            // 重新更新自动封期的缓存
+            //AutoClosePeriod::instance()->setCloseTimeAndPeriodStat();
+            // 初始化任务队列
+            Queue::instance()->initRedis();
+            // 初始化备份历史奖金数据表
+//            TaskFunc::initAutoBakBalance();
+        }
+    }
+
+    public function onWorkerStop($server, $workerId){
+    }
+
+    public function onWorkerExit($server, $workerId){
+    }
+
+    public function onOpen($fd){
+    }
+
+
+    public function onClose($fd){
+    }
+
+
+    public function onMessage($fd, $data){
+        // 如果传过来的是一个用户ID,则把$fd和userId绑定存入缓存
+        $data = Json::decode($data);
+        if(isset($data['userId']) && $data['userId'] != ''){
+            Cache::setWebSocketFd($data['app'], $data['userId'], $fd);
+        }
+    }
+
+    /**
+     * 处理异步请求
+     * @param $path
+     * @param array $params
+     * @param array $settings
+     * @return bool
+     * @throws \yii\base\Exception
+     */
+    public function asyncHandle($path, array $params = [], array $settings = []){
+        // 把处理会员的UserId加进数组
+        if(Yii::$app->user->id){
+            $params['handleUserId'] = Yii::$app->user->id;
+            if(Yii::$app->user->userInfo && isset(Yii::$app->user->userInfo['adminName'])){
+                $params['handleUserName'] = Yii::$app->user->userInfo['adminName'];
+            }
+        } else {
+            $params['handleUserId'] = null;
+            $params['handleUserName'] = null;
+        }
+        $taskKey = Cache::setAsyncParams($params);
+        $data = [
+            'data' => [
+                [
+                    'a' => $path,
+                    'p' => [$taskKey],
+                ]
+            ],
+        ];
+        if($this->async(Json::encode($data), $settings)){
+            return $taskKey;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 异步任务结束后给页面会员推送成功或者失败消息
+     * @param $userId
+     * @param string $message
+     * @param bool $success
+     * @param string $type message|progress
+     */
+    public function pushAsyncResultToAdmin($userId, $message='', $success = true, $type='message'){
+        if($userId){
+            $fd = Cache::getWebSocketFd(Cache::SOCKET_ADMIN, $userId);
+            if($fd){
+                try{
+                    $this->pushMsgByCli($fd, ['success'=>$success, 'handle'=>self::HANDLE_ADMIN_ASYNC, 'message'=>$message, 'type'=>$type]);
+                } catch (\Exception $e){
+                }
+
+            }
+        }
+    }
+
+    /**
+     * 向管理员发送异步进度百分比
+     * @param $percent
+     * @param array $other
+     */
+    public function pushAsyncPercentToAdmin($percent, $other=[]){
+        try{
+            $this->pushMsgAllByCli(['handle'=>self::HANDLE_ADMIN_ASYNC_PERCENT, 'percent'=>$percent, 'other'=>$other]);
+        } catch (\Exception $e){
+        }
+    }
+
+    /**
+     * 给前台会员发送webSocket消息
+     * @param null $userId
+     * @param string $message
+     * @param bool $success
+     * @return bool
+     * @throws \Exception
+     */
+    public function pushMsgToUser($userId=null, $message='', $success = true){
+        if($userId){
+            $return = true;
+            $fdAppFd = Cache::getWebSocketFd(Cache::SOCKET_USER_APP, $userId);
+            if($fdAppFd && $return !== false){
+                $return = $this->pushMsg($fdAppFd, ['success'=>$success, 'handle'=>self::HANDLE_USER_PULL_MESSAGE, 'message'=>$message]);
+            }
+            $fdPcFd = Cache::getWebSocketFd(Cache::SOCKET_USER_PC, $userId);
+            if($fdPcFd && $return !== false){
+                $return = $this->pushMsg($fdPcFd, ['success'=>$success, 'handle'=>self::HANDLE_USER_PULL_MESSAGE, 'message'=>$message]);
+            }
+            return $return;
+        } else {
+            return $this->pushMsgAll(['success'=>$success, 'handle'=>self::HANDLE_USER_PULL_MESSAGE, 'message'=>$message]);
+        }
+    }
+
+    /**
+     * 任务运行后
+     * @param $server
+     * @param $workerId
+     * @param $action
+     * @param $params
+     */
+    public function onTaskRunActionStart($server, $workerId, $action, $params){
+        // 为了保证任务继续执行,此处将错误捕捉到不影响任务继续执行
+        try {
+            if(isset($params[0]) && $actionParams = Cache::getAsyncParamsWithoutDel($params[0])){
+                // 记录日志
+                if(strpos($action, 'log/') === false){
+                    $logApiSystem = new AsyncSystem();
+                    $logApiSystem->setRequestRoute($action)->setRequestContent($actionParams)->saveByConsole([
+                        'apiName' => '异步'.$action,
+                        'optUser' => isset($actionParams['handleUserName']) ? $actionParams['handleUserName'] : null,
+                    ]);
+                }
+            }
+        } catch (\Exception $e) {
+            // 忽略错误
+        }
+    }
+
+    /**
+     * 任务运行发生错误时
+     * @param $fd
+     * @param $data
+     * @param $action
+     * @param $params
+     * @param $errorMessage
+     */
+    public function onTaskRunActionError($fd, $data, $action, $params, $errorMessage){
+        // 为了保证任务继续执行,此处将错误捕捉到不影响任务继续执行
+        try {
+            // 记录日志
+            $logApiSystem = new AsyncSystem();
+            $logApiSystem->setRequestRoute($action)->setResponseContent(['errorMessage' => $errorMessage])->saveByConsole([
+                'apiName' => '异步错误'.$action,
+            ]);
+        } catch (\Exception $e) {
+            // 忽略错误
+        }
+    }
+}

+ 5 - 0
common/config/.gitignore

@@ -0,0 +1,5 @@
+main-local.php
+params-local.php
+test-local.php
+config-production.php
+config-development.php

+ 11 - 0
common/config/bootstrap.php

@@ -0,0 +1,11 @@
+<?php
+//路径分隔符
+define('__DS__' ,DIRECTORY_SEPARATOR);
+
+Yii::setAlias('@common', dirname(__DIR__));
+Yii::setAlias('@frontend', dirname(dirname(__DIR__)) . '/frontend');
+Yii::setAlias('@backend', dirname(dirname(__DIR__)) . '/backend');
+Yii::setAlias('@frontendApi', dirname(dirname(__DIR__)) . '/frontendApi');
+Yii::setAlias('@backendApi', dirname(dirname(__DIR__)) . '/backendApi');
+Yii::setAlias('@shopApi', dirname(dirname(__DIR__)) . '/shopApi');
+Yii::setAlias('@console', dirname(dirname(__DIR__)) . '/console');

+ 122 - 0
common/config/config-example.php

@@ -0,0 +1,122 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/2/23
+ * Time: 下午3:53
+ */
+return [
+    // 奖金结算系统主库
+    'db' => [
+        // 数据库连接
+        'dsn' => 'oci:dbname=//192.168.0.253:1522/orclbonus;charset=utf8',
+        // 数据库用户名
+        'username' => 'c##bonusdb',
+        // 数据库密码
+        'password' => '123456',
+        // 数据库字符集
+        'charset' => 'utf8',
+        // 数据表前缀
+        'tablePrefix' => 'AR_',
+    ],
+    // 商城系统主库
+    'dbShop' => [
+        'dsn' => 'oci:dbname=//192.168.0.253:1521/orclshop;charset=utf8',
+        'username' => 'c##shopdb',
+        'password' => '123456',
+        'charset' => 'utf8',
+        'tablePrefix' => 'AR_',
+    ],
+    // 日志库
+    'dbLog' => [
+        'dsn' => 'mongodb://@192.168.0.253:27017/anran_bonus_log',
+        'options' => [
+            "username" => "anran_bonus_log_user",
+            "password" => "123456"
+        ],
+    ],
+    // 奖金结算专属库(为不占用主库资源,不影响会员正常业务,该库专门用于奖金结算,可单独架设,需与主库做主从同步)
+    'dbCalc' => [
+        'dsn' => 'oci:dbname=//192.168.0.253:1522/orclbonus;charset=utf8',
+        'username' => 'c##bonusdb',
+        'password' => '123456',
+        'charset' => 'utf8',
+        'tablePrefix' => 'AR_',
+    ],
+    // 商城用于结算的专属从库(为不占用系统主库资源,不影响会员的正常业务,该库专门用于奖金结算时从商城从库查询报单和订货单,可单独假设,需与商城主库做主从同步)
+    'dbShopCalc' => [
+        'dsn' => 'oci:dbname=//192.168.0.253:1521/orclshop;charset=utf8',
+        'username' => 'c##shopdb',
+        'password' => '123456',
+        'charset' => 'utf8',
+        'tablePrefix' => 'AR_',
+    ],
+    // 移动点位时专用的点位备份库(可用来查看各个时期的会员点位情况)
+    'dbNetPoint' => [
+        'dsn' => 'oci:dbname=//192.168.0.253:1522/orclnet;charset=utf8',
+        'username' => 'c##netdb',
+        'password' => '123456',
+        'charset' => 'utf8',
+        'tablePrefix' => 'AR_',
+    ],
+    // 系统从库
+    'slavesDb' => [
+        'slaveConfig' => [
+            'username' => 'c##bonusslave',
+            'password' => '123456',
+            'charset' => 'utf8',
+            'tablePrefix' => 'AR_',
+            'attributes' => [
+                PDO::ATTR_TIMEOUT => 10,  // 从库超时切换间隔时间
+            ],
+        ],
+        'slaves' => [
+            ['dsn' => 'oci:dbname=//192.168.0.253:1522/orclbonusslave;charset=utf8'],
+            // 其他奖金结算系统的从库从下面继续添加配置
+        ],
+    ],
+    // Token专用 Redis 库
+    'tokenRedis' => [
+        // 服务器
+        'hostname' => 'localhost',
+        // 端口
+        'port' => 6379,
+        // 密码
+        'password' => '123456',
+        // 库索引
+        'database' => 1,
+    ],
+    // 缓存专用 Redis 库
+    'cacheRedis' => [
+        'hostname' => 'localhost',
+        'port' => 6379,
+        'password' => '123456',
+        'database' => 3,
+    ],
+    // 其他数据 Redis 库
+    'otherRedis' => [
+        'hostname' => 'localhost',
+        'port' => 6379,
+        'password' => '123456',
+        'database' => 5,
+    ],
+    // 远程静态上传服务器地址
+    'remoteUploadHost' => 'http://ar.upload.ming',
+    'idCardVerify' => [
+        'host' => 'http://121.40.238.29:20000',
+        'clientId' => '122371433052508160',
+    ],
+    // swoole异步服务器及端口
+    'swooleTimerConfig' => [
+        'host' => '127.0.0.1',
+        'port' => '9513',
+    ],
+    // swoole的RPC服务器及端口
+    'swooleRPCConfig' => [
+        'host' => '127.0.0.1',
+        'port' => '9515',
+        'ipWhiteList' => [],
+        'username' => 'test',
+        'password' => 'ttt',
+    ],
+];

+ 94 - 0
common/config/config-oci.php

@@ -0,0 +1,94 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/2/23
+ * Time: 下午3:53
+ */
+return [
+    // 奖金结算系统主库
+    'db' => [
+        // 数据库连接
+        'dsn' => 'oci:dbname=//39.104.27.110:1528/akbonus;charset=utf8',
+        // 数据库用户名
+        'username' => 'akbonus',
+        // 数据库密码
+        'password' => 'hxds_1319',
+        // 数据库字符集
+        'charset' => 'utf8',
+        // 数据表前缀
+        'tablePrefix' => 'AR_',
+    ],
+    // 日志库
+    'dbLog' => [
+        'dsn' => 'mongodb://39.104.27.110:27017/aikang_log_db',
+        'options' => [
+            "username" => "aikang_bonus_log_user",
+            "password" => "mongo@db.useron2020"
+        ],
+    ],
+    // 奖金结算专属库(为不占用主库资源,不影响会员正常业务,该库专门用于奖金结算,可单独架设,需与主库做主从同步)
+    'dbCalc' => [
+        'dsn' => 'oci:dbname=//39.104.27.110:1528/akbonus;charset=utf8',
+        'username' => 'akbonus',
+        'password' => 'hxds_1319',
+        'charset' => 'utf8',
+        'tablePrefix' => 'AR_',
+    ],
+    // 移动点位时专用的点位备份库(可用来查看各个时期的会员点位情况)
+    'dbNetPoint' => [
+        'dsn' => 'oci:dbname=//39.104.27.110:1528/akbonus;charset=utf8',
+        'username' => 'akbonus',
+        'password' => 'hxds_1319',
+        'charset' => 'utf8',
+        'tablePrefix' => 'AR_',
+    ],
+    // 系统从库
+    'slavesDb' => [
+        'slaveConfig' => [
+            'username' => 'akbonus',
+            'password' => 'hxds_1319',
+            'charset' => 'utf8',
+            'tablePrefix' => 'AR_',
+            'attributes' => [
+                PDO::ATTR_TIMEOUT => 10,  // 从库超时切换间隔时间
+            ],
+        ],
+        'slaves' => [
+            ['dsn' => 'oci:dbname=//39.104.27.110:1528/akbonus;charset=utf8'],
+            // 其他奖金结算系统的从库从下面继续添加配置
+        ],
+    ],
+    // Token专用 Redis 库
+    'tokenRedis' => [
+        // 服务器
+        'hostname' => '39.104.27.110',
+        // 端口
+        'port' => 6379,
+        // 密码
+        'password' => 'hxds_1319',
+        // 库索引
+        'database' => 13,
+    ],
+    // 缓存专用 Redis 库
+    'cacheRedis' => [
+        'hostname' => '39.104.27.110',
+        'port' => 6379,
+        'password' => 'hxds_1319',
+        'database' => 14,
+    ],
+    // 其他数据 Redis 库
+    'otherRedis' => [
+        'hostname' => '39.104.27.110',
+        'port' => 6379,
+        'password' => 'hxds_1319',
+        'database' => 15,
+    ],
+    // 远程静态上传服务器地址 @todo 明哥提供
+    'remoteUploadHost' => 'http://ar.upload.ming',
+    // swoole异步服务器及端口
+    'swooleTimerConfig' => [
+        'host' => '127.0.0.1',
+        'port' => '9513',
+    ],
+];

+ 117 - 0
common/config/config-product.php

@@ -0,0 +1,117 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/2/23
+ * Time: 下午3:53
+ */
+return [
+    'preparePerfLimit' => true, // 预计算月业绩,测试环境周一到周日都能看,正式环境只能周一看true为周一到周日都能看
+    // 奖金结算系统主库
+    'db' => [
+        // 数据库连接
+        'dsn' => 'mysql:host=127.0.0.1;dbname=CALC_SERVE',
+        // 数据库用户名
+        'username' => 'root',
+        // 数据库密码
+        'password' => 'mypass',
+        // 数据库字符集
+        'charset' => 'utf8',
+        // 数据表前缀
+        'tablePrefix' => 'AR_',
+    ],
+    // 日志库
+    'dbLog' => [
+        'dsn' => 'mongodb://localhost:27017/aikang_log_db',
+        'options' => [
+            "username" => "aikang_bonus_log_user",
+            "password" => "mongo@db.useron2020"
+        ],
+    ],
+    // 奖金结算专属库(为不占用主库资源,不影响会员正常业务,该库专门用于奖金结算,可单独架设,需与主库做主从同步)
+    'dbCalc' => [
+        'dsn' => 'mysql:host=127.0.0.1;dbname=CALC_SERVE',
+        'username' => 'root',
+        'password' => 'mypass',
+        'charset' => 'utf8',
+        'tablePrefix' => 'AR_',
+    ],
+    // 移动点位时专用的点位备份库(可用来查看各个时期的会员点位情况)
+    'dbNetPoint' => [
+        'dsn' => 'mysql:host=127.0.0.1;dbname=CALC_SERVE',
+        'username' => 'root',
+        'password' => 'mypass',
+        'charset' => 'utf8',
+        'tablePrefix' => 'AR_',
+    ],
+    // 系统从库
+    'slavesDb' => [
+        'slaveConfig' => [
+            'username' => 'root',
+            'password' => 'mypass',
+            'charset' => 'utf8',
+            'tablePrefix' => 'AR_',
+            'attributes' => [
+                PDO::ATTR_TIMEOUT => 10,  // 从库超时切换间隔时间
+            ],
+        ],
+        'slaves' => [
+            ['dsn' => 'mysql:host=127.0.0.1;dbname=CALC_SERVE'],
+            // 其他奖金结算系统的从库从下面继续添加配置
+        ],
+    ],
+    // Token专用 Redis 库
+    'tokenRedis' => [
+        // 服务器
+        'hostname' => '127.0.0.1',
+        //'username' => '',
+        // 端口
+        'port' => 6379,
+        // 密码
+        //'password' => '',
+        // 库索引
+        'database' => 0,
+    ],
+    // 缓存专用 Redis 库
+    'cacheRedis' => [
+        'hostname' => '127.0.0.1',
+        //'username' => '',
+        'port' => 6379,
+        //'password' => '',
+        'database' => 0,
+    ],
+    // 其他数据 Redis 库
+    'otherRedis' => [
+        'hostname' => '127.0.0.1',
+        //'username' => '',
+        'port' => 6379,
+        //'password' => '',
+        'database' => 0,
+    ],
+    // 远程静态上传服务器地址 @todo 明哥提供
+    //'remoteUploadHost' => 'http://ar.upload.ming',
+    'remoteUploadHost' => 'http://localhost:9017',
+    // 远程静态上传服务器地址
+    // 'idCardVerify' => [
+    //     'host' => 'http://121.40.238.29:20000',
+    //     'clientId' => '122371433052508160',
+    // ],
+    'idCardVerify' => [
+        'host' => 'https://pay.lingyonggong.cn',
+        'clientId' => '482924578939613184',
+    ],
+    // swoole异步服务器及端口
+    'swooleTimerConfig' => [
+        'host' => '127.0.0.1',
+        'port' => '9513',
+    ],
+    'swooleClientConfig' => [
+        'host' => '127.0.0.1',
+        'port' => '9513',
+    ],
+    // swoole奖金服务器及端口
+    'swooleBonusClientConfig' => [
+        'host' => '127.0.0.1',
+        'port' => '9513',
+    ],
+];

+ 103 - 0
common/config/config-test.php

@@ -0,0 +1,103 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/2/23
+ * Time: 下午3:53
+ */
+return [
+    // 奖金结算系统主库
+    'db' => [
+        // 数据库连接
+        'dsn' => 'mysql:host=localhost;dbname=aikang_test',
+        // 数据库用户名
+        'username' => 'root',
+        // 数据库密码
+        'password' => '123456',
+        // 数据库字符集
+        'charset' => 'utf8',
+        // 数据表前缀
+        'tablePrefix' => 'AR_',
+    ],
+    // 日志库
+    'dbLog' => [
+        'dsn' => 'mongodb://39.104.27.110:27017/aikang_log_db',
+        'options' => [
+            "username" => "aikang_bonus_log_user",
+            "password" => "mongo@db.useron2020"
+        ],
+    ],
+    // 奖金结算专属库(为不占用主库资源,不影响会员正常业务,该库专门用于奖金结算,可单独架设,需与主库做主从同步)
+    'dbCalc' => [
+        'dsn' => 'mysql:host=localhost;dbname=aikang_test',
+        'username' => 'root',
+        'password' => '123456',
+        'charset' => 'utf8',
+        'tablePrefix' => 'AR_',
+    ],
+    // 移动点位时专用的点位备份库(可用来查看各个时期的会员点位情况)
+    'dbNetPoint' => [
+        'dsn' => 'mysql:host=localhost;dbname=aikang_test',
+        'username' => 'root',
+        'password' => '123456',
+        'charset' => 'utf8',
+        'tablePrefix' => 'AR_',
+    ],
+    // 系统从库
+    'slavesDb' => [
+        'slaveConfig' => [
+            'username' => 'root',
+            'password' => '123456',
+            'charset' => 'utf8',
+            'tablePrefix' => 'AR_',
+            'attributes' => [
+                PDO::ATTR_TIMEOUT => 10,  // 从库超时切换间隔时间
+            ],
+        ],
+        'slaves' => [
+            ['dsn' => 'mysql:host=localhost;dbname=aikang_test'],
+            // 其他奖金结算系统的从库从下面继续添加配置
+        ],
+    ],
+    // Token专用 Redis 库
+    'tokenRedis' => [
+        // 服务器
+        'hostname' => '39.104.27.110',
+        // 端口
+        'port' => 6379,
+        // 密码
+        'password' => 'hxds_1319',
+        // 库索引
+        'database' => 13,
+    ],
+    // 缓存专用 Redis 库
+    'cacheRedis' => [
+        'hostname' => '39.104.27.110',
+        'port' => 6379,
+        'password' => 'hxds_1319',
+        'database' => 14,
+    ],
+    // 其他数据 Redis 库
+    'otherRedis' => [
+        'hostname' => '39.104.27.110',
+        'port' => 6379,
+        'password' => 'hxds_1319',
+        'database' => 15,
+    ],
+    // 远程静态上传服务器地址 @todo 明哥提供
+    'remoteUploadHost' => 'http://ar.upload.ming',
+    // swoole异步服务器及端口
+    'swooleTimerConfig' => [
+        'host' => '127.0.0.1',
+        'port' => '9513',
+    ],
+    'swooleClientConfig' => [
+        'host' => '127.0.0.1',
+        'port' => '9513',
+    ],
+    // swoole奖金服务器及端口
+    'swooleBonusClientConfig' => [
+        'host' => '127.0.0.1',
+        'port' => '9513',
+    ],
+];

+ 107 - 0
common/config/config.php

@@ -0,0 +1,107 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/2/23
+ * Time: 下午3:53
+ */
+return [
+    // 奖金结算系统主库
+    'db' => [
+        // 数据库连接
+        'dsn' => 'mysql:host=127.0.0.1;dbname=CALC_SERVE',
+        // 数据库用户名
+        'username' => 'root',
+        // 数据库密码
+        'password' => 'mypass',
+        // 数据库字符集
+        'charset' => 'utf8',
+        // 数据表前缀
+        'tablePrefix' => 'AR_',
+    ],
+    // 日志库
+    'dbLog' => [
+        'dsn' => 'mongodb://localhost:27017/aikang_log_db',
+        'options' => [
+            "username" => "aikang_bonus_log_user",
+            "password" => "mongo@db.useron2020"
+        ],
+    ],
+    // 奖金结算专属库(为不占用主库资源,不影响会员正常业务,该库专门用于奖金结算,可单独架设,需与主库做主从同步)
+    'dbCalc' => [
+        'dsn' => 'mysql:host=127.0.0.1;dbname=CALC_SERVE',
+        'username' => 'root',
+        'password' => 'mypass',
+        'charset' => 'utf8',
+        'tablePrefix' => 'AR_',
+    ],
+    // 移动点位时专用的点位备份库(可用来查看各个时期的会员点位情况)
+    'dbNetPoint' => [
+        'dsn' => 'mysql:host=127.0.0.1;dbname=CALC_SERVE',
+        'username' => 'root',
+        'password' => 'mypass',
+        'charset' => 'utf8',
+        'tablePrefix' => 'AR_',
+    ],
+    // 系统从库
+    'slavesDb' => [
+        'slaveConfig' => [
+            'username' => 'root',
+            'password' => 'mypass',
+            'charset' => 'utf8',
+            'tablePrefix' => 'AR_',
+            'attributes' => [
+                PDO::ATTR_TIMEOUT => 10,  // 从库超时切换间隔时间
+            ],
+        ],
+        'slaves' => [
+            ['dsn' => 'mysql:host=127.0.0.1;dbname=CALC_SERVE'],
+            // 其他奖金结算系统的从库从下面继续添加配置
+        ],
+    ],
+    // Token专用 Redis 库
+    'tokenRedis' => [
+        // 服务器
+        'hostname' => '127.0.0.1',
+        //'username' => '',
+        // 端口
+        'port' => 6379,
+        // 密码
+        //'password' => '',
+        // 库索引
+        'database' => 0,
+    ],
+    // 缓存专用 Redis 库
+    'cacheRedis' => [
+        'hostname' => '127.0.0.1',
+        //'username' => '',
+        'port' => 6379,
+        //'password' => '',
+        'database' => 0,
+    ],
+    // 其他数据 Redis 库
+    'otherRedis' => [
+        'hostname' => '127.0.0.1',
+        //'username' => '',
+        'port' => 6379,
+        //'password' => '',
+        'database' => 0,
+    ],
+    // 远程静态上传服务器地址 @todo 明哥提供
+    //'remoteUploadHost' => 'http://ar.upload.ming',
+    'remoteUploadHost' => 'http://localhost:9017',
+    // swoole异步服务器及端口
+    'swooleTimerConfig' => [
+        'host' => '127.0.0.1',
+        'port' => '9513',
+    ],
+    'swooleClientConfig' => [
+        'host' => '127.0.0.1',
+        'port' => '9513',
+    ],
+    // swoole奖金服务器及端口
+    'swooleBonusClientConfig' => [
+        'host' => '127.0.0.1',
+        'port' => '9513',
+    ],
+];

+ 89 - 0
common/config/main.php

@@ -0,0 +1,89 @@
+<?php
+
+// 根据运行环境加载不同的配置文件
+if (YII_ENV_DEV) {
+    $mainConfig = require_once __DIR__ . '/config-development.php';
+} else if (YII_ENV_TEST) {
+    $mainConfig = require_once __DIR__ . '/config-test.php';
+} else if (YII_ENV_PROD) {
+    $mainConfig = require_once __DIR__ . '/config-product.php';
+}
+
+return [
+    'language' => 'en',
+    'timeZone' => 'Africa/Lagos',
+    'aliases' => [
+        '@bower' => '@vendor/bower-asset',
+        '@npm'   => '@vendor/npm-asset',
+//        'anlity/swooleAsyncTimer' => '@backend/runtime/tmp-extensions/yii2-swoole-async-timer',
+    ],
+    'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
+    'components' => [
+        'db' => array_merge([
+            'class' => 'yii\db\Connection',
+            'enableSlaves' => false,
+//            'enableSavepoint' => false,
+        ], $mainConfig['db'], $mainConfig['slavesDb']),
+//        'dbShop' => array_merge(['class' => 'yii\db\Connection', 'schemaMap' => ['oci' => 'common\helpers\Schema']], $mainConfig['dbShop']),
+        'dbLog' => array_merge(['class' => 'yii\mongodb\Connection',], $mainConfig['dbLog']),
+        'dbCalc' => array_merge(['class' => 'yii\db\Connection'], $mainConfig['dbCalc']),
+//        'dbShopCalc' => array_merge(['class' => 'yii\db\Connection', 'schemaMap' => ['oci' => 'common\helpers\Schema'],], $mainConfig['dbShopCalc']),
+        'dbNetPoint' => array_merge(['class' => 'yii\db\Connection'], $mainConfig['dbNetPoint']),
+        // 'cache' => [
+        //     //'class' => 'yii\caching\FileCache',
+        //     'class' => 'dcb9\redis\Cache',
+        //     'redis' => $mainConfig['cacheRedis'],
+        // ],
+        'cache' => [
+            'class' => 'yii\redis\Cache',
+            'redis' => $mainConfig['cacheRedis'],
+            // 'redis' => [
+            //     'hostname' => '127.0.0.1',
+            //     'port' => 6379,
+            //     'database' => 0,
+            // ]
+        ],
+        'tokenRedis' => array_merge(['class' => 'yii\redis\Connection'], $mainConfig['tokenRedis']),
+        'redis' => array_merge(['class' => 'yii\redis\Connection'], $mainConfig['otherRedis']),
+        // 'tokenRedis' => array_merge(['class' => 'common\components\Redis'], $mainConfig['tokenRedis']),
+        // 'redis' => array_merge(['class' => 'common\components\Redis'], $mainConfig['otherRedis']),
+        'mailer' => [
+            'class' => 'yii\swiftmailer\Mailer',
+            'viewPath' => '@common/mail',
+            // send all mails to a file by default. You have to set
+            // 'useFileTransport' to false and configure a transport
+            // for the mailer to send real emails.
+            'useFileTransport' => true,
+        ],
+        'swooleAsyncTimer' => [
+            'class' => 'common\components\SwooleAsyncTimer',
+        ],
+        // payStack配置
+        'Paystack' => [
+            'class'         => 'smladeoye\paystack\Paystack',
+            'environment'   => YII_ENV,
+            'testPublicKey' => 'pk_test_2eed10135c4a958c5073795b22854ded9d1a6c55',
+            'testSecretKey' => 'sk_test_5ece72377432376f5cf6bb5c468395a650220309',
+            'livePublicKey' => 'pk_live_fae524f9d073d877beeb661fd825a37a9bc91f0a',
+            'liveSecretKey' => 'sk_live_b93c6bbee2cc0e9c594547bf4779ec9852b9d055',
+        ],
+        // 配置SQL语句输出
+        'log' => [
+            'traceLevel' => YII_DEBUG ? 3 : 0,
+            'targets' => [
+                [
+                    'class' => 'yii\log\FileTarget',
+                    'levels' => ['error', 'warning', 'info'],
+                    'categories' => ['yii\db\*', 'app\models\*'],
+                    'logFile' => '@runtime/logs/sql.log',
+                ],
+                'db' => [ 'class' => 'yii\log\FileTarget'],
+            ],
+        ],
+    ],
+    'controllerMap' => [
+        'swoole_server' => [
+            'class' => 'anlity\swooleAsyncTimer\SwooleAsyncTimerController',
+        ],
+    ],
+];

+ 283 - 0
common/config/params-nation.php

@@ -0,0 +1,283 @@
+<?php
+return array (
+    1 =>
+        array (
+            'id' => '1',
+            'name' => '汉族',
+        ),
+    2 =>
+        array (
+            'id' => '2',
+            'name' => '蒙古族',
+        ),
+    3 =>
+        array (
+            'id' => '3',
+            'name' => '回族',
+        ),
+    4 =>
+        array (
+            'id' => '4',
+            'name' => '藏族',
+        ),
+    5 =>
+        array (
+            'id' => '5',
+            'name' => '维吾尔族',
+        ),
+    6 =>
+        array (
+            'id' => '6',
+            'name' => '苗族',
+        ),
+    7 =>
+        array (
+            'id' => '7',
+            'name' => '彝族',
+        ),
+    8 =>
+        array (
+            'id' => '8',
+            'name' => '壮族',
+        ),
+    9 =>
+        array (
+            'id' => '9',
+            'name' => '布依族',
+        ),
+    10 =>
+        array (
+            'id' => '10',
+            'name' => '朝鲜族',
+        ),
+    11 =>
+        array (
+            'id' => '11',
+            'name' => '满族',
+        ),
+    12 =>
+        array (
+            'id' => '12',
+            'name' => '侗族',
+        ),
+    13 =>
+        array (
+            'id' => '13',
+            'name' => '瑶族',
+        ),
+    14 =>
+        array (
+            'id' => '14',
+            'name' => '白族',
+        ),
+    15 =>
+        array (
+            'id' => '15',
+            'name' => '土家族',
+        ),
+    16 =>
+        array (
+            'id' => '16',
+            'name' => '哈尼族',
+        ),
+    17 =>
+        array (
+            'id' => '17',
+            'name' => '哈萨克族',
+        ),
+    18 =>
+        array (
+            'id' => '18',
+            'name' => '傣族',
+        ),
+    19 =>
+        array (
+            'id' => '19',
+            'name' => '黎族',
+        ),
+    20 =>
+        array (
+            'id' => '20',
+            'name' => '傈僳族',
+        ),
+    21 =>
+        array (
+            'id' => '21',
+            'name' => '佤族',
+        ),
+    22 =>
+        array (
+            'id' => '22',
+            'name' => '畲族',
+        ),
+    23 =>
+        array (
+            'id' => '23',
+            'name' => '高山族',
+        ),
+    24 =>
+        array (
+            'id' => '24',
+            'name' => '拉祜族',
+        ),
+    25 =>
+        array (
+            'id' => '25',
+            'name' => '水族',
+        ),
+    26 =>
+        array (
+            'id' => '26',
+            'name' => '东乡族',
+        ),
+    27 =>
+        array (
+            'id' => '27',
+            'name' => '纳西族',
+        ),
+    28 =>
+        array (
+            'id' => '28',
+            'name' => '景颇族',
+        ),
+    29 =>
+        array (
+            'id' => '29',
+            'name' => '柯尔克孜族',
+        ),
+    30 =>
+        array (
+            'id' => '30',
+            'name' => '土族',
+        ),
+    31 =>
+        array (
+            'id' => '31',
+            'name' => '达斡尔族',
+        ),
+    32 =>
+        array (
+            'id' => '32',
+            'name' => '仫佬族',
+        ),
+    33 =>
+        array (
+            'id' => '33',
+            'name' => '羌族',
+        ),
+    34 =>
+        array (
+            'id' => '34',
+            'name' => '布朗族',
+        ),
+    35 =>
+        array (
+            'id' => '35',
+            'name' => '撒拉族',
+        ),
+    36 =>
+        array (
+            'id' => '36',
+            'name' => '毛难族',
+        ),
+    37 =>
+        array (
+            'id' => '37',
+            'name' => '仡佬族',
+        ),
+    38 =>
+        array (
+            'id' => '38',
+            'name' => '锡伯族',
+        ),
+    39 =>
+        array (
+            'id' => '39',
+            'name' => '阿昌族',
+        ),
+    40 =>
+        array (
+            'id' => '40',
+            'name' => '普米族',
+        ),
+    41 =>
+        array (
+            'id' => '41',
+            'name' => '塔吉克族',
+        ),
+    42 =>
+        array (
+            'id' => '42',
+            'name' => '怒族',
+        ),
+    43 =>
+        array (
+            'id' => '43',
+            'name' => '乌孜别克族',
+        ),
+    44 =>
+        array (
+            'id' => '44',
+            'name' => '俄罗斯族',
+        ),
+    45 =>
+        array (
+            'id' => '45',
+            'name' => '鄂温克族',
+        ),
+    46 =>
+        array (
+            'id' => '46',
+            'name' => '崩龙族',
+        ),
+    47 =>
+        array (
+            'id' => '47',
+            'name' => '保安族',
+        ),
+    48 =>
+        array (
+            'id' => '48',
+            'name' => '裕固族',
+        ),
+    49 =>
+        array (
+            'id' => '49',
+            'name' => '京族',
+        ),
+    50 =>
+        array (
+            'id' => '50',
+            'name' => '塔塔尔族',
+        ),
+    51 =>
+        array (
+            'id' => '51',
+            'name' => '独龙族',
+        ),
+    52 =>
+        array (
+            'id' => '52',
+            'name' => '鄂伦春族',
+        ),
+    53 =>
+        array (
+            'id' => '53',
+            'name' => '赫哲族',
+        ),
+    54 =>
+        array (
+            'id' => '54',
+            'name' => '门巴族',
+        ),
+    55 =>
+        array (
+            'id' => '55',
+            'name' => '珞巴族',
+        ),
+    56 =>
+        array (
+            'id' => '56',
+            'name' => '基诺族',
+        ),
+);

+ 31 - 0
common/config/params-rpc.php

@@ -0,0 +1,31 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/3/9
+ * Time: 上午9:33
+ */
+return [
+    'swooleRPC' => [
+        'host'             => $mainConfig['swooleRPCConfig']['host'], 		//服务启动IP
+        'port'             => $mainConfig['swooleRPCConfig']['port'],      		//服务启动端口
+        'process_name'     => 'swooleRPCServerBonus',	//服务进程名
+        'open_tcp_nodelay' => '1',         		//启用open_tcp_nodelay
+        'daemonize'        => '1',				//守护进程化
+        'worker_num'       => '2',				//work进程数目
+        'task_worker_num'  => '2',				//task进程的数量
+        'task_max_request' => '10000',			//work进程最大处理的请求数
+        'task_tmpdir'      => dirname(__DIR__).'/runtime/task',		 //设置task的数据临时目录
+        'log_file'         => dirname(__DIR__).'/runtime/logs/swooleRPC.log', //指定swoole错误日志文件
+        'client_timeout'   => '20',				//client链接服务器时超时时间(s)
+        'pidfile'          => dirname(__DIR__).'/runtime/swoolePid/swooleBonusRPC.pid', 	//服务启动进程id文件保存位置
+
+        //--以上配置项均来自swoole-server的同名配置,可随意参考swoole-server配置说明自主增删--
+        'debug'            => true,             //是否开启调试模式
+        'log_size'         => 204800000, 		//运行时日志 单个文件大小
+        'log_dir'          => dirname(__DIR__).'/runtime/logs',			 //运行时日志 存放目录
+        'ipWhiteList'      => $mainConfig['swooleRPCConfig']['ipWhiteList'],
+        'username'      => $mainConfig['swooleRPCConfig']['username'],
+        'password'      => $mainConfig['swooleRPCConfig']['password'],
+    ]
+];

+ 40 - 0
common/config/params-swoole.php

@@ -0,0 +1,40 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/3/9
+ * Time: 上午9:33
+ */
+return [
+    'swooleAsyncTimer' => [
+        'host'             => $mainConfig['swooleClientConfig']['host'], 		//服务启动IP
+        'port'             => $mainConfig['swooleClientConfig']['port'],      		//服务启动端口
+        'serverHost'             => $mainConfig['swooleTimerConfig']['host'], 		//服务启动IP
+        'serverPort'             => $mainConfig['swooleTimerConfig']['port'],      		//服务启动端口
+        'process_name'     => 'akBonusSwooleServer',	//服务进程名
+        'open_tcp_nodelay' => '1',         		//启用open_tcp_nodelay
+        'daemonize'        => '1',				//守护进程化
+        'worker_num'       => '2',				//work进程数目
+        'task_worker_num'  => '2',				//task进程的数量
+        'task_max_request' => '10000',			//work进程最大处理的请求数
+        'task_enable_coroutine' => true,        // task支持协程
+        'task_tmpdir'      => dirname(__DIR__).'/runtime/task',		 //设置task的数据临时目录
+        'log_file'         => dirname(__DIR__).'/runtime/logs/swooleHttp.log', //指定swoole错误日志文件
+        'client_timeout'   => '20',				//client链接服务器时超时时间(s)
+        'pidfile'          => dirname(__DIR__).'/runtime/swoolePid/swooleBonus.pid', 	//服务启动进程id文件保存位置
+
+        //--以上配置项均来自swoole-server的同名配置,可随意参考swoole-server配置说明自主增删--
+        'sender_client'    => 'swoole',         //请求服务端的客户端方式(swoole|curl)
+        'auth_key'         => '4m8s4Pjv9xroqo6D', //授权密钥
+        'max_time_diff'    => 0,              //请求服务端允许的最大时间差
+        'debug'            => true,             //是否开启调试模式
+        'with_timer'       => true,             //是否使用定时器
+        'timer_interval'   => 5000,             //定时器时间间隔
+        'log_size'         => 204800000, 		//运行时日志 单个文件大小
+        'log_dir'          => dirname(__DIR__).'/runtime/logs',			 //运行时日志 存放目录
+    ],
+    'swooleBonusConfig' => [
+        'host'             => $mainConfig['swooleBonusClientConfig']['host'], 		//IP
+        'port'             => $mainConfig['swooleBonusClientConfig']['port'],      		//端口
+    ],
+];

+ 355 - 0
common/config/params.php

@@ -0,0 +1,355 @@
+<?php
+$nationParams = require_once __DIR__ . '/params-nation.php';
+return [
+    'adminEmail' => 'admin@example.com',
+    'supportEmail' => 'support@example.com',
+    'nation' => $nationParams,
+    'backAccessTokenExpiresIn' => 3000 * 60,
+    'backRefreshTokenExpiresIn' => 3000 * 60 * 60,
+    'frontAccessTokenExpiresIn' => 3000 * 60,
+    'frontRefreshTokenExpiresIn' => 3000 * 60 * 60,
+    'user.passwordResetTokenExpire' => 3600,
+    'operationTimeOut' => 60 * 60 * 24,     // 这里设置的15分钟超时
+    'pageSize' => 20,
+    'http' => [
+        'shopApi' => [
+            'authKey' => 'sErypUtORfuloNdiTYLINArdtHErSAnDkiRPRElTaiNgIneUSEardopEnTAcErNO',
+            'timeDiff' => 0,
+        ],
+        'remoteUploadApi' => [
+            'authKey' => 'k4Ao7KWVbvg3Z2L6KLwN9OoDjQL5SioJffIPoODATxCynuEVEAt0278kg7r9FHiS',
+            'host' => $mainConfig['remoteUploadHost'],
+            'remoteUploadNotifyUrl' => '',
+        ],
+        'backendToFrontendApi' => [
+            'authKey' => 'hYkDsjhj6CfGMKbRMjoKtMauMEHiufyikGmziqy2anXYTMXhsUkrPC6YbvyHmdLD',
+            'timeDiff' => 0,
+        ],
+        'lingYunGongApi' => [
+            'host' => $mainConfig['idCardVerify']['host'],
+            'authToken' => [
+                'path' => '/uaa/v1/auth/tokens',
+                'clientId' => $mainConfig['idCardVerify']['clientId'],
+            ],
+            'hasIdCardInfoPath' => '/socialwork/v1/external/freedom-emps/has-idcard-info/%s',
+        ]
+//        'requestShop' => [
+//            'url' => $mainConfig['requestShopUrl'],
+//            'username' => 'bonus_api',
+//            'password' => 'bonus_api',
+//        ]
+
+    ],
+    'shopWalletType' => [
+        'cash' =>[
+            'value'=>'cash',
+            'title'=>'Cash wallet',//现金钱包
+        ],
+    ],
+    'shopFlowType' => [
+        'remit' => [
+            'value' => 'remit',
+            'label' => 'Remittance recharge',//汇款充值
+        ],
+        'recharge' => [
+            'value' => 'recharge',
+            'label' => 'Rhird-party recharge',//第三方充值
+        ],
+        'transfer_out' => [
+            'value' => 'transfer_out',
+            'label' => 'Transfer out',//转出
+        ],
+        'transfer_in' => [
+            'value' => 'transfer_in',
+            'label' => 'Rransfer into',//转入
+        ],
+        'pay' => [
+            'value' => 'pay',
+            'label' => 'Pay',//支付
+        ],
+        'decRecharge' => [
+            'value' => 'decRecharge',
+            'label' => 'Welcome pack recharge',//报单充入
+        ],
+        'orderRecharge' => [
+            'value' => 'orderRecharge',
+            'label' => 'Order recharge',//订货充入
+        ],
+        'freight' => [
+            'value' => 'freight',
+            'label' => 'Freight payment',//支付运费
+        ],
+        'adjust_recharge' => [
+            'value' => 'adjust_recharge',
+            'label' => 'Adjusted recharge',//待调整充入
+        ],
+        'adjust_deduct' => [
+            'value' => 'adjust_deduct',
+            'label' => 'Adjusted deduct',//待调整扣除
+        ],
+        'adjust_transfer_out' => [
+            'value' => 'adjust_transfer_out',
+            'label' => 'Adjusted transfer out',//待调整转出
+        ],
+        'adjust_transfer_in' => [
+            'value' => 'adjust_transfer_in',
+            'label' => 'Adjusted transfer in',//待调整转入
+        ],
+        'exchange_recharge' => [
+            'value' => 'exchange_recharge',
+            'label' => 'Exchange recharge',//换货充入
+        ],
+        'exchange_deduct' => [
+            'value' => 'exchange_deduct',
+            'label' => 'Exchange deduct',//换货扣除
+        ],
+        'delete_order_deduct' => [
+            'value' => 'delete_order_deduct',
+            'label' => 'Delete order deduct',//删单自动扣除
+        ],
+        'delete_order_recharge' => [
+            'value' => 'delete_order_recharge',
+            'label' => 'Delete order recharge',//删单自动充入
+        ],
+        'delete_order_return' => [
+            'value' => 'delete_order_return',
+            'label' => 'Delete order return',//删单扣回
+        ],
+        'repair_pay' => [
+            'value' => 'repair_pay',
+            'label' => 'Repair payment',//支付维修费
+        ],
+        'after_sale_exchange_recharge' => [
+            'value' => 'after_sale_exchange_recharge',
+            'label' => 'After-sale exchange recharge',//售后调货充入
+        ],
+        'after_sale_exchange_deduct' => [
+            'value' => 'after_sale_exchange_deduct',
+            'label' => 'After-sale exchange deduct',//售后调货扣费
+        ],
+        'oms_deduct' => [
+            'value' => 'oms_deduct',
+            'label' => 'OMS deduct',//OMS扣款
+        ],
+        'deduct' => [
+            'value' => 'deduct',
+            'label' => 'Deduct',//扣除
+        ],
+        'incr' => [
+            'value' => 'incr',
+            'label' => 'Increase',//增加
+        ],
+        'other' => [
+            'value' => 'other',
+            'label' => 'Other',//其它
+        ],
+        'handle' => [
+            'value' => 'handle',
+            'label' => 'Administrator manual adjustment',//管理员手动调整
+        ],
+    ],
+    'enableLog' => true,
+    'isRemoteUpload' => false, // 是否上传至远程
+    'excelLocalDir' => 'excel_export', // 是否上传至远程
+    'superAdminRoleId' => '666A7F102B8D6C66E055736AECE8644D',
+    'mainAdminId' => '65F27BCE7FD278FFE055736AECE8644D',
+    'mainUserId' => '670B84FD7C216D4EE055736AECE8644D',
+    'hbCouponsProId' => '1000000000', // 货补优惠券
+    'storeDecRoleId' => '8976C62F40064D8AB357BE07C8A3CA8F', // 报单中心里面店铺的ID
+    'countyStoreDecRoleId' => '810916A06B304162902408A6B9E5D177', // 报单中心里面区级店的ID
+    'daysDiff' => 0,
+    'userStatus' => [
+        0 => [
+            'value' => '0',
+            'label' => 'Inactive',//未激活
+        ],
+        1 => [
+            'value' => '1',
+            'label' => 'Normal',//正常
+        ],
+        2 => [
+            'value' => '2',
+            'label' => 'Logout',//注销
+        ],
+        3 => [
+            'value' => '3',
+            'label' => 'Blacklist',//黑名单
+        ],
+        4 => [
+            'value' => '4',
+            'label' => 'Suspended',//停发
+        ],
+        9 => [
+            'value' => '9',
+            'label' => 'Permanent shut down',//永久关停
+        ],
+    ],
+    'bonusWalletType' => [
+        'bonus' => [
+            'name' => 'bonus',
+            'label' => 'Account bonus',//会员账户奖金
+        ],
+        'reconsume_points' => [
+            'name' => 'bonus',
+            'label' => 'Reselling points balance',//复销积分余额
+        ],
+        'cash' => [
+            'name' => 'cash',
+            'label' => 'Account Ecoin',//会员账户余额
+        ],
+        'exchange_points' => [
+            'name' => 'bonus',
+            'label' => 'Exchange points balance',//兑换积分余额
+        ],
+//        'cf' => [
+//            'name' => 'cf',
+//            'label' => '车房养老奖余额',
+//        ],
+//        'lx' => [
+//            'name' => 'lx',
+//            'label' => '领袖分红奖余额',
+//        ],
+        'tourism_points' => [
+            'name' => 'tourism_points',
+            'label' => 'Travel points balance',//旅游积分余额
+        ],
+        'garage_points' => [
+            'name' => 'garage_points',
+            'label' => 'Car points balance',//名车积分余额
+        ],
+        'villa_points' => [
+            'name' => 'villa_points',
+            'label' => 'Villa points balance',//豪宅积分余额
+        ],
+    ],
+    'auditStatus' => [
+        'un' => [
+            'value' => 0,
+            'label' => 'Unaudited'//未审核
+        ],
+        'true' => [
+            'value' => 1,
+            'label' => 'Audited'//已审核
+        ],
+        'false' => [
+            'value' => 2,
+            'label' => 'Audit failure'//审核失败
+        ],
+        'reject' => [
+            'value' => 3,
+            'label' => 'Refused'//已拒绝
+        ],
+    ],
+    // 发货状态
+    'deliveryStatus' => [
+        '0' => [
+            'value' => 0,
+            'label' => 'Undelivered'//未发货
+        ],
+        'notDelivery' => [
+            'value' => 0,
+            'label' => 'Undelivered'//未发货
+        ],
+        '1' => [
+            'value' => 1,
+            'label' => 'Delivered'//已发货
+        ],
+        'delivered' => [
+            'value' => 1,
+            'label' => 'Delivered'//已发货
+        ],
+        '2' => [
+            'value' => 2,
+            'label' => 'Confirm receipt'//确认收货
+        ],
+        'confirm' => [
+            'value' => 2,
+            'label' => 'Confirm receipt'//确认收货
+        ],
+    ],
+    'orderStatus' => [
+        '0' => [
+            'value' => 0,
+            'label' => 'Unpaid'//待支付
+        ],
+        'notPaid' => [
+            'value' => 0,
+            'label' => 'Unpaid'//待支付
+        ],
+        '1' => [
+            'value' => 1,
+            'label' => 'Paid'//已支付
+        ],
+        'paid' => [
+            'value' => 1,
+            'label' => 'Paid'//已支付
+        ],
+        // 此时进入发货流程参考发货状态
+        '3' => [
+            'value' => 3,
+            'label' => 'Logistics status'//物流状态
+        ],
+        'delivery' => [
+            'value' => 3,
+            'label' => 'Logistics status'//物流状态
+        ],
+        '4' => [
+            'value' => 4,
+            'label' => 'Order completion'//订单完成
+        ],
+        'complete' => [
+            'value' => 4,
+            'label' => 'Order completion'//订单完成
+        ],
+        '5' => [
+            'value' => 5,
+            'label' => 'Order cancellation',//订单取消
+        ],
+        'cancel' => [
+            'value' => 5,
+            'label' => 'Order cancellation',//订单取消
+        ],
+        '6' => [
+            'value' => 6,
+            'label' => 'Order deletion',//订单删除
+        ],
+        'del' => [
+            'value' => 6,
+            'label' => 'Order deletion',//订单删除
+        ],
+        '7' => [
+            'value' => 7,
+            'label' => 'Order refund',//订单退款
+        ],
+        'refund' => [
+            'value' => 7,
+            'label' => 'Order refund',//订单退款
+        ],
+        '-1' => [
+            'value' => -1,
+            'label' => 'Payment failed',//支付失败
+        ],
+        'failPaid' => [
+            'value' => -1,
+            'label' => 'Payment failed',//支付失败
+        ],
+    ],
+    'exportModule' => [
+        'shop' => ['label' => 'Mall management', 'value'=>'shop'],//商城管理
+        'user' => ['label' => 'Member management', 'value'=>'user'],//会员管理
+        'atlas' => ['label' => 'Network Chart', 'value'=>'atlas'],//网络图谱
+        'reconsume' => ['label' => 'Resale management', 'value'=>'reconsume'],//复销管理
+        'bonus' => ['label' => 'Bonus management', 'value'=>'bonus'],//奖金管理
+        //'report' => ['label' => '报表管理', 'value'=>'report'],
+        'finance' => ['label' => 'financial management', 'value'=>'finance'],//财务管理
+        //'log' => ['label' => '日志管理', 'value'=>'log'],
+    ],
+    'snowFake' => [
+        'dataCenterId' => 1,//
+        'workerId' => 1,
+    ],
+    'pageSnowFake' => [
+        'dataCenterId' => 2,//
+        'workerId' => 2,
+    ],
+];

+ 14 - 0
common/config/test.php

@@ -0,0 +1,14 @@
+<?php
+return [
+    'id' => 'app-common-tests',
+    'basePath' => dirname(__DIR__),
+    'components' => [
+        'user' => [
+            'class' => 'yii\web\User',
+            'identityClass' => 'common\models\User',
+        ],
+        'request' => [
+            'cookieValidationKey' => 'test',
+        ],
+    ],
+];

+ 9 - 0
common/fixtures/UserFixture.php

@@ -0,0 +1,9 @@
+<?php
+namespace common\fixtures;
+
+use yii\test\ActiveFixture;
+
+class UserFixture extends ActiveFixture
+{
+    public $modelClass = 'common\models\User';
+}

+ 241 - 0
common/helpers/Cache.php

@@ -0,0 +1,241 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/4/11
+ * Time: 下午1:10
+ */
+
+namespace common\helpers;
+
+use backendApi\modules\v1\models\AdminRole;
+use common\models\Config;
+use common\models\OcrApi;
+use common\models\DeclarationLevel;
+use common\models\EmployLevel;
+use common\models\Region;
+use common\models\SmsApi;
+use common\models\SmsTemplate;
+use common\models\StarCrownLevel;
+use common\models\User;
+use common\models\UserNetwork;
+use common\models\UserRelation;
+use common\models\WithdrawLevel;
+use Yii;
+use backendApi\modules\v1\models\Admin;
+use common\models\UserInfo;
+
+class Cache
+{
+    const SYSTEM_CONFIG_KEY = 'sys:config';
+    const DEC_LEVEL_CONFIG_KEY = 'sys:decLevel';
+    const DEC_ROLE_CONFIG_KEY = 'sys:decRole';
+    const EMP_LEVEL_CONFIG_KEY = 'sys:empLevel';
+    const CROWN_LEVEL_CONFIG_KEY = 'sys:crownLevel';
+
+    const ASYNC_KEY_PREFIX = 'async_';
+    const SOCKET_KEY_PREFIX = 'socket_';
+    const SOCKET_ADMIN = 'admin';
+    const SOCKET_USER_PC = 'userPc';
+    const SOCKET_USER_APP = 'userAPP';
+
+    const USER_CLOSE_KEY_PREFIX = 'user:close_';
+    const USER_INFO_KEY = 'user:baseInfo';
+    const USER_NETWORK_PARENTS = 'user:networkParents';
+    const USER_RELATION_PARENTS = 'user:relationParents';
+    const USER_CREATED_AT_LIST = 'user:createdAtList_';
+
+    /**
+     * WebSocket的fd加入缓存
+     * @param $app
+     * @param $userId
+     * @param $fd
+     * @return mixed
+     */
+    public static function setWebSocketFd($app, $userId, $fd){
+        if(isset($userId) && $userId != ''){
+            if($app == self::SOCKET_ADMIN){
+                $model = Admin::class;
+                $field = 'ID';
+            } elseif($app == self::SOCKET_USER_PC) {
+                $model = UserInfo::class;
+                $field = 'USER_ID';
+                $app = self::SOCKET_USER_PC;
+            } else {
+                $model = UserInfo::class;
+                $field = 'USER_ID';
+                $app = self::SOCKET_USER_APP;
+            }
+
+            if($model::find()->where($field.'=:'.$field, [':'.$field=>$userId])->exists()){
+                Yii::$app->redis->hset(self::SOCKET_KEY_PREFIX.$app, $userId, $fd);
+            }
+        }
+        return $fd;
+    }
+
+    /**
+     * 获取会员的WebSocket的fd
+     * @param $app
+     * @param $userId
+     * @return mixed
+     */
+    public static function getWebSocketFd($app, $userId){
+        return Yii::$app->redis->hget(self::SOCKET_KEY_PREFIX.$app, $userId);
+    }
+
+    /**
+     * 获取系统配置信息
+     * @return array|mixed|\yii\db\ActiveRecord[]
+     */
+    public static function getSystemConfig(){
+        return Config::getFromCache();
+    }
+
+    /**
+     * 更新系统配置
+     * @return array|\yii\db\ActiveRecord[]
+     */
+    public static function updateSystemConfig(){
+        return Config::updateToCache();
+    }
+
+    /**
+     * 获取报单级别
+     * @return array|mixed|\yii\db\ActiveRecord[]
+     */
+    public static function getDecLevelConfig(){
+        return DeclarationLevel::getFromCache();
+    }
+
+    /**
+     *  更新报单级别
+     * @return array|\yii\db\ActiveRecord[]
+     */
+    public static function updateDecLevelConfig(){
+        return DeclarationLevel::updateToCache();
+    }
+
+    /**
+     * 获取地区
+     * @return array|mixed|\yii\db\ActiveRecord[]
+     */
+    public static function getRegionConfig(){
+        return Region::getFromCache();
+    }
+
+    /**
+     *  更新地区
+     * @return array|\yii\db\ActiveRecord[]
+     */
+    public static function updateRegionConfig(){
+        return Region::updateToCache();
+    }
+
+    /**
+     * 获取聘级
+     * @return array|mixed|\yii\db\ActiveRecord[]
+     */
+    public static function getEmpLevelConfig(){
+        return EmployLevel::getFromCache();
+    }
+
+    /**
+     * 获取星级
+     * @return array|mixed|\yii\db\ActiveRecord[]
+     */
+    public static function getStarCrownLevelConfig(){
+        return StarCrownLevel::getFromCache();
+    }
+
+    /**
+     *  更新聘级
+     * @return array|\yii\db\ActiveRecord[]
+     */
+    public static function updateEmpLevelConfig(){
+        return EmployLevel::updateToCache();
+    }
+
+    /**
+     * 获取用户基本信息
+     * @param $userId
+     * @return array|null|\yii\db\ActiveRecord
+     */
+    public static function getUserBaseInfo($userId){
+        return User::getBaseInfoFromRedis($userId);
+    }
+
+    /**
+     * 执行异步任务时往缓存中把要传递的参数都存起来,方便在异步方法中直接调用
+     * @param $params
+     * @return string
+     * @throws \yii\base\Exception
+     */
+    public static function setAsyncParams($params){
+        // 生成一个随机key
+        $taskKey = self::createRandomKey(self::ASYNC_KEY_PREFIX);
+        Yii::$app->cache->set($taskKey, $params);
+        return $taskKey;
+    }
+
+    /**
+     * 获取异步参数
+     * @param $taskKey
+     * @return mixed
+     */
+    public static function getAsyncParams($taskKey){
+        $result = Yii::$app->cache->get($taskKey);
+        Yii::$app->cache->delete($taskKey);
+        return $result;
+    }
+
+    /**
+     * 获取异步参数但不删除异步参数缓存
+     * (用于临时获取一下,但还不影响正式获取后删除的逻辑)
+     * @param $taskKey
+     * @return mixed
+     */
+    public static function getAsyncParamsWithoutDel($taskKey){
+        return Yii::$app->cache->get($taskKey);
+    }
+
+    /**
+     * @param $key
+     * @return bool
+     */
+    public static function deleteAsyncParams($key){
+        return Yii::$app->cache->delete($key);
+    }
+
+    /**
+     * 生成一个随机不重复的key
+     * @param $prefix
+     * @return string
+     * @throws \yii\base\Exception
+     */
+    public static function createRandomKey($prefix){
+        $key = Yii::$app->security->generateRandomString(20);
+        if(Yii::$app->cache->exists($prefix.$key)){
+            self::createRandomKey($prefix);
+        }
+        return $prefix.$key;
+    }
+
+    /**
+     * 获取全部安置网络上级
+     * @param $userId
+     * @return array|mixed
+     */
+    public static function getAllNetworkParents($userId){
+        return UserNetwork::getAllParentsFromRedis($userId);
+    }
+
+    /**
+     * 获取全部推荐网络的上级
+     * @param $userId
+     * @return array|mixed
+     */
+    public static function getAllRelationParents($userId){
+        return UserRelation::getAllParentsFromRedis($userId);
+    }
+}

+ 513 - 0
common/helpers/Date.php

@@ -0,0 +1,513 @@
+<?php
+
+namespace common\helpers;
+
+use yii\db\Expression;
+
+/**
+ * 日期
+ *
+ * @package    Helper
+ * @author     Aries <forphp@qq.com>
+ */
+class Date {
+    const OCI_TIME_FORMAT_FULL = 'YYYY-MM-DD HH24:MI:SS';
+    const OCI_TIME_FORMAT_DATE = 'YYYY-MM-DD';
+    const OCI_TIME_FORMAT_MONTH = 'YYYY-MM';
+    const OCI_TIME_FORMAT_YEAR = 'YYYY';
+    const OCI_TIME_FORMAT_SHORT_DATE = 'YYYYMMDD';
+    const OCI_TIME_FORMAT_SHORT_MONTH = 'YYYYMM';
+    const OCI_DATE_FORMAT_PHP = 'Y-m-d';
+
+    /**
+     * 检测日期的格式
+     * @param $value
+     * @return bool
+     */
+    public static function validator($value) {
+        if (!preg_match("#^\d{4}([/-])([0][0-9]|[1][0-2])\\1([0-2][0-9]|[3][0-1])$#", $value)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 获取当前时间,主要用于console里面获取当前时间,方便日后系统日期在这里面修改
+     * @param int $days
+     * @return int|mixed
+     */
+    public static function nowTime($days = 0) {
+        $days = \Yii::$app->params['daysDiff'];
+        if (\Yii::$app->id == 'app-console') {
+            return time() + $days * 24 * 60 * 60;
+        } else {
+            return __SYSTEM_TIME__ + $days * 24 * 60 * 60;
+        }
+    }
+
+    /**
+     * 时间转换
+     *
+     * @param null $value
+     * @param string $format
+     * @return false|null|string
+     */
+    public static function convert($value = null, $format = 'Y-m-d') {
+        if (is_null($value)) $value = date($format, self::nowTime()); //相对于某一时间的昨天
+        //if (!self::validator($value)) $value = date($format, self::nowTime());
+        if (Validator::validateQuickLy('chMonth', $value)) {
+            return $value . '-01';
+        }
+        if (!Validator::validateQuickLy('chDate', $value)) {
+            $value = date($format, $value);
+        }
+        return $value;
+    }
+
+    /**
+     * 今天
+     *
+     * @param string $format
+     * @return false|string
+     */
+    public static function today($format = 'Y-m-d') {
+        return date($format, self::nowTime());
+    }
+
+    /**
+     * 今天的开始时间
+     *
+     * @return false|int
+     */
+    public static function todayStart() {
+        $relative = self::convert();
+        return strtotime($relative);
+    }
+
+    /**
+     * 今天的结束时间
+     *
+     * @return false|int
+     */
+    public static function todayEnd() {
+        $relative = self::convert();
+        return strtotime($relative . ' +1 day') - 1;
+    }
+
+    /**
+     * 昨天
+     *
+     * @param null $relative
+     * @param string $format
+     * @return false|int|string
+     */
+    public static function yesterday($relative = null, $format = null) {
+        $relative = self::convert($relative);
+        $yestoday = strtotime($relative . '-1 day');
+        if (!is_null($format)) {
+            return date($format, $yestoday);
+        }
+        return $yestoday;
+    }
+
+    /**
+     * 昨天的开始时间[0点]
+     *
+     * @param null $relative
+     * @return false|int
+     */
+    public static function yesterdayStart($relative = null) {
+        $date = self::yesterday($relative, 'Y-m-d');
+        return strtotime($date);
+    }
+
+    /**
+     * 昨天的结束时间[23:59:59]
+     *
+     * @param null $relative
+     * @return false|int
+     */
+    public static function yesterdayEnd($relative = null) {
+        $date = self::yesterday($relative, 'Y-m-d');
+        return strtotime($date . ' +1 day') - 1;
+    }
+
+    /**
+     * 上个月
+     *
+     * @param null $relative
+     * @param string $format
+     * @return false|int|string
+     */
+    public static function lastMonth($relative = null, $format = null) {
+        $relative = self::convert($relative);
+        $lastMonth = strtotime($relative . 'first day of previous month');
+        if(!is_null($format)){
+            return date($format ,$lastMonth);
+        }
+        return $lastMonth;
+    }
+
+    /**
+     * 上几个月的时间
+     * @param int $num
+     * @param null $relative
+     * @param null $format
+     * @return false|int|string
+     */
+    public static function lastNumMonth($num = 1, $relative = null, $format = null) {
+        $relative = self::convert($relative,'Y-m');
+        $lastMonth = strtotime($relative . '-' . $num . ' month');
+        if (!is_null($format)) {
+            return date($format, $lastMonth);
+        }
+        return $lastMonth;
+    }
+
+    /**
+     * 上几天的时间
+     * @param int $num
+     * @param null $relative
+     * @param null $format
+     * @return false|int|string
+     */
+    public static function lastNumDay($num = 1, $relative = null, $format = null) {
+        $relative = self::convert($relative,'Y-m-d');
+        $lastDay = strtotime($relative . '-' . $num . ' day');
+        if (!is_null($format)) {
+            return date($format, $lastDay);
+        }
+        return $lastDay;
+    }
+
+    /**
+     * 下月
+     * @param null $relative
+     * @param null $format
+     * @return false|int|string
+     */
+    public static function nextMonth($relative = null, $format = null) {
+        $relative = self::convert($relative);
+        $nextMonth = strtotime($relative . ' first day of next month');
+        if(!is_null($format)){
+            return date($format ,$nextMonth);
+        }
+        return $nextMonth;
+    }
+
+    /**
+     * 下个月的指定日期
+     * @param null $relative
+     * @param null $format
+     * @return false|int
+     */
+    public static function nextMonthDay($relative = null, $format = null) {
+        $day = date('d', $relative);
+        $nextMonthFirstDay = self::nextMonth($relative, $format = null);
+        return strtotime(date('Y-m-'.$day, $nextMonthFirstDay));
+    }
+
+    /**
+     * 一年的开始
+     * @param null $relative
+     * @return false|int
+     */
+    public static function yearStart($relative = null) {
+        $relative = self::convert($relative, 'Y-01-01');
+        return strtotime($relative);
+    }
+
+    /**
+     * 一年的结束
+     * @param null $relative
+     * @return false|int
+     */
+    public static function yearEnd($relative = null) {
+        $relative = self::convert($relative, 'Y-12-31');
+        return strtotime($relative . ' +1 day') - 1;
+    }
+
+    /**
+     * 月开始时间
+     *
+     * @param null $relative
+     * @return false|int
+     */
+    public static function monthStart($relative = null) {
+        $relative = self::convert($relative, 'Y-m-01');
+        return strtotime($relative);
+    }
+
+    /**
+     * 月结束时间[23:59:59]
+     *
+     * @param null $relative
+     * @return false|int
+     */
+    public static function monthEnd($relative = null) {
+        $firstday = self::convert($relative, 'Y-m-01');
+        return strtotime($firstday . " +1 month") - 1;
+    }
+
+    /**
+     * 某一天开始的时间 00:00:00
+     *
+     * @param null $relative
+     * @return false|int
+     */
+    public static function dayStart($relative = null) {
+        $day = self::convert($relative);
+        return strtotime($day);
+    }
+
+    /**
+     * 某一天开始的时间 23:59:59
+     *
+     * @param null $relative
+     * @return false|int
+     */
+    public static function dayEnd($relative = null) {
+        $day = self::convert($relative);
+        return strtotime($day . ' +1 day') - 1;
+    }
+
+    /**
+     * 一天中某个小时的时间戳
+     * @param null $relative
+     * @param $hour
+     * @return false|int
+     */
+    public static function dayHour($relative = null, $hour = '00') {
+        $day = self::convert($relative);
+        $day = $day . " $hour:00:00";
+        return strtotime($day);
+    }
+
+    /**
+     * 一天中某小时某分钟的时间戳
+     * @param null $relative
+     * @param string $hourMinute
+     * @return false|int
+     */
+    public static function dayMinute($relative = null, $hourMinute = '00:00') {
+        $day = self::convert($relative);
+        $day = $day . " $hourMinute:00";
+        return strtotime($day);
+    }
+
+    /**
+     * 本周的开始到结束
+     *
+     * @param null $relative
+     * @return array
+     */
+    public static function thisWeek($relative = null) {
+        if (is_null($relative)) {
+            $relative = self::nowTime();
+        } else if (Validator::validateQuickLy('chDate', $relative)) {
+            $relative = strtotime($relative);
+        }
+        $startTime = strtotime('last Monday', $relative); //周一
+        $endTime = strtotime('Sunday', $relative);  //周日
+        return [
+            'start' => self::dayStart($startTime),
+            'end' => self::dayEnd($endTime),
+        ];
+    }
+
+    /**
+     * 七天的数据
+     *
+     * @param string $format
+     * @param null $relative
+     * @return array
+     */
+    public static function sevenDays($format = 'Y-m-d', $relative = null) {
+        $data = [];
+        if (is_null($relative)) $relative = self::nowTime();
+        if (Validator::validateQuickLy('chDate', $relative)) {
+            $relative = strtotime($relative);
+        }
+        for ($i = 7; $i >= 0; $i--) {
+            $time = strtotime("-$i day", $relative);
+            $data[] = [
+                'date' => date($format, $time),
+                'time' => $time,
+                'shortDate' => date('m-d', $time),
+            ];
+        }
+        $data[] = [
+            'date' => date($format, $relative),
+            'time' => $relative,
+            'shortDate' => date('m-d', $relative),
+        ];
+        return $data;
+    }
+
+    /**
+     * 半年
+     *
+     * @param string $format
+     * @param null $relative
+     * @return array
+     */
+    public static function halfYear($format = 'Y-m-d', $relative = null) {
+        $data = [];
+        if (is_null($relative)) $relative = self::nowTime();
+        if (Validator::validateQuickLy('chDate', $relative)) {
+            $relative = strtotime($relative);
+        }
+        for ($i = 5; $i >= 0; $i--) {
+            $time = strtotime("-$i month", $relative);
+            $month = date('n', $time);
+            $data[] = [
+                'date' => date($format, $time),
+                'time' => $time,
+                'month' => $month,
+            ];
+        }
+        return $data;
+    }
+
+    /**
+     * 日期间的差
+     * @param $start
+     * @param $end
+     * @return \DateInterval|false
+     */
+    public static function diff($start, $end) {
+        $start = Validator::validateQuickLy('chDate', $start) ? $start : date('Y-m-d', $start);
+        $end = Validator::validateQuickLy('chDate', $end) ? $end : date('Y-m-d', $end);
+        $sdate = date_create($start);
+        $edate = date_create($end);
+        $diff = date_diff($sdate, $edate);
+        $result = [];
+        $result['y'] = $diff->y + $diff->m / 12 + $diff->d / 365.25;
+        $result['m'] = $diff->y * 12 + $diff->m + $diff->d / 30 + $diff->h / 24;
+        $result['d'] = $diff->y * 365.25 + $diff->m * 30 + $diff->d + $diff->h / 24 + $diff->i / 60;
+        $result['h'] = ($diff->y * 365.25 + $diff->m * 30 + $diff->d) * 24 + $diff->h + $diff->i / 60;
+        $result['i'] = (($diff->y * 365.25 + $diff->m * 30 + $diff->d) * 24 + $diff->h) * 60 + $diff->i + $diff->s / 60;
+        $result['s'] = ((($diff->y * 365.25 + $diff->m * 30 + $diff->d) * 24 + $diff->h) * 60 + $diff->i) * 60 + $diff->s;
+        return $result;
+    }
+
+    /**
+     * 所传日期是周几
+     * @param $relative
+     * @return false|string
+     */
+    public static function dateWeek($relative = null) {
+        $day = self::convert($relative);
+        return date('w', strtotime($day));
+    }
+
+    /**
+     * 是不是UNIXTIME
+     * @param $time
+     * @return bool
+     */
+    public static function isUnixtime($time) {
+        $len = strlen($time);
+        if ($len < 10) {
+            return false;
+        }
+        if ($len > 10) {
+            $time = substr($time, 0, 10);
+        }
+        return ctype_digit($time) && $time <= 2147483647;
+    }
+
+    /**
+     * 获取Oracle支持的to_date的时间格式
+     * @param null $dateTime
+     * @param string $format
+     * @return Expression
+     */
+    public static function ociToDate($dateTime = null, $format = self::OCI_TIME_FORMAT_FULL) {
+        if ($dateTime === null) {
+            $dateTime = self::nowTime();
+        }
+
+//        if (self::isUnixtime($dateTime)) {
+//            $dateTime = date('Y-m-d H:i:s', $dateTime);
+//        }
+//
+//        return new Expression("TO_DATE('$dateTime', '$format')");
+
+        if (!self::isUnixtime($dateTime)) {
+            $dateTime = strtotime($dateTime);
+        }
+
+        return date('Y-m-d', $dateTime);
+    }
+
+    /**
+     * UTC时间转时间戳
+     * @param $dateTime
+     * @return false|int
+     */
+    public static function utcToTime($dateTime) {
+        return strtotime($dateTime);
+    }
+
+    /**
+     * 是否是正确的年月格式
+     * @param $yearMonth
+     * @return false|int
+     */
+    public static function isYearMonth($yearMonth) {
+        return preg_match('/^\d{4}[0-1]\d$/', $yearMonth);
+    }
+
+    /**
+     * 当前年份
+     * @return false|string
+     */
+    public static function nowYear() {
+        return date('Y', self::nowTime());
+    }
+
+    /**
+     * 当月
+     * @return false|string
+     */
+    public static function nowYearMonth() {
+        return date('Ym', self::nowTime());
+    }
+
+    /**
+     * 获取日期区间的值
+     * @param array $params
+     * @return array|bool
+     */
+    public static function getDateRange($params = []){
+        $default = [
+            'startKey'=>'startDate',
+            'endKey'=>'endDate',
+            'value'=>null,
+        ];
+        $params = Tool::deepParse($params ,$default);
+        if(!is_null($params['value'])){
+            list($startDate, $endDate) = $params['value'];
+        }else{
+            $startDate = \Yii::$app->request->all($params['startKey']);
+            $endDate = \Yii::$app->request->all($params['endKey']);
+            $startDate = urldecode($startDate);
+            $endDate = urldecode($endDate);
+        }
+        $startTime = strtotime($startDate);
+        $endTime = strtotime($endDate);
+        if(!$startTime || !$endTime){
+            return false;
+        }
+        $monthStart = self::monthStart($startTime);
+        return [
+            'startDate'=>$startDate,
+            'endDate'=>$endDate,
+            'startTime'=>$startTime,
+            'monthStart'=>$monthStart,
+            'monthStartDate'=>date(self::OCI_DATE_FORMAT_PHP, $monthStart),
+            'endTime'=>strtotime($endDate.'+1 day') - 1, //获取$endDate的23:59:59
+        ];
+    }
+}

+ 899 - 0
common/helpers/Excel.php

@@ -0,0 +1,899 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/5/17
+ * Time: 上午11:34
+ */
+
+namespace common\helpers;
+
+use common\helpers\http\RemoteUploadApi;
+use common\models\ExcelAddUser;
+use common\models\ExcelChangeBalance;
+use common\models\ExcelImport;
+use common\models\ExcelRegInfo;
+use common\models\ExcelWithdrawPaidFalse;
+use common\models\Export;
+use common\models\ExportFile;
+use common\models\forms\AdminAddUserForm;
+use common\models\forms\ChangeBalanceForm;
+use common\models\forms\ExcelOrderDecForm;
+use common\models\forms\ExcelOrderShopForm;
+use common\models\forms\ExcelOrderStandardForm;
+use common\models\forms\RegInfoAuditForm;
+use common\models\forms\WithdrawForm;
+use common\models\Uploads;
+use common\models\UserNetwork;
+use yii\base\BaseObject;
+use yii\base\Exception;
+use yii\base\StaticInstanceTrait;
+
+class Excel extends BaseObject {
+    use StaticInstanceTrait;
+
+    const CHILD_PATH = 'excel_export';
+
+    public $isRemote = true;
+    public $uploadInfo;
+    public $exportInfo;
+
+    private $_errors = [];
+    const EXCEL_STRUCTURE = [
+        'addUser' => [
+            'formClass' => AdminAddUserForm::class,
+            'formScenario' => 'add',
+            'formAction' => 'edit',
+            'errorTipField' => 'USER_NAME',
+            'excelTableClass' => ExcelAddUser::class,
+            'excelTableField' => [
+                '序号' => 'SORT',
+                '会员编号' => 'USER_NAME',
+                '会员姓名' => 'REAL_NAME',
+                '会员级别' => 'DEC_LV',
+                '身份证' => 'ID_CARD',
+                '手机' => 'MOBILE',
+                '备用手机号' => 'TEL',
+                '省/市' => 'AREA_PROVINCE',
+                '市/区' => 'AREA_CITY',
+                '区/县' => 'AREA_COUNTY',
+                '身份证地址' => 'ADDRESS',
+                '开户银行' => 'OPEN_BANK',
+                '开户行地址' => 'BANK_ADDRESS',
+                '银行账号' => 'BANK_NO',
+                '银行 省/市' => 'BANK_PROVINCE',
+                '银行 市/区' => 'BANK_CITY',
+                '银行 区/县' => 'BANK_COUNTY',
+                '是否报单中心' => 'IS_DEC',
+                '报单中心级别' => 'DEC_ROLE',
+                '所属报单中心编号' => 'DEC_USER_NAME',
+                '生日' => 'BIRTHDAY',
+                '接点人' => 'CON_USER_NAME',
+                '开拓人' => 'REC_USER_NAME',
+                '区位' => 'LOCATION',
+            ],
+            'formField' => [
+                'USER_NAME' => [
+                    'name' => 'userName',
+                    'type' => 'common',
+                ],
+                'REAL_NAME' => [
+                    'name' => 'realName',
+                    'type' => 'common',
+                ],
+                'DEC_LV' => [
+                    'name' => 'decLv',
+                    'type' => 'table',
+                    'table' => 'DECLARATION_LEVEL',
+                    'field' => 'LEVEL_NAME',
+                    'select' => 'ID',
+                ],
+                'ID_CARD' => [
+                    'name' => 'idCard',
+                    'type' => 'common',
+                ],
+                'MOBILE' => [
+                    'name' => 'mobile',
+                    'type' => 'common',
+                ],
+                'TEL' => [
+                    'name' => 'tel',
+                    'type' => 'common',
+                ],
+                'AREA_PROVINCE' => [
+                    'name' => 'areaSelected',
+                    'isArray' => true,
+                    'type' => 'table',
+                    'table' => 'REGION',
+                    'field' => 'REGION_NAME',
+                    'where' => 'DEEP=2',
+                    'select' => 'REGION_CODE',
+                ],
+                'AREA_CITY' => [
+                    'name' => 'areaSelected',
+                    'isArray' => true,
+                    'type' => 'table',
+                    'table' => 'REGION',
+                    'field' => 'REGION_NAME',
+                    'where' => 'DEEP=3',
+                    'select' => 'REGION_CODE',
+                ],
+                'AREA_COUNTY' => [
+                    'name' => 'areaSelected',
+                    'isArray' => true,
+                    'type' => 'table',
+                    'table' => 'REGION',
+                    'field' => 'REGION_NAME',
+                    'where' => 'DEEP=4',
+                    'select' => 'REGION_CODE',
+                ],
+                'ADDRESS' => [
+                    'name' => 'address',
+                    'type' => 'common',
+                ],
+                'OPEN_BANK' => [
+                    'name' => 'openBank',
+                    'type' => 'table',
+                    'table' => 'OPEN_BANK',
+                    'field' => 'BANK_NAME',
+                    'select' => 'BANK_CODE',
+                ],
+                'BANK_ADDRESS' => [
+                    'name' => 'bankAddress',
+                    'type' => 'common',
+                ],
+                'BANK_NO' => [
+                    'name' => 'bankNo',
+                    'type' => 'common',
+                ],
+                'BANK_PROVINCE' => [
+                    'name' => 'bankAreaSelected',
+                    'isArray' => true,
+                    'type' => 'table',
+                    'table' => 'REGION',
+                    'field' => 'REGION_NAME',
+                    'where' => 'DEEP=2',
+                    'select' => 'REGION_CODE',
+                ],
+                'BANK_CITY' => [
+                    'name' => 'bankAreaSelected',
+                    'isArray' => true,
+                    'type' => 'table',
+                    'table' => 'REGION',
+                    'field' => 'REGION_NAME',
+                    'where' => 'DEEP=3',
+                    'select' => 'REGION_CODE',
+                ],
+                'BANK_COUNTY' => [
+                    'name' => 'bankAreaSelected',
+                    'isArray' => true,
+                    'type' => 'table',
+                    'table' => 'REGION',
+                    'field' => 'REGION_NAME',
+                    'where' => 'DEEP=4',
+                    'select' => 'REGION_CODE',
+                ],
+                'IS_DEC' => [
+                    'name' => 'isDec',
+                    'type' => 'bool',
+                ],
+                'DEC_ROLE' => [
+                    'name' => 'decRoleId',
+                    'type' => 'table',
+                    'table' => 'DEC_ROLE',
+                    'field' => 'ROLE_NAME',
+                    'select' => 'ID',
+                    'isAllowNull' => true,
+                ],
+                'DEC_USER_NAME' => [
+                    'name' => 'decUserName',
+                    'type' => 'common',
+                ],
+                'BIRTHDAY' => [
+                    'name' => 'birthday',
+                    'type' => 'common',
+                ],
+                'CON_USER_NAME' => [
+                    'name' => 'conUserName',
+                    'type' => 'common',
+                ],
+                'REC_USER_NAME' => [
+                    'name' => 'recUserName',
+                    'type' => 'common',
+                ],
+                'LOCATION' => [
+                    'name' => 'location',
+                    'type' => 'int',
+                ],
+            ]
+        ],
+        'withdrawPaidFalse' => [
+            'formClass' => WithdrawForm::class,
+            'formScenario' => 'excelPaidFalse',
+            'formAction' => 'excelPaidFalse',
+            'errorTipField' => 'SN',
+            'excelTableClass' => ExcelWithdrawPaidFalse::class,
+            'excelTableField' => [
+                '序号' => 'SORT',
+                '提现流水号' => 'SN',
+                '提现期数' => 'WITHDRAW_PERIOD_NUM',
+                '付款日期' => 'PAID_AT',
+                '付款失败原因' => 'PAID_FAIL_REMARK',
+                '会员编号' => 'USER_NAME',
+                '会员姓名' => 'REAL_NAME',
+                '实付金额' => 'AMOUNT',
+                '实时开户名' => 'BANK_REAL_NAME',
+                '实时银行账户' => 'BANK_NO',
+            ],
+            'formField' => [
+                'SN' => [
+                    'name' => 'sn',
+                    'type' => 'common',
+                ],
+                'WITHDRAW_PERIOD_NUM' => [
+                    'name' => 'withdrawPeriodNum',
+                    'type' => 'common',
+                ],
+                'PAID_AT' => [
+                    'name' => 'paidAt',
+                    'type' => 'common',
+                ],
+                'PAID_FAIL_REMARK' => [
+                    'name' => 'paidFailRemark',
+                    'type' => 'common',
+                ],
+                'USER_NAME' => [
+                    'name' => 'userName',
+                    'type' => 'common',
+                ],
+                'REAL_NAME' => [
+                    'name' => 'realName',
+                    'type' => 'common',
+                ],
+                'AMOUNT' => [
+                    'name' => 'amount',
+                    'type' => 'common',
+                ],
+                'BANK_REAL_NAME' => [
+                    'name' => 'bankRealName',
+                    'type' => 'common',
+                ],
+                'BANK_NO' => [
+                    'name' => 'bankNo',
+                    'type' => 'common',
+                ],
+            ]
+        ],
+        'changeBalance' => [
+            'formClass' => ChangeBalanceForm::class,
+            'formScenario' => 'excelChangeBalance',
+            'formAction' => 'excelChangeBalance',
+            'errorTipField' => 'USER_NAME',
+            'excelTableClass' => ExcelChangeBalance::class,
+            'excelTableField' => [
+                '序号' => 'SORT',
+                '会员编号' => 'USER_NAME',
+                '会员姓名' => 'REAL_NAME',
+                '账户类型' => 'TYPE',
+                '交易类型' => 'DEAL_TYPE',
+                '调整金额' => 'AMOUNT',
+                '备注' => 'REMARK',
+                '备注是否前台显示' => 'REMARK_IS_SHOW',
+            ],
+            'formField' => [
+                'USER_NAME' => [
+                    'name' => 'userName',
+                    'type' => 'common',
+                ],
+                'REAL_NAME' => [
+                    'name' => 'realName',
+                    'type' => 'common',
+                ],
+                'TYPE' => [
+                    'name' => 'type',
+                    'type' => 'param',
+                    'index' => 'bonusWalletType',
+                ],
+                'DEAL_TYPE' => [
+                    'name' => 'dealType',
+                    'type' => 'table',
+                    'db' => 'db',
+                    'table' => 'DEAL_TYPE',
+                    'field' => 'TYPE_NAME',
+                    'select' => 'ID',
+                ],
+                'AMOUNT' => [
+                    'name' => 'amount',
+                    'type' => 'common',
+                ],
+                'REMARK' => [
+                    'name' => 'remark',
+                    'type' => 'common',
+                ],
+                'REMARK_IS_SHOW' => [
+                    'name' => 'isShow',
+                    'type' => 'bool',
+                ],
+            ]
+        ],
+        'regInfo' => [
+            'formClass' => RegInfoAuditForm::class,
+            'formScenario' => 'excelAdd',
+            'formAction' => 'excelAdd',
+            'errorTipField' => 'USER_NAME',
+            'excelTableClass' => ExcelRegInfo::class,
+            'excelTableField' => [
+                '序号' => 'SORT',
+                '会员编号' => 'USER_NAME',
+                '会员姓名' => 'REAL_NAME',
+                '身份证' => 'ID_CARD',
+                '注册类型' => 'REG_TYPE',
+                '注册名称' => 'REG_NAME',
+                '统一社会信用代码' => 'CREDIT_CODE',
+                '经营场所' => 'PREMISES',
+                '注册有效期' => 'REG_EXPIRES',
+                '法人' => 'LEGAL_PERSON',
+                '企业开户名称' => 'OPEN_NAME',
+                '企业开户银行' => 'OPEN_BANK',
+                '企业开户行地址' => 'BANK_ADDRESS',
+                '企业银行账号' => 'BANK_NO',
+                '企业银行 省/市' => 'BANK_PROVINCE',
+                '企业银行 市/区' => 'BANK_CITY',
+                '企业银行 区/县' => 'BANK_COUNTY',
+                '备注' => 'REMARK',
+            ],
+            'formField' => [
+                'USER_NAME' => [
+                    'name' => 'userName',
+                    'type' => 'common',
+                ],
+                'REAL_NAME' => [
+                    'name' => 'realName',
+                    'type' => 'common',
+                ],
+                'ID_CARD' => [
+                    'name' => 'idCard',
+                    'type' => 'common',
+                ],
+                'REG_TYPE' => [
+                    'name' => 'type',
+                    'type' => 'table',
+                    'db' => 'db',
+                    'table' => 'REG_TYPE',
+                    'field' => 'TYPE_NAME',
+                    'select' => 'ID',
+                ],
+                'REG_NAME' => [
+                    'name' => 'regName',
+                    'type' => 'common',
+                ],
+                'CREDIT_CODE' => [
+                    'name' => 'creditCode',
+                    'type' => 'common',
+                ],
+                'PREMISES' => [
+                    'name' => 'premises',
+                    'type' => 'common',
+                ],
+                'REG_EXPIRES' => [
+                    'name' => 'regExpires',
+                    'type' => 'common',
+                ],
+                'LEGAL_PERSON' => [
+                    'name' => 'legalPerson',
+                    'type' => 'common',
+                ],
+                'OPEN_NAME' => [
+                    'name' => 'openName',
+                    'type' => 'common',
+                ],
+                'OPEN_BANK' => [
+                    'name' => 'openBank',
+                    'type' => 'table',
+                    'table' => 'OPEN_BANK',
+                    'field' => 'BANK_NAME',
+                    'select' => 'BANK_CODE',
+                    'isAllowNull' => true,
+                ],
+                'BANK_ADDRESS' => [
+                    'name' => 'bankAddress',
+                    'type' => 'common',
+                    'isAllowNull' => true,
+                ],
+                'BANK_NO' => [
+                    'name' => 'bankNo',
+                    'type' => 'common',
+                    'isAllowNull' => true,
+                ],
+                'BANK_PROVINCE' => [
+                    'name' => 'bankAreaSelected',
+                    'isArray' => true,
+                    'type' => 'table',
+                    'table' => 'REGION',
+                    'field' => 'REGION_NAME',
+                    'where' => 'DEEP=2',
+                    'select' => 'REGION_CODE',
+                    'isAllowNull' => true,
+                ],
+                'BANK_CITY' => [
+                    'name' => 'bankAreaSelected',
+                    'isArray' => true,
+                    'type' => 'table',
+                    'table' => 'REGION',
+                    'field' => 'REGION_NAME',
+                    'where' => 'DEEP=3',
+                    'select' => 'REGION_CODE',
+                    'isAllowNull' => true,
+                ],
+                'BANK_COUNTY' => [
+                    'name' => 'bankAreaSelected',
+                    'isArray' => true,
+                    'type' => 'table',
+                    'table' => 'REGION',
+                    'field' => 'REGION_NAME',
+                    'where' => 'DEEP=4',
+                    'select' => 'REGION_CODE',
+                    'isAllowNull' => true,
+                ],
+                'REMARK' => [
+                    'name' => 'createRemark',
+                    'type' => 'common',
+                ],
+            ]
+        ],
+        'orderShop' => [
+            'excelTableClass' => ExcelOrderShopForm::class,
+            'excelTableAction' => 'run',
+        ],
+        'orderDec' => [
+            'excelTableClass' => ExcelOrderDecForm::class,
+            'excelTableAction' => 'run',
+        ],
+        'orderShopStandard' => [
+            'excelTableClass' => ExcelOrderStandardForm::class,
+            'excelTableAction' => 'run',
+        ],
+    ];
+
+    public function init() {
+        parent::init();
+        $this->isRemote = \Yii::$app->params['isRemoteUpload'];
+    }
+
+    /**
+     * 加入错误错误
+     * @param $attr
+     * @param $error
+     */
+    public function addError($attr, $error) {
+        $this->_errors[$attr][] = $error;
+    }
+
+    /**
+     * 获取错误信息
+     * @return array
+     */
+    public function getErrors() {
+        return $this->_errors;
+    }
+
+    /**
+     * 导出到文件
+     * @param $exportId
+     * @param array $dataArr
+     * @param array $columns
+     * @param $fileName
+     * @param callable|null $callBack
+     * @return bool
+     */
+    public function exportToFile($exportId, array $dataArr, array $columns, $fileName, callable $callBack = null) {
+        $result = true;
+        $headers = $columns;
+        $columns = array_keys($columns);
+        $fileName = $fileName . '_export_' . date('YmdHis') . uniqid() . '.xlsx';
+        $path = \Yii::getAlias('@common/runtime/' . self::CHILD_PATH . '/');
+        if (!is_dir($path)) {
+            mkdir($path, 0755);
+        }
+        try {
+            \sunmoon\phpspreadsheet\Excel::export([
+                'models' => $dataArr,
+                'columns' => $columns,
+                'headers' => $headers,
+                'savePath' => $path,
+                'asAttachment' => false,
+                'format' => 'Xlsx',
+                'fileName' => $fileName,
+                'exit' => false,
+                'exportIterationCallback' => $callBack,
+            ]);
+            // 把导出的文件上传至静态文件服务器
+            if ($this->isRemote) {
+                $remoteUploadApi = RemoteUploadApi::instance();
+                if ($uploadResult = $remoteUploadApi->upload($path . $fileName)) {
+                    $this->exportInfo = [
+                        'fileName' => $uploadResult['name'],
+                        'url' => $uploadResult['url'],
+                        'fileSize' => $uploadResult['size'] ?? null,
+                        'md5' => $uploadResult['md5'] ?? null,
+                    ];
+                } else {
+                    throw new Exception('文件远程上传失败');
+                }
+                // 删除本地临时文件
+                unlink($path . $fileName);
+            } else {
+                $this->exportInfo = [
+                    'fileName' => $fileName,
+                    'url' => $path . $fileName,
+                    'fileSize' => null,
+                    'md5' => null,
+                ];
+            }
+            // 把文件对应的相关资料存入数据库中
+            $uploads = new ExportFile();
+            $uploads->FILE_NAME = $this->exportInfo['fileName'];
+            $uploads->EXPORT_ID = $exportId;
+            $uploads->URL = $this->exportInfo['url'];
+            $uploads->FILE_SIZE = $this->exportInfo['fileSize'] ?? null;
+            $uploads->MD5 = $this->exportInfo['md5'] ?? null;
+            $uploads->CREATED_AT = Date::nowTime();
+            if (!$uploads->save()) {
+                throw new Exception(Form::formatErrorsForApi($uploads->getErrors()));
+            }
+        } catch (\Exception $e) {
+            $this->addError('export', $e->getMessage());
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 过滤读取到的数据把空的未填写的行都删掉
+     * @param $data
+     * @return mixed
+     */
+    public static function filterData(&$data) {
+        foreach ($data as $key => $value) {
+            $isAllNull = true;
+            foreach ($value as $k => $v) {
+                if ($v !== null) {
+                    $isAllNull = false;
+                }
+            }
+            if ($isAllNull) {
+                unset($data[$key]);
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * 分页导入数据
+     * @param $excelStructureKey
+     * @param $excelImportId
+     * @param int $rowCount
+     * @param int $startRow
+     * @param int $limit
+     * @return int
+     * @throws Exception
+     */
+    public function pageImportDataFromExcel($excelStructureKey, $excelImportId, $rowCount = 1000, $startRow = 1, $limit = 1000) {
+        if ($startRow > 1) {
+            $startRow = $startRow + 1;
+            $limit = $limit - 1;
+        }
+        $fileNameArray = ExcelImport::find()->select('U.FILE_NAME')->from(ExcelImport::tableName() . ' AS ET')->join('LEFT JOIN', Uploads::tableName() . ' AS U', 'ET.UPLOAD_ID=U.ID')->where('ET.ID=:ID', [':ID' => $excelImportId])->asArray()->one();
+        $filePath = \Yii::getAlias('@common/runtime/uploads/' . $fileNameArray['FILE_NAME']);
+        if ($startRow > $rowCount) {
+            return 0;
+        }
+        try {
+            // 临时文件不存在则创建
+            $tempFileName = \Yii::getAlias('@common/runtime/uploads/' . 'importTemp.txt');
+            if (!file_exists($tempFileName)) {
+                $fp = fopen($tempFileName, 'w+');
+                fclose($fp);
+            } else {
+                if ($startRow == 1) {
+                    file_put_contents($tempFileName, '');
+                }
+            }
+            $data = \sunmoon\phpspreadsheet\Excel::import($filePath, [
+                'setFirstRecordAsKeys' => true,
+                'readStartRow' => $startRow,
+                'readEndRow' => $startRow + $limit,
+                'storeFile' => $tempFileName,
+                'dropKeysRow' => $startRow == 1 ? false : true,
+            ]);
+        } catch (\Exception $e) {
+            throw new Exception($e->getMessage());
+        }
+        self::filterData($data);
+
+
+        if ($data) {
+
+//            if($startRow != 1){
+//                unset($data[0]);
+//            }
+//            if(!$data) return 0;
+//            self::filterData($data);
+            $this->importDataToExcelTable($excelStructureKey, $excelImportId, $data);
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Excel数据导入到待导入的表中
+     * @param $excelStructureKey
+     * @param $excelImportId
+     * @param $excelData
+     */
+    public function importDataToExcelTable($excelStructureKey, $excelImportId, $excelData) {
+        $tableClass = self::EXCEL_STRUCTURE[$excelStructureKey]['excelTableClass'];
+        $tableIndex = self::EXCEL_STRUCTURE[$excelStructureKey]['excelTableField'];
+        $insertData = [];
+
+        foreach ($excelData as $data) {
+            $oneInsertData = [
+                'EXCEL_IMPORT_ID' => $excelImportId,
+                'CREATED_AT' => Date::nowTime(),
+            ];
+            foreach ($data as $key => $value) {
+                $oneInsertData[$tableIndex[$key]] = $value;
+            }
+            $insertData[] = $oneInsertData;
+        }
+        $tableClass::batchInsert($insertData);
+    }
+
+    /**
+     * 分页从待导入表中导入数据到真实数据中
+     * @param $excelStructureKey
+     * @param $excelImportId
+     * @param int $offset
+     * @param int $limit
+     * @return int
+     * @throws Exception
+     */
+    public function pageImportDataFromExcelTable($excelStructureKey, $excelImportId, $offset = 0, $limit = 1000) {
+        $tableClass = self::EXCEL_STRUCTURE[$excelStructureKey]['excelTableClass'];
+        // 获取1000条数据
+        $allData = $tableClass::find()->where('EXCEL_IMPORT_ID=:EXCEL_IMPORT_ID', [':EXCEL_IMPORT_ID' => $excelImportId])->orderBy('SORT ASC')->offset($offset)->limit($limit)->asArray()->all();
+        if ($allData) {
+            $this->importToDbWithForm($excelStructureKey, $allData);
+            return 1;
+        }
+        return 0;
+    }
+
+    /**
+     * 通过表单类导入
+     * @param $excelStructureKey
+     * @param $excelDatas
+     * @throws Exception
+     */
+    public function importToDbWithForm($excelStructureKey, $excelDatas) {
+        foreach ($excelDatas as $value) {
+            $formClassName = self::EXCEL_STRUCTURE[$excelStructureKey]['formClass'];
+            $formScenario = self::EXCEL_STRUCTURE[$excelStructureKey]['formScenario'];
+            $formAction = self::EXCEL_STRUCTURE[$excelStructureKey]['formAction'];
+            $formData = $this->excelDataToFormData($excelStructureKey, $value);
+            if ($formData) {
+                $form = new $formClassName();
+                $form->scenario = $formScenario;
+                foreach ($formData as $formKey => $formValue) {
+                    $form->$formKey = $formValue;
+                }
+                $errorTipField = self::EXCEL_STRUCTURE[$excelStructureKey]['errorTipField'];
+                $tableClass = self::EXCEL_STRUCTURE[$excelStructureKey]['excelTableClass'];
+
+                if (!$form->$formAction()) {
+
+                    // 失败信息写入到待导入数据库excel表中
+                    $tableClass::updateAll(['STATUS' => 2, 'ERROR_REMARK' => Form::formatErrorsForApi($form->getErrors())], 'ID=:ID', [':ID' => $value['ID']]);
+                    throw new Exception('错误序号【'.$value['SORT'].'】会员编号【'.$value['USER_NAME'].'】。导入' . $value[$errorTipField] . '发生错误,原因:' . Form::formatErrorsForApi($form->getErrors()));
+                } else {
+                    $tableClass::updateAll(['STATUS' => 1], 'ID=:ID', [':ID' => $value['ID']]);
+                }
+            }
+        }
+    }
+
+    /**
+     * excel数据转为表单可提交的数据
+     * @param $excelStructureKey
+     * @param $excelData
+     * @return array
+     * @throws Exception
+     */
+    public function excelDataToFormData($excelStructureKey, $excelData) {
+        $formFieldArr = self::EXCEL_STRUCTURE[$excelStructureKey]['formField'];
+        $excelTableField = self::EXCEL_STRUCTURE[$excelStructureKey]['excelTableField'];
+        $sort = $excelData['SORT'];
+        $result = [];
+        foreach ($excelData as $key => $value) {
+            if (in_array($key, ['ID', 'EXCEL_IMPORT_ID', 'STATUS', 'ERROR_REMARK', 'CREATED_AT', 'SORT', 'ROWNUMID'])) continue;
+            if (!array_key_exists($key, $formFieldArr)) {
+                throw new Exception('excel表格字段不符合要求,字段为:' . $key);
+            }
+            $resultKey = $formFieldArr[$key]['name'];
+            // 这里要判断是否允许为空值,如果允许则可以直接付空值
+            if (isset($formFieldArr[$key]['isAllowNull']) && $formFieldArr[$key]['isAllowNull'] && $value === null) {
+                $result[$resultKey] = $value;
+            } elseif ($formFieldArr[$key]['type'] === 'common') {
+                $result[$resultKey] = $value;
+            } elseif ($formFieldArr[$key]['type'] === 'bool') {
+                $result[$resultKey] = intval($value == '是');
+            } elseif ($formFieldArr[$key]['type'] === 'int') {
+                $result[$resultKey] = intval($value);
+            } elseif ($formFieldArr[$key]['type'] === 'param') {
+                // 从param的数据中获取
+                $tempArray = \Yii::$app->params[$formFieldArr[$key]['index']];
+                if ($formFieldArr[$key]['name'] === 'nation') {
+                    array_unshift($tempArray, [
+                        'id' => '0',
+                        'name' => '',
+                    ]);
+                    $nationsArray = array_column($tempArray, 'name');
+                    $tempResultIndex = array_search($value, $nationsArray);
+                    if ($tempResultIndex === false){
+                        if($errTableField = array_search($key, $excelTableField)){
+                            throw new Exception('序号' . $sort . '的表格【' . $errTableField . '】值填写有误,系统中未找到填写的"' . $value . '"');
+                        }else{
+                            throw new Exception('序号' . $sort . '的表格' . $key . '值填写有误,系统中未找到"' . $value . '"');
+                        }
+                    }
+                    $result[$resultKey] = $tempArray[$tempResultIndex]['id'];
+                } elseif ($formFieldArr[$key]['index'] === 'bonusWalletType') {
+                    $nationsArray = array_column($tempArray, 'name');
+                    $tempResultIndex = array_search($value, $nationsArray);
+                    if ($tempResultIndex === false){
+                        if($errTableField = array_search($key, $excelTableField)){
+                            throw new Exception('序号' . $sort . '的表格【' . $errTableField . '】值填写有误,系统中未找到填写的"' . $value . '"');
+                        }else{
+                            throw new Exception('序号' . $sort . '的表格' . $key . '值填写有误,系统中未找到"' . $value . '"');
+                        }
+                    }
+                    $result[$resultKey] = $nationsArray[$tempResultIndex];
+                }
+            } elseif ($formFieldArr[$key]['type'] === 'table') {
+                // 从数据库中获取对应的参数值
+                $db = $formFieldArr[$key]['db'] ?? 'db';
+                $field = $formFieldArr[$key]['field'];
+                $select = $formFieldArr[$key]['select'];
+                $table = $formFieldArr[$key]['table'];
+                $where = $field . '=:' . $field;
+                $where .= (isset($formFieldArr[$key]['where']) && $formFieldArr[$key]['where']) ? ' AND ' . $formFieldArr[$key]['where'] : '';
+                $bindValues = [$field => $value];
+                $tempResult = \Yii::$app->$db->createCommand("SELECT $select FROM {{%$table}} WHERE $where")->bindValues($bindValues)->queryOne();
+                if (!$tempResult){
+                    if($errTableField = array_search($key, $excelTableField)){
+                        throw new Exception('序号' . $sort . '的表格【' . $errTableField . '】值填写有误,系统中未找到填写的"' . $value . '"');
+                    }else{
+                        throw new Exception('序号' . $sort . '的表格' . $key . '值填写有误,系统中未找到"' . $value . '"');
+                    }
+                }
+                if (isset($formFieldArr[$key]['isArray']) && $formFieldArr[$key]['isArray']) {
+                    $result[$resultKey][] = $tempResult[$select];
+                } else {
+                    $result[$resultKey] = $tempResult[$select];
+                }
+
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * 分页导入数据
+     * @param $excelStructureKey
+     * @param $excelImportId
+     * @param int $rowCount
+     * @param int $startRow
+     * @param int $limit
+     * @param int $orderDay
+     * @param int $orderType
+     * @return int
+     * @throws Exception
+     */
+    public function pageImportCustomDataFromExcel($excelStructureKey, $excelImportId, $rowCount, $startRow, $limit, $orderDay, $orderType = 'cash') {
+        if ($startRow > 1) {
+            $startRow = $startRow + 1;
+            $limit = $limit - 1;
+        }
+        $fileNameArray = ExcelImport::find()->select('U.FILE_NAME')->from(ExcelImport::tableName() . ' AS ET')->join('LEFT JOIN', Uploads::tableName() . ' AS U', 'ET.UPLOAD_ID=U.ID')->where('ET.ID=:ID', [':ID' => $excelImportId])->asArray()->one();
+        $filePath = \Yii::getAlias('@common/runtime/uploads/' . $fileNameArray['FILE_NAME']);
+        if ($startRow > $rowCount) {
+            return 0;
+        }
+        try {
+            // 临时文件不存在则创建
+            $tempFileName = \Yii::getAlias('@common/runtime/uploads/' . 'import'.$excelStructureKey.'Temp.txt');
+            if (!file_exists($tempFileName)) {
+                $fp = fopen($tempFileName, 'w+');
+                fclose($fp);
+            } else {
+                if ($startRow == 1) {
+                    file_put_contents($tempFileName, '');
+                }
+            }
+            $data = \sunmoon\phpspreadsheet\Excel::import($filePath, [
+                'setFirstRecordAsKeys' => true,
+                'readStartRow' => $startRow,
+                'readEndRow' => $startRow + $limit,
+                'storeFile' => $tempFileName,
+                'dropKeysRow' => $startRow == 1 ? false : true,
+            ]);
+        } catch (\Exception $e) {
+            throw new Exception($e->getMessage());
+        }
+        self::filterData($data);
+
+
+        if ($data) {
+            $tableClass = self::EXCEL_STRUCTURE[$excelStructureKey]['excelTableClass'];
+            $tableAction = self::EXCEL_STRUCTURE[$excelStructureKey]['excelTableAction'];
+            $form = new $tableClass();
+            if (!$form->$tableAction($data,$orderDay,$orderType)) {
+                return 0;
+            }
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * 分页导入数据
+     * @param string $fileName
+     * @param int $rowCount
+     * @param int $startRow
+     * @param int $limit
+     * @return array
+     * @throws Exception
+     */
+    public function pageImportDataByExcelFile($fileName, $rowCount = 1000, $startRow = 1, $limit = 1000) {
+        if ($startRow > 1) {
+            $startRow = $startRow + 1;
+            $limit = $limit - 1;
+        }
+        $filePath = \Yii::getAlias('@common/runtime/uploads/' . $fileName);
+        if ($startRow > $rowCount) {
+            return [];
+        }
+        try {
+            // 临时文件不存在则创建
+            $tempFileName = \Yii::getAlias('@common/runtime/uploads/' . 'importTemp.txt');
+            if (!file_exists($tempFileName)) {
+                $fp = fopen($tempFileName, 'w+');
+                fclose($fp);
+            } else {
+                if ($startRow == 1) {
+                    file_put_contents($tempFileName, '');
+                }
+            }
+            $data = \sunmoon\phpspreadsheet\Excel::import($filePath, [
+                'setFirstRecordAsKeys' => true,
+                'readStartRow' => $startRow,
+                'readEndRow' => $startRow + $limit,
+                'storeFile' => $tempFileName,
+                'dropKeysRow' => $startRow == 1 ? false : true,
+            ]);
+        } catch (\Exception $e) {
+            throw new Exception($e->getMessage());
+        }
+        self::filterData($data);
+
+
+        if ($data) {
+            return $data;
+        } else {
+            return [];
+        }
+    }
+
+}

+ 49 - 0
common/helpers/Form.php

@@ -0,0 +1,49 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Leo
+ * Date: 2017/9/2
+ * Time: 下午3:58
+ */
+
+namespace common\helpers;
+
+class Form {
+
+    /**
+     * 格式化错误
+     * @param $formErrors
+     * @return array
+     */
+    public static function formatErrors($formErrors){
+        $errorResult = [];
+        if(is_array($formErrors)){
+            foreach ($formErrors as $error) {
+                if(is_array($error)){
+                    foreach($error as $oneError){
+                        $errorResult[] = $oneError;
+                    }
+                } else {
+                    $errorResult[] = $error;
+                }
+            }
+        } else {
+            $errorResult[] = $formErrors;
+        }
+        return $errorResult;
+    }
+
+    /**
+     * 格式化错误
+     * @param $formErrors
+     * @return string
+     */
+    public static function formatErrorsForApi($formErrors){
+        $errors = self::formatErrors($formErrors);
+        $errorResult = '';
+        foreach($errors as $key=>$oneError){
+            $errorResult .= ($key+1).'.'.$oneError.' ';
+        }
+        return $errorResult;
+    }
+}

+ 3481 - 0
common/helpers/IDValidator/GB2260.php

@@ -0,0 +1,3481 @@
+<?php
+namespace common\helpers\IDValidator;
+
+/**
+ * 地址码
+ */
+class GB2260 {
+	private static $gb2260;
+	public static function getGB2260() {
+		if (empty ( self::$gb2260 )) {
+			self::$gb2260 = array (
+					"110000" => "北京市",
+					"110100" => "北京市市辖区",
+					"110101" => "北京市东城区",
+					"110102" => "北京市西城区",
+					"110103" => "北京市崇文区",
+					"110104" => "北京市宣武区",
+					"110105" => "北京市朝阳区",
+					"110106" => "北京市丰台区",
+					"110107" => "北京市石景山区",
+					"110108" => "北京市海淀区",
+					"110109" => "北京市门头沟区",
+					"110111" => "北京市房山区",
+					"110112" => "北京市通州区",
+					"110113" => "北京市顺义区",
+					"110200" => "北京市县",
+					"110221" => "北京市昌平县",
+					"110224" => "北京市大兴县",
+					"110226" => "北京市平谷县",
+					"110227" => "北京市怀柔县",
+					"110228" => "北京市密云县",
+					"110229" => "北京市延庆县",
+					"120000" => "天津市",
+					"120100" => "天津市市辖区",
+					"120101" => "天津市和平区",
+					"120102" => "天津市河东区",
+					"120103" => "天津市河西区",
+					"120104" => "天津市南开区",
+					"120105" => "天津市河北区",
+					"120106" => "天津市红桥区",
+					"120107" => "天津市塘沽区",
+					"120108" => "天津市汉沽区",
+					"120109" => "天津市大港区",
+					"120110" => "天津市东丽区",
+					"120111" => "天津市西青区",
+					"120112" => "天津市津南区",
+					"120113" => "天津市北辰区",
+					"120200" => "天津市县",
+					"120221" => "天津市宁河县",
+					"120222" => "天津市武清县",
+					"120223" => "天津市静海县",
+					"120224" => "天津市宝坻县",
+					"120225" => "天津市蓟县",
+					"130000" => "河北省",
+					"130100" => "河北省石家庄市",
+					"130101" => "河北省石家庄市市辖区",
+					"130102" => "河北省石家庄市长安区",
+					"130103" => "河北省石家庄市桥东区",
+					"130104" => "河北省石家庄市桥西区",
+					"130105" => "河北省石家庄市新华区",
+					"130106" => "河北省石家庄市郊区",
+					"130107" => "河北省石家庄市井陉矿区",
+					"130121" => "河北省石家庄市井陉县",
+					"130123" => "河北省石家庄市正定县",
+					"130124" => "河北省石家庄市栾城县",
+					"130125" => "河北省石家庄市行唐县",
+					"130126" => "河北省石家庄市灵寿县",
+					"130127" => "河北省石家庄市高邑县",
+					"130128" => "河北省石家庄市深泽县",
+					"130129" => "河北省石家庄市赞皇县",
+					"130130" => "河北省石家庄市无极县",
+					"130131" => "河北省石家庄市平山县",
+					"130132" => "河北省石家庄市元氏县",
+					"130133" => "河北省石家庄市赵县",
+					"130181" => "河北省石家庄市辛集市",
+					"130182" => "河北省石家庄市藁城市",
+					"130183" => "河北省石家庄市晋州市",
+					"130184" => "河北省石家庄市新乐市",
+					"130185" => "河北省石家庄市鹿泉市",
+					"130200" => "河北省唐山市",
+					"130201" => "河北省唐山市市辖区",
+					"130202" => "河北省唐山市路南区",
+					"130203" => "河北省唐山市路北区",
+					"130204" => "河北省唐山市古冶区",
+					"130205" => "河北省唐山市开平区",
+					"130206" => "河北省唐山市新区",
+					"130221" => "河北省唐山市丰润县",
+					"130223" => "河北省唐山市滦县",
+					"130224" => "河北省唐山市滦南县",
+					"130225" => "河北省唐山市乐亭县",
+					"130227" => "河北省唐山市迁西县",
+					"130229" => "河北省唐山市玉田县",
+					"130230" => "河北省唐山市唐海县",
+					"130281" => "河北省唐山市遵化市",
+					"130282" => "河北省唐山市丰南市",
+					"130283" => "河北省唐山市迁安市",
+					"130300" => "河北省秦皇岛市秦皇岛市",
+					"130301" => "河北省秦皇岛市市辖区",
+					"130302" => "河北省秦皇岛市海港区",
+					"130303" => "河北省秦皇岛市山海关区",
+					"130304" => "河北省秦皇岛市北戴河区",
+					"130321" => "河北省秦皇岛市青龙满族自治县",
+					"130322" => "河北省秦皇岛市昌黎县",
+					"130323" => "河北省秦皇岛市抚宁县",
+					"130324" => "河北省秦皇岛市卢龙县",
+					"130400" => "河北省邯郸市邯郸市",
+					"130401" => "河北省邯郸市市辖区",
+					"130402" => "河北省邯郸市邯山区",
+					"130403" => "河北省邯郸市丛台区",
+					"130404" => "河北省邯郸市复兴区",
+					"130406" => "河北省邯郸市峰峰矿区",
+					"130421" => "河北省邯郸市邯郸县",
+					"130423" => "河北省邯郸市临漳县",
+					"130424" => "河北省邯郸市成安县",
+					"130425" => "河北省邯郸市大名县",
+					"130426" => "河北省邯郸市涉县",
+					"130427" => "河北省邯郸市磁县",
+					"130428" => "河北省邯郸市肥乡县",
+					"130429" => "河北省邯郸市永年县",
+					"130430" => "河北省邯郸市邱县",
+					"130431" => "河北省邯郸市鸡泽县",
+					"130432" => "河北省邯郸市广平县",
+					"130433" => "河北省邯郸市馆陶县",
+					"130434" => "河北省邯郸市魏县",
+					"130435" => "河北省邯郸市曲周县",
+					"130481" => "河北省邯郸市武安市",
+					"130500" => "河北省邢台市",
+					"130501" => "河北省邢台市市辖区",
+					"130502" => "河北省邢台市桥东区",
+					"130503" => "河北省邢台市桥西区",
+					"130521" => "河北省邢台市邢台县",
+					"130522" => "河北省邢台市临城县",
+					"130523" => "河北省邢台市内丘县",
+					"130524" => "河北省邢台市柏乡县",
+					"130525" => "河北省邢台市隆尧县",
+					"130526" => "河北省邢台市任县",
+					"130527" => "河北省邢台市南和县",
+					"130528" => "河北省邢台市宁晋县",
+					"130529" => "河北省邢台市巨鹿县",
+					"130530" => "河北省邢台市新河县",
+					"130531" => "河北省邢台市广宗县",
+					"130532" => "河北省邢台市平乡县",
+					"130533" => "河北省邢台市威县",
+					"130534" => "河北省邢台市清河县",
+					"130535" => "河北省邢台市临西县",
+					"130581" => "河北省邢台市南宫市",
+					"130582" => "河北省邢台市沙河市",
+					"130600" => "河北省保定市",
+					"130601" => "河北省保定市市辖区",
+					"130602" => "河北省保定市新市区",
+					"130603" => "河北省保定市北市区",
+					"130604" => "河北省保定市南市区",
+					"130621" => "河北省保定市满城县",
+					"130622" => "河北省保定市清苑县",
+					"130623" => "河北省保定市涞水县",
+					"130624" => "河北省保定市阜平县",
+					"130625" => "河北省保定市徐水县",
+					"130626" => "河北省保定市定兴县",
+					"130627" => "河北省保定市唐县",
+					"130628" => "河北省保定市高阳县",
+					"130629" => "河北省保定市容城县",
+					"130630" => "河北省保定市涞源县",
+					"130631" => "河北省保定市望都县",
+					"130632" => "河北省保定市安新县",
+					"130633" => "河北省保定市易县",
+					"130634" => "河北省保定市曲阳县",
+					"130635" => "河北省保定市蠡县",
+					"130636" => "河北省保定市顺平县",
+					"130637" => "河北省保定市博野县",
+					"130638" => "河北省保定市雄县",
+					"130681" => "河北省保定市涿州市",
+					"130682" => "河北省保定市定州市",
+					"130683" => "河北省保定市安国市",
+					"130684" => "河北省保定市高碑店市",
+					"130700" => "河北省张家口市",
+					"130701" => "河北省张家口市市辖区",
+					"130702" => "河北省张家口市桥东区",
+					"130703" => "河北省张家口市桥西区",
+					"130705" => "河北省张家口市宣化区",
+					"130706" => "河北省张家口市下花园区",
+					"130721" => "河北省张家口市宣化县",
+					"130722" => "河北省张家口市张北县",
+					"130723" => "河北省张家口市康保县",
+					"130724" => "河北省张家口市沽源县",
+					"130725" => "河北省张家口市尚义县",
+					"130726" => "河北省张家口市蔚县",
+					"130727" => "河北省张家口市阳原县",
+					"130728" => "河北省张家口市怀安县",
+					"130729" => "河北省张家口市万全县",
+					"130730" => "河北省张家口市怀来县",
+					"130731" => "河北省张家口市涿鹿县",
+					"130732" => "河北省张家口市赤城县",
+					"130733" => "河北省张家口市崇礼县",
+					"130800" => "河北省承德市",
+					"130801" => "河北省承德市市辖区",
+					"130802" => "河北省承德市双桥区",
+					"130803" => "河北省承德市双滦区",
+					"130804" => "河北省承德市鹰手营子矿区",
+					"130821" => "河北省承德市承德县",
+					"130822" => "河北省承德市兴隆县",
+					"130823" => "河北省承德市平泉县",
+					"130824" => "河北省承德市滦平县",
+					"130825" => "河北省承德市隆化县",
+					"130826" => "河北省承德市丰宁满族自治县",
+					"130827" => "河北省承德市宽城满族自治县",
+					"130828" => "河北省承德市围场满族蒙古族自治县",
+					"130900" => "河北省沧州市",
+					"130901" => "河北省沧州市市辖区",
+					"130902" => "河北省沧州市新华区",
+					"130903" => "河北省沧州市运河区",
+					"130921" => "河北省沧州市沧县",
+					"130922" => "河北省沧州市青县",
+					"130923" => "河北省沧州市东光县",
+					"130924" => "河北省沧州市海兴县",
+					"130925" => "河北省沧州市盐山县",
+					"130926" => "河北省沧州市肃宁县",
+					"130927" => "河北省沧州市南皮县",
+					"130928" => "河北省沧州市吴桥县",
+					"130929" => "河北省沧州市献县",
+					"130930" => "河北省沧州市孟村回族自治县",
+					"130981" => "河北省沧州市泊头市",
+					"130982" => "河北省沧州市任丘市",
+					"130983" => "河北省沧州市黄骅市",
+					"130984" => "河北省沧州市河间市",
+					"131000" => "河北省廊坊市",
+					"131001" => "河北省廊坊市市辖区",
+					"131002" => "河北省廊坊市安次区",
+					"131022" => "河北省廊坊市固安县",
+					"131023" => "河北省廊坊市永清县",
+					"131024" => "河北省廊坊市香河县",
+					"131025" => "河北省廊坊市大城县",
+					"131026" => "河北省廊坊市文安县",
+					"131028" => "河北省廊坊市大厂回族自治县",
+					"131081" => "河北省廊坊市霸州市",
+					"131082" => "河北省廊坊市三河市",
+					"131100" => "河北省衡水市",
+					"131101" => "河北省衡水市市辖区",
+					"131102" => "河北省衡水市桃城区",
+					"131121" => "河北省衡水市枣强县",
+					"131122" => "河北省衡水市武邑县",
+					"131123" => "河北省衡水市武强县",
+					"131124" => "河北省衡水市饶阳县",
+					"131125" => "河北省衡水市安平县",
+					"131126" => "河北省衡水市故城县",
+					"131127" => "河北省衡水市景县",
+					"131128" => "河北省衡水市阜城县",
+					"131181" => "河北省衡水市冀州市",
+					"131182" => "河北省衡水市深州市",
+					"140000" => "山西省",
+					"140100" => "山西省太原市",
+					"140101" => "山西省太原市市辖区",
+					"140105" => "山西省太原市小店区",
+					"140106" => "山西省太原市迎泽区",
+					"140107" => "山西省太原市杏花岭区",
+					"140108" => "山西省太原市尖草坪区",
+					"140109" => "山西省太原市万柏林区",
+					"140110" => "山西省太原市晋源区",
+					"140121" => "山西省太原市清徐县",
+					"140122" => "山西省太原市阳曲县",
+					"140123" => "山西省太原市娄烦县",
+					"140181" => "山西省太原市古交市",
+					"140200" => "山西省大同市",
+					"140201" => "山西省大同市市辖区",
+					"140202" => "山西省大同市城区",
+					"140203" => "山西省大同市矿区",
+					"140211" => "山西省大同市南郊区",
+					"140212" => "山西省大同市新荣区",
+					"140221" => "山西省大同市阳高县",
+					"140222" => "山西省大同市天镇县",
+					"140223" => "山西省大同市广灵县",
+					"140224" => "山西省大同市灵丘县",
+					"140225" => "山西省大同市浑源县",
+					"140226" => "山西省大同市左云县",
+					"140227" => "山西省大同市大同县",
+					"140300" => "山西省阳泉市",
+					"140301" => "山西省阳泉市市辖区",
+					"140302" => "山西省阳泉市城区",
+					"140303" => "山西省阳泉市矿区",
+					"140311" => "山西省阳泉市郊区",
+					"140321" => "山西省阳泉市平定县",
+					"140322" => "山西省阳泉市盂县",
+					"140400" => "山西省长治市",
+					"140401" => "山西省长治市市辖区",
+					"140402" => "山西省长治市城区",
+					"140411" => "山西省长治市郊区",
+					"140421" => "山西省长治市长治县",
+					"140423" => "山西省长治市襄垣县",
+					"140424" => "山西省长治市屯留县",
+					"140425" => "山西省长治市平顺县",
+					"140426" => "山西省长治市黎城县",
+					"140427" => "山西省长治市壶关县",
+					"140428" => "山西省长治市长子县",
+					"140429" => "山西省长治市武乡县",
+					"140430" => "山西省长治市沁县",
+					"140431" => "山西省长治市沁源县",
+					"140481" => "山西省长治市潞城市",
+					"140500" => "山西省晋城市",
+					"140501" => "山西省晋城市市辖区",
+					"140502" => "山西省晋城市城区",
+					"140521" => "山西省晋城市沁水县",
+					"140522" => "山西省晋城市阳城县",
+					"140524" => "山西省晋城市陵川县",
+					"140525" => "山西省晋城市泽州县",
+					"140581" => "山西省晋城市高平市",
+					"140600" => "山西省晋城市朔州市",
+					"140601" => "山西省晋城市市辖区",
+					"140602" => "山西省晋城市朔城区",
+					"140603" => "山西省晋城市平鲁区",
+					"140621" => "山西省晋城市山阴县",
+					"140622" => "山西省晋城市应县",
+					"140623" => "山西省晋城市右玉县",
+					"140624" => "山西省晋城市怀仁县",
+					"142200" => "山西省忻州地区",
+					"142201" => "山西省忻州地区忻州市",
+					"142202" => "山西省忻州地区原平市",
+					"142222" => "山西省忻州地区定襄县",
+					"142223" => "山西省忻州地区五台县",
+					"142225" => "山西省忻州地区代县",
+					"142226" => "山西省忻州地区繁峙县",
+					"142227" => "山西省忻州地区宁武县",
+					"142228" => "山西省忻州地区静乐县",
+					"142229" => "山西省忻州地区神池县",
+					"142230" => "山西省忻州地区五寨县",
+					"142231" => "山西省忻州地区岢岚县",
+					"142232" => "山西省忻州地区河曲县",
+					"142233" => "山西省忻州地区保德县",
+					"142234" => "山西省忻州地区偏关县",
+					"142300" => "山西省忻州地区吕梁地区",
+					"142301" => "山西省忻州地区孝义市",
+					"142302" => "山西省忻州地区离石市",
+					"142303" => "山西省忻州地区汾阳市",
+					"142322" => "山西省忻州地区文水县",
+					"142323" => "山西省忻州地区交城县",
+					"142325" => "山西省忻州地区兴县",
+					"142326" => "山西省忻州地区临县",
+					"142327" => "山西省忻州地区柳林县",
+					"142328" => "山西省忻州地区石楼县",
+					"142329" => "山西省忻州地区岚县",
+					"142330" => "山西省忻州地区方山县",
+					"142332" => "山西省忻州地区中阳县",
+					"142333" => "山西省忻州地区交口县",
+					"142400" => "山西省晋中地区",
+					"142401" => "山西省晋中地区榆次市",
+					"142402" => "山西省晋中地区介休市",
+					"142421" => "山西省晋中地区榆社县",
+					"142422" => "山西省晋中地区左权县",
+					"142423" => "山西省晋中地区和顺县",
+					"142424" => "山西省晋中地区昔阳县",
+					"142427" => "山西省晋中地区寿阳县",
+					"142429" => "山西省晋中地区太谷县",
+					"142430" => "山西省晋中地区祁县",
+					"142431" => "山西省晋中地区平遥县",
+					"142433" => "山西省晋中地区灵石县",
+					"142600" => "山西省临汾地区",
+					"142601" => "山西省临汾地区临汾市",
+					"142602" => "山西省临汾地区侯马市",
+					"142603" => "山西省临汾地区霍州市",
+					"142621" => "山西省临汾地区曲沃县",
+					"142622" => "山西省临汾地区翼城县",
+					"142623" => "山西省临汾地区襄汾县",
+					"142625" => "山西省临汾地区洪洞县",
+					"142627" => "山西省临汾地区古县",
+					"142628" => "山西省临汾地区安泽县",
+					"142629" => "山西省临汾地区浮山县",
+					"142630" => "山西省临汾地区吉县",
+					"142631" => "山西省临汾地区乡宁县",
+					"142632" => "山西省临汾地区蒲县",
+					"142633" => "山西省临汾地区大宁县",
+					"142634" => "山西省临汾地区永和县",
+					"142635" => "山西省临汾地区隰县",
+					"142636" => "山西省临汾地区汾西县",
+					"142700" => "山西省运城地区",
+					"142701" => "山西省运城地区运城市",
+					"142702" => "山西省运城地区永济市",
+					"142703" => "山西省运城地区河津市",
+					"142723" => "山西省运城地区芮城县",
+					"142724" => "山西省运城地区临猗县",
+					"142725" => "山西省运城地区万荣县",
+					"142726" => "山西省运城地区新绛县",
+					"142727" => "山西省运城地区稷山县",
+					"142729" => "山西省运城地区闻喜县",
+					"142730" => "山西省运城地区夏县",
+					"142731" => "山西省运城地区绛县",
+					"142732" => "山西省运城地区平陆县",
+					"142733" => "山西省运城地区垣曲县",
+					"150000" => "内蒙古自治区",
+					"150100" => "内蒙古自治区呼和浩特市",
+					"150101" => "内蒙古自治区呼和浩特市市辖区",
+					"150102" => "内蒙古自治区呼和浩特市新城区",
+					"150103" => "内蒙古自治区呼和浩特市回民区",
+					"150104" => "内蒙古自治区呼和浩特市玉泉区",
+					"150105" => "内蒙古自治区呼和浩特市郊区",
+					"150121" => "内蒙古自治区呼和浩特市土默特左旗",
+					"150122" => "内蒙古自治区呼和浩特市托克托县",
+					"150123" => "内蒙古自治区呼和浩特市和林格尔县",
+					"150124" => "内蒙古自治区呼和浩特市清水河县",
+					"150125" => "内蒙古自治区呼和浩特市武川县",
+					"150200" => "内蒙古自治区包头市",
+					"150201" => "内蒙古自治区包头市市辖区",
+					"150202" => "内蒙古自治区包头市东河区",
+					"150203" => "内蒙古自治区包头市昆都伦区",
+					"150204" => "内蒙古自治区包头市青山区",
+					"150205" => "内蒙古自治区包头市石拐矿区",
+					"150206" => "内蒙古自治区包头市白云矿区",
+					"150207" => "内蒙古自治区包头市郊区",
+					"150221" => "内蒙古自治区包头市土默特右旗",
+					"150222" => "内蒙古自治区包头市固阳县",
+					"150223" => "内蒙古自治区包头市达尔罕茂明安联合旗",
+					"150300" => "内蒙古自治区乌海市",
+					"150301" => "内蒙古自治区乌海市市辖区",
+					"150302" => "内蒙古自治区乌海市海勃湾区",
+					"150303" => "内蒙古自治区乌海市海南区",
+					"150304" => "内蒙古自治区乌海市乌达区",
+					"150400" => "内蒙古自治区赤峰市",
+					"150401" => "内蒙古自治区赤峰市市辖区",
+					"150402" => "内蒙古自治区赤峰市红山区",
+					"150403" => "内蒙古自治区赤峰市元宝山区",
+					"150404" => "内蒙古自治区赤峰市松山区",
+					"150421" => "内蒙古自治区赤峰市阿鲁科尔沁旗",
+					"150422" => "内蒙古自治区赤峰市巴林左旗",
+					"150423" => "内蒙古自治区赤峰市巴林右旗",
+					"150424" => "内蒙古自治区赤峰市林西县",
+					"150425" => "内蒙古自治区赤峰市克什克腾旗",
+					"150426" => "内蒙古自治区赤峰市翁牛特旗",
+					"150428" => "内蒙古自治区赤峰市喀喇沁旗",
+					"150429" => "内蒙古自治区赤峰市宁城县",
+					"150430" => "内蒙古自治区赤峰市敖汉旗",
+					"152100" => "内蒙古自治区呼伦贝尔盟",
+					"152101" => "内蒙古自治区呼伦贝尔盟海拉尔市",
+					"152102" => "内蒙古自治区呼伦贝尔盟满洲里市",
+					"152103" => "内蒙古自治区呼伦贝尔盟扎兰屯市",
+					"152104" => "内蒙古自治区呼伦贝尔盟牙克石市",
+					"152105" => "内蒙古自治区呼伦贝尔盟根河市",
+					"152106" => "内蒙古自治区呼伦贝尔盟额尔古纳市",
+					"152122" => "内蒙古自治区呼伦贝尔盟阿荣旗",
+					"152123" => "内蒙古自治区呼伦贝尔盟莫力达瓦达斡尔族自治旗",
+					"152127" => "内蒙古自治区呼伦贝尔盟鄂伦春自治旗",
+					"152128" => "内蒙古自治区呼伦贝尔盟鄂温克族自治旗",
+					"152129" => "内蒙古自治区呼伦贝尔盟新巴尔虎右旗",
+					"152130" => "内蒙古自治区呼伦贝尔盟新巴尔虎左旗",
+					"152131" => "内蒙古自治区呼伦贝尔盟陈巴尔虎旗",
+					"152200" => "内蒙古自治区兴安盟",
+					"152201" => "内蒙古自治区兴安盟乌兰浩特市",
+					"152202" => "内蒙古自治区兴安盟阿尔山市",
+					"152221" => "内蒙古自治区兴安盟科尔沁右翼前旗",
+					"152222" => "内蒙古自治区兴安盟科尔沁右翼中旗",
+					"152223" => "内蒙古自治区兴安盟扎赉特旗",
+					"152224" => "内蒙古自治区兴安盟突泉县",
+					"152300" => "内蒙古自治区哲里木盟",
+					"152301" => "内蒙古自治区哲里木盟通辽市",
+					"152302" => "内蒙古自治区哲里木盟霍林郭勒市",
+					"152322" => "内蒙古自治区哲里木盟科尔沁左翼中旗",
+					"152323" => "内蒙古自治区哲里木盟科尔沁左翼后旗",
+					"152324" => "内蒙古自治区哲里木盟开鲁县",
+					"152325" => "内蒙古自治区哲里木盟库伦旗",
+					"152326" => "内蒙古自治区哲里木盟奈曼旗",
+					"152327" => "内蒙古自治区哲里木盟扎鲁特旗",
+					"152500" => "内蒙古自治区锡林郭勒盟",
+					"152501" => "内蒙古自治区锡林郭勒盟二连浩特市",
+					"152502" => "内蒙古自治区锡林郭勒盟锡林浩特市",
+					"152522" => "内蒙古自治区锡林郭勒盟阿巴嘎旗",
+					"152523" => "内蒙古自治区锡林郭勒盟苏尼特左旗",
+					"152524" => "内蒙古自治区锡林郭勒盟苏尼特右旗",
+					"152525" => "内蒙古自治区锡林郭勒盟东乌珠穆沁旗",
+					"152526" => "内蒙古自治区锡林郭勒盟西乌珠穆沁旗",
+					"152527" => "内蒙古自治区锡林郭勒盟太仆寺旗",
+					"152528" => "内蒙古自治区锡林郭勒盟镶黄旗",
+					"152529" => "内蒙古自治区锡林郭勒盟正镶白旗",
+					"152530" => "内蒙古自治区锡林郭勒盟正蓝旗",
+					"152531" => "内蒙古自治区锡林郭勒盟多伦县",
+					"152600" => "内蒙古自治区乌兰察布盟",
+					"152601" => "内蒙古自治区乌兰察布盟集宁市",
+					"152602" => "内蒙古自治区乌兰察布盟丰镇市",
+					"152624" => "内蒙古自治区乌兰察布盟卓资县",
+					"152625" => "内蒙古自治区乌兰察布盟化德县",
+					"152626" => "内蒙古自治区乌兰察布盟商都县",
+					"152627" => "内蒙古自治区乌兰察布盟兴和县",
+					"152629" => "内蒙古自治区乌兰察布盟凉城县",
+					"152630" => "内蒙古自治区乌兰察布盟察哈尔右翼前旗",
+					"152631" => "内蒙古自治区乌兰察布盟察哈尔右翼中旗",
+					"152632" => "内蒙古自治区乌兰察布盟察哈尔右翼后旗",
+					"152634" => "内蒙古自治区乌兰察布盟四子王旗",
+					"152700" => "内蒙古自治区伊克昭盟",
+					"152701" => "内蒙古自治区伊克昭盟东胜市",
+					"152722" => "内蒙古自治区伊克昭盟达拉特旗",
+					"152723" => "内蒙古自治区伊克昭盟准格尔旗",
+					"152724" => "内蒙古自治区伊克昭盟鄂托克前旗",
+					"152725" => "内蒙古自治区伊克昭盟鄂托克旗",
+					"152726" => "内蒙古自治区伊克昭盟杭锦旗",
+					"152727" => "内蒙古自治区伊克昭盟乌审旗",
+					"152728" => "内蒙古自治区伊克昭盟伊金霍洛旗",
+					"152800" => "内蒙古自治区巴彦淖尔盟",
+					"152801" => "内蒙古自治区巴彦淖尔盟临河市",
+					"152822" => "内蒙古自治区巴彦淖尔盟五原县",
+					"152823" => "内蒙古自治区巴彦淖尔盟磴口县",
+					"152824" => "内蒙古自治区巴彦淖尔盟乌拉特前旗",
+					"152825" => "内蒙古自治区巴彦淖尔盟乌拉特中旗",
+					"152826" => "内蒙古自治区巴彦淖尔盟乌拉特后旗",
+					"152827" => "内蒙古自治区巴彦淖尔盟杭锦后旗",
+					"152900" => "内蒙古自治区阿拉善盟",
+					"152921" => "内蒙古自治区阿拉善盟阿拉善左旗",
+					"152922" => "内蒙古自治区阿拉善盟阿拉善右旗",
+					"152923" => "内蒙古自治区阿拉善盟额济纳旗",
+					"210000" => "辽宁省",
+					"210100" => "辽宁省沈阳市",
+					"210101" => "辽宁省沈阳市市辖区",
+					"210102" => "辽宁省沈阳市和平区",
+					"210103" => "辽宁省沈阳市沈河区",
+					"210104" => "辽宁省沈阳市大东区",
+					"210105" => "辽宁省沈阳市皇姑区",
+					"210106" => "辽宁省沈阳市铁西区",
+					"210111" => "辽宁省沈阳市苏家屯区",
+					"210112" => "辽宁省沈阳市东陵区",
+					"210113" => "辽宁省沈阳市新城子区",
+					"210114" => "辽宁省沈阳市于洪区",
+					"210122" => "辽宁省沈阳市辽中县",
+					"210123" => "辽宁省沈阳市康平县",
+					"210124" => "辽宁省沈阳市法库县",
+					"210181" => "辽宁省沈阳市新民市",
+					"210200" => "辽宁省大连市",
+					"210201" => "辽宁省大连市市辖区",
+					"210202" => "辽宁省大连市中山区",
+					"210203" => "辽宁省大连市西岗区",
+					"210204" => "辽宁省大连市沙河口区",
+					"210211" => "辽宁省大连市甘井子区",
+					"210212" => "辽宁省大连市旅顺口区",
+					"210213" => "辽宁省大连市金州区",
+					"210224" => "辽宁省大连市长海县",
+					"210281" => "辽宁省大连市瓦房店市",
+					"210282" => "辽宁省大连市普兰店市",
+					"210283" => "辽宁省大连市庄河市",
+					"210300" => "辽宁省鞍山市",
+					"210301" => "辽宁省鞍山市市辖区",
+					"210302" => "辽宁省鞍山市铁东区",
+					"210303" => "辽宁省鞍山市铁西区",
+					"210304" => "辽宁省鞍山市立山区",
+					"210311" => "辽宁省鞍山市千山区",
+					"210321" => "辽宁省鞍山市台安县",
+					"210323" => "辽宁省鞍山市岫岩满族自治县",
+					"210381" => "辽宁省鞍山市海城市",
+					"210400" => "辽宁省抚顺市",
+					"210401" => "辽宁省抚顺市市辖区",
+					"210402" => "辽宁省抚顺市新抚区",
+					"210403" => "辽宁省抚顺市露天区",
+					"210404" => "辽宁省抚顺市望花区",
+					"210411" => "辽宁省抚顺市顺城区",
+					"210421" => "辽宁省抚顺市抚顺县",
+					"210422" => "辽宁省抚顺市新宾满族自治县",
+					"210423" => "辽宁省抚顺市清原满族自治县",
+					"210500" => "辽宁省本溪市",
+					"210501" => "辽宁省本溪市市辖区",
+					"210502" => "辽宁省本溪市平山区",
+					"210503" => "辽宁省本溪市溪湖区",
+					"210504" => "辽宁省本溪市明山区",
+					"210505" => "辽宁省本溪市南芬区",
+					"210521" => "辽宁省本溪市本溪满族自治县",
+					"210522" => "辽宁省本溪市桓仁满族自治县",
+					"210600" => "辽宁省丹东市",
+					"210601" => "辽宁省丹东市市辖区",
+					"210602" => "辽宁省丹东市元宝区",
+					"210603" => "辽宁省丹东市振兴区",
+					"210604" => "辽宁省丹东市振安区",
+					"210624" => "辽宁省丹东市宽甸满族自治县",
+					"210681" => "辽宁省丹东市东港市",
+					"210682" => "辽宁省丹东市凤城市",
+					"210700" => "辽宁省锦州市",
+					"210701" => "辽宁省锦州市市辖区",
+					"210702" => "辽宁省锦州市古塔区",
+					"210703" => "辽宁省锦州市凌河区",
+					"210711" => "辽宁省锦州市太和区",
+					"210726" => "辽宁省锦州市黑山县",
+					"210727" => "辽宁省锦州市义县",
+					"210781" => "辽宁省锦州市凌海市",
+					"210782" => "辽宁省锦州市北宁市",
+					"210800" => "辽宁省营口市",
+					"210801" => "辽宁省营口市市辖区",
+					"210802" => "辽宁省营口市站前区",
+					"210803" => "辽宁省营口市西市区",
+					"210804" => "辽宁省营口市鲅鱼圈区",
+					"210811" => "辽宁省营口市老边区",
+					"210881" => "辽宁省营口市盖州市",
+					"210882" => "辽宁省营口市大石桥市",
+					"210900" => "辽宁省阜新市",
+					"210901" => "辽宁省阜新市市辖区",
+					"210902" => "辽宁省阜新市海州区",
+					"210903" => "辽宁省阜新市新邱区",
+					"210904" => "辽宁省阜新市太平区",
+					"210905" => "辽宁省阜新市清河门区",
+					"210911" => "辽宁省阜新市细河区",
+					"210921" => "辽宁省阜新市阜新蒙古族自治县",
+					"210922" => "辽宁省阜新市彰武县",
+					"211000" => "辽宁省辽阳市",
+					"211001" => "辽宁省辽阳市市辖区",
+					"211002" => "辽宁省辽阳市白塔区",
+					"211003" => "辽宁省辽阳市文圣区",
+					"211004" => "辽宁省辽阳市宏伟区",
+					"211005" => "辽宁省辽阳市弓长岭区",
+					"211011" => "辽宁省辽阳市太子河区",
+					"211021" => "辽宁省辽阳市辽阳县",
+					"211081" => "辽宁省辽阳市灯塔市",
+					"211100" => "辽宁省盘锦市",
+					"211101" => "辽宁省盘锦市市辖区",
+					"211102" => "辽宁省盘锦市双台子区",
+					"211103" => "辽宁省盘锦市兴隆台区",
+					"211121" => "辽宁省盘锦市大洼县",
+					"211122" => "辽宁省盘锦市盘山县",
+					"211200" => "辽宁省铁岭市",
+					"211201" => "辽宁省铁岭市市辖区",
+					"211202" => "辽宁省铁岭市银州区",
+					"211204" => "辽宁省铁岭市清河区",
+					"211221" => "辽宁省铁岭市铁岭县",
+					"211223" => "辽宁省铁岭市西丰县",
+					"211224" => "辽宁省铁岭市昌图县",
+					"211281" => "辽宁省铁岭市铁法市",
+					"211282" => "辽宁省铁岭市开原市",
+					"211300" => "辽宁省朝阳市",
+					"211301" => "辽宁省朝阳市市辖区",
+					"211302" => "辽宁省朝阳市双塔区",
+					"211303" => "辽宁省朝阳市龙城区",
+					"211321" => "辽宁省朝阳市朝阳县",
+					"211322" => "辽宁省朝阳市建平县",
+					"211324" => "辽宁省朝阳市喀喇沁左翼蒙古族自治县",
+					"211381" => "辽宁省朝阳市北票市",
+					"211382" => "辽宁省朝阳市凌源市",
+					"211400" => "辽宁省葫芦岛市",
+					"211401" => "辽宁省葫芦岛市市辖区",
+					"211402" => "辽宁省葫芦岛市连山区",
+					"211403" => "辽宁省葫芦岛市龙港区",
+					"211404" => "辽宁省葫芦岛市南票区",
+					"211421" => "辽宁省葫芦岛市绥中县",
+					"211422" => "辽宁省葫芦岛市建昌县",
+					"211481" => "辽宁省葫芦岛市兴城市",
+					"220000" => "吉林省",
+					"220100" => "吉林省长春市",
+					"220101" => "吉林省长春市市辖区",
+					"220102" => "吉林省长春市南关区",
+					"220103" => "吉林省长春市宽城区",
+					"220104" => "吉林省长春市朝阳区",
+					"220105" => "吉林省长春市二道区",
+					"220106" => "吉林省长春市绿园区",
+					"220112" => "吉林省长春市双阳区",
+					"220122" => "吉林省长春市农安县",
+					"220181" => "吉林省长春市九台市",
+					"220182" => "吉林省长春市榆树市",
+					"220183" => "吉林省长春市德惠市",
+					"220200" => "吉林省吉林市",
+					"220201" => "吉林省吉林市市辖区",
+					"220202" => "吉林省吉林市昌邑区",
+					"220203" => "吉林省吉林市龙潭区",
+					"220204" => "吉林省吉林市船营区",
+					"220211" => "吉林省吉林市丰满区",
+					"220221" => "吉林省吉林市永吉县",
+					"220281" => "吉林省吉林市蛟河市",
+					"220282" => "吉林省吉林市桦甸市",
+					"220283" => "吉林省吉林市舒兰市",
+					"220284" => "吉林省吉林市磐石市",
+					"220300" => "吉林省四平市",
+					"220301" => "吉林省四平市市辖区",
+					"220302" => "吉林省四平市铁西区",
+					"220303" => "吉林省四平市铁东区",
+					"220322" => "吉林省四平市梨树县",
+					"220323" => "吉林省四平市伊通满族自治县",
+					"220381" => "吉林省四平市公主岭市",
+					"220382" => "吉林省四平市双辽市",
+					"220400" => "吉林省辽源市",
+					"220401" => "吉林省辽源市市辖区",
+					"220402" => "吉林省辽源市龙山区",
+					"220403" => "吉林省辽源市西安区",
+					"220421" => "吉林省辽源市东丰县",
+					"220422" => "吉林省辽源市东辽县",
+					"220500" => "吉林省通化市",
+					"220501" => "吉林省通化市市辖区",
+					"220502" => "吉林省通化市东昌区",
+					"220503" => "吉林省通化市二道江区",
+					"220521" => "吉林省通化市通化县",
+					"220523" => "吉林省通化市辉南县",
+					"220524" => "吉林省通化市柳河县",
+					"220581" => "吉林省通化市梅河口市",
+					"220582" => "吉林省通化市集安市",
+					"220600" => "吉林省白山市",
+					"220601" => "吉林省白山市市辖区",
+					"220602" => "吉林省白山市八道江区",
+					"220621" => "吉林省白山市抚松县",
+					"220622" => "吉林省白山市靖宇县",
+					"220623" => "吉林省白山市长白朝鲜族自治县",
+					"220625" => "吉林省白山市江源县",
+					"220681" => "吉林省白山市临江市",
+					"220700" => "吉林省松原市",
+					"220701" => "吉林省松原市市辖区",
+					"220702" => "吉林省松原市宁江区",
+					"220721" => "吉林省松原市前郭尔罗斯蒙古族自治县",
+					"220722" => "吉林省松原市长岭县",
+					"220723" => "吉林省松原市乾安县",
+					"220724" => "吉林省松原市扶余县",
+					"220800" => "吉林省白城市",
+					"220801" => "吉林省白城市市辖区",
+					"220802" => "吉林省白城市洮北区",
+					"220821" => "吉林省白城市镇赉县",
+					"220822" => "吉林省白城市通榆县",
+					"220881" => "吉林省白城市洮南市",
+					"220882" => "吉林省白城市大安市",
+					"222400" => "吉林省延边朝鲜族自治州",
+					"222401" => "吉林省延边朝鲜族自治州延吉市",
+					"222402" => "吉林省延边朝鲜族自治州图们市",
+					"222403" => "吉林省延边朝鲜族自治州敦化市",
+					"222404" => "吉林省延边朝鲜族自治州珲春市",
+					"222405" => "吉林省延边朝鲜族自治州龙井市",
+					"222406" => "吉林省延边朝鲜族自治州和龙市",
+					"222424" => "吉林省延边朝鲜族自治州汪清县",
+					"222426" => "吉林省延边朝鲜族自治州安图县",
+					"230000" => "黑龙江省",
+					"230100" => "黑龙江省哈尔滨市",
+					"230101" => "黑龙江省哈尔滨市市辖区",
+					"230102" => "黑龙江省哈尔滨市道里区",
+					"230103" => "黑龙江省哈尔滨市南岗区",
+					"230104" => "黑龙江省哈尔滨市道外区",
+					"230105" => "黑龙江省哈尔滨市太平区",
+					"230106" => "黑龙江省哈尔滨市香坊区",
+					"230107" => "黑龙江省哈尔滨市动力区",
+					"230108" => "黑龙江省哈尔滨市平房区",
+					"230121" => "黑龙江省哈尔滨市呼兰县",
+					"230123" => "黑龙江省哈尔滨市依兰县",
+					"230124" => "黑龙江省哈尔滨市方正县",
+					"230125" => "黑龙江省哈尔滨市宾县",
+					"230126" => "黑龙江省哈尔滨市巴彦县",
+					"230127" => "黑龙江省哈尔滨市木兰县",
+					"230128" => "黑龙江省哈尔滨市通河县",
+					"230129" => "黑龙江省哈尔滨市延寿县",
+					"230181" => "黑龙江省哈尔滨市阿城市",
+					"230182" => "黑龙江省哈尔滨市双城市",
+					"230183" => "黑龙江省哈尔滨市尚志市",
+					"230184" => "黑龙江省哈尔滨市五常市",
+					"230200" => "黑龙江省齐齐哈尔市",
+					"230201" => "黑龙江省齐齐哈尔市市辖区",
+					"230202" => "黑龙江省齐齐哈尔市龙沙区",
+					"230203" => "黑龙江省齐齐哈尔市建华区",
+					"230204" => "黑龙江省齐齐哈尔市铁锋区",
+					"230205" => "黑龙江省齐齐哈尔市昂昂溪区",
+					"230206" => "黑龙江省齐齐哈尔市富拉尔基区",
+					"230207" => "黑龙江省齐齐哈尔市碾子山区",
+					"230208" => "黑龙江省齐齐哈尔市梅里斯达斡尔族区",
+					"230221" => "黑龙江省齐齐哈尔市龙江县",
+					"230223" => "黑龙江省齐齐哈尔市依安县",
+					"230224" => "黑龙江省齐齐哈尔市泰来县",
+					"230225" => "黑龙江省齐齐哈尔市甘南县",
+					"230227" => "黑龙江省齐齐哈尔市富裕县",
+					"230229" => "黑龙江省齐齐哈尔市克山县",
+					"230230" => "黑龙江省齐齐哈尔市克东县",
+					"230231" => "黑龙江省齐齐哈尔市拜泉县",
+					"230281" => "黑龙江省齐齐哈尔市讷河市",
+					"230300" => "黑龙江省鸡西市",
+					"230301" => "黑龙江省鸡西市市辖区",
+					"230302" => "黑龙江省鸡西市鸡冠区",
+					"230303" => "黑龙江省鸡西市恒山区",
+					"230304" => "黑龙江省鸡西市滴道区",
+					"230305" => "黑龙江省鸡西市梨树区",
+					"230306" => "黑龙江省鸡西市城子河区",
+					"230307" => "黑龙江省鸡西市麻山区",
+					"230321" => "黑龙江省鸡西市鸡东县",
+					"230381" => "黑龙江省鸡西市虎林市",
+					"230382" => "黑龙江省鸡西市密山市",
+					"230400" => "黑龙江省鹤岗市",
+					"230401" => "黑龙江省鹤岗市市辖区",
+					"230402" => "黑龙江省鹤岗市向阳区",
+					"230403" => "黑龙江省鹤岗市工农区",
+					"230404" => "黑龙江省鹤岗市南山区",
+					"230405" => "黑龙江省鹤岗市兴安区",
+					"230406" => "黑龙江省鹤岗市东山区",
+					"230407" => "黑龙江省鹤岗市兴山区",
+					"230421" => "黑龙江省鹤岗市萝北县",
+					"230422" => "黑龙江省鹤岗市绥滨县",
+					"230500" => "黑龙江省双鸭山市",
+					"230501" => "黑龙江省双鸭山市市辖区",
+					"230502" => "黑龙江省双鸭山市尖山区",
+					"230503" => "黑龙江省双鸭山市岭东区",
+					"230505" => "黑龙江省双鸭山市四方台区",
+					"230506" => "黑龙江省双鸭山市宝山区",
+					"230521" => "黑龙江省双鸭山市集贤县",
+					"230522" => "黑龙江省双鸭山市友谊县",
+					"230523" => "黑龙江省双鸭山市宝清县",
+					"230524" => "黑龙江省双鸭山市饶河县",
+					"230600" => "黑龙江省大庆市",
+					"230601" => "黑龙江省大庆市市辖区",
+					"230602" => "黑龙江省大庆市萨尔图区",
+					"230603" => "黑龙江省大庆市龙凤区",
+					"230604" => "黑龙江省大庆市让胡路区",
+					"230605" => "黑龙江省大庆市红岗区",
+					"230606" => "黑龙江省大庆市大同区",
+					"230621" => "黑龙江省大庆市肇州县",
+					"230622" => "黑龙江省大庆市肇源县",
+					"230623" => "黑龙江省大庆市林甸县",
+					"230624" => "黑龙江省大庆市杜尔伯特蒙古族自治县",
+					"230700" => "黑龙江省伊春市",
+					"230701" => "黑龙江省伊春市市辖区",
+					"230702" => "黑龙江省伊春市伊春区",
+					"230703" => "黑龙江省伊春市南岔区",
+					"230704" => "黑龙江省伊春市友好区",
+					"230705" => "黑龙江省伊春市西林区",
+					"230706" => "黑龙江省伊春市翠峦区",
+					"230707" => "黑龙江省伊春市新青区",
+					"230708" => "黑龙江省伊春市美溪区",
+					"230709" => "黑龙江省伊春市金山屯区",
+					"230710" => "黑龙江省伊春市五营区",
+					"230711" => "黑龙江省伊春市乌马河区",
+					"230712" => "黑龙江省伊春市汤旺河区",
+					"230713" => "黑龙江省伊春市带岭区",
+					"230714" => "黑龙江省伊春市乌伊岭区",
+					"230715" => "黑龙江省伊春市红星区",
+					"230716" => "黑龙江省伊春市上甘岭区",
+					"230722" => "黑龙江省伊春市嘉荫县",
+					"230781" => "黑龙江省伊春市铁力市",
+					"230800" => "黑龙江省佳木斯市",
+					"230801" => "黑龙江省佳木斯市市辖区",
+					"230802" => "黑龙江省佳木斯市永红区",
+					"230803" => "黑龙江省佳木斯市向阳区",
+					"230804" => "黑龙江省佳木斯市前进区",
+					"230805" => "黑龙江省佳木斯市东风区",
+					"230811" => "黑龙江省佳木斯市郊区",
+					"230822" => "黑龙江省佳木斯市桦南县",
+					"230826" => "黑龙江省佳木斯市桦川县",
+					"230828" => "黑龙江省佳木斯市汤原县",
+					"230833" => "黑龙江省佳木斯市抚远县",
+					"230881" => "黑龙江省佳木斯市同江市",
+					"230882" => "黑龙江省佳木斯市富锦市",
+					"230900" => "黑龙江省七台河市",
+					"230901" => "黑龙江省七台河市市辖区",
+					"230902" => "黑龙江省七台河市新兴区",
+					"230903" => "黑龙江省七台河市桃山区",
+					"230904" => "黑龙江省七台河市茄子河区",
+					"230921" => "黑龙江省七台河市勃利县",
+					"231000" => "黑龙江省牡丹江市",
+					"231001" => "黑龙江省牡丹江市市辖区",
+					"231002" => "黑龙江省牡丹江市东安区",
+					"231003" => "黑龙江省牡丹江市阳明区",
+					"231004" => "黑龙江省牡丹江市爱民区",
+					"231005" => "黑龙江省牡丹江市西安区",
+					"231024" => "黑龙江省牡丹江市东宁县",
+					"231025" => "黑龙江省牡丹江市林口县",
+					"231081" => "黑龙江省牡丹江市绥芬河市",
+					"231083" => "黑龙江省牡丹江市海林市",
+					"231084" => "黑龙江省牡丹江市宁安市",
+					"231085" => "黑龙江省牡丹江市穆棱市",
+					"231100" => "黑龙江省黑河市",
+					"231101" => "黑龙江省黑河市市辖区",
+					"231102" => "黑龙江省黑河市爱辉区",
+					"231121" => "黑龙江省黑河市嫩江县",
+					"231123" => "黑龙江省黑河市逊克县",
+					"231124" => "黑龙江省黑河市孙吴县",
+					"231181" => "黑龙江省黑河市北安市",
+					"231182" => "黑龙江省黑河市五大连池市",
+					"232300" => "黑龙江省绥化地区",
+					"232301" => "黑龙江省绥化地区绥化市",
+					"232302" => "黑龙江省绥化地区安达市",
+					"232303" => "黑龙江省绥化地区肇东市",
+					"232304" => "黑龙江省绥化地区海伦市",
+					"232324" => "黑龙江省绥化地区望奎县",
+					"232325" => "黑龙江省绥化地区兰西县",
+					"232326" => "黑龙江省绥化地区青冈县",
+					"232330" => "黑龙江省绥化地区庆安县",
+					"232331" => "黑龙江省绥化地区明水县",
+					"232332" => "黑龙江省绥化地区绥棱县",
+					"232700" => "黑龙江省大兴安岭地区",
+					"232721" => "黑龙江省大兴安岭地区呼玛县",
+					"232722" => "黑龙江省大兴安岭地区塔河县",
+					"232723" => "黑龙江省大兴安岭地区漠河县",
+					"310000" => "上海市",
+					"310100" => "上海市市辖区",
+					"310101" => "上海市黄浦区",
+					"310102" => "上海市南市区",
+					"310103" => "上海市卢湾区",
+					"310104" => "上海市徐汇区",
+					"310105" => "上海市长宁区",
+					"310106" => "上海市静安区",
+					"310107" => "上海市普陀区",
+					"310108" => "上海市闸北区",
+					"310109" => "上海市虹口区",
+					"310110" => "上海市杨浦区",
+					"310112" => "上海市闵行区",
+					"310113" => "上海市宝山区",
+					"310114" => "上海市嘉定区",
+					"310115" => "上海市浦东新区",
+					"310116" => "上海市金山区",
+					"310117" => "上海市松江区",
+					"310200" => "上海市县",
+					"310225" => "上海市南汇县",
+					"310226" => "上海市奉贤县",
+					"310229" => "上海市青浦县",
+					"310230" => "上海市崇明县",
+					"320000" => "江苏省",
+					"320100" => "江苏省南京市",
+					"320101" => "江苏省南京市市辖区",
+					"320102" => "江苏省南京市玄武区",
+					"320103" => "江苏省南京市白下区",
+					"320104" => "江苏省南京市秦淮区",
+					"320105" => "江苏省南京市建邺区",
+					"320106" => "江苏省南京市鼓楼区",
+					"320107" => "江苏省南京市下关区",
+					"320111" => "江苏省南京市浦口区",
+					"320112" => "江苏省南京市大厂区",
+					"320113" => "江苏省南京市栖霞区",
+					"320114" => "江苏省南京市雨花台区",
+					"320121" => "江苏省南京市江宁县",
+					"320122" => "江苏省南京市江浦县",
+					"320123" => "江苏省南京市六合县",
+					"320124" => "江苏省南京市溧水县",
+					"320125" => "江苏省南京市高淳县",
+					"320200" => "江苏省无锡市",
+					"320201" => "江苏省无锡市市辖区",
+					"320202" => "江苏省无锡市崇安区",
+					"320203" => "江苏省无锡市南长区",
+					"320204" => "江苏省无锡市北塘区",
+					"320211" => "江苏省无锡市郊区",
+					"320281" => "江苏省无锡市江阴市",
+					"320282" => "江苏省无锡市宜兴市",
+					"320283" => "江苏省无锡市锡山市",
+					"320300" => "江苏省徐州市",
+					"320301" => "江苏省徐州市市辖区",
+					"320302" => "江苏省徐州市鼓楼区",
+					"320303" => "江苏省徐州市云龙区",
+					"320304" => "江苏省徐州市九里区",
+					"320305" => "江苏省徐州市贾汪区",
+					"320311" => "江苏省徐州市泉山区",
+					"320321" => "江苏省徐州市丰县",
+					"320322" => "江苏省徐州市沛县",
+					"320323" => "江苏省徐州市铜山县",
+					"320324" => "江苏省徐州市睢宁县",
+					"320381" => "江苏省徐州市新沂市",
+					"320382" => "江苏省徐州市邳州市",
+					"320400" => "江苏省常州市",
+					"320401" => "江苏省常州市市辖区",
+					"320402" => "江苏省常州市天宁区",
+					"320404" => "江苏省常州市钟楼区",
+					"320405" => "江苏省常州市戚墅堰区",
+					"320411" => "江苏省常州市郊区",
+					"320481" => "江苏省常州市溧阳市",
+					"320482" => "江苏省常州市金坛市",
+					"320483" => "江苏省常州市武进市",
+					"320500" => "江苏省苏州市",
+					"320501" => "江苏省苏州市市辖区",
+					"320502" => "江苏省苏州市沧浪区",
+					"320503" => "江苏省苏州市平江区",
+					"320504" => "江苏省苏州市金阊区",
+					"320511" => "江苏省苏州市郊区",
+					"320581" => "江苏省苏州市常熟市",
+					"320582" => "江苏省苏州市张家港市",
+					"320583" => "江苏省苏州市昆山市",
+					"320584" => "江苏省苏州市吴江市",
+					"320585" => "江苏省苏州市太仓市",
+					"320586" => "江苏省苏州市吴县市",
+					"320600" => "江苏省南通市",
+					"320601" => "江苏省南通市市辖区",
+					"320602" => "江苏省南通市崇川区",
+					"320611" => "江苏省南通市港闸区",
+					"320621" => "江苏省南通市海安县",
+					"320623" => "江苏省南通市如东县",
+					"320681" => "江苏省南通市启东市",
+					"320682" => "江苏省南通市如皋市",
+					"320683" => "江苏省南通市通州市",
+					"320684" => "江苏省南通市海门市",
+					"320700" => "江苏省连云港市",
+					"320701" => "江苏省连云港市市辖区",
+					"320703" => "江苏省连云港市连云区",
+					"320704" => "江苏省连云港市云台区",
+					"320705" => "江苏省连云港市新浦区",
+					"320706" => "江苏省连云港市海州区",
+					"320721" => "江苏省连云港市赣榆县",
+					"320722" => "江苏省连云港市东海县",
+					"320723" => "江苏省连云港市灌云县",
+					"320724" => "江苏省连云港市灌南县",
+					"320800" => "江苏省淮阴市",
+					"320801" => "江苏省淮阴市市辖区",
+					"320802" => "江苏省淮阴市清河区",
+					"320811" => "江苏省淮阴市清浦区",
+					"320821" => "江苏省淮阴市淮阴县",
+					"320826" => "江苏省淮阴市涟水县",
+					"320829" => "江苏省淮阴市洪泽县",
+					"320830" => "江苏省淮阴市盱眙县",
+					"320831" => "江苏省淮阴市金湖县",
+					"320882" => "江苏省淮阴市淮安市",
+					"320900" => "江苏省盐城市",
+					"320901" => "江苏省盐城市市辖区",
+					"320902" => "江苏省盐城市城区",
+					"320921" => "江苏省盐城市响水县",
+					"320922" => "江苏省盐城市滨海县",
+					"320923" => "江苏省盐城市阜宁县",
+					"320924" => "江苏省盐城市射阳县",
+					"320925" => "江苏省盐城市建湖县",
+					"320928" => "江苏省盐城市盐都县",
+					"320981" => "江苏省盐城市东台市",
+					"320982" => "江苏省盐城市大丰市",
+					"321000" => "江苏省扬州市",
+					"321001" => "江苏省扬州市市辖区",
+					"321002" => "江苏省扬州市广陵区",
+					"321011" => "江苏省扬州市郊区",
+					"321023" => "江苏省扬州市宝应县",
+					"321027" => "江苏省扬州市邗江县",
+					"321081" => "江苏省扬州市仪征市",
+					"321084" => "江苏省扬州市高邮市",
+					"321088" => "江苏省扬州市江都市",
+					"321100" => "江苏省镇江市",
+					"321101" => "江苏省镇江市市辖区",
+					"321102" => "江苏省镇江市京口区",
+					"321111" => "江苏省镇江市润州区",
+					"321121" => "江苏省镇江市丹徒县",
+					"321181" => "江苏省镇江市丹阳市",
+					"321182" => "江苏省镇江市扬中市",
+					"321183" => "江苏省镇江市句容市",
+					"321200" => "江苏省泰州市",
+					"321201" => "江苏省泰州市市辖区",
+					"321202" => "江苏省泰州市海陵区",
+					"321203" => "江苏省泰州市高港区",
+					"321281" => "江苏省泰州市兴化市",
+					"321282" => "江苏省泰州市靖江市",
+					"321283" => "江苏省泰州市泰兴市",
+					"321284" => "江苏省泰州市姜堰市",
+					"321300" => "江苏省宿迁市",
+					"321301" => "江苏省宿迁市市辖区",
+					"321302" => "江苏省宿迁市宿城区",
+					"321321" => "江苏省宿迁市宿豫县",
+					"321322" => "江苏省宿迁市沭阳县",
+					"321323" => "江苏省宿迁市泗阳县",
+					"321324" => "江苏省宿迁市泗洪县",
+					"330000" => "浙江省",
+					"330100" => "浙江省杭州市",
+					"330101" => "浙江省杭州市市辖区",
+					"330102" => "浙江省杭州市上城区",
+					"330103" => "浙江省杭州市下城区",
+					"330104" => "浙江省杭州市江干区",
+					"330105" => "浙江省杭州市拱墅区",
+					"330106" => "浙江省杭州市西湖区",
+					"330108" => "浙江省杭州市滨江区",
+					"330122" => "浙江省杭州市桐庐县",
+					"330127" => "浙江省杭州市淳安县",
+					"330181" => "浙江省杭州市萧山市",
+					"330182" => "浙江省杭州市建德市",
+					"330183" => "浙江省杭州市富阳市",
+					"330184" => "浙江省杭州市余杭市",
+					"330185" => "浙江省杭州市临安市",
+					"330200" => "浙江省宁波市",
+					"330201" => "浙江省宁波市市辖区",
+					"330203" => "浙江省宁波市海曙区",
+					"330204" => "浙江省宁波市江东区",
+					"330205" => "浙江省宁波市江北区",
+					"330206" => "浙江省宁波市北仑区",
+					"330211" => "浙江省宁波市镇海区",
+					"330225" => "浙江省宁波市象山县",
+					"330226" => "浙江省宁波市宁海县",
+					"330227" => "浙江省宁波市鄞县",
+					"330281" => "浙江省宁波市余姚市",
+					"330282" => "浙江省宁波市慈溪市",
+					"330283" => "浙江省宁波市奉化市",
+					"330300" => "浙江省温州市",
+					"330301" => "浙江省温州市市辖区",
+					"330302" => "浙江省温州市鹿城区",
+					"330303" => "浙江省温州市龙湾区",
+					"330304" => "浙江省温州市瓯海区",
+					"330322" => "浙江省温州市洞头县",
+					"330324" => "浙江省温州市永嘉县",
+					"330326" => "浙江省温州市平阳县",
+					"330327" => "浙江省温州市苍南县",
+					"330328" => "浙江省温州市文成县",
+					"330329" => "浙江省温州市泰顺县",
+					"330381" => "浙江省温州市瑞安市",
+					"330382" => "浙江省温州市乐清市",
+					"330400" => "浙江省嘉兴市",
+					"330401" => "浙江省嘉兴市市辖区",
+					"330402" => "浙江省嘉兴市秀城区",
+					"330411" => "浙江省嘉兴市郊区",
+					"330421" => "浙江省嘉兴市嘉善县",
+					"330424" => "浙江省嘉兴市海盐县",
+					"330481" => "浙江省嘉兴市海宁市",
+					"330482" => "浙江省嘉兴市平湖市",
+					"330483" => "浙江省嘉兴市桐乡市",
+					"330500" => "浙江省湖州市",
+					"330501" => "浙江省湖州市市辖区",
+					"330521" => "浙江省湖州市德清县",
+					"330522" => "浙江省湖州市长兴县",
+					"330523" => "浙江省湖州市安吉县",
+					"330600" => "浙江省绍兴市",
+					"330601" => "浙江省绍兴市市辖区",
+					"330602" => "浙江省绍兴市越城区",
+					"330621" => "浙江省绍兴市绍兴县",
+					"330624" => "浙江省绍兴市新昌县",
+					"330681" => "浙江省绍兴市诸暨市",
+					"330682" => "浙江省绍兴市上虞市",
+					"330683" => "浙江省绍兴市嵊州市",
+					"330700" => "浙江省金华市",
+					"330701" => "浙江省金华市市辖区",
+					"330702" => "浙江省金华市婺城区",
+					"330721" => "浙江省金华市金华县",
+					"330723" => "浙江省金华市武义县",
+					"330726" => "浙江省金华市浦江县",
+					"330727" => "浙江省金华市磐安县",
+					"330781" => "浙江省金华市兰溪市",
+					"330782" => "浙江省金华市义乌市",
+					"330783" => "浙江省金华市东阳市",
+					"330784" => "浙江省金华市永康市",
+					"330800" => "浙江省衢州市",
+					"330801" => "浙江省衢州市市辖区",
+					"330802" => "浙江省衢州市柯城区",
+					"330821" => "浙江省衢州市衢县",
+					"330822" => "浙江省衢州市常山县",
+					"330824" => "浙江省衢州市开化县",
+					"330825" => "浙江省衢州市龙游县",
+					"330881" => "浙江省衢州市江山市",
+					"330900" => "浙江省舟山市",
+					"330901" => "浙江省舟山市市辖区",
+					"330902" => "浙江省舟山市定海区",
+					"330903" => "浙江省舟山市普陀区",
+					"330921" => "浙江省舟山市岱山县",
+					"330922" => "浙江省舟山市嵊泗县",
+					"331000" => "浙江省台州市",
+					"331001" => "浙江省台州市市辖区",
+					"331002" => "浙江省台州市椒江区",
+					"331003" => "浙江省台州市黄岩区",
+					"331004" => "浙江省台州市路桥区",
+					"331021" => "浙江省台州市玉环县",
+					"331022" => "浙江省台州市三门县",
+					"331023" => "浙江省台州市天台县",
+					"331024" => "浙江省台州市仙居县",
+					"331081" => "浙江省台州市温岭市",
+					"331082" => "浙江省台州市临海市",
+					"332500" => "浙江省丽水地区",
+					"332501" => "浙江省丽水地区丽水市",
+					"332502" => "浙江省丽水地区龙泉市",
+					"332522" => "浙江省丽水地区青田县",
+					"332523" => "浙江省丽水地区云和县",
+					"332525" => "浙江省丽水地区庆元县",
+					"332526" => "浙江省丽水地区缙云县",
+					"332527" => "浙江省丽水地区遂昌县",
+					"332528" => "浙江省丽水地区松阳县",
+					"332529" => "浙江省丽水地区景宁畲族自治县",
+					"340000" => "安徽省",
+					"340100" => "安徽省合肥市",
+					"340101" => "安徽省合肥市市辖区",
+					"340102" => "安徽省合肥市东市区",
+					"340103" => "安徽省合肥市中市区",
+					"340104" => "安徽省合肥市西市区",
+					"340111" => "安徽省合肥市郊区",
+					"340121" => "安徽省合肥市长丰县",
+					"340122" => "安徽省合肥市肥东县",
+					"340123" => "安徽省合肥市肥西县",
+					"340200" => "安徽省芜湖市",
+					"340201" => "安徽省芜湖市市辖区",
+					"340202" => "安徽省芜湖市镜湖区",
+					"340203" => "安徽省芜湖市马塘区",
+					"340204" => "安徽省芜湖市新芜区",
+					"340207" => "安徽省芜湖市鸠江区",
+					"340221" => "安徽省芜湖市芜湖县",
+					"340222" => "安徽省芜湖市繁昌县",
+					"340223" => "安徽省芜湖市南陵县",
+					"340300" => "安徽省蚌埠市",
+					"340301" => "安徽省蚌埠市市辖区",
+					"340302" => "安徽省蚌埠市东市区",
+					"340303" => "安徽省蚌埠市中市区",
+					"340304" => "安徽省蚌埠市西市区",
+					"340311" => "安徽省蚌埠市郊区",
+					"340321" => "安徽省蚌埠市怀远县",
+					"340322" => "安徽省蚌埠市五河县",
+					"340323" => "安徽省蚌埠市固镇县",
+					"340400" => "安徽省淮南市",
+					"340401" => "安徽省淮南市市辖区",
+					"340402" => "安徽省淮南市大通区",
+					"340403" => "安徽省淮南市田家庵区",
+					"340404" => "安徽省淮南市谢家集区",
+					"340405" => "安徽省淮南市八公山区",
+					"340406" => "安徽省淮南市潘集区",
+					"340421" => "安徽省淮南市凤台县",
+					"340500" => "安徽省马鞍山市",
+					"340501" => "安徽省马鞍山市市辖区",
+					"340502" => "安徽省马鞍山市金家庄区",
+					"340503" => "安徽省马鞍山市花山区",
+					"340504" => "安徽省马鞍山市雨山区",
+					"340505" => "安徽省马鞍山市向山区",
+					"340521" => "安徽省马鞍山市当涂县",
+					"340600" => "安徽省淮北市",
+					"340601" => "安徽省淮北市市辖区",
+					"340602" => "安徽省淮北市杜集区",
+					"340603" => "安徽省淮北市相山区",
+					"340604" => "安徽省淮北市烈山区",
+					"340621" => "安徽省淮北市濉溪县",
+					"340700" => "安徽省铜陵市",
+					"340701" => "安徽省铜陵市市辖区",
+					"340702" => "安徽省铜陵市铜官山区",
+					"340703" => "安徽省铜陵市狮子山区",
+					"340711" => "安徽省铜陵市郊区",
+					"340721" => "安徽省铜陵市铜陵县",
+					"340800" => "安徽省安庆市",
+					"340801" => "安徽省安庆市市辖区",
+					"340802" => "安徽省安庆市迎江区",
+					"340803" => "安徽省安庆市大观区",
+					"340811" => "安徽省安庆市郊区",
+					"340822" => "安徽省安庆市怀宁县",
+					"340823" => "安徽省安庆市枞阳县",
+					"340824" => "安徽省安庆市潜山县",
+					"340825" => "安徽省安庆市太湖县",
+					"340826" => "安徽省安庆市宿松县",
+					"340827" => "安徽省安庆市望江县",
+					"340828" => "安徽省安庆市岳西县",
+					"340881" => "安徽省安庆市桐城市",
+					"341000" => "安徽省黄山市",
+					"341001" => "安徽省黄山市市辖区",
+					"341002" => "安徽省黄山市屯溪区",
+					"341003" => "安徽省黄山市黄山区",
+					"341004" => "安徽省黄山市徽州区",
+					"341021" => "安徽省黄山市歙县",
+					"341022" => "安徽省黄山市休宁县",
+					"341023" => "安徽省黄山市黟县",
+					"341024" => "安徽省黄山市祁门县",
+					"341100" => "安徽省滁州市",
+					"341101" => "安徽省滁州市市辖区",
+					"341102" => "安徽省滁州市琅琊区",
+					"341103" => "安徽省滁州市南谯区",
+					"341122" => "安徽省滁州市来安县",
+					"341124" => "安徽省滁州市全椒县",
+					"341125" => "安徽省滁州市定远县",
+					"341126" => "安徽省滁州市凤阳县",
+					"341181" => "安徽省滁州市天长市",
+					"341182" => "安徽省滁州市明光市",
+					"341200" => "安徽省阜阳市",
+					"341201" => "安徽省阜阳市市辖区",
+					"341202" => "安徽省阜阳市颍州区",
+					"341203" => "安徽省阜阳市颍东区",
+					"341204" => "安徽省阜阳市颍泉区",
+					"341221" => "安徽省阜阳市临泉县",
+					"341222" => "安徽省阜阳市太和县",
+					"341223" => "安徽省阜阳市涡阳县",
+					"341224" => "安徽省阜阳市蒙城县",
+					"341225" => "安徽省阜阳市阜南县",
+					"341226" => "安徽省阜阳市颍上县",
+					"341227" => "安徽省阜阳市利辛县",
+					"341281" => "安徽省阜阳市亳州市",
+					"341282" => "安徽省阜阳市界首市",
+					"341300" => "安徽省宿州市",
+					"341301" => "安徽省宿州市市辖区",
+					"341302" => "安徽省宿州市甬桥区",
+					"341321" => "安徽省宿州市砀山县",
+					"341322" => "安徽省宿州市萧县",
+					"341323" => "安徽省宿州市灵璧县",
+					"341324" => "安徽省宿州市泗县",
+					"342400" => "安徽省六安地区",
+					"342401" => "安徽省六安地区六安市",
+					"342422" => "安徽省六安地区寿县",
+					"342423" => "安徽省六安地区霍邱县",
+					"342425" => "安徽省六安地区舒城县",
+					"342426" => "安徽省六安地区金寨县",
+					"342427" => "安徽省六安地区霍山县",
+					"342500" => "安徽省宣城地区",
+					"342501" => "安徽省宣城地区宣州市",
+					"342502" => "安徽省宣城地区宁国市",
+					"342522" => "安徽省宣城地区郎溪县",
+					"342523" => "安徽省宣城地区广德县",
+					"342529" => "安徽省宣城地区泾县",
+					"342530" => "安徽省宣城地区旌德县",
+					"342531" => "安徽省宣城地区绩溪县",
+					"342600" => "安徽省巢湖地区",
+					"342601" => "安徽省巢湖地区巢湖市",
+					"342622" => "安徽省巢湖地区庐江县",
+					"342623" => "安徽省巢湖地区无为县",
+					"342625" => "安徽省巢湖地区含山县",
+					"342626" => "安徽省巢湖地区和县",
+					"342900" => "安徽省池州地区",
+					"342901" => "安徽省池州地区贵池市",
+					"342921" => "安徽省池州地区东至县",
+					"342922" => "安徽省池州地区石台县",
+					"342923" => "安徽省池州地区青阳县",
+					"350000" => "福建省",
+					"350100" => "福建省福州市",
+					"350101" => "福建省福州市市辖区",
+					"350102" => "福建省福州市鼓楼区",
+					"350103" => "福建省福州市台江区",
+					"350104" => "福建省福州市仓山区",
+					"350105" => "福建省福州市马尾区",
+					"350111" => "福建省福州市晋安区",
+					"350121" => "福建省福州市闽侯县",
+					"350122" => "福建省福州市连江县",
+					"350123" => "福建省福州市罗源县",
+					"350124" => "福建省福州市闽清县",
+					"350125" => "福建省福州市永泰县",
+					"350128" => "福建省福州市平潭县",
+					"350181" => "福建省福州市福清市",
+					"350182" => "福建省福州市长乐市",
+					"350200" => "福建省厦门市",
+					"350201" => "福建省厦门市市辖区",
+					"350202" => "福建省厦门市鼓浪屿区",
+					"350203" => "福建省厦门市思明区",
+					"350204" => "福建省厦门市开元区",
+					"350205" => "福建省厦门市杏林区",
+					"350206" => "福建省厦门市湖里区",
+					"350211" => "福建省厦门市集美区",
+					"350212" => "福建省厦门市同安区",
+					"350300" => "福建省莆田市",
+					"350301" => "福建省莆田市市辖区",
+					"350302" => "福建省莆田市城厢区",
+					"350303" => "福建省莆田市涵江区",
+					"350321" => "福建省莆田市莆田县",
+					"350322" => "福建省莆田市仙游县",
+					"350400" => "福建省三明市",
+					"350401" => "福建省三明市市辖区",
+					"350402" => "福建省三明市梅列区",
+					"350403" => "福建省三明市三元区",
+					"350421" => "福建省三明市明溪县",
+					"350423" => "福建省三明市清流县",
+					"350424" => "福建省三明市宁化县",
+					"350425" => "福建省三明市大田县",
+					"350426" => "福建省三明市尤溪县",
+					"350427" => "福建省三明市沙县",
+					"350428" => "福建省三明市将乐县",
+					"350429" => "福建省三明市泰宁县",
+					"350430" => "福建省三明市建宁县",
+					"350481" => "福建省三明市永安市",
+					"350500" => "福建省泉州市",
+					"350501" => "福建省泉州市市辖区",
+					"350502" => "福建省泉州市鲤城区",
+					"350503" => "福建省泉州市丰泽区",
+					"350504" => "福建省泉州市洛江区",
+					"350521" => "福建省泉州市惠安县",
+					"350524" => "福建省泉州市安溪县",
+					"350525" => "福建省泉州市永春县",
+					"350526" => "福建省泉州市德化县",
+					"350527" => "福建省泉州市金门县",
+					"350581" => "福建省泉州市石狮市",
+					"350582" => "福建省泉州市晋江市",
+					"350583" => "福建省泉州市南安市",
+					"350600" => "福建省漳州市",
+					"350601" => "福建省漳州市市辖区",
+					"350602" => "福建省漳州市芗城区",
+					"350603" => "福建省漳州市龙文区",
+					"350622" => "福建省漳州市云霄县",
+					"350623" => "福建省漳州市漳浦县",
+					"350624" => "福建省漳州市诏安县",
+					"350625" => "福建省漳州市长泰县",
+					"350626" => "福建省漳州市东山县",
+					"350627" => "福建省漳州市南靖县",
+					"350628" => "福建省漳州市平和县",
+					"350629" => "福建省漳州市华安县",
+					"350681" => "福建省漳州市龙海市",
+					"350700" => "福建省南平市",
+					"350701" => "福建省南平市市辖区",
+					"350702" => "福建省南平市延平区",
+					"350721" => "福建省南平市顺昌县",
+					"350722" => "福建省南平市浦城县",
+					"350723" => "福建省南平市光泽县",
+					"350724" => "福建省南平市松溪县",
+					"350725" => "福建省南平市政和县",
+					"350781" => "福建省南平市邵武市",
+					"350782" => "福建省南平市武夷山市",
+					"350783" => "福建省南平市建瓯市",
+					"350784" => "福建省南平市建阳市",
+					"350800" => "福建省龙岩市",
+					"350801" => "福建省龙岩市市辖区",
+					"350802" => "福建省龙岩市新罗区",
+					"350821" => "福建省龙岩市长汀县",
+					"350822" => "福建省龙岩市永定县",
+					"350823" => "福建省龙岩市上杭县",
+					"350824" => "福建省龙岩市武平县",
+					"350825" => "福建省龙岩市连城县",
+					"350881" => "福建省龙岩市漳平市",
+					"352200" => "福建省宁德地区",
+					"352201" => "福建省宁德地区宁德市",
+					"352202" => "福建省宁德地区福安市",
+					"352203" => "福建省宁德地区福鼎市",
+					"352225" => "福建省宁德地区霞浦县",
+					"352227" => "福建省宁德地区古田县",
+					"352228" => "福建省宁德地区屏南县",
+					"352229" => "福建省宁德地区寿宁县",
+					"352230" => "福建省宁德地区周宁县",
+					"352231" => "福建省宁德地区柘荣县",
+					"360000" => "江西省",
+					"360100" => "江西省南昌市",
+					"360101" => "江西省南昌市市辖区",
+					"360102" => "江西省南昌市东湖区",
+					"360103" => "江西省南昌市西湖区",
+					"360104" => "江西省南昌市青云谱区",
+					"360105" => "江西省南昌市湾里区",
+					"360111" => "江西省南昌市郊区",
+					"360121" => "江西省南昌市南昌县",
+					"360122" => "江西省南昌市新建县",
+					"360123" => "江西省南昌市安义县",
+					"360124" => "江西省南昌市进贤县",
+					"360200" => "江西省景德镇市",
+					"360201" => "江西省景德镇市市辖区",
+					"360202" => "江西省景德镇市昌江区",
+					"360203" => "江西省景德镇市珠山区",
+					"360222" => "江西省景德镇市浮梁县",
+					"360281" => "江西省景德镇市乐平市",
+					"360300" => "江西省萍乡市",
+					"360301" => "江西省萍乡市市辖区",
+					"360302" => "江西省萍乡市安源区",
+					"360313" => "江西省萍乡市湘东区",
+					"360321" => "江西省萍乡市莲花县",
+					"360322" => "江西省萍乡市上栗县",
+					"360323" => "江西省萍乡市芦溪县",
+					"360400" => "江西省九江市",
+					"360401" => "江西省九江市市辖区",
+					"360402" => "江西省九江市庐山区",
+					"360403" => "江西省九江市浔阳区",
+					"360421" => "江西省九江市九江县",
+					"360423" => "江西省九江市武宁县",
+					"360424" => "江西省九江市修水县",
+					"360425" => "江西省九江市永修县",
+					"360426" => "江西省九江市德安县",
+					"360427" => "江西省九江市星子县",
+					"360428" => "江西省九江市都昌县",
+					"360429" => "江西省九江市湖口县",
+					"360430" => "江西省九江市彭泽县",
+					"360481" => "江西省九江市瑞昌市",
+					"360500" => "江西省新余市",
+					"360501" => "江西省新余市市辖区",
+					"360502" => "江西省新余市渝水区",
+					"360521" => "江西省新余市分宜县",
+					"360600" => "江西省鹰潭市",
+					"360601" => "江西省鹰潭市市辖区",
+					"360602" => "江西省鹰潭市月湖区",
+					"360622" => "江西省鹰潭市余江县",
+					"360681" => "江西省鹰潭市贵溪市",
+					"360700" => "江西省赣州市",
+					"360701" => "江西省赣州市市辖区",
+					"360702" => "江西省赣州市章贡区",
+					"360721" => "江西省赣州市赣县",
+					"360722" => "江西省赣州市信丰县",
+					"360723" => "江西省赣州市大余县",
+					"360724" => "江西省赣州市上犹县",
+					"360725" => "江西省赣州市崇义县",
+					"360726" => "江西省赣州市安远县",
+					"360727" => "江西省赣州市龙南县",
+					"360728" => "江西省赣州市定南县",
+					"360729" => "江西省赣州市全南县",
+					"360730" => "江西省赣州市宁都县",
+					"360731" => "江西省赣州市于都县",
+					"360732" => "江西省赣州市兴国县",
+					"360733" => "江西省赣州市会昌县",
+					"360734" => "江西省赣州市寻乌县",
+					"360735" => "江西省赣州市石城县",
+					"360781" => "江西省赣州市瑞金市",
+					"360782" => "江西省赣州市南康市",
+					"362200" => "江西省宜春地区",
+					"362201" => "江西省宜春地区宜春市",
+					"362202" => "江西省宜春地区丰城市",
+					"362203" => "江西省宜春地区樟树市",
+					"362204" => "江西省宜春地区高安市",
+					"362226" => "江西省宜春地区奉新县",
+					"362227" => "江西省宜春地区万载县",
+					"362228" => "江西省宜春地区上高县",
+					"362229" => "江西省宜春地区宜丰县",
+					"362232" => "江西省宜春地区靖安县",
+					"362233" => "江西省宜春地区铜鼓县",
+					"362300" => "江西省上饶地区",
+					"362301" => "江西省上饶地区上饶市",
+					"362302" => "江西省上饶地区德兴市",
+					"362321" => "江西省上饶地区上饶县",
+					"362322" => "江西省上饶地区广丰县",
+					"362323" => "江西省上饶地区玉山县",
+					"362324" => "江西省上饶地区铅山县",
+					"362325" => "江西省上饶地区横峰县",
+					"362326" => "江西省上饶地区弋阳县",
+					"362329" => "江西省上饶地区余干县",
+					"362330" => "江西省上饶地区波阳县",
+					"362331" => "江西省上饶地区万年县",
+					"362334" => "江西省上饶地区婺源县",
+					"362400" => "江西省吉安地区",
+					"362401" => "江西省吉安地区吉安市",
+					"362402" => "江西省吉安地区井冈山市",
+					"362421" => "江西省吉安地区吉安县",
+					"362422" => "江西省吉安地区吉水县",
+					"362423" => "江西省吉安地区峡江县",
+					"362424" => "江西省吉安地区新干县",
+					"362425" => "江西省吉安地区永丰县",
+					"362426" => "江西省吉安地区泰和县",
+					"362427" => "江西省吉安地区遂川县",
+					"362428" => "江西省吉安地区万安县",
+					"362429" => "江西省吉安地区安福县",
+					"362430" => "江西省吉安地区永新县",
+					"362432" => "江西省吉安地区宁冈县",
+					"362500" => "江西省抚州地区",
+					"362502" => "江西省抚州地区临川市",
+					"362522" => "江西省抚州地区南城县",
+					"362523" => "江西省抚州地区黎川县",
+					"362524" => "江西省抚州地区南丰县",
+					"362525" => "江西省抚州地区崇仁县",
+					"362526" => "江西省抚州地区乐安县",
+					"362527" => "江西省抚州地区宜黄县",
+					"362528" => "江西省抚州地区金溪县",
+					"362529" => "江西省抚州地区资溪县",
+					"362531" => "江西省抚州地区东乡县",
+					"362532" => "江西省抚州地区广昌县",
+					"370000" => "山东省",
+					"370100" => "山东省济南市",
+					"370101" => "山东省济南市市辖区",
+					"370102" => "山东省济南市历下区",
+					"370103" => "山东省济南市市中区",
+					"370104" => "山东省济南市槐荫区",
+					"370105" => "山东省济南市天桥区",
+					"370112" => "山东省济南市历城区",
+					"370123" => "山东省济南市长清县",
+					"370124" => "山东省济南市平阴县",
+					"370125" => "山东省济南市济阳县",
+					"370126" => "山东省济南市商河县",
+					"370181" => "山东省济南市章丘市",
+					"370200" => "山东省青岛市",
+					"370201" => "山东省青岛市市辖区",
+					"370202" => "山东省青岛市市南区",
+					"370203" => "山东省青岛市市北区",
+					"370205" => "山东省青岛市四方区",
+					"370211" => "山东省青岛市黄岛区",
+					"370212" => "山东省青岛市崂山区",
+					"370213" => "山东省青岛市李沧区",
+					"370214" => "山东省青岛市城阳区",
+					"370281" => "山东省青岛市胶州市",
+					"370282" => "山东省青岛市即墨市",
+					"370283" => "山东省青岛市平度市",
+					"370284" => "山东省青岛市胶南市",
+					"370285" => "山东省青岛市莱西市",
+					"370300" => "山东省淄博市",
+					"370301" => "山东省淄博市市辖区",
+					"370302" => "山东省淄博市淄川区",
+					"370303" => "山东省淄博市张店区",
+					"370304" => "山东省淄博市博山区",
+					"370305" => "山东省淄博市临淄区",
+					"370306" => "山东省淄博市周村区",
+					"370321" => "山东省淄博市桓台县",
+					"370322" => "山东省淄博市高青县",
+					"370323" => "山东省淄博市沂源县",
+					"370400" => "山东省枣庄市",
+					"370401" => "山东省枣庄市市辖区",
+					"370402" => "山东省枣庄市市中区",
+					"370403" => "山东省枣庄市薛城区",
+					"370404" => "山东省枣庄市峄城区",
+					"370405" => "山东省枣庄市台儿庄区",
+					"370406" => "山东省枣庄市山亭区",
+					"370481" => "山东省枣庄市滕州市",
+					"370500" => "山东省东营市",
+					"370501" => "山东省东营市市辖区",
+					"370502" => "山东省东营市东营区",
+					"370503" => "山东省东营市河口区",
+					"370521" => "山东省东营市垦利县",
+					"370522" => "山东省东营市利津县",
+					"370523" => "山东省东营市广饶县",
+					"370600" => "山东省烟台市",
+					"370601" => "山东省烟台市市辖区",
+					"370602" => "山东省烟台市芝罘区",
+					"370611" => "山东省烟台市福山区",
+					"370612" => "山东省烟台市牟平区",
+					"370613" => "山东省烟台市莱山区",
+					"370634" => "山东省烟台市长岛县",
+					"370681" => "山东省烟台市龙口市",
+					"370682" => "山东省烟台市莱阳市",
+					"370683" => "山东省烟台市莱州市",
+					"370684" => "山东省烟台市蓬莱市",
+					"370685" => "山东省烟台市招远市",
+					"370686" => "山东省烟台市栖霞市",
+					"370687" => "山东省烟台市海阳市",
+					"370700" => "山东省潍坊市",
+					"370701" => "山东省潍坊市市辖区",
+					"370702" => "山东省潍坊市潍城区",
+					"370703" => "山东省潍坊市寒亭区",
+					"370704" => "山东省潍坊市坊子区",
+					"370705" => "山东省潍坊市奎文区",
+					"370724" => "山东省潍坊市临朐县",
+					"370725" => "山东省潍坊市昌乐县",
+					"370781" => "山东省潍坊市青州市",
+					"370782" => "山东省潍坊市诸城市",
+					"370783" => "山东省潍坊市寿光市",
+					"370784" => "山东省潍坊市安丘市",
+					"370785" => "山东省潍坊市高密市",
+					"370786" => "山东省潍坊市昌邑市",
+					"370800" => "山东省济宁市",
+					"370801" => "山东省济宁市市辖区",
+					"370802" => "山东省济宁市市中区",
+					"370811" => "山东省济宁市任城区",
+					"370826" => "山东省济宁市微山县",
+					"370827" => "山东省济宁市鱼台县",
+					"370828" => "山东省济宁市金乡县",
+					"370829" => "山东省济宁市嘉祥县",
+					"370830" => "山东省济宁市汶上县",
+					"370831" => "山东省济宁市泗水县",
+					"370832" => "山东省济宁市梁山县",
+					"370881" => "山东省济宁市曲阜市",
+					"370882" => "山东省济宁市兖州市",
+					"370883" => "山东省济宁市邹城市",
+					"370900" => "山东省泰安市",
+					"370901" => "山东省泰安市市辖区",
+					"370902" => "山东省泰安市泰山区",
+					"370911" => "山东省泰安市郊区",
+					"370921" => "山东省泰安市宁阳县",
+					"370923" => "山东省泰安市东平县",
+					"370982" => "山东省泰安市新泰市",
+					"370983" => "山东省泰安市肥城市",
+					"371000" => "山东省威海市",
+					"371001" => "山东省威海市市辖区",
+					"371002" => "山东省威海市环翠区",
+					"371081" => "山东省威海市文登市",
+					"371082" => "山东省威海市荣成市",
+					"371083" => "山东省威海市乳山市",
+					"371100" => "山东省日照市",
+					"371101" => "山东省日照市市辖区",
+					"371102" => "山东省日照市东港区",
+					"371121" => "山东省日照市五莲县",
+					"371122" => "山东省日照市莒县",
+					"371200" => "山东省莱芜市",
+					"371201" => "山东省莱芜市市辖区",
+					"371202" => "山东省莱芜市莱城区",
+					"371203" => "山东省莱芜市钢城区",
+					"371300" => "山东省临沂市",
+					"371301" => "山东省临沂市市辖区",
+					"371302" => "山东省临沂市兰山区",
+					"371311" => "山东省临沂市罗庄区",
+					"371312" => "山东省临沂市河东区",
+					"371321" => "山东省临沂市沂南县",
+					"371322" => "山东省临沂市郯城县",
+					"371323" => "山东省临沂市沂水县",
+					"371324" => "山东省临沂市苍山县",
+					"371325" => "山东省临沂市费县",
+					"371326" => "山东省临沂市平邑县",
+					"371327" => "山东省临沂市莒南县",
+					"371328" => "山东省临沂市蒙阴县",
+					"371329" => "山东省临沂市临沭县",
+					"371400" => "山东省德州市",
+					"371401" => "山东省德州市市辖区",
+					"371402" => "山东省德州市德城区",
+					"371421" => "山东省德州市陵县",
+					"371422" => "山东省德州市宁津县",
+					"371423" => "山东省德州市庆云县",
+					"371424" => "山东省德州市临邑县",
+					"371425" => "山东省德州市齐河县",
+					"371426" => "山东省德州市平原县",
+					"371427" => "山东省德州市夏津县",
+					"371428" => "山东省德州市武城县",
+					"371481" => "山东省德州市乐陵市",
+					"371482" => "山东省德州市禹城市",
+					"371500" => "山东省聊城市",
+					"371501" => "山东省聊城市市辖区",
+					"371502" => "山东省聊城市东昌府区",
+					"371521" => "山东省聊城市阳谷县",
+					"371522" => "山东省聊城市莘县",
+					"371523" => "山东省聊城市茌平县",
+					"371524" => "山东省聊城市东阿县",
+					"371525" => "山东省聊城市冠县",
+					"371526" => "山东省聊城市高唐县",
+					"371581" => "山东省聊城市临清市",
+					"372300" => "山东省滨州地区",
+					"372301" => "山东省滨州地区滨州市",
+					"372321" => "山东省滨州地区惠民县",
+					"372323" => "山东省滨州地区阳信县",
+					"372324" => "山东省滨州地区无棣县",
+					"372325" => "山东省滨州地区沾化县",
+					"372328" => "山东省滨州地区博兴县",
+					"372330" => "山东省滨州地区邹平县",
+					"372900" => "山东省菏泽地区",
+					"372901" => "山东省菏泽地区菏泽市",
+					"372922" => "山东省菏泽地区曹县",
+					"372923" => "山东省菏泽地区定陶县",
+					"372924" => "山东省菏泽地区成武县",
+					"372925" => "山东省菏泽地区单县",
+					"372926" => "山东省菏泽地区巨野县",
+					"372928" => "山东省菏泽地区郓城县",
+					"372929" => "山东省菏泽地区鄄城县",
+					"372930" => "山东省菏泽地区东明县",
+					"410000" => "河南省",
+					"410100" => "河南省郑州市",
+					"410101" => "河南省郑州市市辖区",
+					"410102" => "河南省郑州市中原区",
+					"410103" => "河南省郑州市二七区",
+					"410104" => "河南省郑州市管城回族区",
+					"410105" => "河南省郑州市金水区",
+					"410106" => "河南省郑州市上街区",
+					"410108" => "河南省郑州市邙山区",
+					"410122" => "河南省郑州市中牟县",
+					"410181" => "河南省郑州市巩义市",
+					"410182" => "河南省郑州市荥阳市",
+					"410183" => "河南省郑州市新密市",
+					"410184" => "河南省郑州市新郑市",
+					"410185" => "河南省郑州市登封市",
+					"410200" => "河南省开封市",
+					"410201" => "河南省开封市市辖区",
+					"410202" => "河南省开封市龙亭区",
+					"410203" => "河南省开封市顺河回族区",
+					"410204" => "河南省开封市鼓楼区",
+					"410205" => "河南省开封市南关区",
+					"410211" => "河南省开封市郊区",
+					"410221" => "河南省开封市杞县",
+					"410222" => "河南省开封市通许县",
+					"410223" => "河南省开封市尉氏县",
+					"410224" => "河南省开封市开封县",
+					"410225" => "河南省开封市兰考县",
+					"410300" => "河南省洛阳市",
+					"410301" => "河南省洛阳市市辖区",
+					"410302" => "河南省洛阳市老城区",
+					"410303" => "河南省洛阳市西工区",
+					"410304" => "河南省洛阳市廛河回族区",
+					"410305" => "河南省洛阳市涧西区",
+					"410306" => "河南省洛阳市吉利区",
+					"410311" => "河南省洛阳市郊区",
+					"410322" => "河南省洛阳市孟津县",
+					"410323" => "河南省洛阳市新安县",
+					"410324" => "河南省洛阳市栾川县",
+					"410325" => "河南省洛阳市嵩县",
+					"410326" => "河南省洛阳市汝阳县",
+					"410327" => "河南省洛阳市宜阳县",
+					"410328" => "河南省洛阳市洛宁县",
+					"410329" => "河南省洛阳市伊川县",
+					"410381" => "河南省洛阳市偃师市",
+					"410400" => "河南省平顶山市",
+					"410401" => "河南省平顶山市市辖区",
+					"410402" => "河南省平顶山市新华区",
+					"410403" => "河南省平顶山市卫东区",
+					"410404" => "河南省平顶山市石龙区",
+					"410411" => "河南省平顶山市湛河区",
+					"410421" => "河南省平顶山市宝丰县",
+					"410422" => "河南省平顶山市叶县",
+					"410423" => "河南省平顶山市鲁山县",
+					"410425" => "河南省平顶山市郏县",
+					"410481" => "河南省平顶山市舞钢市",
+					"410482" => "河南省平顶山市汝州市",
+					"410500" => "河南省安阳市",
+					"410501" => "河南省安阳市市辖区",
+					"410502" => "河南省安阳市文峰区",
+					"410503" => "河南省安阳市北关区",
+					"410504" => "河南省安阳市铁西区",
+					"410511" => "河南省安阳市郊区",
+					"410522" => "河南省安阳市安阳县",
+					"410523" => "河南省安阳市汤阴县",
+					"410526" => "河南省安阳市滑县",
+					"410527" => "河南省安阳市内黄县",
+					"410581" => "河南省安阳市林州市",
+					"410600" => "河南省鹤壁市",
+					"410601" => "河南省鹤壁市市辖区",
+					"410602" => "河南省鹤壁市鹤山区",
+					"410603" => "河南省鹤壁市山城区",
+					"410611" => "河南省鹤壁市郊区",
+					"410621" => "河南省鹤壁市浚县",
+					"410622" => "河南省鹤壁市淇县",
+					"410700" => "河南省新乡市",
+					"410701" => "河南省新乡市市辖区",
+					"410702" => "河南省新乡市红旗区",
+					"410703" => "河南省新乡市新华区",
+					"410704" => "河南省新乡市北站区",
+					"410711" => "河南省新乡市郊区",
+					"410721" => "河南省新乡市新乡县",
+					"410724" => "河南省新乡市获嘉县",
+					"410725" => "河南省新乡市原阳县",
+					"410726" => "河南省新乡市延津县",
+					"410727" => "河南省新乡市封丘县",
+					"410728" => "河南省新乡市长垣县",
+					"410781" => "河南省新乡市卫辉市",
+					"410782" => "河南省新乡市辉县市",
+					"410800" => "河南省焦作市",
+					"410801" => "河南省焦作市市辖区",
+					"410802" => "河南省焦作市解放区",
+					"410803" => "河南省焦作市中站区",
+					"410804" => "河南省焦作市马村区",
+					"410811" => "河南省焦作市山阳区",
+					"410821" => "河南省焦作市修武县",
+					"410822" => "河南省焦作市博爱县",
+					"410823" => "河南省焦作市武陟县",
+					"410825" => "河南省焦作市温县",
+					"410881" => "河南省焦作市济源市",
+					"410882" => "河南省焦作市沁阳市",
+					"410883" => "河南省焦作市孟州市",
+					"410900" => "河南省濮阳市",
+					"410901" => "河南省濮阳市市辖区",
+					"410902" => "河南省濮阳市市区",
+					"410922" => "河南省濮阳市清丰县",
+					"410923" => "河南省濮阳市南乐县",
+					"410926" => "河南省濮阳市范县",
+					"410927" => "河南省濮阳市台前县",
+					"410928" => "河南省濮阳市濮阳县",
+					"411000" => "河南省许昌市",
+					"411001" => "河南省许昌市市辖区",
+					"411002" => "河南省许昌市魏都区",
+					"411023" => "河南省许昌市许昌县",
+					"411024" => "河南省许昌市鄢陵县",
+					"411025" => "河南省许昌市襄城县",
+					"411081" => "河南省许昌市禹州市",
+					"411082" => "河南省许昌市长葛市",
+					"411100" => "河南省漯河市",
+					"411101" => "河南省漯河市市辖区",
+					"411102" => "河南省漯河市源汇区",
+					"411121" => "河南省漯河市舞阳县",
+					"411122" => "河南省漯河市临颍县",
+					"411123" => "河南省漯河市郾城县",
+					"411200" => "河南省三门峡市",
+					"411201" => "河南省三门峡市市辖区",
+					"411202" => "河南省三门峡市湖滨区",
+					"411221" => "河南省三门峡市渑池县",
+					"411222" => "河南省三门峡市陕县",
+					"411224" => "河南省三门峡市卢氏县",
+					"411281" => "河南省三门峡市义马市",
+					"411282" => "河南省三门峡市灵宝市",
+					"411300" => "河南省南阳市",
+					"411301" => "河南省南阳市市辖区",
+					"411302" => "河南省南阳市宛城区",
+					"411303" => "河南省南阳市卧龙区",
+					"411321" => "河南省南阳市南召县",
+					"411322" => "河南省南阳市方城县",
+					"411323" => "河南省南阳市西峡县",
+					"411324" => "河南省南阳市镇平县",
+					"411325" => "河南省南阳市内乡县",
+					"411326" => "河南省南阳市淅川县",
+					"411327" => "河南省南阳市社旗县",
+					"411328" => "河南省南阳市唐河县",
+					"411329" => "河南省南阳市新野县",
+					"411330" => "河南省南阳市桐柏县",
+					"411381" => "河南省南阳市邓州市",
+					"411400" => "河南省商丘市",
+					"411401" => "河南省商丘市市辖区",
+					"411402" => "河南省商丘市梁园区",
+					"411403" => "河南省商丘市睢阳区",
+					"411421" => "河南省商丘市民权县",
+					"411422" => "河南省商丘市睢县",
+					"411423" => "河南省商丘市宁陵县",
+					"411424" => "河南省商丘市柘城县",
+					"411425" => "河南省商丘市虞城县",
+					"411426" => "河南省商丘市夏邑县",
+					"411481" => "河南省商丘市永城市",
+					"411500" => "河南省信阳市",
+					"411501" => "河南省信阳市市辖区",
+					"411502" => "河南省信阳市师河区",
+					"411503" => "河南省信阳市平桥区",
+					"411521" => "河南省信阳市罗山县",
+					"411522" => "河南省信阳市光山县",
+					"411523" => "河南省信阳市新县",
+					"411524" => "河南省信阳市商城县",
+					"411525" => "河南省信阳市固始县",
+					"411526" => "河南省信阳市潢川县",
+					"411527" => "河南省信阳市淮滨县",
+					"411528" => "河南省信阳市息县",
+					"412700" => "河南省周口地区",
+					"412701" => "河南省周口地区周口市",
+					"412702" => "河南省周口地区项城市",
+					"412721" => "河南省周口地区扶沟县",
+					"412722" => "河南省周口地区西华县",
+					"412723" => "河南省周口地区商水县",
+					"412724" => "河南省周口地区太康县",
+					"412725" => "河南省周口地区鹿邑县",
+					"412726" => "河南省周口地区郸城县",
+					"412727" => "河南省周口地区淮阳县",
+					"412728" => "河南省周口地区沈丘县",
+					"412800" => "河南省驻马店地区",
+					"412801" => "河南省驻马店地区驻马店市",
+					"412821" => "河南省驻马店地区确山县",
+					"412822" => "河南省驻马店地区泌阳县",
+					"412823" => "河南省驻马店地区遂平县",
+					"412824" => "河南省驻马店地区西平县",
+					"412825" => "河南省驻马店地区上蔡县",
+					"412826" => "河南省驻马店地区汝南县",
+					"412827" => "河南省驻马店地区平舆县",
+					"412828" => "河南省驻马店地区新蔡县",
+					"412829" => "河南省驻马店地区正阳县",
+					"420000" => "湖北省",
+					"420100" => "湖北省武汉市",
+					"420101" => "湖北省武汉市市辖区",
+					"420102" => "湖北省武汉市江岸区",
+					"420103" => "湖北省武汉市江汉区",
+					"420104" => "湖北省武汉市乔口区",
+					"420105" => "湖北省武汉市汉阳区",
+					"420106" => "湖北省武汉市武昌区",
+					"420107" => "湖北省武汉市青山区",
+					"420111" => "湖北省武汉市洪山区",
+					"420112" => "湖北省武汉市东西湖区",
+					"420113" => "湖北省武汉市汉南区",
+					"420114" => "湖北省武汉市蔡甸区",
+					"420115" => "湖北省武汉市江夏区",
+					"420116" => "湖北省武汉市黄陂区",
+					"420117" => "湖北省武汉市新洲区",
+					"420200" => "湖北省黄石市",
+					"420201" => "湖北省黄石市市辖区",
+					"420202" => "湖北省黄石市黄石港区",
+					"420203" => "湖北省黄石市石灰窑区",
+					"420204" => "湖北省黄石市下陆区",
+					"420205" => "湖北省黄石市铁山区",
+					"420222" => "湖北省黄石市阳新县",
+					"420281" => "湖北省黄石市大冶市",
+					"420300" => "湖北省十堰市",
+					"420301" => "湖北省十堰市市辖区",
+					"420302" => "湖北省十堰市茅箭区",
+					"420303" => "湖北省十堰市张湾区",
+					"420321" => "湖北省十堰市郧县",
+					"420322" => "湖北省十堰市郧西县",
+					"420323" => "湖北省十堰市竹山县",
+					"420324" => "湖北省十堰市竹溪县",
+					"420325" => "湖北省十堰市房县",
+					"420381" => "湖北省十堰市丹江口市",
+					"420500" => "湖北省宜昌市",
+					"420501" => "湖北省宜昌市市辖区",
+					"420502" => "湖北省宜昌市西陵区",
+					"420503" => "湖北省宜昌市伍家岗区",
+					"420504" => "湖北省宜昌市点军区",
+					"420505" => "湖北省宜昌市虎亭区",
+					"420521" => "湖北省宜昌市宜昌县",
+					"420525" => "湖北省宜昌市远安县",
+					"420526" => "湖北省宜昌市兴山县",
+					"420527" => "湖北省宜昌市秭归县",
+					"420528" => "湖北省宜昌市长阳土家族自治县",
+					"420529" => "湖北省宜昌市五峰土家族自治县",
+					"420581" => "湖北省宜昌市宜都市",
+					"420582" => "湖北省宜昌市当阳市",
+					"420583" => "湖北省宜昌市枝江市",
+					"420600" => "湖北省襄樊市",
+					"420601" => "湖北省襄樊市市辖区",
+					"420602" => "湖北省襄樊市襄城区",
+					"420606" => "湖北省襄樊市樊城区",
+					"420621" => "湖北省襄樊市襄阳县",
+					"420624" => "湖北省襄樊市南漳县",
+					"420625" => "湖北省襄樊市谷城县",
+					"420626" => "湖北省襄樊市保康县",
+					"420682" => "湖北省襄樊市老河口市",
+					"420683" => "湖北省襄樊市枣阳市",
+					"420684" => "湖北省襄樊市宜城市",
+					"420700" => "湖北省鄂州市",
+					"420701" => "湖北省鄂州市市辖区",
+					"420702" => "湖北省鄂州市梁子湖区",
+					"420703" => "湖北省鄂州市华容区",
+					"420704" => "湖北省鄂州市鄂城区",
+					"420800" => "湖北省荆门市",
+					"420801" => "湖北省荆门市市辖区",
+					"420802" => "湖北省荆门市东宝区",
+					"420821" => "湖北省荆门市京山县",
+					"420822" => "湖北省荆门市沙洋县",
+					"420881" => "湖北省荆门市钟祥市",
+					"420900" => "湖北省孝感市",
+					"420901" => "湖北省孝感市市辖区",
+					"420902" => "湖北省孝感市孝南区",
+					"420921" => "湖北省孝感市孝昌县",
+					"420922" => "湖北省孝感市大悟县",
+					"420923" => "湖北省孝感市云梦县",
+					"420981" => "湖北省孝感市应城市",
+					"420982" => "湖北省孝感市安陆市",
+					"420983" => "湖北省孝感市广水市",
+					"420984" => "湖北省孝感市汉川市",
+					"421000" => "湖北省荆州市",
+					"421001" => "湖北省荆州市市辖区",
+					"421002" => "湖北省荆州市沙市区",
+					"421003" => "湖北省荆州市荆州区",
+					"421022" => "湖北省荆州市公安县",
+					"421023" => "湖北省荆州市监利县",
+					"421024" => "湖北省荆州市江陵县",
+					"421081" => "湖北省荆州市石首市",
+					"421083" => "湖北省荆州市洪湖市",
+					"421087" => "湖北省荆州市松滋市",
+					"421100" => "湖北省黄冈市",
+					"421101" => "湖北省黄冈市市辖区",
+					"421102" => "湖北省黄冈市黄州区",
+					"421121" => "湖北省黄冈市团风县",
+					"421122" => "湖北省黄冈市红安县",
+					"421123" => "湖北省黄冈市罗田县",
+					"421124" => "湖北省黄冈市英山县",
+					"421125" => "湖北省黄冈市浠水县",
+					"421126" => "湖北省黄冈市蕲春县",
+					"421127" => "湖北省黄冈市黄梅县",
+					"421181" => "湖北省黄冈市麻城市",
+					"421182" => "湖北省黄冈市武穴市",
+					"421200" => "湖北省咸宁市",
+					"421201" => "湖北省咸宁市市辖区",
+					"421202" => "湖北省咸宁市咸安区",
+					"421221" => "湖北省咸宁市嘉鱼县",
+					"421222" => "湖北省咸宁市通城县",
+					"421223" => "湖北省咸宁市崇阳县",
+					"421224" => "湖北省咸宁市通山县",
+					"422800" => "湖北省施土家族苗族自治州",
+					"422801" => "湖北省恩施土家族苗族自治州恩施县",
+					"422802" => "湖北省恩施土家族苗族自治州利川市",
+					"422822" => "湖北省恩施土家族苗族自治州建始县",
+					"422823" => "湖北省恩施土家族苗族自治州巴东县",
+					"422825" => "湖北省恩施土家族苗族自治州宣恩县",
+					"422826" => "湖北省恩施土家族苗族自治州咸丰县",
+					"422827" => "湖北省恩施土家族苗族自治州来凤县",
+					"422828" => "湖北省恩施土家族苗族自治州鹤峰县",
+					"429000" => "湖北省省直辖县级行政单位",
+					"429001" => "湖北省随州市",
+					"429004" => "湖北省仙桃市",
+					"429005" => "湖北省潜江市",
+					"429006" => "湖北省天门市",
+					"429021" => "湖北省神农架林区",
+					"430000" => "湖南省",
+					"430100" => "湖南省长沙市",
+					"430101" => "湖南省长沙市市辖区",
+					"430102" => "湖南省长沙市芙蓉区",
+					"430103" => "湖南省长沙市天心区",
+					"430104" => "湖南省长沙市岳麓区",
+					"430105" => "湖南省长沙市开福区",
+					"430111" => "湖南省长沙市雨花区",
+					"430121" => "湖南省长沙市长沙县",
+					"430122" => "湖南省长沙市望城县",
+					"430124" => "湖南省长沙市宁乡县",
+					"430181" => "湖南省长沙市浏阳市",
+					"430200" => "湖南省株洲市",
+					"430201" => "湖南省株洲市市辖区",
+					"430202" => "湖南省株洲市荷塘区",
+					"430203" => "湖南省株洲市芦淞区",
+					"430204" => "湖南省株洲市石峰区",
+					"430211" => "湖南省株洲市天元区",
+					"430221" => "湖南省株洲市株洲县",
+					"430223" => "湖南省株洲市攸县",
+					"430224" => "湖南省株洲市茶陵县",
+					"430225" => "湖南省株洲市炎陵县",
+					"430281" => "湖南省株洲市醴陵市",
+					"430300" => "湖南省湘潭市",
+					"430301" => "湖南省湘潭市市辖区",
+					"430302" => "湖南省湘潭市雨湖区",
+					"430304" => "湖南省湘潭市岳塘区",
+					"430321" => "湖南省湘潭市湘潭县",
+					"430381" => "湖南省湘潭市湘乡市",
+					"430382" => "湖南省湘潭市韶山市",
+					"430400" => "湖南省衡阳市",
+					"430401" => "湖南省衡阳市市辖区",
+					"430402" => "湖南省衡阳市江东区",
+					"430403" => "湖南省衡阳市城南区",
+					"430404" => "湖南省衡阳市城北区",
+					"430411" => "湖南省衡阳市郊区",
+					"430412" => "湖南省衡阳市南岳区",
+					"430421" => "湖南省衡阳市衡阳县",
+					"430422" => "湖南省衡阳市衡南县",
+					"430423" => "湖南省衡阳市衡山县",
+					"430424" => "湖南省衡阳市衡东县",
+					"430426" => "湖南省衡阳市祁东县",
+					"430481" => "湖南省衡阳市耒阳市",
+					"430482" => "湖南省衡阳市常宁市",
+					"430500" => "湖南省邵阳市",
+					"430501" => "湖南省邵阳市市辖区",
+					"430502" => "湖南省邵阳市双清区",
+					"430503" => "湖南省邵阳市大祥区",
+					"430511" => "湖南省邵阳市北塔区",
+					"430521" => "湖南省邵阳市邵东县",
+					"430522" => "湖南省邵阳市新邵县",
+					"430523" => "湖南省邵阳市邵阳县",
+					"430524" => "湖南省邵阳市隆回县",
+					"430525" => "湖南省邵阳市洞口县",
+					"430527" => "湖南省邵阳市绥宁县",
+					"430528" => "湖南省邵阳市新宁县",
+					"430529" => "湖南省邵阳市城步苗族自治县",
+					"430581" => "湖南省邵阳市武冈市",
+					"430600" => "湖南省岳阳市",
+					"430601" => "湖南省岳阳市市辖区",
+					"430602" => "湖南省岳阳市岳阳楼区",
+					"430603" => "湖南省岳阳市云溪区",
+					"430611" => "湖南省岳阳市君山区",
+					"430621" => "湖南省岳阳市岳阳县",
+					"430623" => "湖南省岳阳市华容县",
+					"430624" => "湖南省岳阳市湘阴县",
+					"430626" => "湖南省岳阳市平江县",
+					"430681" => "湖南省岳阳市汨罗市",
+					"430682" => "湖南省岳阳市临湘市",
+					"430700" => "湖南省常德市",
+					"430701" => "湖南省常德市市辖区",
+					"430702" => "湖南省常德市武陵区",
+					"430703" => "湖南省常德市鼎城区",
+					"430721" => "湖南省常德市安乡县",
+					"430722" => "湖南省常德市汉寿县",
+					"430723" => "湖南省常德市澧县",
+					"430724" => "湖南省常德市临澧县",
+					"430725" => "湖南省常德市桃源县",
+					"430726" => "湖南省常德市石门县",
+					"430781" => "湖南省常德市津市市",
+					"430800" => "湖南省张家界市",
+					"430801" => "湖南省张家界市市辖区",
+					"430802" => "湖南省张家界市永定区",
+					"430811" => "湖南省张家界市武陵源区",
+					"430821" => "湖南省张家界市慈利县",
+					"430822" => "湖南省张家界市桑植县",
+					"430900" => "湖南省益阳市",
+					"430901" => "湖南省益阳市市辖区",
+					"430902" => "湖南省益阳市资阳区",
+					"430903" => "湖南省益阳市赫山区",
+					"430921" => "湖南省益阳市南县",
+					"430922" => "湖南省益阳市桃江县",
+					"430923" => "湖南省益阳市安化县",
+					"430981" => "湖南省益阳市沅江市",
+					"431000" => "湖南省郴州市",
+					"431001" => "湖南省郴州市市辖区",
+					"431002" => "湖南省郴州市北湖区",
+					"431003" => "湖南省郴州市苏仙区",
+					"431021" => "湖南省郴州市桂阳县",
+					"431022" => "湖南省郴州市宜章县",
+					"431023" => "湖南省郴州市永兴县",
+					"431024" => "湖南省郴州市嘉禾县",
+					"431025" => "湖南省郴州市临武县",
+					"431026" => "湖南省郴州市汝城县",
+					"431027" => "湖南省郴州市桂东县",
+					"431028" => "湖南省郴州市安仁县",
+					"431081" => "湖南省郴州市资兴市",
+					"431100" => "湖南省永州市",
+					"431101" => "湖南省永州市市辖区",
+					"431102" => "湖南省永州市芝山区",
+					"431103" => "湖南省永州市冷水滩区",
+					"431121" => "湖南省永州市祁阳县",
+					"431122" => "湖南省永州市东安县",
+					"431123" => "湖南省永州市双牌县",
+					"431124" => "湖南省永州市道县",
+					"431125" => "湖南省永州市江永县",
+					"431126" => "湖南省永州市宁远县",
+					"431127" => "湖南省永州市蓝山县",
+					"431128" => "湖南省永州市新田县",
+					"431129" => "湖南省永州市江华瑶族自治县",
+					"431200" => "湖南省怀化市",
+					"431201" => "湖南省怀化市市辖区",
+					"431202" => "湖南省怀化市鹤城区",
+					"431221" => "湖南省怀化市中方县",
+					"431222" => "湖南省怀化市沅陵县",
+					"431223" => "湖南省怀化市辰溪县",
+					"431224" => "湖南省怀化市溆浦县",
+					"431225" => "湖南省怀化市会同县",
+					"431226" => "湖南省怀化市麻阳苗族自治县",
+					"431227" => "湖南省怀化市新晃侗族自治县",
+					"431228" => "湖南省怀化市芷江侗族自治县",
+					"431229" => "湖南省怀化市靖州苗族侗族自治县",
+					"431230" => "湖南省怀化市通道侗族自治县",
+					"431281" => "湖南省怀化市洪江市",
+					"432500" => "湖南省娄底地区",
+					"432501" => "湖南省娄底地区娄底市",
+					"432502" => "湖南省娄底地区冷水江市",
+					"432503" => "湖南省娄底地区涟源市",
+					"432522" => "湖南省娄底地区双峰县",
+					"432524" => "湖南省娄底地区新化县",
+					"433000" => "湖南省怀化市",
+					"433001" => "湖南省怀化市",
+					"433100" => "湖南省湘西土家族苗族自治州",
+					"433101" => "湖南省湘西土家族苗族自治州吉首市",
+					"433122" => "湖南省湘西土家族苗族自治州泸溪县",
+					"433123" => "湖南省湘西土家族苗族自治州凤凰县",
+					"433124" => "湖南省湘西土家族苗族自治州花垣县",
+					"433125" => "湖南省湘西土家族苗族自治州保靖县",
+					"433126" => "湖南省湘西土家族苗族自治州古丈县",
+					"433127" => "湖南省湘西土家族苗族自治州永顺县",
+					"433130" => "湖南省湘西土家族苗族自治州龙山县",
+					"440000" => "广东省",
+					"440100" => "广东省广州市",
+					"440101" => "广东省广州市市辖区",
+					"440102" => "广东省广州市东山区",
+					"440103" => "广东省广州市荔湾区",
+					"440104" => "广东省广州市越秀区",
+					"440105" => "广东省广州市海珠区",
+					"440106" => "广东省广州市天河区",
+					"440107" => "广东省广州市芳村区",
+					"440111" => "广东省广州市白云区",
+					"440112" => "广东省广州市黄埔区",
+					"440181" => "广东省广州市番禺市",
+					"440182" => "广东省广州市花都市",
+					"440183" => "广东省广州市增城市",
+					"440184" => "广东省广州市从化市",
+					"440200" => "广东省韶关市",
+					"440201" => "广东省韶关市市辖区",
+					"440202" => "广东省韶关市北江区",
+					"440203" => "广东省韶关市武江区",
+					"440204" => "广东省韶关市浈江区",
+					"440221" => "广东省韶关市曲江县",
+					"440222" => "广东省韶关市始兴县",
+					"440224" => "广东省韶关市仁化县",
+					"440229" => "广东省韶关市翁源县",
+					"440232" => "广东省韶关市乳源瑶族自治县",
+					"440233" => "广东省韶关市新丰县",
+					"440281" => "广东省韶关市乐昌市",
+					"440282" => "广东省韶关市南雄市",
+					"440300" => "广东省深圳市",
+					"440301" => "广东省深圳市市辖区",
+					"440303" => "广东省深圳市罗湖区",
+					"440304" => "广东省深圳市福田区",
+					"440305" => "广东省深圳市南山区",
+					"440306" => "广东省深圳市宝安区",
+					"440307" => "广东省深圳市龙岗区",
+					"440308" => "广东省深圳市盐田区",
+					"440400" => "广东省珠海市",
+					"440401" => "广东省珠海市市辖区",
+					"440402" => "广东省珠海市香洲区",
+					"440421" => "广东省珠海市斗门县",
+					"440500" => "广东省汕头市",
+					"440501" => "广东省汕头市市辖区",
+					"440506" => "广东省汕头市达濠区",
+					"440507" => "广东省汕头市龙湖区",
+					"440508" => "广东省汕头市金园区",
+					"440509" => "广东省汕头市升平区",
+					"440510" => "广东省汕头市河浦区",
+					"440523" => "广东省汕头市南澳县",
+					"440582" => "广东省汕头市潮阳市",
+					"440583" => "广东省汕头市澄海市",
+					"440600" => "广东省佛山市",
+					"440601" => "广东省佛山市市辖区",
+					"440602" => "广东省佛山市城区",
+					"440603" => "广东省佛山市石湾区",
+					"440681" => "广东省佛山市顺德市",
+					"440682" => "广东省佛山市南海市",
+					"440683" => "广东省佛山市三水市",
+					"440684" => "广东省佛山市高明市",
+					"440700" => "广东省江门市",
+					"440701" => "广东省江门市市辖区",
+					"440703" => "广东省江门市蓬江区",
+					"440704" => "广东省江门市江海区",
+					"440781" => "广东省江门市台山市",
+					"440782" => "广东省江门市新会市",
+					"440783" => "广东省江门市开平市",
+					"440784" => "广东省江门市鹤山市",
+					"440785" => "广东省江门市恩平市",
+					"440800" => "广东省湛江市",
+					"440801" => "广东省湛江市市辖区",
+					"440802" => "广东省湛江市赤坎区",
+					"440803" => "广东省湛江市霞山区",
+					"440804" => "广东省湛江市坡头区",
+					"440811" => "广东省湛江市麻章区",
+					"440823" => "广东省湛江市遂溪县",
+					"440825" => "广东省湛江市徐闻县",
+					"440881" => "广东省湛江市廉江市",
+					"440882" => "广东省湛江市雷州市",
+					"440883" => "广东省湛江市吴川市",
+					"440900" => "广东省茂名市",
+					"440901" => "广东省茂名市市辖区",
+					"440902" => "广东省茂名市茂南区",
+					"440923" => "广东省茂名市电白县",
+					"440981" => "广东省茂名市高州市",
+					"440982" => "广东省茂名市化州市",
+					"440983" => "广东省茂名市信宜市",
+					"441200" => "广东省肇庆市",
+					"441201" => "广东省肇庆市市辖区",
+					"441202" => "广东省肇庆市端州区",
+					"441203" => "广东省肇庆市鼎湖区",
+					"441223" => "广东省肇庆市广宁县",
+					"441224" => "广东省肇庆市怀集县",
+					"441225" => "广东省肇庆市封开县",
+					"441226" => "广东省肇庆市德庆县",
+					"441283" => "广东省肇庆市高要市",
+					"441284" => "广东省肇庆市四会市",
+					"441300" => "广东省惠州市",
+					"441301" => "广东省惠州市市辖区",
+					"441302" => "广东省惠州市惠城区",
+					"441322" => "广东省惠州市博罗县",
+					"441323" => "广东省惠州市惠东县",
+					"441324" => "广东省惠州市龙门县",
+					"441381" => "广东省惠州市惠阳市",
+					"441400" => "广东省梅州市",
+					"441401" => "广东省梅州市市辖区",
+					"441402" => "广东省梅州市梅江区",
+					"441421" => "广东省梅州市梅县",
+					"441422" => "广东省梅州市大埔县",
+					"441423" => "广东省梅州市丰顺县",
+					"441424" => "广东省梅州市五华县",
+					"441426" => "广东省梅州市平远县",
+					"441427" => "广东省梅州市蕉岭县",
+					"441481" => "广东省梅州市兴宁市",
+					"441500" => "广东省汕尾市",
+					"441501" => "广东省汕尾市市辖区",
+					"441502" => "广东省汕尾市城区",
+					"441521" => "广东省汕尾市海丰县",
+					"441523" => "广东省汕尾市陆河县",
+					"441581" => "广东省汕尾市陆丰市",
+					"441600" => "广东省河源市",
+					"441601" => "广东省河源市市辖区",
+					"441602" => "广东省河源市源城区",
+					"441621" => "广东省河源市紫金县",
+					"441622" => "广东省河源市龙川县",
+					"441623" => "广东省河源市连平县",
+					"441624" => "广东省河源市和平县",
+					"441625" => "广东省河源市东源县",
+					"441700" => "广东省阳江市",
+					"441701" => "广东省阳江市市辖区",
+					"441702" => "广东省阳江市江城区",
+					"441721" => "广东省阳江市阳西县",
+					"441723" => "广东省阳江市阳东县",
+					"441781" => "广东省阳江市阳春市",
+					"441800" => "广东省清远市",
+					"441801" => "广东省清远市市辖区",
+					"441802" => "广东省清远市清城区",
+					"441821" => "广东省清远市佛冈县",
+					"441823" => "广东省清远市阳山县",
+					"441825" => "广东省清远市连山壮族瑶族自治县",
+					"441826" => "广东省清远市连南瑶族自治县",
+					"441827" => "广东省清远市清新县",
+					"441881" => "广东省清远市英德市",
+					"441882" => "广东省清远市连州市",
+					"441900" => "广东省东莞市",
+					"441901" => "广东省东莞市市辖区",
+					"442000" => "广东省中山市",
+					"442001" => "广东省中山市市辖区",
+					"445100" => "广东省潮州市",
+					"445101" => "广东省潮州市市辖区",
+					"445102" => "广东省潮州市湘桥区",
+					"445121" => "广东省潮州市潮安县",
+					"445122" => "广东省潮州市饶平县",
+					"445200" => "广东省揭阳市",
+					"445201" => "广东省揭阳市市辖区",
+					"445202" => "广东省揭阳市榕城区",
+					"445221" => "广东省揭阳市揭东县",
+					"445222" => "广东省揭阳市揭西县",
+					"445224" => "广东省揭阳市惠来县",
+					"445281" => "广东省揭阳市普宁市",
+					"445300" => "广东省云浮市",
+					"445301" => "广东省云浮市市辖区",
+					"445302" => "广东省云浮市云城区",
+					"445321" => "广东省云浮市新兴县",
+					"445322" => "广东省云浮市郁南县",
+					"445323" => "广东省云浮市云安县",
+					"445381" => "广东省云浮市罗定市",
+					"450000" => "广西壮族自治区",
+					"450100" => "广西壮族自治区南宁市",
+					"450101" => "广西壮族自治区南宁市市辖区",
+					"450102" => "广西壮族自治区南宁市兴宁区",
+					"450103" => "广西壮族自治区南宁市新城区",
+					"450104" => "广西壮族自治区南宁市城北区",
+					"450105" => "广西壮族自治区南宁市江南区",
+					"450106" => "广西壮族自治区南宁市永新区",
+					"450111" => "广西壮族自治区南宁市市郊区",
+					"450121" => "广西壮族自治区南宁市邕宁县",
+					"450122" => "广西壮族自治区南宁市武鸣县",
+					"450200" => "广西壮族自治区柳州市",
+					"450201" => "广西壮族自治区柳州市市辖区",
+					"450202" => "广西壮族自治区柳州市城中区",
+					"450203" => "广西壮族自治区柳州市鱼峰区",
+					"450204" => "广西壮族自治区柳州市柳南区",
+					"450205" => "广西壮族自治区柳州市柳北区",
+					"450211" => "广西壮族自治区柳州市市郊区",
+					"450221" => "广西壮族自治区柳州市柳江县",
+					"450222" => "广西壮族自治区柳州市柳城县",
+					"450300" => "广西壮族自治区桂林市",
+					"450301" => "广西壮族自治区桂林市市辖区",
+					"450302" => "广西壮族自治区桂林市秀峰区",
+					"450303" => "广西壮族自治区桂林市叠彩区",
+					"450304" => "广西壮族自治区桂林市象山区",
+					"450305" => "广西壮族自治区桂林市七星区",
+					"450311" => "广西壮族自治区桂林市雁山区",
+					"450321" => "广西壮族自治区桂林市阳朔县",
+					"450322" => "广西壮族自治区桂林市临桂县",
+					"450323" => "广西壮族自治区桂林市灵川县",
+					"450324" => "广西壮族自治区桂林市全州县",
+					"450325" => "广西壮族自治区桂林市兴安县",
+					"450326" => "广西壮族自治区桂林市永福县",
+					"450327" => "广西壮族自治区桂林市灌阳县",
+					"450328" => "广西壮族自治区桂林市龙胜各族自治县",
+					"450329" => "广西壮族自治区桂林市资源县",
+					"450330" => "广西壮族自治区桂林市平乐县",
+					"450331" => "广西壮族自治区桂林市荔浦县",
+					"450332" => "广西壮族自治区桂林市恭城瑶族自治县",
+					"450400" => "广西壮族自治区梧州市",
+					"450401" => "广西壮族自治区梧州市市辖区",
+					"450403" => "广西壮族自治区梧州市万秀区",
+					"450404" => "广西壮族自治区梧州市蝶山区",
+					"450411" => "广西壮族自治区梧州市市郊区",
+					"450421" => "广西壮族自治区梧州市苍梧县",
+					"450422" => "广西壮族自治区梧州市藤县",
+					"450423" => "广西壮族自治区梧州市蒙山县",
+					"450481" => "广西壮族自治区梧州市岑溪市",
+					"450500" => "广西壮族自治区北海市",
+					"450501" => "广西壮族自治区北海市市辖区",
+					"450502" => "广西壮族自治区北海市海城区",
+					"450503" => "广西壮族自治区北海市银海区",
+					"450512" => "广西壮族自治区北海市铁山港区",
+					"450521" => "广西壮族自治区北海市合浦县",
+					"450600" => "广西壮族自治区防城港市",
+					"450601" => "广西壮族自治区防城港市市辖区",
+					"450602" => "广西壮族自治区防城港市港口区",
+					"450603" => "广西壮族自治区防城港市防城区",
+					"450621" => "广西壮族自治区防城港市上思县",
+					"450681" => "广西壮族自治区防城港市东兴市",
+					"450700" => "广西壮族自治区钦州市",
+					"450701" => "广西壮族自治区钦州市市辖区",
+					"450702" => "广西壮族自治区钦州市钦南区",
+					"450703" => "广西壮族自治区钦州市钦北区",
+					"450721" => "广西壮族自治区钦州市灵山县",
+					"450722" => "广西壮族自治区钦州市浦北县",
+					"450800" => "广西壮族自治区贵港市",
+					"450801" => "广西壮族自治区贵港市市辖区",
+					"450802" => "广西壮族自治区贵港市港北区",
+					"450803" => "广西壮族自治区贵港市港南区",
+					"450821" => "广西壮族自治区贵港市平南县",
+					"450881" => "广西壮族自治区贵港市桂平市",
+					"450900" => "广西壮族自治区玉林市",
+					"450901" => "广西壮族自治区玉林市市辖区",
+					"450902" => "广西壮族自治区玉林市玉州区",
+					"450921" => "广西壮族自治区玉林市容县",
+					"450922" => "广西壮族自治区玉林市陆川县",
+					"450923" => "广西壮族自治区玉林市博白县",
+					"450924" => "广西壮族自治区玉林市兴业县",
+					"450981" => "广西壮族自治区玉林市北流市",
+					"452100" => "广西壮族自治区南宁地区",
+					"452101" => "广西壮族自治区南宁地区凭祥市",
+					"452122" => "广西壮族自治区南宁地区横县",
+					"452123" => "广西壮族自治区南宁地区宾阳县",
+					"452124" => "广西壮族自治区南宁地区上林县",
+					"452126" => "广西壮族自治区南宁地区隆安县",
+					"452127" => "广西壮族自治区南宁地区马山县",
+					"452128" => "广西壮族自治区南宁地区扶绥县",
+					"452129" => "广西壮族自治区南宁地区崇左县",
+					"452130" => "广西壮族自治区南宁地区大新县",
+					"452131" => "广西壮族自治区南宁地区天等县",
+					"452132" => "广西壮族自治区南宁地区宁明县",
+					"452133" => "广西壮族自治区南宁地区龙州县",
+					"452200" => "广西壮族自治区柳州地区",
+					"452201" => "广西壮族自治区柳州地区合山市",
+					"452223" => "广西壮族自治区柳州地区鹿寨县",
+					"452224" => "广西壮族自治区柳州地区象州县",
+					"452225" => "广西壮族自治区柳州地区武宣县",
+					"452226" => "广西壮族自治区柳州地区来宾县",
+					"452227" => "广西壮族自治区柳州地区融安县",
+					"452228" => "广西壮族自治区柳州地区三江侗族自治县",
+					"452229" => "广西壮族自治区柳州地区融水苗族自治县",
+					"452230" => "广西壮族自治区柳州地区金秀瑶族自治县",
+					"452231" => "广西壮族自治区柳州地区忻城县",
+					"452400" => "广西壮族自治区贺州地区",
+					"452402" => "广西壮族自治区贺州地区贺州市",
+					"452424" => "广西壮族自治区贺州地区昭平县",
+					"452427" => "广西壮族自治区贺州地区钟山县",
+					"452428" => "广西壮族自治区贺州地区富川瑶族自治县",
+					"452600" => "广西壮族自治区百色地区",
+					"452601" => "广西壮族自治区百色地区百色市",
+					"452622" => "广西壮族自治区百色地区田阳县",
+					"452623" => "广西壮族自治区百色地区田东县",
+					"452624" => "广西壮族自治区百色地区平果县",
+					"452625" => "广西壮族自治区百色地区德保县",
+					"452626" => "广西壮族自治区百色地区靖西县",
+					"452627" => "广西壮族自治区百色地区那坡县",
+					"452628" => "广西壮族自治区百色地区凌云县",
+					"452629" => "广西壮族自治区百色地区乐业县",
+					"452630" => "广西壮族自治区百色地区田林县",
+					"452631" => "广西壮族自治区百色地区隆林各族自治县",
+					"452632" => "广西壮族自治区百色地区西林县",
+					"452700" => "广西壮族自治区河池地区",
+					"452701" => "广西壮族自治区河池地区河池市",
+					"452702" => "广西壮族自治区河池地区宜州市",
+					"452723" => "广西壮族自治区河池地区罗城仫佬族自治县",
+					"452724" => "广西壮族自治区河池地区环江毛南族自治县",
+					"452725" => "广西壮族自治区河池地区南丹县",
+					"452726" => "广西壮族自治区河池地区天峨县",
+					"452727" => "广西壮族自治区河池地区凤山县",
+					"452728" => "广西壮族自治区河池地区东兰县",
+					"452729" => "广西壮族自治区河池地区巴马瑶族自治县",
+					"452730" => "广西壮族自治区河池地区都安瑶族自治县",
+					"452731" => "广西壮族自治区河池地区大化瑶族自治县",
+					"460000" => "海南省",
+					"460001" => "海南省三亚市通什市",
+					"460002" => "海南省三亚市琼海市",
+					"460003" => "海南省三亚市儋州市",
+					"460004" => "海南省三亚市琼山市",
+					"460005" => "海南省三亚市文昌市",
+					"460006" => "海南省三亚市万宁市",
+					"460007" => "海南省三亚市东方市",
+					"460025" => "海南省三亚市定安县",
+					"460026" => "海南省三亚市屯昌县",
+					"460027" => "海南省三亚市澄迈县",
+					"460028" => "海南省三亚市临高县",
+					"460030" => "海南省三亚市白沙黎族自治县",
+					"460031" => "海南省三亚市昌江黎族自治县",
+					"460033" => "海南省三亚市乐东黎族自治县",
+					"460034" => "海南省三亚市陵水黎族自治县",
+					"460035" => "海南省三亚市保亭黎族苗族自治县",
+					"460036" => "海南省三亚市琼中黎族苗族自治县",
+					"460037" => "海南省西沙群岛",
+					"460038" => "海南省南沙群岛",
+					"460039" => "海南省中沙群岛的岛礁及其海域",
+					"460100" => "海南省海口市",
+					"460101" => "海南省海口市市辖区",
+					"460102" => "海南省海口市振东区",
+					"460103" => "海南省海口市新华区",
+					"460104" => "海南省海口市秀英区",
+					"460200" => "海南省三亚市",
+					"460201" => "海南省三亚市市辖区",
+					"500000" => "重庆市",
+					"500100" => "重庆市市辖区",
+					"500101" => "重庆市万州区",
+					"500102" => "重庆市涪陵区",
+					"500103" => "重庆市渝中区",
+					"500104" => "重庆市大渡口区",
+					"500105" => "重庆市江北区",
+					"500106" => "重庆市沙坪坝区",
+					"500107" => "重庆市九龙坡区",
+					"500108" => "重庆市南岸区",
+					"500109" => "重庆市北碚区",
+					"500110" => "重庆市万盛区",
+					"500111" => "重庆市双桥区",
+					"500112" => "重庆市渝北区",
+					"500113" => "重庆市巴南区",
+					"500200" => "重庆市县",
+					"500221" => "重庆市长寿县",
+					"500222" => "重庆市綦江县",
+					"500223" => "重庆市潼南县",
+					"500224" => "重庆市铜梁县",
+					"500225" => "重庆市大足县",
+					"500226" => "重庆市荣昌县",
+					"500227" => "重庆市璧山县",
+					"500228" => "重庆市梁平县",
+					"500229" => "重庆市城口县",
+					"500230" => "重庆市丰都县",
+					"500231" => "重庆市垫江县",
+					"500232" => "重庆市武隆县",
+					"500233" => "重庆市忠县",
+					"500234" => "重庆市开县",
+					"500235" => "重庆市云阳县",
+					"500236" => "重庆市奉节县",
+					"500237" => "重庆市巫山县",
+					"500238" => "重庆市巫溪县",
+					"500239" => "重庆市黔江土家族苗族自治县",
+					"500240" => "重庆市石柱土家族自治县",
+					"500241" => "重庆市秀山土家族苗族自治县",
+					"500242" => "重庆市酉阳土家族苗族自治县",
+					"500243" => "重庆市彭水苗族土家族自治县",
+					"500300" => "重庆市(市)",
+					"500381" => "重庆市江津市",
+					"500382" => "重庆市合川市",
+					"500383" => "重庆市永川市",
+					"500384" => "重庆市南川市",
+					"510000" => "四川省",
+					"510100" => "四川省成都市",
+					"510101" => "四川省成都市市辖区",
+					"510104" => "四川省成都市锦江区",
+					"510105" => "四川省成都市青羊区",
+					"510106" => "四川省成都市金牛区",
+					"510107" => "四川省成都市武侯区",
+					"510108" => "四川省成都市成华区",
+					"510112" => "四川省成都市龙泉驿区",
+					"510113" => "四川省成都市青白江区",
+					"510121" => "四川省成都市金堂县",
+					"510122" => "四川省成都市双流县",
+					"510123" => "四川省成都市温江县",
+					"510124" => "四川省成都市郫县",
+					"510125" => "四川省成都市新都县",
+					"510129" => "四川省成都市大邑县",
+					"510131" => "四川省成都市蒲江县",
+					"510132" => "四川省成都市新津县",
+					"510181" => "四川省成都市都江堰市",
+					"510182" => "四川省成都市彭州市",
+					"510183" => "四川省成都市邛崃市",
+					"510184" => "四川省成都市崇州市",
+					"510300" => "四川省自贡市",
+					"510301" => "四川省自贡市市辖区",
+					"510302" => "四川省自贡市自流井区",
+					"510303" => "四川省自贡市贡井区",
+					"510304" => "四川省自贡市大安区",
+					"510311" => "四川省自贡市沿滩区",
+					"510321" => "四川省自贡市荣县",
+					"510322" => "四川省自贡市富顺县",
+					"510400" => "四川省攀枝花市",
+					"510401" => "四川省攀枝花市市辖区",
+					"510402" => "四川省攀枝花市东区",
+					"510403" => "四川省攀枝花市西区",
+					"510411" => "四川省攀枝花市仁和区",
+					"510421" => "四川省攀枝花市米易县",
+					"510422" => "四川省攀枝花市盐边县",
+					"510500" => "四川省泸州市",
+					"510501" => "四川省泸州市市辖区",
+					"510502" => "四川省泸州市江阳区",
+					"510503" => "四川省泸州市纳溪区",
+					"510504" => "四川省泸州市龙马潭区",
+					"510521" => "四川省泸州市泸县",
+					"510522" => "四川省泸州市合江县",
+					"510524" => "四川省泸州市叙永县",
+					"510525" => "四川省泸州市古蔺县",
+					"510600" => "四川省德阳市",
+					"510601" => "四川省德阳市市辖区",
+					"510603" => "四川省德阳市旌阳区",
+					"510623" => "四川省德阳市中江县",
+					"510626" => "四川省德阳市罗江县",
+					"510681" => "四川省德阳市广汉市",
+					"510682" => "四川省德阳市什邡市",
+					"510683" => "四川省德阳市绵竹市",
+					"510700" => "四川省绵阳市",
+					"510701" => "四川省绵阳市市辖区",
+					"510703" => "四川省绵阳市涪城区",
+					"510704" => "四川省绵阳市游仙区",
+					"510722" => "四川省绵阳市三台县",
+					"510723" => "四川省绵阳市盐亭县",
+					"510724" => "四川省绵阳市安县",
+					"510725" => "四川省绵阳市梓潼县",
+					"510726" => "四川省绵阳市北川县",
+					"510727" => "四川省绵阳市平武县",
+					"510781" => "四川省绵阳市江油市",
+					"510800" => "四川省广元市",
+					"510801" => "四川省广元市市辖区",
+					"510802" => "四川省广元市市中区",
+					"510811" => "四川省广元市元坝区",
+					"510812" => "四川省广元市朝天区",
+					"510821" => "四川省广元市旺苍县",
+					"510822" => "四川省广元市青川县",
+					"510823" => "四川省广元市剑阁县",
+					"510824" => "四川省广元市苍溪县",
+					"510900" => "四川省遂宁市",
+					"510901" => "四川省遂宁市市辖区",
+					"510902" => "四川省遂宁市市中区",
+					"510921" => "四川省遂宁市蓬溪县",
+					"510922" => "四川省遂宁市射洪县",
+					"510923" => "四川省遂宁市大英县",
+					"511000" => "四川省内江市",
+					"511001" => "四川省内江市市辖区",
+					"511002" => "四川省内江市市中区",
+					"511011" => "四川省内江市东兴区",
+					"511024" => "四川省内江市威远县",
+					"511025" => "四川省内江市资中县",
+					"511028" => "四川省内江市隆昌县",
+					"511100" => "四川省乐山市",
+					"511101" => "四川省乐山市市辖区",
+					"511102" => "四川省乐山市市中区",
+					"511111" => "四川省乐山市沙湾区",
+					"511112" => "四川省乐山市五通桥区",
+					"511113" => "四川省乐山市金口河区",
+					"511123" => "四川省乐山市犍为县",
+					"511124" => "四川省乐山市井研县",
+					"511126" => "四川省乐山市夹江县",
+					"511129" => "四川省乐山市沐川县",
+					"511132" => "四川省乐山市峨边彝族自治县",
+					"511133" => "四川省乐山市马边彝族自治县",
+					"511181" => "四川省乐山市峨眉山市",
+					"511300" => "四川省南充市",
+					"511301" => "四川省南充市市辖区",
+					"511302" => "四川省南充市顺庆区",
+					"511303" => "四川省南充市高坪区",
+					"511304" => "四川省南充市嘉陵区",
+					"511321" => "四川省南充市南部县",
+					"511322" => "四川省南充市营山县",
+					"511323" => "四川省南充市蓬安县",
+					"511324" => "四川省南充市仪陇县",
+					"511325" => "四川省南充市西充县",
+					"511381" => "四川省南充市阆中市",
+					"511500" => "四川省宜宾市",
+					"511501" => "四川省宜宾市市辖区",
+					"511502" => "四川省宜宾市翠屏区",
+					"511521" => "四川省宜宾市宜宾县",
+					"511522" => "四川省宜宾市南溪县",
+					"511523" => "四川省宜宾市江安县",
+					"511524" => "四川省宜宾市长宁县",
+					"511525" => "四川省宜宾市高县",
+					"511526" => "四川省宜宾市珙县",
+					"511527" => "四川省宜宾市筠连县",
+					"511528" => "四川省宜宾市兴文县",
+					"511529" => "四川省宜宾市屏山县",
+					"511600" => "四川省广安市",
+					"511601" => "四川省广安市市辖区",
+					"511602" => "四川省广安市广安区",
+					"511621" => "四川省广安市岳池县",
+					"511622" => "四川省广安市武胜县",
+					"511623" => "四川省广安市邻水县",
+					"511681" => "四川省广安市华蓥市",
+					"513000" => "四川省达川地区",
+					"513001" => "四川省达川地区达川市",
+					"513002" => "四川省达川地区万源市",
+					"513021" => "四川省达川地区达县",
+					"513022" => "四川省达川地区宣汉县",
+					"513023" => "四川省达川地区开江县",
+					"513029" => "四川省达川地区大竹县",
+					"513030" => "四川省达川地区渠县",
+					"513100" => "四川省雅安地区",
+					"513101" => "四川省雅安地区雅安市",
+					"513122" => "四川省雅安地区名山县",
+					"513123" => "四川省雅安地区荥经县",
+					"513124" => "四川省雅安地区汉源县",
+					"513125" => "四川省雅安地区石棉县",
+					"513126" => "四川省雅安地区天全县",
+					"513127" => "四川省雅安地区芦山县",
+					"513128" => "四川省雅安地区宝兴县",
+					"513200" => "四川省阿坝藏族羌族自治州",
+					"513221" => "四川省阿坝藏族羌族自治州汶川县",
+					"513222" => "四川省阿坝藏族羌族自治州理县",
+					"513223" => "四川省阿坝藏族羌族自治州茂县",
+					"513224" => "四川省阿坝藏族羌族自治州松潘县",
+					"513225" => "四川省阿坝藏族羌族自治州九寨沟县",
+					"513226" => "四川省阿坝藏族羌族自治州金川县",
+					"513227" => "四川省阿坝藏族羌族自治州小金县",
+					"513228" => "四川省阿坝藏族羌族自治州黑水县",
+					"513229" => "四川省阿坝藏族羌族自治州马尔康县",
+					"513230" => "四川省阿坝藏族羌族自治州壤塘县",
+					"513231" => "四川省阿坝藏族羌族自治州阿坝县",
+					"513232" => "四川省阿坝藏族羌族自治州若尔盖县",
+					"513233" => "四川省阿坝藏族羌族自治州红原县",
+					"513300" => "四川省甘孜藏族自治州",
+					"513321" => "四川省甘孜藏族自治州康定县",
+					"513322" => "四川省甘孜藏族自治州泸定县",
+					"513323" => "四川省甘孜藏族自治州丹巴县",
+					"513324" => "四川省甘孜藏族自治州九龙县",
+					"513325" => "四川省甘孜藏族自治州雅江县",
+					"513326" => "四川省甘孜藏族自治州道孚县",
+					"513327" => "四川省甘孜藏族自治州炉霍县",
+					"513328" => "四川省甘孜藏族自治州甘孜县",
+					"513329" => "四川省甘孜藏族自治州新龙县",
+					"513330" => "四川省甘孜藏族自治州德格县",
+					"513331" => "四川省甘孜藏族自治州白玉县",
+					"513332" => "四川省甘孜藏族自治州石渠县",
+					"513333" => "四川省甘孜藏族自治州色达县",
+					"513334" => "四川省甘孜藏族自治州理塘县",
+					"513335" => "四川省甘孜藏族自治州巴塘县",
+					"513336" => "四川省甘孜藏族自治州乡城县",
+					"513337" => "四川省甘孜藏族自治州稻城县",
+					"513338" => "四川省甘孜藏族自治州得荣县",
+					"513400" => "四川省凉山彝族自治州",
+					"513401" => "四川省凉山彝族自治州西昌市",
+					"513422" => "四川省凉山彝族自治州木里藏族自治县",
+					"513423" => "四川省凉山彝族自治州盐源县",
+					"513424" => "四川省凉山彝族自治州德昌县",
+					"513425" => "四川省凉山彝族自治州会理县",
+					"513426" => "四川省凉山彝族自治州会东县",
+					"513427" => "四川省凉山彝族自治州宁南县",
+					"513428" => "四川省凉山彝族自治州普格县",
+					"513429" => "四川省凉山彝族自治州布拖县",
+					"513430" => "四川省凉山彝族自治州金阳县",
+					"513431" => "四川省凉山彝族自治州昭觉县",
+					"513432" => "四川省凉山彝族自治州喜德县",
+					"513433" => "四川省凉山彝族自治州冕宁县",
+					"513434" => "四川省凉山彝族自治州越西县",
+					"513435" => "四川省凉山彝族自治州甘洛县",
+					"513436" => "四川省凉山彝族自治州美姑县",
+					"513437" => "四川省凉山彝族自治州雷波县",
+					"513700" => "四川省巴中地区",
+					"513701" => "四川省巴中地区巴中市",
+					"513721" => "四川省巴中地区通江县",
+					"513722" => "四川省巴中地区南江县",
+					"513723" => "四川省巴中地区平昌县",
+					"513800" => "四川省眉山地区",
+					"513821" => "四川省眉山地区眉山县",
+					"513822" => "四川省眉山地区仁寿县",
+					"513823" => "四川省眉山地区彭山县",
+					"513824" => "四川省眉山地区洪雅县",
+					"513825" => "四川省眉山地区丹棱县",
+					"513826" => "四川省眉山地区青神县",
+					"513900" => "四川省眉山地区资阳地区",
+					"513901" => "四川省眉山地区资阳市",
+					"513902" => "四川省眉山地区简阳市",
+					"513921" => "四川省眉山地区安岳县",
+					"513922" => "四川省眉山地区乐至县",
+					"520000" => "贵州省",
+					"520100" => "贵州省贵阳市",
+					"520101" => "贵州省贵阳市市辖区",
+					"520102" => "贵州省贵阳市南明区",
+					"520103" => "贵州省贵阳市云岩区",
+					"520111" => "贵州省贵阳市花溪区",
+					"520112" => "贵州省贵阳市乌当区",
+					"520113" => "贵州省贵阳市白云区",
+					"520121" => "贵州省贵阳市开阳县",
+					"520122" => "贵州省贵阳市息烽县",
+					"520123" => "贵州省贵阳市修文县",
+					"520181" => "贵州省贵阳市清镇市",
+					"520200" => "贵州省六盘水市",
+					"520201" => "贵州省六盘水市钟山区",
+					"520202" => "贵州省六盘水市盘县特区",
+					"520203" => "贵州省六盘水市六枝特区",
+					"520221" => "贵州省六盘水市水城县",
+					"520300" => "贵州省遵义市",
+					"520301" => "贵州省遵义市市辖区",
+					"520302" => "贵州省遵义市红花岗区",
+					"520321" => "贵州省遵义市遵义县",
+					"520322" => "贵州省遵义市桐梓县",
+					"520323" => "贵州省遵义市绥阳县",
+					"520324" => "贵州省遵义市正安县",
+					"520325" => "贵州省遵义市道真仡佬族苗族自治县",
+					"520326" => "贵州省遵义市务川仡佬族苗族自治县",
+					"520327" => "贵州省遵义市凤冈县",
+					"520328" => "贵州省遵义市湄潭县",
+					"520329" => "贵州省遵义市余庆县",
+					"520330" => "贵州省遵义市习水县",
+					"520381" => "贵州省遵义市赤水市",
+					"520382" => "贵州省遵义市仁怀市",
+					"522200" => "贵州省铜仁地区",
+					"522201" => "贵州省铜仁地区铜仁市",
+					"522222" => "贵州省铜仁地区江口县",
+					"522223" => "贵州省铜仁地区玉屏侗族自治县",
+					"522224" => "贵州省铜仁地区石阡县",
+					"522225" => "贵州省铜仁地区思南县",
+					"522226" => "贵州省铜仁地区印江土家族苗族自治县",
+					"522227" => "贵州省铜仁地区德江县",
+					"522228" => "贵州省铜仁地区沿河土家族自治县",
+					"522229" => "贵州省铜仁地区松桃苗族自治县",
+					"522230" => "贵州省铜仁地区万山特区",
+					"522300" => "贵州省黔西南布依族苗族自治州",
+					"522301" => "贵州省黔西南布依族苗族自治州兴义市",
+					"522322" => "贵州省黔西南布依族苗族自治州兴仁县",
+					"522323" => "贵州省黔西南布依族苗族自治州普安县",
+					"522324" => "贵州省黔西南布依族苗族自治州晴隆县",
+					"522325" => "贵州省黔西南布依族苗族自治州贞丰县",
+					"522326" => "贵州省黔西南布依族苗族自治州望谟县",
+					"522327" => "贵州省黔西南布依族苗族自治州册亨县",
+					"522328" => "贵州省黔西南布依族苗族自治州安龙县",
+					"522400" => "贵州省毕节地区",
+					"522401" => "贵州省毕节地区毕节市",
+					"522422" => "贵州省毕节地区大方县",
+					"522423" => "贵州省毕节地区黔西县",
+					"522424" => "贵州省毕节地区金沙县",
+					"522425" => "贵州省毕节地区织金县",
+					"522426" => "贵州省毕节地区纳雍县",
+					"522427" => "贵州省毕节地区威宁彝族回族苗族自治县",
+					"522428" => "贵州省毕节地区赫章县",
+					"522500" => "贵州省安顺地区",
+					"522501" => "贵州省安顺地区安顺市",
+					"522526" => "贵州省安顺地区平坝县",
+					"522527" => "贵州省安顺地区普定县",
+					"522528" => "贵州省安顺地区关岭布依族苗族自治县",
+					"522529" => "贵州省安顺地区镇宁布依族苗族自治县",
+					"522530" => "贵州省安顺地区紫云苗族布依族自治县",
+					"522600" => "贵州省黔东南苗族侗族自治州",
+					"522601" => "贵州省黔东南苗族侗族自治州凯里市",
+					"522622" => "贵州省黔东南苗族侗族自治州黄平县",
+					"522623" => "贵州省黔东南苗族侗族自治州施秉县",
+					"522624" => "贵州省黔东南苗族侗族自治州三穗县",
+					"522625" => "贵州省黔东南苗族侗族自治州镇远县",
+					"522626" => "贵州省黔东南苗族侗族自治州岑巩县",
+					"522627" => "贵州省黔东南苗族侗族自治州天柱县",
+					"522628" => "贵州省黔东南苗族侗族自治州锦屏县",
+					"522629" => "贵州省黔东南苗族侗族自治州剑河县",
+					"522630" => "贵州省黔东南苗族侗族自治州台江县",
+					"522631" => "贵州省黔东南苗族侗族自治州黎平县",
+					"522632" => "贵州省黔东南苗族侗族自治州榕江县",
+					"522633" => "贵州省黔东南苗族侗族自治州从江县",
+					"522634" => "贵州省黔东南苗族侗族自治州雷山县",
+					"522635" => "贵州省黔东南苗族侗族自治州麻江县",
+					"522636" => "贵州省黔东南苗族侗族自治州丹寨县",
+					"522700" => "贵州省黔南布依族苗族自治州",
+					"522701" => "贵州省黔南布依族苗族自治州都匀市",
+					"522702" => "贵州省黔南布依族苗族自治州福泉市",
+					"522722" => "贵州省黔南布依族苗族自治州荔波县",
+					"522723" => "贵州省黔南布依族苗族自治州贵定县",
+					"522725" => "贵州省黔南布依族苗族自治州瓮安县",
+					"522726" => "贵州省黔南布依族苗族自治州独山县",
+					"522727" => "贵州省黔南布依族苗族自治州平塘县",
+					"522728" => "贵州省黔南布依族苗族自治州罗甸县",
+					"522729" => "贵州省黔南布依族苗族自治州长顺县",
+					"522730" => "贵州省黔南布依族苗族自治州龙里县",
+					"522731" => "贵州省黔南布依族苗族自治州惠水县",
+					"522732" => "贵州省黔南布依族苗族自治州三都水族自治县",
+					"530000" => "云南省",
+					"530100" => "云南省昆明市",
+					"530101" => "云南省昆明市市辖区",
+					"530102" => "云南省昆明市五华区",
+					"530103" => "云南省昆明市盘龙区",
+					"530111" => "云南省昆明市官渡区",
+					"530112" => "云南省昆明市西山区",
+					"530113" => "云南省昆明市东川区",
+					"530121" => "云南省昆明市呈贡县",
+					"530122" => "云南省昆明市晋宁县",
+					"530124" => "云南省昆明市富民县",
+					"530125" => "云南省昆明市宜良县",
+					"530126" => "云南省昆明市石林彝族自治县",
+					"530127" => "云南省昆明市嵩明县",
+					"530128" => "云南省昆明市禄劝彝族苗族自治县",
+					"530129" => "云南省昆明市寻甸回族彝族自治县",
+					"530181" => "云南省昆明市安宁市",
+					"530300" => "云南省曲靖市",
+					"530301" => "云南省曲靖市市辖区",
+					"530302" => "云南省曲靖市麒麟区",
+					"530321" => "云南省曲靖市马龙县",
+					"530322" => "云南省曲靖市陆良县",
+					"530323" => "云南省曲靖市师宗县",
+					"530324" => "云南省曲靖市罗平县",
+					"530325" => "云南省曲靖市富源县",
+					"530326" => "云南省曲靖市会泽县",
+					"530328" => "云南省曲靖市沾益县",
+					"530381" => "云南省曲靖市宣威市",
+					"530400" => "云南省玉溪市",
+					"530401" => "云南省玉溪市市辖区",
+					"530402" => "云南省玉溪市红塔区",
+					"530421" => "云南省玉溪市江川县",
+					"530422" => "云南省玉溪市澄江县",
+					"530423" => "云南省玉溪市通海县",
+					"530424" => "云南省玉溪市华宁县",
+					"530425" => "云南省玉溪市易门县",
+					"530426" => "云南省玉溪市峨山彝族自治县",
+					"530427" => "云南省玉溪市新平彝族傣族自治县",
+					"530428" => "云南省玉溪市元江哈尼族彝族傣族自治县",
+					"532100" => "云南省昭通地区",
+					"532101" => "云南省昭通地区昭通市",
+					"532122" => "云南省昭通地区鲁甸县",
+					"532123" => "云南省昭通地区巧家县",
+					"532124" => "云南省昭通地区盐津县",
+					"532125" => "云南省昭通地区大关县",
+					"532126" => "云南省昭通地区永善县",
+					"532127" => "云南省昭通地区绥江县",
+					"532128" => "云南省昭通地区镇雄县",
+					"532129" => "云南省昭通地区彝良县",
+					"532130" => "云南省昭通地区威信县",
+					"532131" => "云南省昭通地区水富县",
+					"532300" => "云南省楚雄彝族自治州",
+					"532301" => "云南省楚雄彝族自治州楚雄市",
+					"532322" => "云南省楚雄彝族自治州双柏县",
+					"532323" => "云南省楚雄彝族自治州牟定县",
+					"532324" => "云南省楚雄彝族自治州南华县",
+					"532325" => "云南省楚雄彝族自治州姚安县",
+					"532326" => "云南省楚雄彝族自治州大姚县",
+					"532327" => "云南省楚雄彝族自治州永仁县",
+					"532328" => "云南省楚雄彝族自治州元谋县",
+					"532329" => "云南省楚雄彝族自治州武定县",
+					"532331" => "云南省楚雄彝族自治州禄丰县",
+					"532500" => "云南省红河哈尼族彝族自治州",
+					"532501" => "云南省红河哈尼族彝族自治州个旧市",
+					"532502" => "云南省红河哈尼族彝族自治州开远市",
+					"532522" => "云南省红河哈尼族彝族自治州蒙自县",
+					"532523" => "云南省红河哈尼族彝族自治州屏边苗族自治县",
+					"532524" => "云南省红河哈尼族彝族自治州建水县",
+					"532525" => "云南省红河哈尼族彝族自治州石屏县",
+					"532526" => "云南省红河哈尼族彝族自治州弥勒县",
+					"532527" => "云南省红河哈尼族彝族自治州泸西县",
+					"532528" => "云南省红河哈尼族彝族自治州元阳县",
+					"532529" => "云南省红河哈尼族彝族自治州红河县",
+					"532530" => "云南省红河哈尼族彝族自治州金平苗族瑶族傣族自治县",
+					"532531" => "云南省红河哈尼族彝族自治州绿春县",
+					"532532" => "云南省红河哈尼族彝族自治州河口瑶族自治县",
+					"532600" => "云南省文山壮族苗族自治州",
+					"532621" => "云南省文山壮族苗族自治州文山县",
+					"532622" => "云南省文山壮族苗族自治州砚山县",
+					"532623" => "云南省文山壮族苗族自治州西畴县",
+					"532624" => "云南省文山壮族苗族自治州麻栗坡县",
+					"532625" => "云南省文山壮族苗族自治州马关县",
+					"532626" => "云南省文山壮族苗族自治州丘北县",
+					"532627" => "云南省文山壮族苗族自治州广南县",
+					"532628" => "云南省文山壮族苗族自治州富宁县",
+					"532700" => "云南省思茅地区",
+					"532701" => "云南省思茅地区思茅市",
+					"532722" => "云南省思茅地区普洱哈尼族彝族自治县",
+					"532723" => "云南省思茅地区墨江哈尼族自治县",
+					"532724" => "云南省思茅地区景东彝族自治县",
+					"532725" => "云南省思茅地区景谷傣族彝族自治县",
+					"532726" => "云南省思茅地区镇沅彝族哈尼族拉祜族自治县",
+					"532727" => "云南省思茅地区江城哈尼族彝族自治县",
+					"532728" => "云南省思茅地区孟连傣族拉祜族佤族自治县",
+					"532729" => "云南省思茅地区澜沧拉祜族自治县",
+					"532730" => "云南省思茅地区西盟佤族自治县",
+					"532800" => "云南省西双版纳傣族自治州",
+					"532801" => "云南省西双版纳傣族自治州景洪市",
+					"532822" => "云南省西双版纳傣族自治州勐海县",
+					"532823" => "云南省西双版纳傣族自治州勐腊县",
+					"532900" => "云南省大理白族自治州",
+					"532901" => "云南省大理白族自治州大理市",
+					"532922" => "云南省大理白族自治州漾濞彝族自治县",
+					"532923" => "云南省大理白族自治州祥云县",
+					"532924" => "云南省大理白族自治州宾川县",
+					"532925" => "云南省大理白族自治州弥渡县",
+					"532926" => "云南省大理白族自治州南涧彝族自治县",
+					"532927" => "云南省大理白族自治州巍山彝族回族自治县",
+					"532928" => "云南省大理白族自治州永平县",
+					"532929" => "云南省大理白族自治州云龙县",
+					"532930" => "云南省大理白族自治州洱源县",
+					"532931" => "云南省大理白族自治州剑川县",
+					"532932" => "云南省大理白族自治州鹤庆县",
+					"533000" => "云南省保山地区",
+					"533001" => "云南省保山地区保山市",
+					"533022" => "云南省保山地区施甸县",
+					"533023" => "云南省保山地区腾冲县",
+					"533024" => "云南省保山地区龙陵县",
+					"533025" => "云南省保山地区昌宁县",
+					"533100" => "云南省德宏傣族景颇族自治州",
+					"533101" => "云南省德宏傣族景颇族自治州畹町市",
+					"533102" => "云南省德宏傣族景颇族自治州瑞丽市",
+					"533103" => "云南省德宏傣族景颇族自治州潞西市",
+					"533122" => "云南省德宏傣族景颇族自治州梁河县",
+					"533123" => "云南省德宏傣族景颇族自治州盈江县",
+					"533124" => "云南省德宏傣族景颇族自治州陇川县",
+					"533200" => "云南省丽江地区",
+					"533221" => "云南省丽江地区丽江纳西族自治县",
+					"533222" => "云南省丽江地区永胜县",
+					"533223" => "云南省丽江地区华坪县",
+					"533224" => "云南省丽江地区宁蒗彝族自治县",
+					"533300" => "云南省怒江傈僳族自治州",
+					"533321" => "云南省怒江傈僳族自治州泸水县",
+					"533323" => "云南省怒江傈僳族自治州福贡县",
+					"533324" => "云南省怒江傈僳族自治州贡山独龙族怒族自治县",
+					"533325" => "云南省怒江傈僳族自治州兰坪白族普米族自治县",
+					"533400" => "云南省迪庆藏族自治州",
+					"533421" => "云南省迪庆藏族自治州中甸县",
+					"533422" => "云南省迪庆藏族自治州德钦县",
+					"533423" => "云南省迪庆藏族自治州维西傈僳族自治县",
+					"533500" => "云南省临沧地区",
+					"533521" => "云南省临沧地区临沧县",
+					"533522" => "云南省临沧地区凤庆县",
+					"533523" => "云南省临沧地区云县",
+					"533524" => "云南省临沧地区永德县",
+					"533525" => "云南省临沧地区镇康县",
+					"533526" => "云南省临沧地区双江拉祜族佤族布朗族傣族自治县",
+					"533527" => "云南省临沧地区耿马傣族佤族自治县",
+					"533528" => "云南省临沧地区沧源佤族自治县",
+					"540000" => "西藏自治区",
+					"540100" => "西藏自治区拉萨市",
+					"540101" => "西藏自治区拉萨市市辖区",
+					"540102" => "西藏自治区拉萨市城关区",
+					"540121" => "西藏自治区拉萨市林周县",
+					"540122" => "西藏自治区拉萨市当雄县",
+					"540123" => "西藏自治区拉萨市尼木县",
+					"540124" => "西藏自治区拉萨市曲水县",
+					"540125" => "西藏自治区拉萨市堆龙德庆县",
+					"540126" => "西藏自治区拉萨市达孜县",
+					"540127" => "西藏自治区拉萨市墨竹工卡县",
+					"542100" => "西藏自治区昌都地区",
+					"542121" => "西藏自治区昌都地区昌都县",
+					"542122" => "西藏自治区昌都地区江达县",
+					"542123" => "西藏自治区昌都地区贡觉县",
+					"542124" => "西藏自治区昌都地区类乌齐县",
+					"542125" => "西藏自治区昌都地区丁青县",
+					"542126" => "西藏自治区昌都地区察雅县",
+					"542127" => "西藏自治区昌都地区八宿县",
+					"542128" => "西藏自治区昌都地区左贡县",
+					"542129" => "西藏自治区昌都地区芒康县",
+					"542132" => "西藏自治区昌都地区洛隆县",
+					"542133" => "西藏自治区昌都地区边坝县",
+					"542134" => "西藏自治区昌都地区盐井县",
+					"542135" => "西藏自治区昌都地区碧土县",
+					"542136" => "西藏自治区昌都地区妥坝县",
+					"542137" => "西藏自治区昌都地区生达县",
+					"542200" => "西藏自治区山南地区",
+					"542221" => "西藏自治区山南地区乃东县",
+					"542222" => "西藏自治区山南地区扎囊县",
+					"542223" => "西藏自治区山南地区贡嘎县",
+					"542224" => "西藏自治区山南地区桑日县",
+					"542225" => "西藏自治区山南地区琼结县",
+					"542226" => "西藏自治区山南地区曲松县",
+					"542227" => "西藏自治区山南地区措美县",
+					"542228" => "西藏自治区山南地区洛扎县",
+					"542229" => "西藏自治区山南地区加查县",
+					"542231" => "西藏自治区山南地区隆子县",
+					"542232" => "西藏自治区山南地区错那县",
+					"542233" => "西藏自治区山南地区浪卡子县",
+					"542300" => "西藏自治区日喀则地区",
+					"542301" => "西藏自治区日喀则地区日喀则市",
+					"542322" => "西藏自治区日喀则地区南木林县",
+					"542323" => "西藏自治区日喀则地区江孜县",
+					"542324" => "西藏自治区日喀则地区定日县",
+					"542325" => "西藏自治区日喀则地区萨迦县",
+					"542326" => "西藏自治区日喀则地区拉孜县",
+					"542327" => "西藏自治区日喀则地区昂仁县",
+					"542328" => "西藏自治区日喀则地区谢通门县",
+					"542329" => "西藏自治区日喀则地区白朗县",
+					"542330" => "西藏自治区日喀则地区仁布县",
+					"542331" => "西藏自治区日喀则地区康马县",
+					"542332" => "西藏自治区日喀则地区定结县",
+					"542333" => "西藏自治区日喀则地区仲巴县",
+					"542334" => "西藏自治区日喀则地区亚东县",
+					"542335" => "西藏自治区日喀则地区吉隆县",
+					"542336" => "西藏自治区日喀则地区聂拉木县",
+					"542337" => "西藏自治区日喀则地区萨嘎县",
+					"542338" => "西藏自治区日喀则地区岗巴县",
+					"542400" => "西藏自治区那曲地区",
+					"542421" => "西藏自治区那曲地区那曲县",
+					"542422" => "西藏自治区那曲地区嘉黎县",
+					"542423" => "西藏自治区那曲地区比如县",
+					"542424" => "西藏自治区那曲地区聂荣县",
+					"542425" => "西藏自治区那曲地区安多县",
+					"542426" => "西藏自治区那曲地区申扎县",
+					"542427" => "西藏自治区那曲地区索县",
+					"542428" => "西藏自治区那曲地区班戈县",
+					"542429" => "西藏自治区那曲地区巴青县",
+					"542430" => "西藏自治区那曲地区尼玛县",
+					"542500" => "西藏自治区阿里地区",
+					"542521" => "西藏自治区阿里地区普兰县",
+					"542522" => "西藏自治区阿里地区札达县",
+					"542523" => "西藏自治区阿里地区噶尔县",
+					"542524" => "西藏自治区阿里地区日土县",
+					"542525" => "西藏自治区阿里地区革吉县",
+					"542526" => "西藏自治区阿里地区改则县",
+					"542527" => "西藏自治区阿里地区措勤县",
+					"542528" => "西藏自治区阿里地区隆格尔县",
+					"542600" => "西藏自治区林芝地区",
+					"542621" => "西藏自治区林芝地区林芝县",
+					"542622" => "西藏自治区林芝地区工布江达县",
+					"542623" => "西藏自治区林芝地区米林县",
+					"542624" => "西藏自治区林芝地区墨脱县",
+					"542625" => "西藏自治区林芝地区波密县",
+					"542626" => "西藏自治区林芝地区察隅县",
+					"542627" => "西藏自治区林芝地区朗县",
+					"610000" => "陕西省",
+					"610100" => "陕西省西安市",
+					"610101" => "陕西省西安市市辖区",
+					"610102" => "陕西省西安市新城区",
+					"610103" => "陕西省西安市碑林区",
+					"610104" => "陕西省西安市莲湖区",
+					"610111" => "陕西省西安市灞桥区",
+					"610112" => "陕西省西安市未央区",
+					"610113" => "陕西省西安市雁塔区",
+					"610114" => "陕西省西安市阎良区",
+					"610115" => "陕西省西安市临潼区",
+					"610121" => "陕西省西安市长安县",
+					"610122" => "陕西省西安市蓝田县",
+					"610124" => "陕西省西安市周至县",
+					"610125" => "陕西省西安市户县",
+					"610126" => "陕西省西安市高陵县",
+					"610200" => "陕西省铜川市",
+					"610201" => "陕西省铜川市市辖区",
+					"610202" => "陕西省铜川市城区",
+					"610203" => "陕西省铜川市郊区",
+					"610221" => "陕西省铜川市耀县",
+					"610222" => "陕西省铜川市宜君县",
+					"610300" => "陕西省宝鸡市",
+					"610301" => "陕西省宝鸡市市辖区",
+					"610302" => "陕西省宝鸡市渭滨区",
+					"610303" => "陕西省宝鸡市金台区",
+					"610321" => "陕西省宝鸡市宝鸡县",
+					"610322" => "陕西省宝鸡市凤翔县",
+					"610323" => "陕西省宝鸡市岐山县",
+					"610324" => "陕西省宝鸡市扶风县",
+					"610326" => "陕西省宝鸡市眉县",
+					"610327" => "陕西省宝鸡市陇县",
+					"610328" => "陕西省宝鸡市千阳县",
+					"610329" => "陕西省宝鸡市麟游县",
+					"610330" => "陕西省宝鸡市凤县",
+					"610331" => "陕西省宝鸡市太白县",
+					"610400" => "陕西省咸阳市",
+					"610401" => "陕西省咸阳市市辖区",
+					"610402" => "陕西省咸阳市秦都区",
+					"610403" => "陕西省咸阳市杨陵区",
+					"610404" => "陕西省咸阳市渭城区",
+					"610422" => "陕西省咸阳市三原县",
+					"610423" => "陕西省咸阳市泾阳县",
+					"610424" => "陕西省咸阳市乾县",
+					"610425" => "陕西省咸阳市礼泉县",
+					"610426" => "陕西省咸阳市永寿县",
+					"610427" => "陕西省咸阳市彬县",
+					"610428" => "陕西省咸阳市长武县",
+					"610429" => "陕西省咸阳市旬邑县",
+					"610430" => "陕西省咸阳市淳化县",
+					"610431" => "陕西省咸阳市武功县",
+					"610481" => "陕西省咸阳市兴平市",
+					"610500" => "陕西省渭南市",
+					"610501" => "陕西省渭南市市辖区",
+					"610502" => "陕西省渭南市临渭区",
+					"610521" => "陕西省渭南市华县",
+					"610522" => "陕西省渭南市潼关县",
+					"610523" => "陕西省渭南市大荔县",
+					"610524" => "陕西省渭南市合阳县",
+					"610525" => "陕西省渭南市澄城县",
+					"610526" => "陕西省渭南市蒲城县",
+					"610527" => "陕西省渭南市白水县",
+					"610528" => "陕西省渭南市富平县",
+					"610581" => "陕西省渭南市韩城市",
+					"610582" => "陕西省渭南市华阴市",
+					"610600" => "陕西省延安市",
+					"610601" => "陕西省延安市市辖区",
+					"610602" => "陕西省延安市宝塔区",
+					"610621" => "陕西省延安市延长县",
+					"610622" => "陕西省延安市延川县",
+					"610623" => "陕西省延安市子长县",
+					"610624" => "陕西省延安市安塞县",
+					"610625" => "陕西省延安市志丹县",
+					"610626" => "陕西省延安市吴旗县",
+					"610627" => "陕西省延安市甘泉县",
+					"610628" => "陕西省延安市富县",
+					"610629" => "陕西省延安市洛川县",
+					"610630" => "陕西省延安市宜川县",
+					"610631" => "陕西省延安市黄龙县",
+					"610632" => "陕西省延安市黄陵县",
+					"610700" => "陕西省汉中市",
+					"610701" => "陕西省汉中市市辖区",
+					"610702" => "陕西省汉中市汉台区",
+					"610721" => "陕西省汉中市南郑县",
+					"610722" => "陕西省汉中市城固县",
+					"610723" => "陕西省汉中市洋县",
+					"610724" => "陕西省汉中市西乡县",
+					"610725" => "陕西省汉中市勉县",
+					"610726" => "陕西省汉中市宁强县",
+					"610727" => "陕西省汉中市略阳县",
+					"610728" => "陕西省汉中市镇巴县",
+					"610729" => "陕西省汉中市留坝县",
+					"610730" => "陕西省汉中市佛坪县",
+					"612400" => "陕西省安康地区",
+					"612401" => "陕西省安康地区安康市",
+					"612422" => "陕西省安康地区汉阴县",
+					"612423" => "陕西省安康地区石泉县",
+					"612424" => "陕西省安康地区宁陕县",
+					"612425" => "陕西省安康地区紫阳县",
+					"612426" => "陕西省安康地区岚皋县",
+					"612427" => "陕西省安康地区平利县",
+					"612428" => "陕西省安康地区镇坪县",
+					"612429" => "陕西省安康地区旬阳县",
+					"612430" => "陕西省安康地区白河县",
+					"612500" => "陕西省商洛地区",
+					"612501" => "陕西省商洛地区商州市",
+					"612522" => "陕西省商洛地区洛南县",
+					"612523" => "陕西省商洛地区丹凤县",
+					"612524" => "陕西省商洛地区商南县",
+					"612525" => "陕西省商洛地区山阳县",
+					"612526" => "陕西省商洛地区镇安县",
+					"612527" => "陕西省商洛地区柞水县",
+					"612700" => "陕西省榆林地区",
+					"612701" => "陕西省榆林地区榆林市",
+					"612722" => "陕西省榆林地区神木县",
+					"612723" => "陕西省榆林地区府谷县",
+					"612724" => "陕西省榆林地区横山县",
+					"612725" => "陕西省榆林地区靖边县",
+					"612726" => "陕西省榆林地区定边县",
+					"612727" => "陕西省榆林地区绥德县",
+					"612728" => "陕西省榆林地区米脂县",
+					"612729" => "陕西省榆林地区佳县",
+					"612730" => "陕西省榆林地区吴堡县",
+					"612731" => "陕西省榆林地区清涧县",
+					"612732" => "陕西省榆林地区子洲县",
+					"620000" => "甘肃省",
+					"620100" => "甘肃省兰州市",
+					"620101" => "甘肃省兰州市市辖区",
+					"620102" => "甘肃省兰州市城关区",
+					"620103" => "甘肃省兰州市七里河区",
+					"620104" => "甘肃省兰州市西固区",
+					"620105" => "甘肃省兰州市安宁区",
+					"620111" => "甘肃省兰州市红古区",
+					"620121" => "甘肃省兰州市永登县",
+					"620122" => "甘肃省兰州市皋兰县",
+					"620123" => "甘肃省兰州市榆中县",
+					"620200" => "甘肃省嘉峪关市",
+					"620201" => "甘肃省嘉峪关市市辖区",
+					"620300" => "甘肃省嘉峪关市金昌市",
+					"620301" => "甘肃省嘉峪关市市辖区",
+					"620302" => "甘肃省嘉峪关市金川区",
+					"620321" => "甘肃省嘉峪关市永昌县",
+					"620400" => "甘肃省白银市",
+					"620401" => "甘肃省白银市市辖区",
+					"620402" => "甘肃省白银市白银区",
+					"620403" => "甘肃省白银市平川区",
+					"620421" => "甘肃省白银市靖远县",
+					"620422" => "甘肃省白银市会宁县",
+					"620423" => "甘肃省白银市景泰县",
+					"620500" => "甘肃省天水市",
+					"620501" => "甘肃省天水市市辖区",
+					"620502" => "甘肃省天水市秦城区",
+					"620503" => "甘肃省天水市北道区",
+					"620521" => "甘肃省天水市清水县",
+					"620522" => "甘肃省天水市秦安县",
+					"620523" => "甘肃省天水市甘谷县",
+					"620524" => "甘肃省天水市武山县",
+					"620525" => "甘肃省天水市张家川回族自治县",
+					"622100" => "甘肃省酒泉地区",
+					"622101" => "甘肃省酒泉地区玉门市",
+					"622102" => "甘肃省酒泉地区酒泉市",
+					"622103" => "甘肃省酒泉地区敦煌市",
+					"622123" => "甘肃省酒泉地区金塔县",
+					"622124" => "甘肃省酒泉地区肃北蒙古族自治县",
+					"622125" => "甘肃省酒泉地区阿克塞哈萨克族自治县",
+					"622126" => "甘肃省酒泉地区安西县",
+					"622200" => "甘肃省张掖地区",
+					"622201" => "甘肃省张掖地区张掖市",
+					"622222" => "甘肃省张掖地区肃南裕固族自治县",
+					"622223" => "甘肃省张掖地区民乐县",
+					"622224" => "甘肃省张掖地区临泽县",
+					"622225" => "甘肃省张掖地区高台县",
+					"622226" => "甘肃省张掖地区山丹县",
+					"622300" => "甘肃省武威地区",
+					"622301" => "甘肃省武威地区武威市",
+					"622322" => "甘肃省武威地区民勤县",
+					"622323" => "甘肃省武威地区古浪县",
+					"622326" => "甘肃省武威地区天祝藏族自治县",
+					"622400" => "甘肃省定西地区",
+					"622421" => "甘肃省定西地区定西县",
+					"622424" => "甘肃省定西地区通渭县",
+					"622425" => "甘肃省定西地区陇西县",
+					"622426" => "甘肃省定西地区渭源县",
+					"622427" => "甘肃省定西地区临洮县",
+					"622428" => "甘肃省定西地区漳县",
+					"622429" => "甘肃省定西地区岷县",
+					"622600" => "甘肃省陇南地区",
+					"622621" => "甘肃省陇南地区武都县",
+					"622623" => "甘肃省陇南地区宕昌县",
+					"622624" => "甘肃省陇南地区成县",
+					"622625" => "甘肃省陇南地区康县",
+					"622626" => "甘肃省陇南地区文县",
+					"622627" => "甘肃省陇南地区西和县",
+					"622628" => "甘肃省陇南地区礼县",
+					"622629" => "甘肃省陇南地区两当县",
+					"622630" => "甘肃省陇南地区徽县",
+					"622700" => "甘肃省平凉地区",
+					"622701" => "甘肃省平凉地区平凉市",
+					"622722" => "甘肃省平凉地区泾川县",
+					"622723" => "甘肃省平凉地区灵台县",
+					"622724" => "甘肃省平凉地区崇信县",
+					"622725" => "甘肃省平凉地区华亭县",
+					"622726" => "甘肃省平凉地区庄浪县",
+					"622727" => "甘肃省平凉地区静宁县",
+					"622800" => "甘肃省庆阳地区",
+					"622801" => "甘肃省庆阳地区西峰市",
+					"622821" => "甘肃省庆阳地区庆阳县",
+					"622822" => "甘肃省庆阳地区环县",
+					"622823" => "甘肃省庆阳地区华池县",
+					"622824" => "甘肃省庆阳地区合水县",
+					"622825" => "甘肃省庆阳地区正宁县",
+					"622826" => "甘肃省庆阳地区宁县",
+					"622827" => "甘肃省庆阳地区镇原县",
+					"622900" => "甘肃省临夏回族自治州",
+					"622901" => "甘肃省临夏回族自治州临夏市",
+					"622921" => "甘肃省临夏回族自治州临夏县",
+					"622922" => "甘肃省临夏回族自治州康乐县",
+					"622923" => "甘肃省临夏回族自治州永靖县",
+					"622924" => "甘肃省临夏回族自治州广河县",
+					"622925" => "甘肃省临夏回族自治州和政县",
+					"622926" => "甘肃省临夏回族自治州东乡族自治县",
+					"622927" => "甘肃省临夏回族自治州积石山保安族东乡族撒拉族自治县",
+					"623000" => "甘肃省甘南藏族自治州",
+					"623001" => "甘肃省甘南藏族自治州合作市",
+					"623021" => "甘肃省甘南藏族自治州临潭县",
+					"623022" => "甘肃省甘南藏族自治州卓尼县",
+					"623023" => "甘肃省甘南藏族自治州舟曲县",
+					"623024" => "甘肃省甘南藏族自治州迭部县",
+					"623025" => "甘肃省甘南藏族自治州玛曲县",
+					"623026" => "甘肃省甘南藏族自治州碌曲县",
+					"623027" => "甘肃省甘南藏族自治州夏河县",
+					"630000" => "青海省",
+					"630100" => "青海省西宁市",
+					"630101" => "青海省西宁市市辖区",
+					"630102" => "青海省西宁市城东区",
+					"630103" => "青海省西宁市城中区",
+					"630104" => "青海省西宁市城西区",
+					"630105" => "青海省西宁市城北区",
+					"630121" => "青海省西宁市大通回族土族自治县",
+					"632100" => "青海省海东地区",
+					"632121" => "青海省海东地区平安县",
+					"632122" => "青海省海东地区民和回族土族自治县",
+					"632123" => "青海省海东地区乐都县",
+					"632124" => "青海省海东地区湟中县",
+					"632125" => "青海省海东地区湟源县",
+					"632126" => "青海省海东地区互助土族自治县",
+					"632127" => "青海省海东地区化隆回族自治县",
+					"632128" => "青海省海东地区循化撒拉族自治县",
+					"632200" => "青海省海北藏族自治州",
+					"632221" => "青海省海北藏族自治州门源回族自治县",
+					"632222" => "青海省海北藏族自治州祁连县",
+					"632223" => "青海省海北藏族自治州海晏县",
+					"632224" => "青海省海北藏族自治州刚察县",
+					"632300" => "青海省黄南藏族自治州",
+					"632321" => "青海省黄南藏族自治州同仁县",
+					"632322" => "青海省黄南藏族自治州尖扎县",
+					"632323" => "青海省黄南藏族自治州泽库县",
+					"632324" => "青海省黄南藏族自治州河南蒙古族自治县",
+					"632500" => "青海省海南藏族自治州",
+					"632521" => "青海省海南藏族自治州共和县",
+					"632522" => "青海省海南藏族自治州同德县",
+					"632523" => "青海省海南藏族自治州贵德县",
+					"632524" => "青海省海南藏族自治州兴海县",
+					"632525" => "青海省海南藏族自治州贵南县",
+					"632600" => "青海省果洛藏族自治州",
+					"632621" => "青海省果洛藏族自治州玛沁县",
+					"632622" => "青海省果洛藏族自治州班玛县",
+					"632623" => "青海省果洛藏族自治州甘德县",
+					"632624" => "青海省果洛藏族自治州达日县",
+					"632625" => "青海省果洛藏族自治州久治县",
+					"632626" => "青海省果洛藏族自治州玛多县",
+					"632700" => "青海省玉树藏族自治州",
+					"632721" => "青海省玉树藏族自治州玉树县",
+					"632722" => "青海省玉树藏族自治州杂多县",
+					"632723" => "青海省玉树藏族自治州称多县",
+					"632724" => "青海省玉树藏族自治州治多县",
+					"632725" => "青海省玉树藏族自治州囊谦县",
+					"632726" => "青海省玉树藏族自治州曲麻莱县",
+					"632800" => "青海省海西蒙古族藏族自治州",
+					"632801" => "青海省海西蒙古族藏族自治州格尔木市",
+					"632802" => "青海省海西蒙古族藏族自治州德令哈市",
+					"632821" => "青海省海西蒙古族藏族自治州乌兰县",
+					"632822" => "青海省海西蒙古族藏族自治州都兰县",
+					"632823" => "青海省海西蒙古族藏族自治州天峻县",
+					"640000" => "宁夏回族自治区",
+					"640100" => "宁夏回族自治区银川市",
+					"640101" => "宁夏回族自治区银川市市辖区",
+					"640102" => "宁夏回族自治区银川市城区",
+					"640103" => "宁夏回族自治区银川市新城区",
+					"640111" => "宁夏回族自治区银川市郊区",
+					"640121" => "宁夏回族自治区银川市永宁县",
+					"640122" => "宁夏回族自治区银川市贺兰县",
+					"640200" => "宁夏回族自治区石嘴山市",
+					"640201" => "宁夏回族自治区石嘴山市市辖区",
+					"640202" => "宁夏回族自治区石嘴山市大武口区",
+					"640203" => "宁夏回族自治区石嘴山市石嘴山区",
+					"640204" => "宁夏回族自治区石嘴山市石炭井区",
+					"640221" => "宁夏回族自治区石嘴山市平罗县",
+					"640222" => "宁夏回族自治区石嘴山市陶乐县",
+					"640223" => "宁夏回族自治区石嘴山市惠农县",
+					"640300" => "宁夏回族自治区吴忠市",
+					"640301" => "宁夏回族自治区吴忠市市辖区",
+					"640302" => "宁夏回族自治区吴忠市利通区",
+					"640321" => "宁夏回族自治区吴忠市中卫县",
+					"640322" => "宁夏回族自治区吴忠市中宁县",
+					"640323" => "宁夏回族自治区吴忠市盐池县",
+					"640324" => "宁夏回族自治区吴忠市同心县",
+					"640381" => "宁夏回族自治区吴忠市青铜峡市",
+					"640382" => "宁夏回族自治区吴忠市灵武市",
+					"642200" => "宁夏回族自治区固原地区",
+					"642221" => "宁夏回族自治区固原地区固原县",
+					"642222" => "宁夏回族自治区固原地区海原县",
+					"642223" => "宁夏回族自治区固原地区西吉县",
+					"642224" => "宁夏回族自治区固原地区隆德县",
+					"642225" => "宁夏回族自治区固原地区泾源县",
+					"642226" => "宁夏回族自治区固原地区彭阳县",
+					"650000" => "新疆维吾尔自治区",
+					"650100" => "新疆维吾尔族自治区乌鲁木齐市",
+					"650101" => "新疆维吾尔族自治区乌鲁木齐市市辖区",
+					"650102" => "新疆维吾尔族自治区乌鲁木齐市天山区",
+					"650103" => "新疆维吾尔族自治区乌鲁木齐市沙依巴克区",
+					"650104" => "新疆维吾尔族自治区乌鲁木齐市新市区",
+					"650105" => "新疆维吾尔族自治区乌鲁木齐市水磨沟区",
+					"650106" => "新疆维吾尔族自治区乌鲁木齐市头屯河区",
+					"650107" => "新疆维吾尔族自治区乌鲁木齐市南山矿区",
+					"650108" => "新疆维吾尔族自治区乌鲁木齐市东山区",
+					"650121" => "新疆维吾尔族自治区乌鲁木齐市乌鲁木齐县",
+					"650200" => "新疆维吾尔族自治区克拉玛依市",
+					"650201" => "新疆维吾尔族自治区克拉玛依市市辖区",
+					"650202" => "新疆维吾尔族自治区克拉玛依市独山子区",
+					"650203" => "新疆维吾尔族自治区克拉玛依市克拉玛依区",
+					"650204" => "新疆维吾尔族自治区克拉玛依市白碱滩区",
+					"650205" => "新疆维吾尔族自治区克拉玛依市乌尔禾区",
+					"652100" => "新疆维吾尔族自治区吐鲁番地区",
+					"652101" => "新疆维吾尔族自治区吐鲁番地区吐鲁番市",
+					"652122" => "新疆维吾尔族自治区吐鲁番地区鄯善县",
+					"652123" => "新疆维吾尔族自治区吐鲁番地区托克逊县",
+					"652200" => "新疆维吾尔族自治区哈密地区",
+					"652201" => "新疆维吾尔族自治区哈密地区哈密市",
+					"652222" => "新疆维吾尔族自治区哈密地区巴里坤哈萨克自治县",
+					"652223" => "新疆维吾尔族自治区哈密地区伊吾县",
+					"652300" => "新疆维吾尔族自治区昌吉回族自治州",
+					"652301" => "新疆维吾尔族自治区昌吉回族自治州昌吉市",
+					"652302" => "新疆维吾尔族自治区昌吉回族自治州阜康市",
+					"652303" => "新疆维吾尔族自治区昌吉回族自治州米泉市",
+					"652323" => "新疆维吾尔族自治区昌吉回族自治州呼图壁县",
+					"652324" => "新疆维吾尔族自治区昌吉回族自治州玛纳斯县",
+					"652325" => "新疆维吾尔族自治区昌吉回族自治州奇台县",
+					"652327" => "新疆维吾尔族自治区昌吉回族自治州吉木萨尔县",
+					"652328" => "新疆维吾尔族自治区昌吉回族自治州木垒哈萨克自治县",
+					"652700" => "新疆维吾尔族自治区博尔塔拉蒙古自治州",
+					"652701" => "新疆维吾尔族自治区博尔塔拉蒙古自治州博乐市",
+					"652722" => "新疆维吾尔族自治区博尔塔拉蒙古自治州精河县",
+					"652723" => "新疆维吾尔族自治区博尔塔拉蒙古自治州温泉县",
+					"652800" => "新疆维吾尔族自治区巴音郭楞蒙古自治州",
+					"652801" => "新疆维吾尔族自治区巴音郭楞蒙古自治州库尔勒市",
+					"652822" => "新疆维吾尔族自治区巴音郭楞蒙古自治州轮台县",
+					"652823" => "新疆维吾尔族自治区巴音郭楞蒙古自治州尉犁县",
+					"652824" => "新疆维吾尔族自治区巴音郭楞蒙古自治州若羌县",
+					"652825" => "新疆维吾尔族自治区巴音郭楞蒙古自治州且末县",
+					"652826" => "新疆维吾尔族自治区巴音郭楞蒙古自治州焉耆回族自治县",
+					"652827" => "新疆维吾尔族自治区巴音郭楞蒙古自治州和静县",
+					"652828" => "新疆维吾尔族自治区巴音郭楞蒙古自治州和硕县",
+					"652829" => "新疆维吾尔族自治区巴音郭楞蒙古自治州博湖县",
+					"652900" => "新疆维吾尔族自治区阿克苏地区",
+					"652901" => "新疆维吾尔族自治区阿克苏地区阿克苏市",
+					"652922" => "新疆维吾尔族自治区阿克苏地区温宿县",
+					"652923" => "新疆维吾尔族自治区阿克苏地区库车县",
+					"652924" => "新疆维吾尔族自治区阿克苏地区沙雅县",
+					"652925" => "新疆维吾尔族自治区阿克苏地区新和县",
+					"652926" => "新疆维吾尔族自治区阿克苏地区拜城县",
+					"652927" => "新疆维吾尔族自治区阿克苏地区乌什县",
+					"652928" => "新疆维吾尔族自治区阿克苏地区阿瓦提县",
+					"652929" => "新疆维吾尔族自治区阿克苏地区柯坪县",
+					"653000" => "新疆维吾尔族自治区克孜勒苏柯尔克孜自治州",
+					"653001" => "新疆维吾尔族自治区克孜勒苏柯尔克孜自治州阿图什市",
+					"653022" => "新疆维吾尔族自治区克孜勒苏柯尔克孜自治州阿克陶县",
+					"653023" => "新疆维吾尔族自治区克孜勒苏柯尔克孜自治州阿合奇县",
+					"653024" => "新疆维吾尔族自治区克孜勒苏柯尔克孜自治州乌恰县",
+					"653100" => "新疆维吾尔族自治区喀什地区",
+					"653101" => "新疆维吾尔族自治区喀什地区喀什市",
+					"653121" => "新疆维吾尔族自治区喀什地区疏附县",
+					"653122" => "新疆维吾尔族自治区喀什地区疏勒县",
+					"653123" => "新疆维吾尔族自治区喀什地区英吉沙县",
+					"653124" => "新疆维吾尔族自治区喀什地区泽普县",
+					"653125" => "新疆维吾尔族自治区喀什地区莎车县",
+					"653126" => "新疆维吾尔族自治区喀什地区叶城县",
+					"653127" => "新疆维吾尔族自治区喀什地区麦盖提县",
+					"653128" => "新疆维吾尔族自治区喀什地区岳普湖县",
+					"653129" => "新疆维吾尔族自治区喀什地区伽师县",
+					"653130" => "新疆维吾尔族自治区喀什地区巴楚县",
+					"653131" => "新疆维吾尔族自治区喀什地区塔什库尔干塔吉克自治县",
+					"653200" => "新疆维吾尔族自治区和田地区",
+					"653201" => "新疆维吾尔族自治区和田地区和田市",
+					"653221" => "新疆维吾尔族自治区和田地区和田县",
+					"653222" => "新疆维吾尔族自治区和田地区墨玉县",
+					"653223" => "新疆维吾尔族自治区和田地区皮山县",
+					"653224" => "新疆维吾尔族自治区和田地区洛浦县",
+					"653225" => "新疆维吾尔族自治区和田地区策勒县",
+					"653226" => "新疆维吾尔族自治区和田地区于田县",
+					"653227" => "新疆维吾尔族自治区和田地区民丰县",
+					"654000" => "新疆维吾尔族自治区伊犁哈萨克自治州",
+					"654001" => "新疆维吾尔族自治区伊犁哈萨克自治州奎屯市",
+					"654100" => "新疆维吾尔族自治区伊犁哈萨克自治州伊犁地区",
+					"654101" => "新疆维吾尔族自治区伊犁哈萨克自治州伊宁市",
+					"654121" => "新疆维吾尔族自治区伊犁哈萨克自治州伊宁县",
+					"654122" => "新疆自治区伊犁哈萨克自治州察布查尔锡伯自治县",
+					"654123" => "新疆维吾尔族自治区伊犁哈萨克自治州霍城县",
+					"654124" => "新疆维吾尔族自治区伊犁哈萨克自治州巩留县",
+					"654125" => "新疆维吾尔族自治区伊犁哈萨克自治州新源县",
+					"654126" => "新疆维吾尔族自治区伊犁哈萨克自治州昭苏县",
+					"654127" => "新疆维吾尔族自治区伊犁哈萨克自治州特克斯县",
+					"654128" => "新疆维吾尔族自治区伊犁哈萨克自治州尼勒克县",
+					"654200" => "新疆维吾尔族自治区塔城地区",
+					"654201" => "新疆维吾尔族自治区塔城地区塔城市",
+					"654202" => "新疆维吾尔族自治区塔城地区乌苏市",
+					"654221" => "新疆维吾尔族自治区塔城地区额敏县",
+					"654223" => "新疆维吾尔族自治区塔城地区沙湾县",
+					"654224" => "新疆维吾尔族自治区塔城地区托里县",
+					"654225" => "新疆维吾尔族自治区塔城地区裕民县",
+					"654226" => "新疆维吾尔族自治区塔城地区和布克赛尔蒙古自治县",
+					"654300" => "新疆维吾尔族自治区阿勒泰地区",
+					"654301" => "新疆维吾尔族自治区阿勒泰地区阿勒泰市",
+					"654321" => "新疆维吾尔族自治区阿勒泰地区布尔津县",
+					"654322" => "新疆维吾尔族自治区阿勒泰地区富蕴县",
+					"654323" => "新疆维吾尔族自治区阿勒泰地区福海县",
+					"654324" => "新疆维吾尔族自治区阿勒泰地区哈巴河县",
+					"654325" => "新疆维吾尔族自治区阿勒泰地区青河县",
+					"654326" => "新疆维吾尔族自治区阿勒泰地区吉木乃县",
+					"659000" => "新疆维吾尔族自治区直辖县级行政单位",
+					"659001" => "新疆维吾尔族自治区石河子市" 
+			);
+		}
+		return self::$gb2260;
+	}
+}

+ 187 - 0
common/helpers/IDValidator/IDValidator.php

@@ -0,0 +1,187 @@
+<?php
+namespace common\helpers\IDValidator;
+
+class IDValidator {
+	private static $GB2260;
+	private static $instance;
+	private static $cache = array();
+	private static $util;
+	function __construct() {
+		if (!class_exists("GB2260")){
+			include_once 'GB2260.php';
+		}
+		if (!class_exists("util")){
+			include_once 'util.php';
+		}
+		self::$GB2260 = GB2260::getGB2260();
+		self::$util = util::getInstance();
+	}
+	public static function getInstance() {
+		if (is_null ( self::$instance )) {
+			self::$instance = new IDValidator ();
+		}
+		return self::$instance;
+	}
+	function isValid($id) {
+		$code = self::$util->checkArg ( $id );
+		if ($code === false) {
+			return false;
+		}
+		// 查询cache
+		if (isset ( self::$cache [ $id ] ) && self::$cache [$id] ['valid'] !== false) {
+			return self::$cache [$id] ['valid'];
+		} else {
+			if (! isset ( self::$cache [ $id ] )) {
+				self::$cache [$id] = array ();
+			}
+		}
+
+		$addr = substr ( $code ['body'], 0, 6 );
+		$birth = $code ['type'] === 18 ? substr ( $code ['body'], 6, 8 ) :
+			substr ( $code ['body'], 6, 6 );
+		$order = substr ( $code ['body'], - 3 );
+
+		if (! (self::$util->checkAddr ( $addr ) && self::$util->checkBirth ( $birth ) &&
+			self::$util->checkOrder ( $order ))) {
+			self::$cache [$id] ['valid'] = false;
+			return false;
+		}
+
+		// 15位不含校验码,到此已结束
+		if ($code ['type'] === 15) {
+			self::$cache [$id] ['valid'] = true;
+			return true;
+		}
+
+		/* 校验位部分 */
+
+		// 位置加权
+		$posWeight = array ();
+		for($i = 18; $i > 1; $i --) {
+			$wei = self::$util->weight ( $i );
+			$posWeight [$i] = $wei;
+		}
+
+		// 累加body部分与位置加权的积
+		$bodySum = 0;
+		$bodyArr = str_split( $code ['body'] );
+		for($j = 0; $j < count ( $bodyArr ); $j ++) {
+			$bodySum += (intval ( $bodyArr [$j], 10 ) * $posWeight [18 - $j]);
+		}
+
+		// 得出校验码
+		$checkBit = 12 - ($bodySum % 11);
+		if ($checkBit == 10) {
+			$checkBit = 'X';
+		} else if ($checkBit > 10) {
+			$checkBit = $checkBit % 11;
+		}
+		// 检查校验码
+		if ($checkBit != $code ['checkBit']) {
+			self::$cache [$id] ['valid'] = false;
+			return false;
+		} else {
+			self::$cache [$id] ['valid'] = true;
+			return true;
+		}
+	}
+	// 分析详细信息
+	function getInfo ($id) {
+		// 号码必须有效
+		if ($this->isValid($id) === false) {
+			return false;
+		}
+		// TODO 复用此部分
+		$code = self::$util->checkArg($id);
+
+		// 查询cache
+		// 到此时通过isValid已经有了cache记录
+		if (isset(self::$cache[$id]) && isset(self::$cache[$id]['info'])) {
+			return self::$cache[$id]['info'];
+		}
+
+		$addr = substr($code['body'], 0, 6);
+		$birth = ($code['type'] === 18 ? substr($code['body'], 6, 8) :
+				substr($code['body'], 6, 6));
+		$order = substr($code['body'], -3);
+
+		$info = array();
+		$info['addrCode'] = $addr;
+		if (self::$GB2260 !== null) {
+			$info['addr'] = self::$util->getAddrInfo($addr);
+		}
+		$info ['birth'] = ($code ['type'] === 18 ? (substr ( $birth, 0, 4 ) . '-' . substr ( $birth, 4, 2 ) . '-' . substr ( $birth, - 2 )) : ('19' . substr ( $birth, 0, 2 ) . '-' . substr ( $birth, 2, 2 ) . '-' . substr ( $birth, - 2 )));
+		$info['sex'] = ($order % 2 === 0 ? 0 : 1);
+		$info['length'] = $code['type'];
+		if ($code['type'] === 18) {
+			$info['checkBit'] = $code['checkBit'];
+		}
+
+		// 记录cache
+		self::$cache[$id]['info'] = $info;
+
+		return $info;
+	}
+	
+	// 仿造一个号
+	function makeID ($isFifteen=false) {
+		// 地址码
+		$addr = null;
+		if (self::$GB2260 !== null) {
+			$loopCnt = 0;
+			while ($addr === null) {
+				// 防止死循环
+				if ($loopCnt > 50) {
+					$addr = 110101;
+					break;
+				}
+				$prov = self::$util->str_pad(self::$util->rand(66), 2, '0');
+				$city = self::$util->str_pad(self::$util->rand(20), 2, '0');
+				$area = self::$util->str_pad(self::$util->rand(20), 2, '0');
+				$addrTest = $prov . $city . $area;
+				if (isset(self::$GB2260[$addrTest])) {
+					$addr = $addrTest;
+					break;
+				}
+				$loopCnt ++;
+			}
+		} else {
+			$addr = 110101;
+		}
+	
+		// 出生年
+		$yr = self::$util->str_pad(self::$util->rand(99, 50), 2, '0');
+		$mo = self::$util->str_pad(self::$util->rand(12, 1), 2, '0');
+		$da = self::$util->str_pad(self::$util->rand(28, 1), 2, '0');
+		if ($isFifteen) {
+			return $addr . $yr . $mo . $da
+				. self::$util->str_pad(self::$util->rand(999, 1), 3, '1');
+		}
+	
+		$yr = '19' . $yr;
+		$body = $addr . $yr . $mo . $da . self::$util->str_pad(self::$util->rand(999, 1), 3, '1');
+	
+		// 位置加权
+		$posWeight = array();
+		for ($i = 18; $i > 1; $i--) {
+			$wei = self::$util->weight($i);
+			$posWeight[$i] = $wei;
+		}
+	
+		// 累加body部分与位置加权的积
+		$bodySum = 0;
+		$bodyArr = str_split($body);
+		for ($j = 0; $j < count($bodyArr); $j++) {
+			$bodySum += (intval($bodyArr[$j], 10) * $posWeight[18 - $j]);
+		}
+	
+		// 得出校验码
+		$checkBit = 12 - ($bodySum % 11);
+		if ($checkBit == 10) {
+			$checkBit = 'X';
+		} else if ($checkBit > 10) {
+			$checkBit = $checkBit % 11;
+		}
+		return ($body . $checkBit);
+	}
+}

+ 130 - 0
common/helpers/IDValidator/util.php

@@ -0,0 +1,130 @@
+<?php
+namespace common\helpers\IDValidator;
+
+/**
+ * 工具
+ */
+class util {
+	private static $GB2260;
+	private static $instance;
+	function __construct() {
+		if (!class_exists("GB2260")){
+			include_once 'GB2260.php';
+		}
+		self::$GB2260 = GB2260::getGB2260 ();
+	}
+	public static function getInstance() {
+		if (is_null ( self::$instance )) {
+			self::$instance = new util ();
+		}
+		return self::$instance;
+	}
+	public function checkArg($id) {
+		$id = strtoupper ( $id );
+		$code = null;
+		if (strlen ( $id ) === 18) {
+			// 18位
+			$code = array (
+					"body" => substr ( $id, 0, 17 ),
+					"checkBit" => substr ( $id, - 1 ),
+					"type" => 18
+			);
+		} else if (strlen($id) === 15) {
+			// 15位
+			$code = array (
+					"body" => $id,
+					"type" => 15
+			);
+		} else {
+			return false;
+		}
+		return $code;
+	}
+
+	// 地址码检查
+	function checkAddr($addr) {
+		$addrInfo = $this->getAddrInfo ( $addr );
+		return ($addrInfo === false ? false : true);
+	}
+
+	// 取得地址码信息
+	function getAddrInfo($addr) {
+		// 查询GB/T2260,没有引入GB2260时略过
+		if (self::$GB2260 === null) {
+			return $addr;
+		}
+		if (! isset ( self::$GB2260 [$addr] )) {
+			// 考虑标准不全的情况,搜索不到时向上搜索
+			$tmpAddr = substr ( $addr, 0, 4 ) . '00';
+			if (! isset ( self::$GB2260 [$tmpAddr] )) {
+				$tmpAddr = substr ( $addr, 0, 2 ) . '0000';
+				if (! isset ( self::$GB2260 [$tmpAddr] )) {
+					return false;
+				} else {
+					return self::$GB2260 [$tmpAddr] . '未知地区';
+				}
+			} else {
+				return self::$GB2260 [$tmpAddr] . '未知地区';
+			}
+		} else {
+			return self::$GB2260 [$addr];
+		}
+	}
+
+	// 生日码检查
+	function checkBirth($birth) {
+		$year = $month = $day = 0;
+		if (strlen ( $birth ) == 8) {
+			$year = intval ( substr ( $birth, 0, 4 ) );
+			$month = intval ( substr ( $birth, 4, 2 ) );
+			$day = intval ( substr ( $birth, - 2 ) );
+		} else if (strlen($birth) == 6) {
+			$year = intval ( '19' + substr ( $birth, 0, 2 ) );
+			$month = intval ( substr ( $birth, 2, 2 ) );
+			$day = intval ( substr($birth, - 2 ) );
+		} else {
+			return false;
+		}
+		// TODO 是否需要判断年份
+		/*
+		 * if( year<1800 ){ return false; }
+		 */
+		// TODO 按月份检测
+		if ($month > 12 || $month === 0 || $day > 31 || $day === 0) {
+			return false;
+		}
+
+		return true;
+	}
+
+	// 顺序码检查
+	function checkOrder($order) {
+		// 暂无需检测
+		return true;
+	}
+	// 加权
+	function weight($t) {
+		return pow ( 2, $t - 1 ) % 11;
+	}
+	// 随机整数
+	function rand($max, $min = 1) {
+		//return round ( rand(0, 1) * ($max - $min) ) + $min;
+		return rand($min, $max);
+	}
+	// 数字补位
+	function str_pad($str, $len = 2, $chr = '0', $right = false) {
+		$str = strval($str);
+		if (strlen ( $str ) >= $len) {
+			return $str;
+		} else {
+			for($i = 0, $j = $len - strlen ( $str ); $i < $j; $i ++) {
+				if ($right) {
+					$str = $str . $chr;
+				} else {
+					$str = $chr . $str;
+				}
+			}
+			return $str;
+		}
+	}
+}

+ 16 - 0
common/helpers/Level.php

@@ -0,0 +1,16 @@
+<?php
+/**
+ * 级别管理类
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/3/19
+ * Time: 下午3:07
+ */
+
+namespace common\helpers;
+
+
+class Level
+{
+
+}

+ 146 - 0
common/helpers/Log.php

@@ -0,0 +1,146 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/3/2
+ * Time: 下午1:29
+ */
+
+namespace common\helpers;
+
+use common\models\LogAdminHandle;
+use common\models\LogAdminLogin;
+use common\models\LogApiNotice;
+use common\models\LogAsync;
+use common\models\Period;
+use Yii;
+use yii\helpers\Json;
+
+class Log
+{
+    /**
+     * 管理员操作日志
+     * @param $message
+     * @param int $isKey
+     * @param null $userId
+     * @param null $userName
+     * @param null $remark
+     */
+    public static function adminHandle($message, $isKey = 0, $userId = null, $userName = null, $remark = null){
+        if(Yii::$app->params['enableLog']){
+            $period = Period::instance();
+            $deviceInfo = Yii::$app->request->getDeviceInfo();
+            $userInfo = Yii::$app->user->getUserInfo();
+            $model = new LogAdminHandle();
+            $model->ADMIN_ID = $userInfo['id'];
+            $model->ADMIN_NAME = $userInfo['adminName'];
+            $model->IP = $userInfo['ip'];
+            $model->REQUEST_ROUTE = Yii::$app->controller->id.'/'.Yii::$app->controller->action->id;
+            $model->OPT_CONTENT = $message;
+            $model->KEY_LOG = $isKey;
+            $model->OPT_OBJ_ID = $userId;
+            $model->OPT_OBJ_NAME = $userName;
+            $model->REMARK = $remark;
+            $model->DEVICE_TYPE = Yii::$app->request->getDevice();
+            $model->DEVICE_SYSTEM = $deviceInfo ? $deviceInfo['system'] : null;
+            $model->DEVICE_VERSION = $deviceInfo ? $deviceInfo['version'] : null;
+            $model->DEVICE_NET = $deviceInfo ? $deviceInfo['networkType'] : null;
+            $model->DEVICE_UUID = $deviceInfo ? $deviceInfo['uuid'] : null;
+            $model->USER_AGENT = Yii::$app->request->getUserAgent();
+            $model->P_MONTH = Date::ociToDate();
+            $model->CREATED_AT = Date::nowTime();
+            $model->PERIOD_NUM = $period->getNowPeriodNum();
+            $model->save();
+        }
+    }
+
+    /**
+     * 管理员登录日志
+     */
+    public static function adminLogin(){
+        if(Yii::$app->params['enableLog']) {
+            $period = Period::instance();
+            $userInfo = Yii::$app->user->getUserInfo();
+            $deviceInfo = Yii::$app->request->getDeviceInfo();
+            $model = new LogAdminLogin();
+            $model->ADMIN_ID = $userInfo['id'];
+            $model->ADMIN_NAME = $userInfo['adminName'];
+            $model->IP = $userInfo['ip'];
+            $model->P_MONTH = Date::ociToDate();
+            $model->DEVICE_TYPE = Yii::$app->request->getDevice();
+            $model->DEVICE_SYSTEM = $deviceInfo ? $deviceInfo['system'] : null;
+            $model->DEVICE_VERSION = $deviceInfo ? $deviceInfo['version'] : null;
+            $model->DEVICE_NET = $deviceInfo ? $deviceInfo['networkType'] : null;
+            $model->DEVICE_UUID = $deviceInfo ? $deviceInfo['uuid'] : null;
+            $model->USER_AGENT = Yii::$app->request->getUserAgent();
+            $model->CREATED_AT = Date::nowTime();
+            $model->PERIOD_NUM = $period->getNowPeriodNum();
+            $model->save();
+        }
+    }
+
+    /**
+     * 接口异步通知日志
+     * @param $param
+     */
+    public static function apiNotice($param){
+        if(Yii::$app->params['enableLog']) {
+            [
+                'url' => $url,
+                'method' => $method,
+                'data' => $data,
+                'route' => $route,
+                'status' => $status,
+                'response' => $response,
+                'remark' => $remark,
+            ] = $param;
+            $model = new LogApiNotice();
+            $model->URL = $url;
+            $model->METHOD = $method;
+            $model->DATA = Json::encode($data);
+            $model->ROUTE = $route;
+            $model->STATUS = $status;
+            $model->RESPONSE = Json::encode($response);
+            $model->REMARK = $remark;
+            $model->P_MONTH = Date::ociToDate();
+            $model->CREATED_AT = Date::nowTime();
+            $model->save();
+        }
+    }
+
+    /**
+     * console中异步任务的执行日志
+     * @param $param
+     */
+    public static function async($param){
+//        file_put_contents(Yii::getAlias('@common/runtime/logs/asyncLog.log'), var_export($param, true));
+        if(Yii::$app->params['enableLog']) {
+            [
+                'type' => $type,
+                'route' => $route,
+                'title' => $title,
+                'detail' => $detail,
+                'status' => $status,
+            ] = $param;
+            $model = new LogAsync();
+            $model->TYPE = $type;
+            $model->ROUTE = $route;
+            $model->TITLE = $title;
+            $model->DETAIL = $detail;
+            $model->STATUS = $status;
+            $model->P_MONTH = Date::ociToDate();
+            $model->CREATED_AT = Date::nowTime();
+            $model->save();
+        }
+    }
+
+    /**
+     * 文件日志
+     * @param $message
+     * @param $category
+     */
+    public static function cliInfo($message, $category = 'application'){
+        Yii::getLogger()->flush(true);
+        Yii::info($message, $category);
+    }
+}

+ 55 - 0
common/helpers/LoggerTool.php

@@ -0,0 +1,55 @@
+<?php
+
+namespace common\helpers;
+
+use Monolog\Handler\FirePHPHandler;
+use Monolog\Logger;
+use Monolog\Handler\StreamHandler;
+
+class LoggerTool
+{
+    public static function info($message)
+    {
+        $logger = new Logger('info');
+        // 添加一些处理器
+        $logger->pushHandler(new StreamHandler(__DIR__ . "/../runtime/logs/info.log", Logger::DEBUG));
+        $logger->pushHandler(new FirePHPHandler());
+        $logger->info(json_encode($message));
+    }
+
+    public static function error($message)
+    {
+        $logger = new Logger('error');
+        // 添加一些处理器
+        $logger->pushHandler(new StreamHandler(__DIR__ . "/../runtime/logs/error.log", Logger::DEBUG));
+        $logger->pushHandler(new FirePHPHandler());
+        $logger->error(json_encode($message));
+    }
+
+    public static function warning($message)
+    {
+        $logger = new Logger('warning');
+        // 添加一些处理器
+        $logger->pushHandler(new StreamHandler(__DIR__ . "/../runtime/logs/warning.log", Logger::DEBUG));
+        $logger->pushHandler(new FirePHPHandler());
+        $logger->warning(json_encode($message));
+    }
+
+    public static function notice($message)
+    {
+        $logger = new Logger('notice');
+        // 添加一些处理器
+        $logger->pushHandler(new StreamHandler(__DIR__ . "/../runtime/logs/notice.log", Logger::DEBUG));
+        $logger->pushHandler(new FirePHPHandler());
+        $logger->notice(json_encode($message));
+    }
+
+    public static function debug($message)
+    {
+        $logger = new Logger('debug');
+        // 添加一些处理器
+        $logger->pushHandler(new StreamHandler(__DIR__ . "/../runtime/logs/debug.log", Logger::DEBUG));
+        $logger->pushHandler(new FirePHPHandler());
+        $logger->debug(json_encode($message));
+    }
+}

+ 142 - 0
common/helpers/MongodbSearchFilter.php

@@ -0,0 +1,142 @@
+<?php
+namespace common\helpers;
+use Yii;
+
+class MongodbSearchFilter {
+
+    const SYMBOL_MAP = [
+        '>'    => [
+            'label' => '大于',
+            'sql'   => '$gt',
+        ],
+        '>='   => [
+            'label' => '大于等于',
+            'sql'   => '$gte',
+        ],
+        '<'    => [
+            'label' => '小于',
+            'sql'   => '$lt',
+        ],
+        '<='   => [
+            'label' => '小于等于',
+            'sql'   => '$lte',
+        ],
+        '<>'   => [
+            'label' => '不等于',
+            'sql'   => '$ne',
+        ],
+    ];
+
+    /**
+     * 筛选条件
+     * @param array $tableParams
+     * @return array
+     */
+    public static function filterCondition(array $tableParams = []){
+        $allGet = Yii::$app->request->get();
+        $condition = [];
+        foreach ($tableParams as $getParam => $tableField) {
+            if (isset($allGet[$getParam]) && $allGet[$getParam]) {
+                $getValue = trim($allGet[$getParam], ", \t\n\r\0\x0B");
+                if (strpos($getValue, '|') > 0) {
+                    $childValueArr = explode('|', $getValue);
+                    $condition['$or'] = [];
+                    foreach ($childValueArr as $k => $value) {
+                        $result = self::getConditionAndParams($value, $tableField);
+                        $condition['$or'][] = $result;
+                    }
+                } else {
+                    $result = self::getConditionAndParams($getValue, $tableField);
+                    $condition += $result;
+                }
+            }
+        }
+        return [
+            'condition' => $condition,
+            'request' => $allGet,
+        ];
+    }
+
+    /**
+     * 处理筛选条件
+     * @param $getValue
+     * @param $tableField
+     * @return array
+     */
+    public static function getConditionAndParams($getValue, $tableField) {
+        $condition = [];
+        $isDate = false;
+        $filterModel = '';
+        if (strpos($getValue, ',') > 0) {
+            $getValueArr = explode(',', $getValue);
+            $getSymbol = strtoupper($getValueArr[0]);
+            if ($getSymbol == 'IN') {
+                $bindValueArr = $getValueArr;
+                unset($bindValueArr[0]);
+                $bindValue = implode("','", $bindValueArr);
+                $bindValue = "'$bindValue'";
+            } else {
+                $bindValue = $getValueArr[1];
+                $filterModel = end($getValueArr);
+                reset($getValueArr);
+                if($filterModel == 'date'){
+                    $bindValue = strtotime($getValueArr[1]);
+                    $isDate = true;
+                }
+                elseif($filterModel == 'area'){
+                    $bindValue = array_slice($getValueArr, 1, 3);
+                }
+            }
+        } else {
+            $getSymbol = '=';
+            $bindValue = $getValue;
+        }
+        if ($getSymbol == 'LIKE') {
+            $condition = [
+                $tableField => ['$regex' => $bindValue],
+            ];
+        } elseif ($getSymbol == 'NOTLIKE') {
+            $condition = [
+                $tableField => ['$regex' => '^((?!'.$bindValue.').)*$'],
+            ];
+        } else {
+            if ($isDate && $getSymbol == '=') {
+                $condition = [
+                    $tableField => [
+                        self::SYMBOL_MAP['>=']['sql'] => floatval($bindValue),
+                        self::SYMBOL_MAP['<=']['sql'] => floatval($bindValue + 86399)
+                    ],
+                ];
+            }
+            elseif($filterModel == 'area'){
+                if($bindValue[0]){
+                    $condition = [
+                        $tableField['FIELD'][0] => $bindValue[0],
+                    ];
+                    if($bindValue[1]){
+                        $condition += [
+                            $tableField['FIELD'][1] => $bindValue[1],
+                        ];
+                        if($bindValue[2]){
+                            $condition += [
+                                $tableField['FIELD'][2] => $bindValue[2],
+                            ];
+                        }
+                    }
+                }
+            }
+            elseif($getSymbol == '=') {
+                $condition = [
+                    $tableField => $bindValue,
+                ];
+            }
+            else {
+                $condition = [
+                    $tableField => [self::SYMBOL_MAP[$getSymbol]['sql'] => floatval($bindValue)],
+                ];
+            }
+        }
+        return $condition;
+    }
+
+}

+ 72 - 0
common/helpers/Schema.php

@@ -0,0 +1,72 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/3/23
+ * Time: 上午9:16
+ */
+
+namespace common\helpers;
+
+use yii\db\TableSchema;
+
+class Schema extends \yii\db\oci\Schema
+{
+    /**
+     * 重写载入表结构方法
+     * @param string $name
+     * @return mixed|null|TableSchema
+     */
+    protected function loadTableSchema($name)
+    {
+        $dbName = strtoupper($this->db->username);
+        if(strpos($name, $dbName) === 0){
+            $tableName = $name;
+        } else {
+            $tableName = $dbName.'.'.$name;
+        }
+        $path = \Yii::getAlias('@common/runtime/tableSchema/').$tableName;
+        $table = null;
+        if(file_exists($path)){
+            $cache = file_get_contents($path);
+            $table = unserialize($cache);
+        }
+        if(!$table){
+            $oriEnableSlaves = $this->db->enableSlaves;
+            $this->db->enableSlaves = false;
+            $table = new TableSchema();
+            $this->resolveTableNames($table, $name);
+            if ($this->findColumns($table)) {
+                $this->findConstraints($table);
+                $cacheContent = serialize($table);
+                file_put_contents($path, $cacheContent);
+                $this->db->enableSlaves = $oriEnableSlaves;
+                return $table;
+            }
+            $this->db->enableSlaves = $oriEnableSlaves;
+            return null;
+        }
+        return $table;
+    }
+
+    /**
+     * 对外公共的方法生成表缓存
+     * @param $name
+     */
+    public function generateTableSchema($name){
+        $this->loadTableSchema($name);
+    }
+
+    /**
+     * 清空表结构缓存
+     */
+    public function clearTableSchemaCache(){
+        $path = \Yii::getAlias('@common/runtime/tableSchema/');
+        $waitDel = scandir($path);
+        foreach($waitDel as $file){
+            if($file === '.gitignore') continue;
+            unlink($path.$file);
+        }
+    }
+
+}

+ 514 - 0
common/helpers/Tool.php

@@ -0,0 +1,514 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Leo
+ * Date: 2017/9/3
+ * Time: 下午10:05
+ */
+
+namespace common\helpers;
+
+
+use common\models\ApproachOrderCall;
+use Faker\Provider\Uuid;
+use yii\helpers\Url;
+use yii\httpclient\Client;
+use yii\mongodb\Exception;
+
+class Tool {
+
+    /**
+     * 获取无限级树形分类
+     * @param array $cate
+     * @param int $pid
+     * @param int $level
+     * @param string $html
+     * @return array
+     */
+    public static function categoryTree(array $cate, $pid=0, $level=0, $html='--') {
+        $tree = [];
+        foreach ($cate as $key=>$value){
+            if($value['pid'] == $pid) {
+                $value['level'] = $level + 1;
+                $html = str_repeat($html,$value['level']);
+                $tree[] = $value;
+                $tree = array_merge($tree, self::categoryTree($cate,$value['id'],$level+1,$html));
+            }
+        }
+        return $tree;
+    }
+
+    /**
+     * 解析数据
+     * @param $args
+     * @param null $defaults
+     * @return array
+     */
+    public static function deepParse($args, $defaults = NULL){
+        $result = array();
+        if (is_object($args)){
+            $result = get_object_vars( $args );
+        } elseif (is_array($args)){
+            $result =& $args;
+        }else{
+            parse_str($args, $result);
+        }
+        if (is_array($defaults))
+            return array_merge($defaults, $result);
+        return $result;
+    }
+
+    /**
+     * 格式化金额,保留两位小数
+     * @param $price
+     * @param int $auto  是否自动四舍五入
+     * @return string
+     */
+    public static function formatPrice($price ,$auto = 1){
+        if(!$price) return '0.00';
+        if($auto==1){
+            return sprintf("%.2f", $price);
+        }else{
+            return substr(sprintf("%.3f",$price),0,-1);
+        }
+    }
+
+    /**
+     * 格式化金额,保留2位小数,千分位分割
+     * @param $amount
+     * @param string $decimals
+     */
+    public static function formatAmount($amount, string $decimals = '2')
+    {
+        return (number_format($amount,$decimals));
+    }
+
+    /**
+     * 计算商品税额
+     * @param $amount
+     * @param $taxRate
+     * @param int $buyNo
+     * @return float
+     */
+    public static function calculateTax($amount, $taxRate, int $buyNo = 1): float
+    {
+        $taxAmount = ($amount - $amount / (1 + $taxRate / 100)) * $buyNo;
+        return floatval(self::formatPrice($taxAmount));
+    }
+
+    /**
+     * 前台业绩格式化
+     * @param $perf
+     * @param int $auto
+     * @param int $zoom
+     * @return bool|string
+     */
+    public static function formatFrontPerf($perf, $auto = 0, $zoom = 100) {
+        if (!$perf) return '0.00';
+        $perf = $perf / $zoom;
+        if ($auto == 1) {
+            return sprintf("%.2f", $perf);
+        } else {
+            return substr(sprintf("%.3f", $perf), 0, -1);
+        }
+    }
+
+    /**
+     * 格式化结算奖金
+     * @param $calcBonus
+     * @return string
+     */
+    public static function formatCalcBonus($calcBonus) {
+        return sprintf("%.3f", $calcBonus);
+    }
+
+    /**
+     * 获得文件扩展名
+     * @param $file
+     * @return string
+     */
+    public static function getExt($file) {
+        $ext = pathinfo($file ,PATHINFO_EXTENSION);
+        return strtolower($ext);
+    }
+
+    /**
+     * 获取文件上传的地址
+     * @return string
+     */
+    public static function getUploadUrl(){
+        return \Yii::getAlias('@frontendUrl').'/'.\Yii::$app->params['upload']['dir'];
+    }
+
+    /**
+     * 对二维数组排序
+     *
+     * @param $arrays
+     * @param $sortField
+     * @param int $sortOrder
+     * @param int $sortType
+     * @return bool
+     */
+    public static function sortMultiArray($arrays, $sortField, $sortOrder = SORT_ASC, $sortType = SORT_NUMERIC){
+        if(is_array($arrays)){
+            foreach ($arrays as $array){
+                if(is_array($array)){
+                    $key_arrays[] = $array[$sortField];
+                }else{
+                    return false;
+                }
+            }
+        }else{
+            return false;
+        }
+        array_multisort($key_arrays,$sortOrder,$sortType,$arrays);
+        return $arrays;
+    }
+
+    /**
+     * 清除字符串中的所有空格
+     * @param $string
+     * @return mixed
+     */
+    public static function trimAll($string){
+        $waitClean=array(" "," ");
+        $cleaned=array("","");
+        return str_replace($waitClean,$cleaned,$string);
+    }
+
+    /**
+     * 去除两端的逗号和所有空格
+     * @param $string
+     * @return mixed|string
+     */
+    public static function trimCommaAndSpace($string){
+        $result = self::trimAll($string);
+        $result = trim($result, ',');
+        return $result;
+    }
+
+    /**
+     * 页面跳转
+     * @param string $url
+     * @param string $err
+     * @param bool $parent
+     * @return string
+     */
+    public static function jsJump($url = 'back' ,$err = '' ,$parent = false) {
+        $output = '<script type="text/javascript">';
+        if (!empty($err)) $output .= "alert('{$err}');";
+        ('back' == $url)
+            ? $output .= 'window.history.go(-1);'
+            : ($output .= ($parent == true ? 'parent.' : '') . 'location.href="' . $url . '";');
+        $output .= '</script>';
+        return $output;
+    }
+
+    /**
+     * 发送Curl请求
+     * @param $method
+     * @param $url
+     * @param array $data
+     * @return \yii\httpclient\Response
+     * @throws \yii\httpclient\Exception
+     */
+    public static function sendCurlRequest($method, $url, $data = []){
+        $client = new Client();
+        $request = $client->createRequest()
+            ->setHeaders(['content-type' => 'application/json'])
+            ->addHeaders(['user-agent' => 'bonusSystem'])
+            ->setFormat(Client::FORMAT_JSON)
+            ->setMethod($method)
+            ->setUrl($url);
+        if(!empty($data)){
+            $request->setData($data);
+        }
+        return $request->send();
+    }
+
+    /**
+     * 数字补齐
+     * @param $num
+     * @param int $bit
+     * @param string $fixStr
+     * @return string
+     */
+    public static function numFix($num, $bit = 2, $fixStr = '0'){
+        return str_pad(intval($num),$bit,$fixStr,STR_PAD_LEFT);
+    }
+
+    /**
+     * 替换手机号为隐藏格式
+     * @param $str
+     * @return null|string|string[]
+     */
+    public static function hideMobile($str){
+        return preg_replace('/^(\d{3})\d{4}(\d{4})$/', '${1}****${2}', $str);
+    }
+
+    /**
+     * 替换身份证为隐藏格式
+     * @param $str
+     * @return null|string|string[]
+     */
+    public static function hideIdCard($str){
+        return preg_replace('/^(\d{3})\d{3}(\d{4})\d{8}$/', '${1}***${2}********', $str);
+    }
+
+    /**
+     * 替换银行卡号隐藏格式
+     * @param $str
+     * @return null|string|string[]
+     */
+    public static function hideBankNo($str){
+        if(preg_match('/^(\d{4})\d{11}(\d{4})$/', $str)){
+            return preg_replace('/^(\d{4})\d{11}(\d{4})$/', '${1}***********${2}', $str);
+        } elseif(preg_match('/^(\d{4})\d{8}(\d{4})$/', $str)){
+            return preg_replace('/^(\d{4})\d{8}(\d{4})$/', '${1}********${2}', $str);
+        } else {
+            return $str;
+        }
+    }
+
+    /**
+     * 清空目录下的所有文件
+     * @param $dir
+     * @throws Exception
+     */
+    public static function clearDir($dir){
+        $dirHandle = opendir( $dir );
+        if($dirHandle === false){
+            throw new Exception('打开目录失败');
+        }
+        while( ($file = readdir( $dirHandle )) !== false ){
+            if ( $file != '.' && $file != '..' && $file != '.gitignore' ) {
+                unlink( $dir . '/' . $file );
+            }
+        }
+    }
+
+    /**
+     * 过滤特殊字符
+     * @param $strParam
+     * @return string|string[]|null
+     */
+    public static function filterSpecialChar($strParam){
+        $regex = "/\/|\~|\,|\。|\!|\?|\“|\”|\【|\】|\『|\』|\:|\;|\《|\》|\’|\‘|\ |\·|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\+|\{|\}|\:|\<|\>|\?|\[|\]|\.|\/|\;|\'|\`|\-|\=|\\\|\|/";
+        return preg_replace($regex,"",$strParam);
+    }
+
+    public static function allow_area($area, $search) {
+        $result = false;
+        foreach ($search as $key => $value) {
+            $count = count($value);
+            //不够三项的,补充到三项
+            if ($count < 3) {
+                $num = 3 - $count;
+                $value += array_fill($count, $num, '');
+            }
+            if ($value[0] == '') {  //说明地区选的全部
+                $result = true;
+                break;
+            }
+            if ($value[0] == $area[0] && ($value[1] == '' || $value[1] == $area[1]) && ($value[2] == '' || $value[2] == $area[2])) {
+                $result = true;
+                break;
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * 转驼峰
+     * @param $words
+     * @param string $separator
+     * @return string
+     */
+    public static function toCamelize( $words , $separator = '_') {
+        if(!$words){
+            return '';
+        }
+        $words = $separator. str_replace($separator, " ", strtolower($words));
+        return ltrim(str_replace(" ", "", ucwords($words)), $separator );
+    }
+
+    public static function isConsoleApp(){
+        return (\Yii::$app->id == 'app-console');
+    }
+
+    /**
+     * 根据KEY合并两个数组
+     * @param $arr1
+     * @param $arr2
+     * @param array $keyArr
+     * @param string $keyField
+     * @return mixed
+     */
+    public static function mergeArrayWithKey($arr1,$arr2,$keyArr=[],$keyField='ID'){
+        foreach ($arr1 as $key=>$value){
+            $arr1[$key]=array_merge($arr1[$key],$arr2[$key]??[]);
+            if($keyArr){
+                $arr1[$key][$keyField] = $keyArr[$key]??'';
+            }
+        }
+        return $arr1;
+    }
+
+    /**
+     * 中文UTF8 转 gbk
+     * @param $text
+     * @return false|string|string[]|null
+     */
+    public static function textConvert($text) {
+        if ($text==='') {
+            return '';
+        }
+        if (preg_match('/\,/', $text)){
+            $text = preg_replace('/\,/', '|', $text);
+        }
+        // 只有是中文时才需要转码
+        if (!preg_match('/[\x{4e00}-\x{9fa5}]/u', $text)) {
+            return $text;
+        }
+        return mb_convert_encoding($text, 'gbk', 'utf-8');
+    }
+
+    /**
+     * 数组里面的中文字符串全部转为GBK
+     * @param array $arr
+     * @return array
+     */
+    public static function arrTextConvert(array $arr){
+        foreach($arr as $key => $str){
+            $arr[$key] = self::textConvert($str);
+        }
+        return $arr;
+    }
+
+    /**
+     * 获取目录内的所有文件
+     * @param $dirPath
+     * @return array
+     */
+    public static function dirFiles($dirPath){
+        $files = [];
+        //检测是否存在文件
+        if (is_dir($dirPath)) {
+            //打开目录
+            if ($handle = opendir($dirPath)) {
+                //返回当前文件的条目
+                while (($file = readdir($handle)) !== false) {
+                    //去除特殊目录
+                    if ($file != "." && $file != "..") {
+                        //判断子目录是否还存在子目录
+                        if (!is_dir($dirPath . "/" . $file)) {
+                            $files[] = $dirPath . "/" . $file;
+                        } else {
+                            $files[$file] = self::dirFiles($dirPath . "/" . $file);
+                        }
+                    }
+                }
+                //关闭文件夹
+                closedir($handle);
+            }
+        }
+        //返回文件夹内文件的数组
+        return $files;
+    }
+
+    /**
+     * 获取审核状态tag颜色
+     * @param $val
+     * @return string
+     */
+    public static function statusType($val) {
+        switch ($val) {
+            case '0':
+                return 'info';
+                break;
+            case '1':
+                return 'success';
+                break;
+            case '2':
+                return 'warning';
+                break;
+            case '3':
+                return 'danger';
+                break;
+            default:
+                return '';
+        }
+    }
+
+    /**
+     * 格式化筛选
+     * @param $selData
+     * @param $id
+     * @param $name
+     * @return array
+     */
+    public static function formatFilter($selData, $id, $name) {
+        $arr=[];
+        foreach ($selData as $key=>$value){
+            $arr[$key]['id']=$value[$id];
+            $arr[$key]['name']=$value[$name];
+        }
+        return $arr;
+    }
+
+    /**
+     * 随机字符串
+     * @param int $length
+     * @param string $prefix
+     * @param string $type
+     * @return string
+     */
+    public static function randomString($length = 10, $prefix = '', $type = 'digit') {
+        if ($type == 'digit') {
+            $chars = "0123456789";
+        } else {
+            $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+        }
+        if ($length - strlen($prefix) < 1) {
+            return false;
+        }
+        $random_string = '';
+        for ($i = 0; $i < $length - strlen($prefix); $i++) {
+            $random_string .= $chars [mt_rand(0, strlen($chars) - 1)];
+        }
+        return $prefix.$random_string;
+    }
+
+    /**
+     * 生成UUID
+     * @param $upper boolean 是否大写
+     * @param $symbol string 替换符号
+     * @return string|string[]
+     */
+    public static function generateId(bool $upper = true, string $symbol = '')
+    {
+        $uuid = !$upper ? Uuid::uuid() : strtoupper(Uuid::uuid());
+        return str_replace('-', $symbol, $uuid);
+    }
+
+    /**
+     * PayStack订单写入MongoDB.
+     * @param $call
+     * @return void
+     * @throws \Exception
+     */
+    public static function approachOrderCall($call)
+    {
+        try {
+            $model = new ApproachOrderCall();
+            $model->sn = $call['data']['metadata']['custom_fields'][0]['value'] ?? '';
+            $model->reference = $call['data']['reference'] ?? '';
+            $model->event = $call['event'];
+            $model->data = $call['data'];
+            $model->insert();
+        } catch (Exception $e) {
+            LoggerTool::info($call);
+            LoggerTool::error(sprintf('[%s] [%s] [%s]', $e->getFile(), $e->getLine(), $e->getMessage()));
+        }
+    }
+}

+ 139 - 0
common/helpers/Validator.php

@@ -0,0 +1,139 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Leo
+ * Date: 2017/11/12
+ * Time: 下午3:20
+ */
+
+namespace common\helpers;
+
+
+use common\helpers\IDValidator\IDValidator;
+use yii\base\InvalidConfigException;
+use yii\base\Model;
+
+class Validator extends \yii\validators\Validator {
+
+    /**
+     * 扩展的检验规则(最好与Yii自带的校验器区分开,优先使用这里的规则)
+     * @var array
+     * 例如:
+     * 'zh' => ['/^[\x{4e00}-\x{9fa5}]+$/u', '请输入中文'], // 这里传值是正则和消息提示
+     * 'idCard' => 'idCardValidate', // 这里传值是类里面存在的方法名
+     */
+    public static $extensionRule = [
+        'chDate'	    =>	["#^\d{4}([/-])([0][0-9]|[1][0-2])\\1([0-2][0-9]|[3][0-1])$#", '必须为日期格式'],
+        'chMonth'       =>  ["#^\d{4}([/-])([0][0-9]|[1][0-2]|[0-9])$#", '必须为年月格式'],
+        'phone'			=>	["#^\d{3}-\d{8}|\d{4}-\d{7}|\d{4}-\d{8}$#", '必须为电话格式'],
+        'mobile'        =>	["|^0[7-9]\d{9}$|", '格式输入不正确'],
+        'alpha'         =>  ["|^[a-zA-Z]+$|", '必须为英文字母格式'],
+        'zh'            =>  ['/^[\x{4e00}-\x{9fa5}]+$/u', '必须为中文'],
+        'price'         =>  ["/^[0-9]+([.]{1}[0-9]{1,2})?$/", ' must be in the amount format'], // 必须为金额格式
+        'fullPrice'         =>  ["/^\-?[0-9]+([.]{1}[0-9]{1,2})?$/", ' must be in the amount format'], // 必须为金额格式
+        'simpleIdCard'  =>  ["/^\d{6}(19|2\d)?\d{2}(0[1-9]|1[012])(0[1-9]|[12]\d|3[01])\d{3}(\d|X)?$/", '必须为身份证号码格式'],
+        'userName'  =>  ["|^[a-z0-9]+$|", '只允许小写英文数字'],
+        'realName'  =>  ["/^([\x{4E00}-\x{FA29}]|[\x{E7C7}-\x{E7F3}]|[a-zA-Z0-9])*$/u", '不允许包含特殊字符'],
+        'idCard'        => 'idCardValidate',
+        'yearMonth'       =>  ["/^\d{4}([0][0-9]|[1][0-2]|[0-9])$/", '必须为年月格式'],
+    ];
+
+    /**
+     * 校验方法
+     * @var
+     */
+    public $validateMethod;
+
+    /**
+     * init
+     * @throws InvalidConfigException
+     */
+    public function init()
+    {
+        parent::init();
+        if(!array_key_exists($this->validateMethod, self::$extensionRule)){
+            $this->validateMethod = '';
+            //throw new InvalidConfigException('校验方法不存在,请查看是否传值正确');
+        }
+    }
+
+    /**
+     * 用类的方法去校验
+     * @param $methodName
+     * @param $value
+     * @return mixed
+     * @throws InvalidConfigException
+     */
+    public function validateByMethod($methodName, $value){
+        if(method_exists($this, $methodName)){
+            return $this->$methodName($value);
+        } else {
+            throw new InvalidConfigException('校验方法不存在');
+        }
+    }
+
+    /**
+     * 用正则的方式去校验
+     * @param $pattern
+     * @param $value
+     * @return int
+     */
+    public function validateByRegular($pattern, $value){
+        return preg_match($pattern, $value);
+    }
+
+    /**
+     * 校验值(主要用于表单model类里面的规则校验)
+     * @param mixed $value
+     * @return array|null
+     * @throws InvalidConfigException
+     */
+    protected function validateValue($value)
+    {
+        if(!$this->validateMethod){
+            throw new InvalidConfigException('校验方法不存在,请查看是否传值正确');
+        }
+        $rule = self::$extensionRule[$this->validateMethod];
+        $valid = false;
+        if(is_array($rule)){
+            $valid = $this->validateByRegular($rule[0], $value);
+            if ($this->message === null) {
+                $this->message = \Yii::t('yii', '{attribute}'.$rule[1]);
+            }
+        } elseif(is_string($rule)){
+            $valid = $this->validateByMethod($rule, $value);
+        } else {
+            throw new InvalidConfigException('校验规则不存在');
+        }
+        return $valid ? null : [$this->message, []];
+    }
+
+    /**
+     * 单独校验某个值用快速的类方法
+     * @param $method
+     * @param $value
+     * @return bool
+     */
+    public static function validateQuickLy($method, $value){
+        $validator = new self();
+        $validator->validateMethod = $method;
+        $valid = $validator->validateValue($value);
+        unset($validator);
+        return $valid ? false : true;
+    }
+
+    // 以下为特殊的规则校验方法(用简单的正则无法实现的)
+
+    /**
+     * @param $value
+     */
+    private function idCardValidate($value){
+        $iDValidator = IDValidator::getInstance();
+        if(!$iDValidator->isValid($value)){
+            if ($this->message === null) {
+                $this->message = \Yii::t('yii', '{attribute}'.'不是真实有效的身份证号码');
+            }
+        }
+    }
+
+}

+ 4063 - 0
common/helpers/bonus/BonusCalc.php

@@ -0,0 +1,4063 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/8/2
+ * Time: 上午10:32
+ */
+
+namespace common\helpers\bonus;
+
+use common\helpers\Cache;
+use common\helpers\Date;
+use common\helpers\LoggerTool;
+use common\helpers\snowflake\SnowFake;
+use common\helpers\Tool;
+use common\models\CalcBonus;
+use common\models\CalcBonusBD;
+use common\models\CalcBonusBS;
+use common\models\CalcBonusQuarter;
+use common\models\CalcBonusBT;
+use common\models\CalcBonusCF;
+use common\models\CalcBonusFL;
+use common\models\CalcBonusFW;
+use common\models\CalcBonusFX;
+use common\models\CalcBonusGarage;
+use common\models\CalcBonusGL;
+use common\models\CalcBonusGX;
+use common\models\CalcBonusHB;
+use common\models\CalcBonusLS;
+use common\models\CalcBonusLX;
+use common\models\CalcBonusQY;
+use common\models\CalcBonusStandard;
+use common\models\CalcBonusTG;
+use common\models\CalcBonusTourism;
+use common\models\CalcBonusVilla;
+use common\models\CalcBonusVIP;
+use common\models\CalcBonusXF;
+use common\models\CalcBonusYC;
+use common\models\CalcBonusYJ;
+use common\models\CalcMonthBonusUser;
+use common\models\Config;
+use common\models\FlowDeductZR;
+use common\models\PerfCompany;
+use common\models\PerfMonth;
+use common\models\PerfPeriod;
+use common\models\Period;
+use common\models\ResendQY;
+use common\models\DeclarationLevel;
+use common\models\DecOrder;
+use common\models\EmployLevel;
+use common\models\FlowBonus;
+use common\models\PerfActiveUser;
+use common\models\ScoreMonth;
+use common\models\StarCrownLevel;
+use common\models\User;
+use common\models\UserInfo;
+use common\models\UserPerf;
+use SebastianBergmann\CodeCoverage\Report\PHP;
+use yii\base\BaseObject;
+use yii\base\Exception;
+use yii\base\StaticInstanceTrait;
+use yii\helpers\Json;
+use yii\db\Query;
+
+class BonusCalc extends BaseObject {
+    use StaticInstanceTrait;
+
+    private $_limit = 10000;
+    private $_gxLimit = 500;
+    private $_handleUserId;
+    private $_companyMonthPerf = 0;
+    private $_cfTotalPercent = 0;
+    private $_lxTotalPercent = 0;
+    private $_sysConfig = [];
+    private $_decLevelConfig = [];
+    private $_empLevelConfig = [];
+    private $_starCrownLevelConfig = [];
+    private $_decRoleConfig = [];
+    private $_errors = [];
+    private $_periodNum = 0;
+    private $_periodId;
+    private $_isCalcMonth = 0;
+    private $_calcYear;
+    private $_calcMonth;
+    private $_calcYearMonth;
+    private $_calcMonthPeriodNumCount = 0;
+    private $_lastCalcYear;
+    private $_lastCalcMonth;
+    private $_lastCalcYearMonth;
+    private $_lastPeriodNum;
+    private $_lastPeriodYear;
+    private $_lastPeriodMonth;
+    private $_lastPeriodYearMonth;
+    //pv
+    private $_pvRatio;
+    private $_calcZone = ['openTravel', 'openCar', 'openHouse'];
+
+    const LOOP_FINISH = 1;
+    const LOOP_CONTINUE = 2;
+    const GX_FLOOR_LIMIT = 5; // 共享奖向上找的最大层级
+    const GX_LIMIT_SWITCH = true; // 是否开启共享奖向上找的最大限制  true 为开启 false 关闭  
+
+    const ORDER_TYPE_TO_FW_COEFFICIENT = [
+        'ZC' => 'fwCoefficientFromZc',
+        'FX_CASH' => 'fwCoefficientFromFxCash',
+        'FX_POINT' => 'fwCoefficientFromFxPoint',
+    ];
+
+    const JX_STANDARD_BASE_AMOUNT = 10000;
+    const JX_STANDARD_MAX_TIMES = 50;
+    const JX_STANDARD_BONUS_PERCENT = 15;
+
+
+    //最小报单pv
+    const MIN_BD_PV = 980;
+
+    /**
+     * 1、抓取所有报单、订单,把能当时产生业绩的报单存入业绩单。例如注册单,订单
+     * 2、从业绩单中循环,并计算上下级业绩
+     * 3、计算级别
+     * 4、计算各个奖项
+     * 5、给会员追加总业绩,在结算中不妥,打算把追加总业绩放到挂网中,因为结算完成以后的业绩都不是最终的,有可能还会变
+     */
+
+    /**
+     * 初始化
+     */
+    public function init() {
+        parent::init();
+
+    }
+
+    /**
+     * 设置期数
+     * @param int $periodNum
+     * @return int
+     */
+    public function setPeriodNum(int $periodNum) {
+        return $this->_periodNum = $periodNum;
+    }
+
+    /**
+     * 获取期数
+     * @return int
+     */
+    public function getPeriodNum() {
+        return $this->_periodNum;
+    }
+
+    /**
+     * 加入错误错误
+     * @param $attr
+     * @param $error
+     */
+    public function addError($attr, $error) {
+        $this->_errors[$attr][] = $error;
+    }
+
+    /**
+     * 获取错误信息
+     * @return array
+     */
+    public function getErrors() {
+        return $this->_errors;
+    }
+
+    /**
+     * 开始执行结算步骤
+     * @param $periodNum
+     * @param null $handleUserId
+     * @return bool
+     */
+    public function calcStep($periodNum, $handleUserId = null) {
+        try {
+            $this->_errors = [];
+            $this->setPeriodNum($periodNum);
+            $this->_handleUserId = $handleUserId;
+            $t1 = microtime(true);
+            // 初始化结算任务
+            $this->initCalcTask();
+            // 设置结算状态
+            $this->setCalcStatus('start');
+            // 清空所有本期结算用到的缓存
+            CalcCache::clearCalcBonusCache($this->_periodNum);
+            // 清空相关表数据
+            $this->clearCalcTableData();
+            $t2 = microtime(true);
+            echo('初始化、清空缓存及相关数据表完成,耗时:' . round($t2 - $t1, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(5);
+            $t3 = microtime(true);
+            $this->_updatePercent(10);
+
+            // 奖金部分
+            if($this->_sysConfig['openFW']['VALUE']) {
+                $this->calcBonusBDStepOne();
+                $this->calcBonusBDStepTwo();
+            }
+            $t4 = microtime(true);
+            echo('计算服务奖'.($this->_sysConfig['openFW']['VALUE']?'完成':'关闭').',耗时:' . round($t4 - $t3, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(15);
+
+            // 销售奖/推广奖
+            if($this->_sysConfig['openTG']['VALUE']) {
+                $this->calcBonusTG();
+            }
+            $t5 = microtime(true);
+            echo('计算推广奖'.($this->_sysConfig['openTG']['VALUE']?'完成':'关闭').',耗时:' . round($t5 - $t4, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(20);
+
+//            if($this->_sysConfig['openXF']['VALUE']) {
+//                if( $this->_sysConfig['consumeRecPercent']['VALUE'] > 0 ) {
+//                    $this->calcBonusXFToRec();
+//                }
+//                if( $this->_sysConfig['consumeSelfPercent']['VALUE'] > 0 ) {
+//                    $this->calcBonusXFToSelf();
+//                }
+//            }
+//            $t6 = microtime(true);
+//            echo('计算消费奖'.($this->_sysConfig['openXF']['VALUE']?'完成':'关闭').',耗时:' . round($t6 - $t5, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+//            $this->_updatePercent(25);
+//
+//            if($this->_sysConfig['openYJ']['VALUE']) {
+//                $this->calcBonusBdYJ();
+//            }
+//            $t7 = microtime(true);
+//            echo('计算报单业绩奖'.($this->_sysConfig['openYJ']['VALUE']?'完成':'关闭').',耗时:' . round($t7 - $t6, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+//            $this->_updatePercent(30);
+//
+//            if($this->_sysConfig['fxOpenYJ']['VALUE']) {
+//                $this->calcBonusFxYJ();
+//            }
+            $t8 = microtime(true);
+//            echo('计算复消业绩奖'.($this->_sysConfig['fxOpenYJ']['VALUE']?'完成':'关闭').',耗时:' . round($t8 - $t7, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+//            $this->_updatePercent(35);
+
+            // 绩效奖/团队奖
+            if($this->_sysConfig['openQY']['VALUE']) {
+                $this->calcBonusQY();
+//                $this->calcBonusBdQY();
+//                $this->calcBonusFxQY();
+            }
+            $t9 = microtime(true);
+            echo('计算团队奖'.($this->_sysConfig['openQY']['VALUE']?'完成':'关闭').',耗时:' . round($t9 - $t8, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(65);
+
+//            if($this->_sysConfig['openGX']['VALUE']) {
+//                $this->calcBonusGXBefore();
+//                $this->calcBonusGX();
+//            }
+//            $t11 = microtime(true);
+//            echo('计算共享奖'.($this->_sysConfig['openGX']['VALUE']?'完成':'关闭').',耗时:' . round($t11 - $t9, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+//            $this->_updatePercent(68);
+//
+//            if($this->_sysConfig['openGL']['VALUE']) {
+//                $this->calcBonusBdGL();
+//            }
+//            $t13 = microtime(true);
+//            echo('计算报单管理奖'.($this->_sysConfig['openGL']['VALUE']?'完成':'关闭').',耗时:' . round($t13 - $t11, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+//            $this->_updatePercent(50);
+//
+//            if($this->_sysConfig['fxOpenGL']['VALUE']) {
+//                $this->calcBonusFxGL();
+//            }
+//            $t14 = microtime(true);
+//            echo('计算复消管理奖'.($this->_sysConfig['fxOpenGL']['VALUE']?'完成':'关闭').',耗时:' . round($t14 - $t13, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+//            $this->_updatePercent(70);
+//
+//            if($this->_sysConfig['openYC']['VALUE']) {
+//                $this->calcBonusYCStepOne();
+//                $this->calcBonusYCStepTwo();
+//            }
+//            $t16 = microtime(true);
+//            echo('计算荣衔奖'.($this->_sysConfig['openYC']['VALUE']?'完成':'关闭').',耗时:' . round($t16 - $t13, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+//            $this->_updatePercent(55);
+//
+//            if($this->_sysConfig['openVIP']['VALUE']) {
+//                $this->calcBonusVIP();
+//            }
+//            $t17 = microtime(true);
+//            echo('计算VIP奖'.($this->_sysConfig['openVIP']['VALUE']?'完成':'关闭').',耗时:' . round($t17 - $t16, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+//            $this->_updatePercent(60);
+//
+//            if($this->_sysConfig['openJXS']['VALUE']) {
+//                $this->calcBonusStandard();
+//            }
+//            $t18 = microtime(true);
+//            echo('计算达标奖'.($this->_sysConfig['openJXS']['VALUE']?'完成':'关闭').',耗时:' . round($t18 - $t17, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+//            $this->_updatePercent(65);
+
+            $t19 = microtime(true);
+            // 蓝星奖入库,实际上是插入有奖金会员数据缓存中.
+            echo('计算蓝星奖开始,' . date('Y-m-d H:i:s', $t19) . PHP_EOL);
+            // 调用存储过程,计算蓝星管理奖金
+            $this->calcBsProcedure();
+            // 将有【蓝星业绩奖金】的用户加入到有奖金缓存用户中
+            $this->calcBonusBsYJ();
+            // 将有【蓝星管理奖金】的用户加入到有奖金缓存用户中
+            $this->calcBonusBsGL();
+            $t20 = microtime(true);
+            echo('计算蓝星奖'.($this->_sysConfig['openGL']['VALUE']?'完成':'关闭').',耗时:' . round($t20 - $t19, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(67);
+
+//            $this->calcBonusTourism($this->_sysConfig['openTourism']);
+            $t21 = microtime(true);
+//            echo('计算旅游奖' . ($this->_sysConfig['openTourism']['VALUE'] ? '完成' : '关闭') . ',耗时:' . round($t21 - $t20, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+//            $this->_updatePercent(68);
+
+            $this->calcBonusVilla();
+            $t22 = microtime(true);
+            echo('计算房奖' . ($this->_sysConfig['openVilla']['VALUE'] ? '完成' : '关闭').',耗时:' . round($t22 - $t21, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL . PHP_EOL);
+            $this->_updatePercent(69);
+
+            $this->calcBonusGarage();
+            $t23 = microtime(true);
+            echo('计算车奖' . ($this->_sysConfig['openGarage']['VALUE'] ? '完成' : '关闭').',耗时:' . round($t23 - $t22, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL . PHP_EOL);
+            $this->_updatePercent(69);
+
+            // 计算季度奖
+            $this->calcQuarter();
+            // 将用户写入缓存
+            $this->calcQuarterUser();
+            $t24 = microtime(true);
+            echo('计算季度奖' . ($this->_sysConfig['openQuarter']['VALUE'] ? '完成' : '关闭').',耗时:' . round($t24 - $t23, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL . PHP_EOL);
+
+            //把奖金会员写入缓存
+            $this->loopMonthBonusUserFromDbToCache();
+            $t30 = microtime(true);
+            echo('奖金会员写入缓存完成,耗时:' . round($t30 - $t22, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(70);
+
+            //奖金写库
+            // 这里增加新的奖金入库操作.
+            $this->loopBonusUsers();
+            // 入库完成,将各个奖金计算流水会员聘级,更新成蓝星奖当时计算的聘级
+            $this->loopCalcBlueEmpLv();
+            $this->_updatePercent(75);
+            unset($calcWrite);
+            $t31 = microtime(true);
+            echo('奖金写库操作完成,耗时:' . round($t31 - $t30, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+
+            //把本期奖金会员入库 - 把缓存中的月奖用户信息存到数据库.存储过程的入库不在这里进行,这里代码取的缓存,存储过程没有缓存,在上面进行入库
+            $this->loopMonthBonusUserToDb();
+            $t32 = microtime(true);
+            echo('奖金会员入库完成,耗时:' . round($t32 - $t31, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(80);
+
+            // 计算基础积分,不可以奖金入库之前计算这样可能会丢掉只有本期的奖金的会员
+            $this->calcBaseScore();
+            $this->_updatePercent(90);
+            $t33 = microtime(true);
+            echo('计算基础积分,耗时:' . round($t33 - $t32, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+
+            //积分入库
+            $this->loopWriteScore();
+            $this->_updatePercent(100);
+            $t34 = microtime(true);
+            echo('积分写库操作完成,耗时:' . round($t34 - $t33, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+
+            $t35 = microtime(true);
+            echo('结算全部完成,共耗时:' . round($t35 - $t34, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL . PHP_EOL);
+        } catch (\Exception $e) {
+            $this->errorCalcTask();
+            $this->addError('calc', sprintf('File【%s】, Line【%s】, Msg【%s】', $e->getFile(), $e->getLine(), $e->getMessage()));
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 结算完成
+     */
+    public function endCalcTask() {
+        // 更新结算状态
+        $this->setCalcStatus('end');
+    }
+
+    /**
+     * 结算错误
+     */
+    public function errorCalcTask() {
+        // 清空所有本期结算用到的缓存
+        CalcCache::clearCalcBonusCache($this->_periodNum);
+        // 更新结算状态
+        $this->setCalcStatus('fail');
+    }
+
+    /**
+     * 初始化结算任务
+     * @throws \yii\db\Exception
+     */
+    public function initCalcTask() {
+        $this->_sysConfig = Cache::getSystemConfig();
+        $this->_decLevelConfig = Cache::getDecLevelConfig();
+        $this->_empLevelConfig = Cache::getEmpLevelConfig();
+        $this->_starCrownLevelConfig = Cache::getStarCrownLevelConfig();
+        $this->_decRoleConfig = CalcCache::getDecRoleConfig($this->_periodNum);
+        $periodNum = $this->_periodNum;
+        // 获取本年月和上年月
+        $periodObj = Period::instance();
+        $periodDataArr = $periodObj->setPeriodNum($periodNum);
+        $this->_periodId = $periodDataArr['ID'];
+        $this->_isCalcMonth = $periodObj->isCalcMonth($periodNum);
+        $this->_calcYear = $periodObj->getYear($periodNum);
+        $this->_calcMonth = $periodObj->getMonth($periodNum);
+        $this->_calcYearMonth = $periodObj->getYearMonth($periodNum);
+        $this->_calcMonthPeriodNumCount = $periodObj->getYearMonthAllPeriodNumCount($this->_calcYear, $this->_calcMonth);
+        $lastYearMonthArr = $periodObj->getLastMonth($periodNum);
+        $this->_lastCalcYear = $lastYearMonthArr['year'];
+        $this->_lastCalcMonth = $lastYearMonthArr['month'];
+        $this->_lastCalcYearMonth = $lastYearMonthArr['yearMonth'];
+        $this->_lastPeriodNum = $periodNum - 1;
+        if (Period::isExistsPeriodNum($this->_lastPeriodNum)) {
+            $this->_lastPeriodYear = $periodObj->getYear($this->_lastPeriodNum);
+            $this->_lastPeriodMonth = $periodObj->getMonth($this->_lastPeriodNum);
+            $this->_lastPeriodYearMonth = $periodObj->getYearMonth($this->_lastPeriodNum);
+        } else {
+            $this->_lastPeriodYear = 0;
+            $this->_lastPeriodMonth = 0;
+            $this->_lastPeriodYearMonth = 0;
+        }
+        $this->_cfTotalPercent = CalcCache::getCFTotalPercent($this->_periodNum);
+        $this->_lxTotalPercent = CalcCache::getLXTotalPercent($this->_periodNum);
+        $this->_pvRatio = $this->_sysConfig['pvRatio']['VALUE'];
+        if($perfCompany = PerfCompany::findOne(['CALC_MONTH' => $this->_calcYearMonth])){
+            $this->_companyMonthPerf = $perfCompany['PV'];
+        }
+    }
+
+    /**
+     * 设置结算状态
+     * @param $type
+     * start|end|fail
+     */
+    public function setCalcStatus($type) {
+        if ($type == 'start') {
+            Period::updateAll(['IS_CALCING' => 1, 'IS_CALCULATED' => Period::CALCULATE_NONE, 'CALCULATE_STARTED_AT' => Date::nowTime()], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        } elseif ($type == 'end') {
+            Period::updateAll(['IS_CALCING' => 0, 'IS_CALCULATED' => Period::CALCULATE_FINISH, 'CALCULATED_AT' => Date::nowTime()], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        } elseif ($type == 'fail') {
+            Period::updateAll(['IS_CALCING' => 0, 'IS_CALCULATED' => Period::CALCULATE_FAIL, 'CALCULATED_AT' => 0], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        }
+    }
+
+    /**
+     * 清空相关表数据
+     */
+    public function clearCalcTableData() {
+        // 奖金表
+        CalcBonus::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+
+        CalcBonusQY::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+        CalcBonusBD::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+        CalcBonusTG::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+        CalcBonusYJ::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+        CalcBonusGX::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+        CalcBonusGL::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+        // 月结时要清空的数据
+        if ($this->_isCalcMonth) {
+            CalcBonusYC::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+            CalcBonusVIP::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+            CalcBonusStandard::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+            ScoreMonth::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+            CalcBonusTourism::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+            CalcBonusGarage::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+            CalcBonusVilla::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+        }
+    }
+
+    /**
+     * === 奖金结算部分 ===
+     */
+
+    /**
+     * 服务奖
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusBD(int $offset = 0) {
+        $periodNum = $this->_periodNum;
+        // 从缓存获取分页有业绩的会员信息
+        $allData = CalcCache::getHasBDUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                $decInfo = CalcCache::getBDUsersInfo($userId, $this->_periodNum);
+                if( !$decInfo ) continue;
+
+                //服务奖使用报单原金额
+                $decBonus = Tool::formatPrice($decInfo['DEC_AMOUNT'] * $this->_sysConfig['decPercent']['VALUE'] / 100);
+                if ($decBonus <= 0) continue;
+                $bonusUserId = $decInfo['DEC_ID'];
+                if( !$bonusUserId ) continue;
+                // 获取会员的报单级别
+                $bonusUserInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+
+                //总金额限制
+                $decBonus = $this->bonusTotalLimit($decBonus, $bonusUserId, $bonusUserInfo['REC_NUM'], $bonusUserInfo['ZC_AMOUNT']);
+                if( $decBonus <= 0 ) continue;
+
+
+                //扣除相应的复消积分和管理费
+                $deductData = $this->deduct($bonusUserId, $decBonus);
+
+                // 把对碰后的奖金存入缓存中
+                CalcCache::bonus($bonusUserId, $periodNum, 'BONUS_BD', $decBonus, $deductData);
+
+                //来源会员信息
+                $fromUserInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                //服务奖流水
+                $insertBonusData[] = [
+                    'ID' => SnowFake::instance()->generateId(),
+                    'USER_ID' => $bonusUserId,
+                    'LAST_DEC_LV' => $bonusUserInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $bonusUserInfo['EMP_LV'],
+                    'LAST_STATUS' => $bonusUserInfo['STATUS'],
+                    'FROM_USER_ID' => $userId,
+                    'LAST_FROM_DEC_LV' => $fromUserInfo['DEC_LV'],
+                    'LAST_FROM_EMP_LV' => $fromUserInfo['EMP_LV'],
+                    'LAST_FROM_STATUS' => $fromUserInfo['STATUS'],
+                    'AMOUNT' => $deductData['surplus'],
+                    'RECONSUME_POINTS' => $deductData['reConsumePoints'],
+                    'MANAGE_TAX' => $deductData['manageTax'],
+                    'ORI_BONUS' => $decBonus,
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_YEAR' => $this->_calcYear,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                    'CREATED_AT' => Date::nowTime(),
+                    'LOGS' => json_encode([
+                        'decInfoArr' => $decInfo,
+                        'decPercentConfig' => $this->_sysConfig['decPercent']['VALUE'],
+                        'recNum' => $bonusUserInfo['REC_NUM'],
+                        'decAmount' => $bonusUserInfo['ZC_AMOUNT'],
+                        'bonusTotalLimit' => [
+                            $this->_sysConfig['bonusTotalZeroLimit']['VALUE'],
+                            $this->_sysConfig['bonusTotalOneLimit']['VALUE'],
+                            $this->_sysConfig['bonusTotalTwoLimit']['VALUE'],
+                        ],
+                    ]),
+                ];
+
+                unset($decInfo, $decBonus, $bonusUserId, $bonusUserInfo, $userId, $deductData, $fromUserInfo);
+            }
+            CalcBonusBD::batchInsert($insertBonusData);
+            unset($allData, $insertBonusData);
+            return $this->calcBonusBD($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 推广奖
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusTG(int $offset = 0) {
+        $periodNum = $this->_periodNum;
+        // 从缓存获取分页有业绩的会员信息
+        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                // 从缓存中获取会员的业绩信息
+                $perfData = CalcCache::nowPeriodPerf($userId, $periodNum);
+                if( !$perfData ) continue;
+                //个人业绩都算推荐奖,包括报单和复消、二次购物
+                $perfPv = $perfData['PV_PCS_ZC'] ?? 0;
+                if( $perfPv <= 0 ) continue;
+
+                //推广奖使用个人PCS
+                $recBonus = Tool::formatPrice($perfPv * $this->_sysConfig['recPercent']['VALUE'] / 100);
+                if ($recBonus <= 0) continue;
+                // 把对碰后的奖金存入缓存中
+                $perfUserInfo = CalcCache::getUserInfo($userId, $periodNum);
+                $bonusUserId = $perfUserInfo['REC_UID'] ?? '';
+                if( !$bonusUserId ) continue;
+
+                // 获取会员的报单级别
+                $userBaseInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+                //判断级别上限
+//                $recBonus = $this->declarationLevelCap($recBonus, $bonusUserId, $userBaseInfo['DEC_LV']);
+//                if( $recBonus <= 0 ) continue;
+
+                $recBonus = $this->bonusTotalLimit($recBonus, $bonusUserId, $userBaseInfo['REC_NUM'], $userBaseInfo['ZC_AMOUNT']);
+                if( $recBonus <= 0 ) continue;
+
+                //扣除相应的复消积分和管理费
+                $deductData = $this->deduct($bonusUserId, $recBonus);
+
+                CalcCache::bonus($bonusUserId, $periodNum, 'BONUS_TG', $recBonus, $deductData);
+
+                //来源会员信息
+                $fromUserInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                //推广奖流水
+                $insertBonusData[] = [
+                    'ID' => SnowFake::instance()->generateId(),
+                    'USER_ID' => $bonusUserId,
+                    'LAST_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $userBaseInfo['EMP_LV'],
+                    'LAST_STATUS' => $userBaseInfo['STATUS'],
+                    'FROM_USER_ID' => $userId,
+                    'LAST_FROM_DEC_LV' => $fromUserInfo['DEC_LV'],
+                    'LAST_FROM_EMP_LV' => $fromUserInfo['EMP_LV'],
+                    'LAST_FROM_STATUS' => $fromUserInfo['STATUS'],
+                    'AMOUNT' => $deductData['surplus'],
+                    'ORI_BONUS' => $recBonus,
+                    'RECONSUME_POINTS' => $deductData['reConsumePoints'],
+                    'MANAGE_TAX' => $deductData['manageTax'],
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_YEAR' => $this->_calcYear,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                    'CREATED_AT' => Date::nowTime(),
+                    'LOGS' => json_encode([
+                        'perfPv' => $perfPv,
+                        'recPercentConfig' => $this->_sysConfig['recPercent']['VALUE'],
+                        'recNum' => $userBaseInfo['REC_NUM'],
+                        'decAmount' => $userBaseInfo['ZC_AMOUNT'],
+                        'decLevel' => $userBaseInfo['DEC_LV'],
+                        'bonusTotalLimit' => [
+                            $this->_sysConfig['bonusTotalZeroLimit']['VALUE'],
+                            $this->_sysConfig['bonusTotalOneLimit']['VALUE'],
+                            $this->_sysConfig['bonusTotalTwoLimit']['VALUE'],
+                        ],
+                    ]),
+                ];
+
+                unset($perfData, $perfPv, $perfUserInfo, $recBonus, $bonusUserId, $userBaseInfo, $userId, $deductData, $fromUserInfo);
+            }
+            CalcBonusTG::batchInsert($insertBonusData);
+            unset($allData, $insertBonusData);
+            return $this->calcBonusTG($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 消费奖-推荐人
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusXFToRec(int $offset = 0) {
+        $periodNum = $this->_periodNum;
+        // 从缓存获取分页有业绩的会员信息
+        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                // 从缓存中获取会员的业绩信息
+                $perfData = CalcCache::nowPeriodPerf($userId, $periodNum);
+                if( !$perfData ) continue;
+                //个人业绩都算推荐奖,包括报单和复消、二次购物
+                $perfPv = $perfData['PV_PCS_FX'] ?? 0;
+                if( $perfPv <= 0 ) continue;
+
+                //推广奖使用个人PCS
+                $recBonus = Tool::formatPrice($perfPv * $this->_sysConfig['consumeRecPercent']['VALUE'] / 100);
+                if ($recBonus <= 0) continue;
+                // 把对碰后的奖金存入缓存中
+                $perfUserInfo = CalcCache::getUserInfo($userId, $periodNum);
+                $bonusUserId = $perfUserInfo['REC_UID'] ?? '';
+                if( !$bonusUserId ) continue;
+
+                // 获取会员的报单级别
+                $userBaseInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+                //判断级别上限
+//                $recBonus = $this->declarationLevelCap($recBonus, $bonusUserId, $userBaseInfo['DEC_LV']);
+//                if( $recBonus <= 0 ) continue;
+
+                $recBonus = $this->bonusTotalLimit($recBonus, $bonusUserId, $userBaseInfo['REC_NUM'], $userBaseInfo['ZC_AMOUNT']);
+                if( $recBonus <= 0 ) continue;
+
+                //扣除相应的复消积分和管理费
+                $deductData = $this->deduct($bonusUserId, $recBonus);
+
+                CalcCache::bonus($bonusUserId, $periodNum, 'BONUS_XF', $recBonus, $deductData);
+
+                //来源会员信息
+                $fromUserInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                //推广奖流水
+                $insertBonusData[] = [
+                    'ID' => SnowFake::instance()->generateId(),
+                    'USER_ID' => $bonusUserId,
+                    'LAST_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $userBaseInfo['EMP_LV'],
+                    'LAST_STATUS' => $userBaseInfo['STATUS'],
+                    'FROM_USER_ID' => $userId,
+                    'LAST_FROM_DEC_LV' => $fromUserInfo['DEC_LV'],
+                    'LAST_FROM_EMP_LV' => $fromUserInfo['EMP_LV'],
+                    'LAST_FROM_STATUS' => $fromUserInfo['STATUS'],
+                    'AMOUNT' => $deductData['surplus'],
+                    'ORI_BONUS' => $recBonus,
+                    'RECONSUME_POINTS' => $deductData['reConsumePoints'],
+                    'MANAGE_TAX' => $deductData['manageTax'],
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_YEAR' => $this->_calcYear,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                    'CREATED_AT' => Date::nowTime(),
+                    'LOGS' => json_encode([
+                        'perfPv' => $perfPv,
+                        'recPercentConfig' => $this->_sysConfig['consumeRecPercent']['VALUE'],
+                        'recNum' => $userBaseInfo['REC_NUM'],
+                        'decAmount' => $userBaseInfo['ZC_AMOUNT'],
+                        'decLevel' => $userBaseInfo['DEC_LV'],
+                        'bonusTotalLimit' => [
+                            $this->_sysConfig['bonusTotalZeroLimit']['VALUE'],
+                            $this->_sysConfig['bonusTotalOneLimit']['VALUE'],
+                            $this->_sysConfig['bonusTotalTwoLimit']['VALUE'],
+                        ],
+                    ]),
+                ];
+
+                unset($perfData, $perfPv, $perfUserInfo, $recBonus, $bonusUserId, $userBaseInfo, $userId, $deductData, $fromUserInfo);
+            }
+            CalcBonusXF::batchInsert($insertBonusData);
+            unset($allData, $insertBonusData);
+            return $this->calcBonusXFToRec($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 消费奖-自己
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusXFToSelf(int $offset = 0) {
+        $periodNum = $this->_periodNum;
+        // 从缓存获取分页有业绩的会员信息
+        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                // 从缓存中获取会员的业绩信息
+                $perfData = CalcCache::nowPeriodPerf($userId, $periodNum);
+                if( !$perfData ) continue;
+                //个人业绩都算推荐奖,包括报单和复消、二次购物
+                $perfPv = $perfData['PV_PCS_FX'] ?? 0;
+                if( $perfPv <= 0 ) continue;
+
+                //推广奖使用个人PCS
+                $recBonus = Tool::formatPrice($perfPv * $this->_sysConfig['consumeSelfPercent']['VALUE'] / 100);
+                if ($recBonus <= 0) continue;
+                // 把对碰后的奖金存入缓存中
+//                $perfUserInfo = CalcCache::getUserInfo($userId, $periodNum);
+                $bonusUserId = $userId;
+                if( !$bonusUserId ) continue;
+
+                // 获取会员的报单级别
+                $userBaseInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+                //判断级别上限
+//                $recBonus = $this->declarationLevelCap($recBonus, $bonusUserId, $userBaseInfo['DEC_LV']);
+//                if( $recBonus <= 0 ) continue;
+
+                $recBonus = $this->bonusTotalLimit($recBonus, $bonusUserId, $userBaseInfo['REC_NUM'], $userBaseInfo['ZC_AMOUNT']);
+                if( $recBonus <= 0 ) continue;
+
+                //扣除相应的复消积分和管理费
+                $deductData = $this->deduct($bonusUserId, $recBonus);
+
+                CalcCache::bonus($bonusUserId, $periodNum, 'BONUS_XF', $recBonus, $deductData);
+
+                //来源会员信息
+                $fromUserInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                //推广奖流水
+                $insertBonusData[] = [
+                    'ID' => SnowFake::instance()->generateId(),
+                    'USER_ID' => $bonusUserId,
+                    'LAST_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $userBaseInfo['EMP_LV'],
+                    'LAST_STATUS' => $userBaseInfo['STATUS'],
+                    'FROM_USER_ID' => $userId,
+                    'LAST_FROM_DEC_LV' => $fromUserInfo['DEC_LV'],
+                    'LAST_FROM_EMP_LV' => $fromUserInfo['EMP_LV'],
+                    'LAST_FROM_STATUS' => $fromUserInfo['STATUS'],
+                    'AMOUNT' => $deductData['surplus'],
+                    'ORI_BONUS' => $recBonus,
+                    'RECONSUME_POINTS' => $deductData['reConsumePoints'],
+                    'MANAGE_TAX' => $deductData['manageTax'],
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_YEAR' => $this->_calcYear,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                    'CREATED_AT' => Date::nowTime(),
+                    'LOGS' => json_encode([
+                        'perfPv' => $perfPv,
+                        'recPercentConfig' => $this->_sysConfig['consumeSelfPercent']['VALUE'],
+                        'recNum' => $userBaseInfo['REC_NUM'],
+                        'decAmount' => $userBaseInfo['ZC_AMOUNT'],
+                        'decLevel' => $userBaseInfo['DEC_LV'],
+                        'bonusTotalLimit' => [
+                            $this->_sysConfig['bonusTotalZeroLimit']['VALUE'],
+                            $this->_sysConfig['bonusTotalOneLimit']['VALUE'],
+                            $this->_sysConfig['bonusTotalTwoLimit']['VALUE'],
+                        ],
+                    ]),
+                ];
+
+                unset($perfData, $perfPv, $recBonus, $bonusUserId, $userBaseInfo, $userId, $deductData, $fromUserInfo);
+            }
+            CalcBonusXF::batchInsert($insertBonusData);
+            unset($allData, $insertBonusData);
+            return $this->calcBonusXFToSelf($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+
+    /**
+     * 服务奖第一步
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusBDStepOne(int $offset = 0) {
+        echo sprintf("时间:[%s]服务奖第【1】步,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        $periodNum = $this->_periodNum;
+        // 从缓存获取分页有业绩的会员信息
+        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                // 从缓存中获取会员的业绩信息
+                $perfData = CalcCache::nowPeriodPerf($userId, $periodNum);
+                if( !$perfData ) continue;
+
+                //
+                $decRoleBonusFrom = explode(',', $this->_sysConfig['decRoleBonusFrom']['VALUE']);
+                $validPvPcs = 0;
+                foreach ($decRoleBonusFrom as $orderType) {
+                    $orderTypeName = sprintf('PV_PCS_%s', $orderType);
+                    $orderTypeValue = $perfData[$orderTypeName] ?? 0;
+
+                    $coefficientName = self::ORDER_TYPE_TO_FW_COEFFICIENT[$orderType];
+                    $coefficient = $this->_sysConfig[$coefficientName]['VALUE'] ?? 1;
+                    $validPvPcs += $orderTypeValue * $coefficient;
+
+                    unset($orderType, $orderTypeName, $orderTypeValue, $coefficientName, $coefficient);
+                }
+                unset($perfData, $decRoleBonusFrom);
+                if ( $validPvPcs <= 0 ) continue;
+
+                $this->loopRelationParentDo($userId, function ($parent) use($userId, $validPvPcs){
+
+                    //判断parent的报单中心级别 和 服务奖比例
+                    $bonusUserId = $parent['PARENT_UID'];
+                    //计算级别之后更新过userInfo的缓存,缓存中级别发生了变化
+                    $bonusUserInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+                    $isDec = $bonusUserInfo['IS_DEC'];
+                    if($isDec == 0) return self::LOOP_CONTINUE;
+                    $decRoleId = $bonusUserInfo['DEC_ROLE_ID'];
+                    if( !$decRoleId ) return self::LOOP_CONTINUE;
+                    if( !isset($this->_decRoleConfig[$decRoleId]) ) return self::LOOP_CONTINUE;
+
+                    $parentDecRoleLevel = $this->_decRoleConfig[$decRoleId];
+                    $parentFwBonusPercent = $parentDecRoleLevel['FW_BONUS_PERCENT'] ?? 0;
+                    $cacheMaxPercent = CalcCache::fwMaxBonusPercent($userId, $this->_periodNum);
+                    $diffPercent = $parentFwBonusPercent - $cacheMaxPercent;
+                    if( $diffPercent <= 0 ) return self::LOOP_CONTINUE;
+
+                    $fwBonus = $validPvPcs * $diffPercent / 100;
+                    if( $fwBonus <= 0  ) return self::LOOP_CONTINUE;
+
+                    //给本人添加服务奖比例
+                    CalcCache::fwMaxBonusPercent($userId, $this->_periodNum, $parentFwBonusPercent);
+                    //记录奖金和奖金来源到缓存 并实现在缓存中奖金累加
+                    CalcCache::saveFwBonusList($bonusUserId, $this->_periodNum, $fwBonus, ['fromUid'=>$userId, 'fromPvPcs'=>$validPvPcs]);
+                    CalcCache::addHasFwBonusUsers($bonusUserId, $this->_periodNum);
+
+                    //达到最大的比例就不在向上找了
+//                    if( $parentFwBonusPercent >= $maxLevelPercent ) return self::LOOP_FINISH;
+                    unset($bonusUserId, $bonusUserInfo, $isDec, $decRoleId, $parentDecRoleLevel, $parentFwBonusPercent, $cacheMaxPercent, $diffPercent, $fwBonus);
+                });
+
+                unset($userId, $validPvPcs);
+
+
+            }
+            unset($allData, $insertBonusData);
+            return $this->calcBonusBDStepOne($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 服务奖第二步
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusBDStepTwo(int $offset = 0) {
+        echo sprintf("时间:[%s]服务奖第【2】步,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+
+        $allData = CalcCache::getHasFwBonusUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                $fwBonusData = CalcCache::getFwBonusList($userId, $this->_periodNum);
+                if( !$fwBonusData ) continue;
+                $fwBonus = $fwBonusData['fwBonus'] ?? 0;
+                if( $fwBonus <=0  ) continue;
+                //总金额限制
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                $fwBonus = $this->bonusTotalLimit($fwBonus, $userId, $userBaseInfo['REC_NUM'], $userBaseInfo['ZC_AMOUNT']);
+
+                if( $fwBonus <= 0 ) continue;
+
+                //扣除相应的复消积分和管理费
+//                $deductData = $this->deduct($userId, $fwBonus);
+
+                CalcCache::bonus($userId, $this->_periodNum, 'BONUS_BD', $fwBonus);
+
+                $decRoleId = $userBaseInfo['DEC_ROLE_ID'];
+                $insertBonusData[] = [
+                    'ID' => SnowFake::instance()->generateId(),
+                    'USER_ID' => $userId,
+                    'LAST_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $userBaseInfo['EMP_LV'],
+                    'LAST_STATUS' => $userBaseInfo['STATUS'],
+                    'FROM_USER_ID' => $userId,
+                    'LAST_FROM_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_FROM_EMP_LV' => $userBaseInfo['EMP_LV'],
+                    'LAST_FROM_STATUS' => $userBaseInfo['STATUS'],
+                    'AMOUNT' => $fwBonus,
+                    'ORI_BONUS' => $fwBonus,
+                    'RECONSUME_POINTS' => 0,
+                    'MANAGE_TAX' => 0,
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_YEAR' => $this->_calcYear,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                    'CREATED_AT' => Date::nowTime(),
+                    'LOGS' => json_encode([
+                        'decRoleId' => $decRoleId,
+                    ])
+//                    'FROM_DATA' => json_encode($ycBonusData['fromData']),
+                ];
+
+                unset($userId, $fwBonusData, $userBaseInfo, $decRoleId, $fwBonus);
+
+            }
+
+            CalcBonusBD::batchInsert($insertBonusData);
+            unset($insertBonusData, $allData);
+            $this->calcBonusBDStepTwo($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 报单业绩奖
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusBdYJ(int $offset = 0) {
+        $periodNum = $this->_periodNum;
+        // 从缓存获取分页有业绩的会员信息
+        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            foreach ($allData as $userId) {
+                $insertBonusData = [];
+
+                // 从缓存中获取会员的业绩信息
+                $perfData = CalcCache::nowPeriodPerf($userId, $periodNum);
+                if( !$perfData ) continue;
+                //个人业绩都算见点奖,包括报单和复消、二次购物
+                $perfPv = $perfData['PV_PCS_ZC'] ?? 0;
+                if( $perfPv <= 0 ) continue;
+
+                //业绩奖使用报单PV
+//                $oriPointsBonus = Tool::formatPrice($perfPv * $this->_sysConfig['pointsPercent']['VALUE'] / 100);
+//                if ($oriPointsBonus <= 0) continue;
+                //偶数层数
+                $pointsEvenLayer = $this->_sysConfig['pointsLayer']['VALUE'];
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                $userEmpLevel = $userBaseInfo['EMP_LV'];
+                $netWorkParents = Cache::getAllNetworkParents($userId);
+                foreach ($netWorkParents as $netWorkParent) {
+//                    $pointsBonus = $oriPointsBonus;
+                    //自已距顶端的深度减去父级距顶端的深度
+                    $diffLayer = $netWorkParent['TOP_DEEP'] - $netWorkParent['PARENT_DEEP'];
+                    if( $diffLayer <= 0) continue;
+
+                    if( $diffLayer > 2 * $pointsEvenLayer ) continue;
+
+                    if( $diffLayer % 2 == 1 ) continue;
+
+                    $pointsPercentName = sprintf('pointsPercent_%d', $diffLayer);
+                    if( isset($this->_sysConfig[$pointsPercentName]['VALUE']) ) {
+                        $diffLayerPercent = $this->_sysConfig[$pointsPercentName]['VALUE'];
+                    }else {
+                        $diffLayerPercent = $this->_sysConfig['pointsPercent']['VALUE'];
+                    }
+                    $pointsBonus = Tool::formatPrice($perfPv * $diffLayerPercent / 100);
+                    if( $pointsBonus <= 0 ) continue;
+
+                    // 把对碰后的奖金存入缓存中
+                    $bonusUserId = $netWorkParent['PARENT_UID'];
+                    if( !$bonusUserId ) continue;
+
+                    // 获取会员的报单级别
+                    $bonusUserBaseInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+                    //判断上限 包括级别奖金上限和总上限
+//                    $pointsBonus = $this->declarationLevelCap($pointsBonus, $bonusUserId, $bonusUserBaseInfo['DEC_LV']);
+//                    if( $pointsBonus <= 0 ) continue;
+
+                    //总金额限制
+                    $pointsBonus = $this->bonusTotalLimit($pointsBonus, $bonusUserId, $bonusUserBaseInfo['REC_NUM'], $bonusUserBaseInfo['ZC_AMOUNT']);
+                    if( $pointsBonus <= 0 ) continue;
+
+                    //扣除相应的复消积分和管理费
+                    $deductData = $this->deduct($bonusUserId, $pointsBonus);
+
+                    CalcCache::bonus($bonusUserId, $periodNum, 'BONUS_YJ', $pointsBonus, $deductData, CalcCache::FROM_MEANS_BD);
+
+                    //业绩奖流水
+                    $insertBonusData[] = [
+                        'ID' => SnowFake::instance()->generateId(),
+                        'USER_ID' => $bonusUserId,
+                        'LAST_DEC_LV' => $bonusUserBaseInfo['DEC_LV'],
+                        'LAST_EMP_LV' => $bonusUserBaseInfo['EMP_LV'],
+                        'LAST_STATUS' => $bonusUserBaseInfo['STATUS'],
+                        'FROM_USER_ID' => $userId,
+                        'LAST_FROM_DEC_LV' => $userBaseInfo['DEC_LV'],
+                        'LAST_FROM_EMP_LV' => $userEmpLevel,
+                        'LAST_FROM_STATUS' => $userBaseInfo['STATUS'],
+                        'AMOUNT' => $deductData['surplus'],
+                        'ORI_BONUS' => $pointsBonus,
+                        'RECONSUME_POINTS' => $deductData['reConsumePoints'],
+                        'MANAGE_TAX' => $deductData['manageTax'],
+                        'PERIOD_NUM' => $this->_periodNum,
+                        'CALC_YEAR' => $this->_calcYear,
+                        'CALC_MONTH' => $this->_calcYearMonth,
+                        'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                        'CREATED_AT' => Date::nowTime(),
+                        'LOGS' => json_encode([
+                            'perfPv' => $perfPv,
+                            'pointsLayerConfig' => $this->_sysConfig['pointsLayer']['VALUE'],
+                            'pointsPercentConfig' => $diffLayerPercent,
+                            'recNum' => $bonusUserBaseInfo['REC_NUM'],
+                            'decAmount' => $bonusUserBaseInfo['ZC_AMOUNT'],
+                            'decLevel' => $bonusUserBaseInfo['DEC_LV'],
+                            'bonusTotalLimit' => [
+                                $this->_sysConfig['bonusTotalZeroLimit']['VALUE'],
+                                $this->_sysConfig['bonusTotalOneLimit']['VALUE'],
+                                $this->_sysConfig['bonusTotalTwoLimit']['VALUE'],
+                            ],
+                        ]),
+                    ];
+
+                    unset($netWorkParent, $pointsBonus, $diffLayer, $bonusUserId, $bonusUserBaseInfo, $deductData, $diffLayerPercent, $pointsPercentName);
+
+                }
+
+                CalcBonusYJ::batchInsert($insertBonusData);
+
+                unset($insertBonusData, $userId, $perfData, $perfPv, $oriPointsBonus, $pointsEvenLayer, $userBaseInfo, $userEmpLevel, $netWorkParents);
+            }
+            unset($allData);
+            return $this->calcBonusBdYJ($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+
+    /**
+     * 复消业绩奖
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusFxYJ(int $offset = 0) {
+        $periodNum = $this->_periodNum;
+        // 从缓存获取分页有业绩的会员信息
+        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            foreach ($allData as $userId) {
+                $insertBonusData = [];
+
+                // 从缓存中获取会员的业绩信息
+                $perfData = CalcCache::nowPeriodPerf($userId, $periodNum);
+                if( !$perfData ) continue;
+                //个人业绩都算见点奖,包括报单和复消、二次购物
+                $perfPv = $perfData['PV_PCS_FX'] ?? 0;
+                if( $perfPv <= 0 ) continue;
+
+                //业绩奖使用报单PV
+//                $oriPointsBonus = Tool::formatPrice($perfPv * $this->_sysConfig['pointsPercent']['VALUE'] / 100);
+//                if ($oriPointsBonus <= 0) continue;
+                //偶数层数
+                $pointsEvenLayer = $this->_sysConfig['fxPointsLayer']['VALUE'];
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                $userEmpLevel = $userBaseInfo['EMP_LV'];
+                $netWorkParents = Cache::getAllNetworkParents($userId);
+                foreach ($netWorkParents as $netWorkParent) {
+//                    $pointsBonus = $oriPointsBonus;
+                    //自已距顶端的深度减去父级距顶端的深度
+                    $diffLayer = $netWorkParent['TOP_DEEP'] - $netWorkParent['PARENT_DEEP'];
+                    if( $diffLayer <= 0) continue;
+
+                    if( $diffLayer > 2 * $pointsEvenLayer ) continue;
+
+                    if( $diffLayer % 2 == 1 ) continue;
+
+                    $pointsPercentName = sprintf('fxPointsPercent_%d', $diffLayer);
+                    if( isset($this->_sysConfig[$pointsPercentName]['VALUE']) ) {
+                        $diffLayerPercent = $this->_sysConfig[$pointsPercentName]['VALUE'];
+                    }else {
+                        $diffLayerPercent = $this->_sysConfig['fxPointsPercent']['VALUE'];
+                    }
+                    $pointsBonus = Tool::formatPrice($perfPv * $diffLayerPercent / 100);
+                    if( $pointsBonus <= 0 ) continue;
+
+                    // 把对碰后的奖金存入缓存中
+                    $bonusUserId = $netWorkParent['PARENT_UID'];
+                    if( !$bonusUserId ) continue;
+
+                    // 获取会员的报单级别
+                    $bonusUserBaseInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+                    //判断上限 包括级别奖金上限和总上限
+//                    $pointsBonus = $this->declarationLevelCap($pointsBonus, $bonusUserId, $bonusUserBaseInfo['DEC_LV']);
+//                    if( $pointsBonus <= 0 ) continue;
+
+                    //总金额限制
+                    $pointsBonus = $this->bonusTotalLimit($pointsBonus, $bonusUserId, $bonusUserBaseInfo['REC_NUM'], $bonusUserBaseInfo['ZC_AMOUNT']);
+                    if( $pointsBonus <= 0 ) continue;
+
+                    //扣除相应的复消积分和管理费
+                    $deductData = $this->deduct($bonusUserId, $pointsBonus);
+
+                    CalcCache::bonus($bonusUserId, $periodNum, 'BONUS_YJ', $pointsBonus, $deductData, CalcCache::FROM_MEANS_FX);
+
+                    //业绩奖流水
+                    $insertBonusData[] = [
+                        'ID' => SnowFake::instance()->generateId(),
+                        'USER_ID' => $bonusUserId,
+                        'LAST_DEC_LV' => $bonusUserBaseInfo['DEC_LV'],
+                        'LAST_EMP_LV' => $bonusUserBaseInfo['EMP_LV'],
+                        'LAST_STATUS' => $bonusUserBaseInfo['STATUS'],
+                        'FROM_USER_ID' => $userId,
+                        'LAST_FROM_DEC_LV' => $userBaseInfo['DEC_LV'],
+                        'LAST_FROM_EMP_LV' => $userEmpLevel,
+                        'LAST_FROM_STATUS' => $userBaseInfo['STATUS'],
+                        'AMOUNT' => $deductData['surplus'],
+                        'ORI_BONUS' => $pointsBonus,
+                        'RECONSUME_POINTS' => $deductData['reConsumePoints'],
+                        'MANAGE_TAX' => $deductData['manageTax'],
+                        'PERIOD_NUM' => $this->_periodNum,
+                        'CALC_YEAR' => $this->_calcYear,
+                        'CALC_MONTH' => $this->_calcYearMonth,
+                        'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                        'CREATED_AT' => Date::nowTime(),
+                        'LOGS' => json_encode([
+                            'perfPv' => $perfPv,
+                            'pointsLayerConfig' => $this->_sysConfig['fxPointsLayer']['VALUE'],
+                            'pointsPercentConfig' => $diffLayerPercent,
+                            'recNum' => $bonusUserBaseInfo['REC_NUM'],
+                            'decAmount' => $bonusUserBaseInfo['ZC_AMOUNT'],
+                            'decLevel' => $bonusUserBaseInfo['DEC_LV'],
+                            'bonusTotalLimit' => [
+                                $this->_sysConfig['bonusTotalZeroLimit']['VALUE'],
+                                $this->_sysConfig['bonusTotalOneLimit']['VALUE'],
+                                $this->_sysConfig['bonusTotalTwoLimit']['VALUE'],
+                            ],
+                        ]),
+                    ];
+
+                    unset($netWorkParent, $pointsBonus, $diffLayer, $bonusUserId, $bonusUserBaseInfo, $deductData, $diffLayerPercent, $pointsPercentName);
+
+                }
+
+                CalcBonusYJ::batchInsert($insertBonusData);
+
+                unset($insertBonusData, $userId, $perfData, $perfPv, $oriPointsBonus, $pointsEvenLayer, $userBaseInfo, $userEmpLevel, $netWorkParents);
+            }
+            unset($allData);
+            return $this->calcBonusFxYJ($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 团队奖
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusQY(int $offset = 0) {
+        echo sprintf("时间:[%s]团队奖,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        $periodNum = $this->_periodNum;
+        // 从缓存获取分页有业绩的会员信息
+        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                // 从缓存中获取会员的业绩信息
+                $perfData = CalcCache::nowPeriodPerf($userId, $periodNum);
+                // 从缓存中获取会员的上期结余业绩信息
+                $pervSurplusPerf = CalcCache::surplusPerf($userId, $periodNum);
+                // 本期 + 上期结余
+                $perfArr = [
+                    'SURPLUS_1L' => $perfData['PV_1L_TOUCH'] + $pervSurplusPerf['SURPLUS_1L'],
+                    'SURPLUS_2L' => $perfData['PV_2L_TOUCH'] + $pervSurplusPerf['SURPLUS_2L'],
+                    'SURPLUS_3L' => $perfData['PV_3L_TOUCH'] + $pervSurplusPerf['SURPLUS_3L'],
+                    'SURPLUS_4L' => $perfData['PV_4L_TOUCH'] + $pervSurplusPerf['SURPLUS_4L'],
+                    'SURPLUS_5L' => $perfData['PV_5L_TOUCH'] + $pervSurplusPerf['SURPLUS_5L'],
+                ];
+                $oriPerfArr = [
+                    'perfArr' => $perfArr,
+                    'touchBonus' => 0,
+                ];
+
+                // 获取会员的报单级别
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                $decLevelConfig = $this->_decLevelConfig;
+                $nowDecLevelConfig = $decLevelConfig[$userBaseInfo['DEC_LV']];
+                // 对碰
+                $touchBonusArr = $this->touchPerf($oriPerfArr, $perfArr, $nowDecLevelConfig['QY_PERCENT']/100);
+                $touchPerfArr = [];
+                foreach ($touchBonusArr['perfArr'] as $keyR => $perfR) {
+                    $touchPerfArr[$keyR] = $perfR;
+                }
+                // 对碰完成后把结余的业绩存入本期业绩缓存中
+                CalcCache::nowPeriodPerf($userId, $periodNum, $touchPerfArr);
+                //更新数据库
+                PerfPeriod::updateAll($touchPerfArr, 'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM', [
+                    'USER_ID' => $userId,
+                    'PERIOD_NUM' => $periodNum,
+                ]);
+                if ($touchBonusArr['touchBonus'] <= 0) continue;
+
+                $teamBonus = $touchBonusArr['touchBonus'];
+                $capBonusQy = $teamBonus; // 封顶前的奖金
+                //判断级别上限,个人奖金封顶限制
+                $teamBonus = $this->declarationLevelCap($teamBonus, $userId, $userBaseInfo['DEC_LV']);
+                if( $teamBonus <= 0 ) continue;
+                // 将封顶前的金额加入用户奖金缓存中,此金额不能发放(总奖金,总实际奖金) 
+                CalcCache::bonus($userId, $periodNum, 'CAPPED_BONUS_QY', $capBonusQy); 
+
+                $teamBonus = $this->bonusTotalLimit($teamBonus, $userId, $userBaseInfo['REC_NUM'], $userBaseInfo['ZC_AMOUNT']);
+                if( $teamBonus <= 0 ) continue;
+
+                //扣除相应的复消积分和管理费
+                $deductData = $this->deduct($userId, $teamBonus);
+
+                // 把对碰后的奖金存入缓存中
+                CalcCache::bonus($userId, $periodNum, 'BONUS_QY', $teamBonus, $deductData);
+
+                // TODO:取小腿值
+                $payLeg = min([$perfArr['SURPLUS_1L'], $perfArr['SURPLUS_2L']]);
+                // 计算荣衔星级
+                $starCrown = StarCrownLevel::getStarCrown($payLeg);
+                // 星级放入缓存
+                CalcCache::addUserStarCrown($userId, $periodNum, $starCrown['ID']);
+
+                //团队奖流水
+                $insertBonusData[] = [
+                    'ID' => SnowFake::instance()->generateId(),
+                    'USER_ID' => $userId,
+                    'ORI_CAPPED_BONUS_QY' => $capBonusQy,
+                    'LAST_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $userBaseInfo['EMP_LV'],
+                    'LAST_CROWN_LV' => $starCrown['ID'],
+                    'LAST_STATUS' => $userBaseInfo['STATUS'],
+                    'AMOUNT' => $deductData['surplus'],
+                    'ORI_BONUS' => $teamBonus,
+                    'RECONSUME_POINTS' => $deductData['reConsumePoints'],
+                    'MANAGE_TAX' => $deductData['manageTax'],
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_YEAR' => $this->_calcYear,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                    'CREATED_AT' => Date::nowTime(),
+                    'LOGS' => json_encode([
+                        'perfArr' => $perfArr,
+                        'touchPerfArrOri' => $touchBonusArr['perfArr'],
+                        'touchPerfArr' => $touchPerfArr,
+                        'nowDecLevelConfig' => $nowDecLevelConfig,
+                        'decLevel' => $userBaseInfo['DEC_LV'],
+                    ]),
+                ];
+
+                unset($perfData, $pervSurplusPerf, $perfArr, $oriPerfArr, $touchPerfArr, $userBaseInfo, $decLevelConfig, $touchBonusArr, $userId, $nowDecLevelConfig, $teamBonus, $deductData);
+            }
+            CalcBonusQY::batchInsert($insertBonusData);
+            unset($allData, $insertBonusData);
+            return $this->calcBonusQY($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 报单团队奖
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusBdQY(int $offset = 0) {
+        echo sprintf("时间:[%s]报单团队奖,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        $periodNum = $this->_periodNum;
+        // 从缓存获取分页有业绩的会员信息
+        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                // 从缓存中获取会员的业绩信息
+                $perfData = CalcCache::nowPeriodPerf($userId, $periodNum);
+                // 从缓存中获取会员的上期结余业绩信息
+                $pervSurplusPerf = CalcCache::surplusPerf($userId, $periodNum);
+                // 本期 + 上期结余
+                $perfArr = [
+                    'SURPLUS_1L_ZC' => $perfData['PV_1L_ZC'] + $pervSurplusPerf['SURPLUS_1L_ZC'],
+                    'SURPLUS_2L_ZC' => $perfData['PV_2L_ZC'] + $pervSurplusPerf['SURPLUS_2L_ZC'],
+                    'SURPLUS_3L_ZC' => $perfData['PV_3L_ZC'] + $pervSurplusPerf['SURPLUS_3L_ZC'],
+                    'SURPLUS_4L_ZC' => $perfData['PV_4L_ZC'] + $pervSurplusPerf['SURPLUS_4L_ZC'],
+                    'SURPLUS_5L_ZC' => $perfData['PV_5L_ZC'] + $pervSurplusPerf['SURPLUS_5L_ZC'],
+                ];
+                $oriPerfArr = [
+                    'perfArr' => $perfArr,
+                    'touchBonus' => 0,
+                ];
+                // 获取会员的报单级别
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                $decLevelConfig = $this->_decLevelConfig;
+                $nowDecLevelConfig = $decLevelConfig[$userBaseInfo['DEC_LV']];
+                // 对碰
+                $touchBonusArr = $this->touchPerf($oriPerfArr, $perfArr, $nowDecLevelConfig['QY_PERCENT']/100);
+                $touchPerfArr = [];
+                foreach ($touchBonusArr['perfArr'] as $keyR => $perfR) {
+                    $touchPerfArr[$keyR] = $perfR;
+                }
+                // 对碰完成后把结余的业绩存入本期业绩缓存中
+                CalcCache::nowPeriodPerf($userId, $periodNum, $touchPerfArr);
+                //更新数据库
+                PerfPeriod::updateAll($touchPerfArr, 'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM', [
+                    'USER_ID' => $userId,
+                    'PERIOD_NUM' => $periodNum,
+                ]);
+                if ($touchBonusArr['touchBonus'] <= 0) continue;
+
+                $teamBonus = $touchBonusArr['touchBonus'];
+                //判断级别上限
+//                $teamBonus = $this->declarationLevelCap($teamBonus, $userId, $userBaseInfo['DEC_LV']);
+//                if( $teamBonus <= 0 ) continue;
+
+//                $teamBonus = $this->bonusTotalLimit($teamBonus, $userId, $userBaseInfo['REC_NUM'], $userBaseInfo['ZC_AMOUNT']);
+//                if( $teamBonus <= 0 ) continue;
+
+                //扣除相应的复消积分和管理费
+//                $deductData = $this->deduct($userId, $teamBonus);
+
+                // 把对碰后的奖金存入缓存中
+                CalcCache::bonus($userId, $periodNum, 'BONUS_QY_BD', $teamBonus);
+
+                unset($perfData, $pervSurplusPerf, $perfArr, $oriPerfArr, $touchPerfArr, $userBaseInfo, $decLevelConfig, $touchBonusArr, $userId, $nowDecLevelConfig, $teamBonus, $deductData);
+            }
+            unset($allData, $insertBonusData);
+            return $this->calcBonusBdQY($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+
+    /**
+     * 复消团队奖
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusFxQY(int $offset = 0) {
+        echo sprintf("时间:[%s]复消团队奖,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        $periodNum = $this->_periodNum;
+        // 从缓存获取分页有业绩的会员信息
+        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                // 从缓存中获取会员的业绩信息
+                $perfData = CalcCache::nowPeriodPerf($userId, $periodNum);
+                // 从缓存中获取会员的上期结余业绩信息
+                $pervSurplusPerf = CalcCache::surplusPerf($userId, $periodNum);
+                // 本期 + 上期结余
+                $perfArr = [
+                    'SURPLUS_1L_FX' => $perfData['PV_1L_FX'] + $pervSurplusPerf['SURPLUS_1L_FX'],
+                    'SURPLUS_2L_FX' => $perfData['PV_2L_FX'] + $pervSurplusPerf['SURPLUS_2L_FX'],
+                    'SURPLUS_3L_FX' => $perfData['PV_3L_FX'] + $pervSurplusPerf['SURPLUS_3L_FX'],
+                    'SURPLUS_4L_FX' => $perfData['PV_4L_FX'] + $pervSurplusPerf['SURPLUS_4L_FX'],
+                    'SURPLUS_5L_FX' => $perfData['PV_5L_FX'] + $pervSurplusPerf['SURPLUS_5L_FX'],
+                ];
+                $oriPerfArr = [
+                    'perfArr' => $perfArr,
+                    'touchBonus' => 0,
+                ];
+                // 获取会员的报单级别
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                $decLevelConfig = $this->_decLevelConfig;
+                $nowDecLevelConfig = $decLevelConfig[$userBaseInfo['DEC_LV']];
+                // 对碰
+                $touchBonusArr = $this->touchPerf($oriPerfArr, $perfArr, $nowDecLevelConfig['QY_PERCENT']/100);
+                //大区封顶
+                $touchPerfArr = [];
+                foreach ($touchBonusArr['perfArr'] as $keyR => $perfR) {
+//                    if ($perfR > $nowDecLevelConfig['QY_BIG_CAP']) $perfR = $nowDecLevelConfig['QY_BIG_CAP'];
+                    $touchPerfArr[$keyR] = $perfR;
+                }
+                // 对碰完成后把结余的业绩存入本期业绩缓存中
+                CalcCache::nowPeriodPerf($userId, $periodNum, $touchPerfArr);
+                //更新数据库
+                PerfPeriod::updateAll($touchPerfArr, 'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM', [
+                    'USER_ID' => $userId,
+                    'PERIOD_NUM' => $periodNum,
+                ]);
+                if ($touchBonusArr['touchBonus'] <= 0) continue;
+
+                $teamBonus = $touchBonusArr['touchBonus'];
+                //判断级别上限
+//                $teamBonus = $this->declarationLevelCap($teamBonus, $userId, $userBaseInfo['DEC_LV']);
+//                if( $teamBonus <= 0 ) continue;
+//
+//                $teamBonus = $this->bonusTotalLimit($teamBonus, $userId, $userBaseInfo['REC_NUM'], $userBaseInfo['ZC_AMOUNT']);
+//                if( $teamBonus <= 0 ) continue;
+
+                //扣除相应的复消积分和管理费
+//                $deductData = $this->deduct($userId, $teamBonus);
+
+                // 把对碰后的奖金存入缓存中
+                CalcCache::bonus($userId, $periodNum, 'BONUS_QY_FX', $teamBonus);
+                unset($perfData, $pervSurplusPerf, $perfArr, $oriPerfArr, $touchPerfArr, $userBaseInfo, $decLevelConfig, $touchBonusArr, $userId, $nowDecLevelConfig, $teamBonus, $deductData);
+            }
+            unset($allData, $insertBonusData);
+            return $this->calcBonusFxQY($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 计算共享奖之前的操作
+     * @param int $offset
+     * @return bool
+     */
+    public function calcBonusGXBefore(int $offset = 0) {
+        //获取所有激活会员
+        $allData = CalcCache::getActiveUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            echo sprintf("时间:[%s]共享奖之前,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+            foreach ($allData as $userId) {
+                //判断$userId 能否得共享奖
+
+                // //判断 是否本期小市场(安置关系)新增980pv 能否得奖
+                // if ( !$this->checkSmallMarketPerf($userId) ) {
+                //     unset($userId);
+                //     continue;
+                // }
+
+                // 20220407 小市场980判断->改成判断此期用户是否有原报单团队奖金
+                if ( !$this->checkHasOriBonusQyBd($userId) ) {
+                    unset($userId);
+                    continue;
+                }
+
+                //获取当前会员所有的推荐上级, 并查看是否有首单团队奖
+                $validDeep = 0;
+                $this->loopRelationParentDo($userId, function ($parent) use ($userId, &$validDeep) {
+                    //判断$parent是否有首单团队奖
+                    $parentBonus = CalcCache::bonus($parent['PARENT_UID'], $this->_periodNum);
+                    if( $parentBonus['ORI_BONUS_QY_BD'] > 0 ) {
+                        $validDeep += 1;
+
+                        //记录得奖缓存
+                        CalcCache::addShareBonusOneRelation($userId, $parent['PARENT_UID'], $this->_periodNum, $validDeep);
+                    }
+                    unset($parent, $parentBonus);
+
+                    if ( $validDeep >= 2 ) {
+                        return self::LOOP_FINISH;
+                    }
+                });
+
+                unset($userId, $validDeep);
+            }
+            unset($allData);
+            return $this->calcBonusGXBefore($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 共享奖
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusGX(int $offset = 0) {
+        // 从缓存获取分页有的会员信息
+        $allData = CalcCache::getHasIncomeUsers($this->_periodNum, $offset, $this->_gxLimit);
+        if ($allData) {
+            echo sprintf("时间:[%s]共享奖,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+            $insertBonusData = [];
+            // 获取线上找几层和是否开启找几层的限制
+            $configsGx = Config::find()->where(
+                'CONFIG_NAME="gxNoPvFindLimitSwitch" OR CONFIG_NAME="gxNoPvFindLimitTimes"'
+            )->orderBy('SORT ASC')->indexBy('CONFIG_NAME')->asArray()->all();
+            $openFindLimit = isset($configsGx['gxNoPvFindLimitSwitch']) ? $configsGx['gxNoPvFindLimitSwitch']['VALUE'] : 1; // 是否开放了 得奖人不满足980就找一个满足980的人来得这个奖 的最大向上循环次数
+            $findLimitTimes = isset($configsGx['gxNoPvFindLimitTimes']) ? $configsGx['gxNoPvFindLimitTimes']['VALUE'] : 5 ; // 开放的限制层数
+            foreach ($allData as $userId) {
+                $bonusUserData = [];
+                $bonusPercentOne = $this->_sysConfig['shareRecPercent']['VALUE'] / 100;
+                $bonusPercentTwo = $this->_sysConfig['sharePercent']['VALUE'] / 100;
+
+                // 从缓存中获取会员的收入信息
+                $incomeBonus = CalcCache::bonus($userId, $this->_periodNum);
+                $lastTwoIncome = $incomeBonus['ORI_BONUS_QY_BD'] ?? 0;
+                $shareBonusOne = $lastTwoIncome * $bonusPercentOne;
+                unset($lastTwoIncome);
+                //找到上2代 新增个人+推荐团队业绩 加权平均分配 这个奖
+                if( $shareBonusOne > 0) {
+                    $bonusOneData = $this->gxLastTwoBonusData($userId, 1, $shareBonusOne);
+                    $bonusTwoData = $this->gxLastTwoBonusData($userId, 2, $shareBonusOne);
+                    $bonusUserData = array_merge($bonusUserData, $bonusOneData);
+                    $bonusUserData = array_merge($bonusUserData, $bonusTwoData);
+                    unset($bonusOneData, $bonusTwoData);
+                }
+
+                $nextFiveIncome = $incomeBonus['ORI_BONUS_QY_BD'] ?? 0;
+                $shareBonusTwo = $nextFiveIncome * $bonusPercentTwo;
+                unset($nextFiveIncome);
+
+                //找到下5代(安置网) 紧缩 相对偶数层2、4、6、8、10层
+                if( $shareBonusTwo > 0 ) {
+                    $validDeep = 1;
+                    $this->loopNetworkParentDo($userId, function ($parent) use ($userId, $shareBonusTwo, &$validDeep, &$bonusUserData) {
+                        if ( $validDeep % 2 === 0 ) {
+                            $bonusUserData[] = [
+                                'bonusUid' => $parent['PARENT_UID'],
+                                'bonus' => $shareBonusTwo,
+                                'validMinPv' => true,
+                            ];
+                        }
+                        //判断$parent是否有首单团队奖
+                        $parentBonus = CalcCache::bonus($parent['PARENT_UID'], $this->_periodNum);
+                        if( $parentBonus['ORI_BONUS_QY_BD'] > 0 ) {//有效层
+                            $validDeep += 1;
+                        }
+
+                        unset($parent, $parentBonus);
+
+                        if ( $validDeep >= 11 ) {
+                            return self::LOOP_FINISH;
+                        }
+                    });
+
+                    unset($validDeep);
+                }
+                if ( !$bonusUserData ) continue;
+
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                $userEmpLevel = $userBaseInfo['EMP_LV'];
+                foreach ($bonusUserData as $bonusUserDataEvery) {
+                    $bonusUserId = $bonusUserDataEvery['bonusUid'];
+                    //判断 是否本期小市场(安置关系)新增980pv 能否得奖->20220407改成判断得奖人此期是否有报单团队奖金
+                    // if ( $bonusUserDataEvery['validMinPv'] && !$this->checkSmallMarketPerf($bonusUserId) ) { 20220407
+                    if ( $bonusUserDataEvery['validMinPv'] && !$this->checkHasOriBonusQyBd($bonusUserId) ) {
+//                        unset($bonusUserDataEvery, $bonusUserId); 
+//                        continue;
+                        //得奖人不满足980就找一个满足980的人来得这个奖
+                        // 20220407改成判断得奖人此期是否有报单团队奖金,如果没有则找一个此期有报单团队奖金得人
+                        $bonusUserId = $this->getMinBdPvNetworkParent($bonusUserId, $openFindLimit, $findLimitTimes);
+                        if ( !$bonusUserId ) {
+                            unset($bonusUserDataEvery, $bonusUserId);
+                            continue;
+                        }
+                    }
+
+                    $bonus = Tool::formatPrice($bonusUserDataEvery['bonus']);
+                    $bonusUserBaseInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+                    //总金额限制
+                    $bonus = $this->bonusTotalLimit($bonus, $bonusUserId, $bonusUserBaseInfo['REC_NUM'], $bonusUserBaseInfo['ZC_AMOUNT']);
+                    if( $bonus <= 0 ) continue;
+
+                    //扣除相应的复消积分和管理费
+                    $deductData = $this->deduct($bonusUserId, $bonus);
+
+                    CalcCache::bonus($bonusUserId, $this->_periodNum, 'BONUS_GX', $bonus, $deductData);
+                    CalcCache::addHasMonthBonusUsers($bonusUserId, $this->_periodNum);
+
+                    //共享奖流水
+                    $insertBonusData[] = [
+                        'ID' => SnowFake::instance()->generateId(),
+                        'USER_ID' => $bonusUserId,
+                        'LAST_DEC_LV' => $bonusUserBaseInfo['DEC_LV'],
+                        'LAST_EMP_LV' => $bonusUserBaseInfo['EMP_LV'],
+                        'LAST_STATUS' => $bonusUserBaseInfo['STATUS'],
+                        'FROM_USER_ID' => $userId,
+                        'LAST_FROM_DEC_LV' => $userBaseInfo['DEC_LV'],
+                        'LAST_FROM_EMP_LV' => $userEmpLevel,
+                        'LAST_FROM_STATUS' => $userBaseInfo['STATUS'],
+                        'AMOUNT' => $deductData['surplus'],
+                        'ORI_BONUS' => $bonus,
+                        'RECONSUME_POINTS' => $deductData['reConsumePoints'],
+                        'MANAGE_TAX' => $deductData['manageTax'],
+                        'PERIOD_NUM' => $this->_periodNum,
+                        'CALC_YEAR' => $this->_calcYear,
+                        'CALC_MONTH' => $this->_calcYearMonth,
+                        'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                        'CREATED_AT' => Date::nowTime(),
+                        'LOGS' => json_encode([
+                            'sharePercent' => $bonusPercentOne,
+                            'incomeBonus' => $incomeBonus,
+                            'recNum' => $userBaseInfo['REC_NUM'],
+                            'decAmount' => $userBaseInfo['ZC_AMOUNT'],
+                            'bonusTotalLimit' => [
+                                $this->_sysConfig['bonusTotalZeroLimit']['VALUE'],
+                                $this->_sysConfig['bonusTotalOneLimit']['VALUE'],
+                                $this->_sysConfig['bonusTotalTwoLimit']['VALUE'],
+                            ],
+                        ]),
+                    ];
+
+                    unset($bonusUserDataEvery, $bonusUserId, $bonus, $bonusUserBaseInfo, $deductData);
+                }
+                unset($userId, $bonusUserData, $userBaseInfo, $userEmpLevel, $bonusPercentOne, $bonusPercentTwo, $incomeBonus, $sharePerson);
+//                echo ('内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            }
+            if($insertBonusData) CalcBonusGX::batchInsert($insertBonusData);
+            unset($allData, $insertBonusData);
+            return $this->calcBonusGX($offset + $this->_gxLimit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 季度奖计算
+     *
+     */
+    public function calcQuarter() {
+        if( !$this->_isCalcMonth || !in_array($this->_calcMonth, [3,6,9,12])) {
+            // 不是结算月,则不进行计算
+//            echo('不是季结点,进这里,不计算季度奖'. PHP_EOL);
+            return false;
+        }
+        $result = \Yii::$app->db->createCommand("CALL QtrCalc(:periodNum)")
+            ->bindValue(':periodNum' , $this->_periodNum )
+            ->execute();
+
+        return $result;
+    }
+
+    // 执行蓝星管理奖金的存储过程
+    public function calcBsProcedure() {
+        if( !$this->_isCalcMonth ) {
+            // 不是结算月,则不进行计算
+            return false;
+        }
+        $result = \Yii::$app->db->createCommand("CALL CalcBlue(:periodNum)") 
+                      ->bindValue(':periodNum' , $this->_periodNum )
+                      ->execute();
+                      
+        return $result;
+    }
+
+    // 执行旅游奖的计算
+    public function calcBonusTourism() {
+        // 月结,如果不是月结点,则直接退出
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+
+        $bonusConfig = $this->_sysConfig['openTourism'];
+        // 达标条件:聘级、级别、奖项比例
+        $config = json_decode($bonusConfig['OPTIONS'], true);
+        // 奖金总比例
+        $mate = $bonusConfig['VALUE'] / 100;
+        // 会员级别
+        $minDecLevel = $config['OPTIONS']['declarationLevel'] ?? [];
+        // 月度公司总PV
+        $monthTotalPV = PerfMonth::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
+            ->sum('PV_PCS');
+        // 用于分发的奖金总数
+        $transferAmount = $monthTotalPV * $mate;
+
+        // 基于蓝星奖结果计算符合获奖条件的会员StarDirector
+        $userStarDirector = CalcBonusBS::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
+            ->select('USER_ID,LEVEL_ID,LAST_DEC_LV,LAST_EMP_LV,LAST_STATUS')
+            ->groupBy('USER_ID')
+            ->asArray()
+            ->all();
+        $userStarDirectorObj = array_column($userStarDirector, NULL, 'USER_ID');
+
+        // 基于团队奖/绩效奖结果计算会员的StarCrown
+        $userStarCrown = CalcBonusQY::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
+            ->select('USER_ID,LAST_CROWN_LV')
+            ->groupBy('USER_ID')
+            ->asArray()
+            ->all();
+        $userStarCrownObj = array_column($userStarCrown, NULL, 'USER_ID');
+
+        // 合并用户ID,去重
+        $bonusUsers = array_unique(array_merge(array_keys($userStarDirectorObj), array_keys($userStarCrownObj)));
+
+        // 奖金点数综合
+        $bonusPointComplex = 0;
+        $insertBonusData = [];
+        foreach($bonusUsers as $userId) {
+            // 计算奖金:取starDirectorPoint和starCrownPoint的大个值
+            $starDirectorPoint = $this->_empLevelConfig[$userStarDirectorObj[$userId]['LEVEL_ID']]['TOURISM_PERCENT'] ?? 0;
+            $starCrownPoint = $this->_starCrownLevelConfig[$userStarCrownObj[$userId]['LAST_CROWN_LV']]['TOURISM_PERCENT'] ?? 0;
+            // 奖金比例:
+            $bonusPoint = max($starDirectorPoint, $starCrownPoint);
+            if ($bonusPoint <= 0) {
+                continue;
+            }
+            
+            $insertBonusData[] = [
+                'ID' => SnowFake::instance()->generateId(),
+                'USER_ID' => $userId,
+                'LAST_DEC_LV' => $userStarDirectorObj[$userId]['LAST_DEC_LV'],
+                'LAST_EMP_LV' => $userStarDirectorObj[$userId]['LAST_EMP_LV'],
+                'LAST_STATUS' => $userStarDirectorObj[$userId]['LAST_STATUS'],
+                'LAST_CROWN_LV' => $userStarCrownObj[$userId]['LAST_CROWN_LV'],
+                'AMOUNT_STANDARD' => 0,
+                'POINT' => $bonusPoint,
+                'PERIOD_NUM' => $this->_periodNum,
+                'CALC_YEAR' => $this->_calcYear,
+                'CALC_MONTH' => $this->_calcYearMonth,
+                'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                'CREATED_AT' => Date::nowTime(),
+                'PERF' => $monthTotalPV,
+                'TRANSFER_RATE' => $mate,
+                'TRANSFER_AMOUNT' => Tool::formatPrice($transferAmount),
+                'CAP_AMOUNT' => 0,
+                'POINT_COMPLEX' => 0,
+            ];
+
+            $bonusPointComplex += $bonusPoint;
+        }
+
+        // 数据写入总表
+        if ($insertBonusData) {
+            foreach ($insertBonusData as &$bonusData) {
+                // 计算奖金
+                $amount = Tool::formatPrice($transferAmount * ($bonusData['POINT'] / $bonusPointComplex));
+                if ($amount <= 0) {
+                    continue;
+                }
+
+                // 会员级别达到要求才会发放奖金
+                if ($bonusData['LAST_DEC_LV'] == $minDecLevel) {
+                    // 放入缓存
+                    CalcCache::tourismBonus($bonusData['USER_ID'], $this->_periodNum, $amount);
+                    // 加入月奖的会员
+                    CalcCache::addHasMonthBonusUsers($bonusData['USER_ID'], $this->_periodNum);
+                }
+
+                $bonusData['AMOUNT'] = $amount;
+                $bonusData['POINT_COMPLEX'] = $bonusPointComplex;
+            }
+
+            CalcBonusTourism::batchInsert($insertBonusData);
+        }
+
+        return true;
+    }
+
+    // 执行房奖的计算
+    public function calcBonusVilla() {
+        // 月结,如果不是月结点,则直接退出
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+
+        $bonusConfig = $this->_sysConfig['openVilla'];
+        // 达标条件:聘级、级别、奖项比例
+        $config = json_decode($bonusConfig['OPTIONS'], true);
+        // 奖金总比例
+        $mate = $bonusConfig['VALUE'] / 100;
+        // 个人奖金封顶
+        $capBonus = intval($this->_sysConfig['openVillaCap']['VALUE'] ?? 0);
+        // 会员级别
+        $minDecLevel = $config['declarationLevel'] ?? [];
+
+        // 月度公司总PV
+        $monthTotalPV = PerfMonth::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
+            ->sum('PV_PCS');
+        // 用于分发的奖金总数
+        $transferAmount = $monthTotalPV * $mate;
+
+        // 基于团队奖/绩效奖结果计算会员的StarCrown.StarCrown基于周期计算,一个月会产生多次,取月周期中的最高星级
+        $subQuery = CalcBonusQY::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH = :CALC_MONTH AND LAST_CROWN_LV <> :NO_CROWN_LV', [':CALC_MONTH' => $this->_calcYearMonth, ':NO_CROWN_LV' => StarCrownLevel::NO_LEVEL_ID])
+            ->select('USER_ID,LAST_DEC_LV,LAST_CROWN_LV,LAST_STATUS,LEVEL_NAME,SORT')
+            ->joinWith(['starCrown' => function($query) {
+                $query->select(['LEVEL_NAME', 'SORT']);
+            }])
+            ->having(1)
+            ->orderBy('USER_ID ASC, SORT DESC');
+        $userStarCrownObj = (new Query())->from(['u' => $subQuery])->select('USER_ID,LAST_DEC_LV,LAST_CROWN_LV,LAST_STATUS,LEVEL_NAME,SORT')->groupBy('USER_ID')->indexBy('USER_ID')->all();
+
+        // 奖金点数综合
+        $bonusPointComplex = 0;
+        $insertBonusData = [];
+        foreach($userStarCrownObj as $item) {
+            // 奖金比例
+            $bonusPoint = $this->_starCrownLevelConfig[$item['LAST_CROWN_LV']]['VILLA_PERCENT'] ?? 0;
+            if (!$bonusPoint) {
+                continue;
+            }
+
+            // 会员级别达到要求才会发放奖金
+            if ($item['LAST_DEC_LV'] != $minDecLevel) {
+                continue;
+            }
+
+            $insertBonusData[] = [
+                'ID' => SnowFake::instance()->generateId(),
+                'USER_ID' => $item['USER_ID'],
+                'LAST_DEC_LV' => $item['LAST_DEC_LV'] ?? '',
+                'LAST_EMP_LV' => $item['LAST_EMP_LV'] ?? '',
+                'LAST_STATUS' => $item['LAST_STATUS'] ?? 0,
+                'LAST_CROWN_LV' => $item['LAST_CROWN_LV'] ?? '',
+                'AMOUNT' => 0,
+                'POINT' => $bonusPoint,
+                'PERIOD_NUM' => $this->_periodNum,
+                'CALC_YEAR' => $this->_calcYear,
+                'CALC_MONTH' => $this->_calcYearMonth,
+                'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                'CREATED_AT' => Date::nowTime(),
+                'PERF' => $monthTotalPV,
+                'TRANSFER_RATE' => $mate,
+                'TRANSFER_AMOUNT' => Tool::formatPrice($transferAmount),
+                'CAP_AMOUNT' => 0,
+                'POINT_COMPLEX' => 0,
+            ];
+
+            $bonusPointComplex += $bonusPoint;
+        }
+
+        // 数据写入总表
+        if ($insertBonusData) {
+            // 计算个人奖金
+            foreach ($insertBonusData as &$bonusData) {
+                // 计算奖金
+                $amount = Tool::formatPrice($transferAmount * ($bonusData['POINT'] / $bonusPointComplex));
+                if ($amount <= 0) {
+                    continue;
+                }
+                // 封顶前奖金数
+                $capAmount = $amount;
+                // 奖金数不能大于封顶值
+                $amount = ($amount > $capBonus) ? $capBonus : $amount;
+
+                $bonusData['AMOUNT'] = $amount;
+                $bonusData['CAP_AMOUNT'] = $capAmount;
+                $bonusData['POINT_COMPLEX'] = $bonusPointComplex;
+
+                // 放入缓存
+                CalcCache::villaBonus($bonusData['USER_ID'], $this->_periodNum, $amount);
+                // 加入月奖的会员
+                CalcCache::addHasMonthBonusUsers($bonusData['USER_ID'], $this->_periodNum);
+            }
+
+            CalcBonusVilla::batchInsert($insertBonusData);
+        }
+
+        return true;
+    }
+
+    // 执行车奖的计算
+    public function calcBonusGarage() {
+        // 月结,如果不是月结点,则直接退出
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+
+        $bonusConfig = $this->_sysConfig['openGarage'];
+        // 达标条件:聘级、级别、奖项比例
+        $config = json_decode($bonusConfig['OPTIONS'], true);
+        // 奖金总比例
+        $mate = $bonusConfig['VALUE'] / 100;
+        // 会员级别
+        $minDecLevel = $config['declarationLevel'] ?? [];
+        // 个人奖金封顶
+        $capBonus = intval($this->_sysConfig['openGarageCap']['VALUE'] ?? 0);
+
+        // 月度公司总PV
+        $monthTotalPV = PerfMonth::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
+            ->sum('PV_PCS');
+        // 用于分发的奖金总数
+        $transferAmount = $monthTotalPV * $mate;
+
+        // 基于蓝星奖结果计算符合获奖条件的会员StarDirector
+        $userStarDirector = CalcBonusBS::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH = :CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
+            ->select('USER_ID,LEVEL_ID,LAST_DEC_LV,LAST_STATUS')
+            ->groupBy('USER_ID')
+            ->asArray()
+            ->all();
+        $userStarDirectorObj = array_column($userStarDirector, NULL, 'USER_ID');
+
+        // 基于团队奖/绩效奖结果计算会员的StarCrown.StarCrown基于周期计算,一个月会产生多次,取月周期中的最高星级
+        $subQuery = CalcBonusQY::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH = :CALC_MONTH AND LAST_CROWN_LV <> :NO_CROWN_LV', [':CALC_MONTH' => $this->_calcYearMonth, ':NO_CROWN_LV' => StarCrownLevel::NO_LEVEL_ID])
+            ->select('USER_ID,LAST_DEC_LV,LAST_CROWN_LV,LAST_STATUS,LEVEL_NAME,SORT')
+            ->joinWith(['starCrown' => function($query) {
+                $query->select(['LEVEL_NAME', 'SORT']);
+            }])
+            ->having(1)
+            ->orderBy('USER_ID ASC, SORT DESC');
+        $userStarCrownObj = (new Query())->from(['u' => $subQuery])->select('USER_ID,LAST_DEC_LV,LAST_CROWN_LV,LAST_STATUS,LEVEL_NAME,SORT')->groupBy('USER_ID')->indexBy('USER_ID')->all();
+        
+        // 合并用户ID,去重
+        $bonusUsers = array_unique(array_merge(array_keys($userStarDirectorObj), array_keys($userStarCrownObj)));
+        sort($bonusUsers);
+
+        // 奖金点数综合
+        $bonusPointComplex = 0;
+        $insertBonusData = [];
+        foreach($bonusUsers as $userId) {
+            // 计算奖金:取starDirectorPoint和starCrownPoint的大个值
+            $starDirectorPoint = !isset($userStarDirectorObj[$userId]['LEVEL_ID']) ? 0 : ($this->_empLevelConfig[$userStarDirectorObj[$userId]['LEVEL_ID']]['GARAGE_PERCENT'] ?? 0);
+            $starCrownPoint = !isset($userStarCrownObj[$userId]['LAST_CROWN_LV']) ? 0: ($this->_starCrownLevelConfig[$userStarCrownObj[$userId]['LAST_CROWN_LV']]['GARAGE_PERCENT'] ?? 0);
+            // 奖金比例:
+            $bonusPoint = max($starDirectorPoint, $starCrownPoint);
+            if ($bonusPoint <= 0) {
+                continue;
+            }
+
+            // 会员级别达到要求才会发放奖金
+            $lastDecLv = $userStarDirectorObj[$userId]['LAST_DEC_LV'] ?? ($userStarCrownObj[$userId]['LAST_DEC_LV'] ?? '');
+            if ($lastDecLv != $minDecLevel) {
+                continue;
+            }
+
+            $insertBonusData[] = [
+                'ID' => SnowFake::instance()->generateId(),
+                'USER_ID' => $userId,
+                'LAST_DEC_LV' => $userStarDirectorObj[$userId]['LAST_DEC_LV'] ?? ($userStarCrownObj[$userId]['LAST_DEC_LV'] ?? ''),
+                'LAST_EMP_LV' => $userStarDirectorObj[$userId]['LEVEL_ID'] ?? '',
+                'LAST_STATUS' => $userStarDirectorObj[$userId]['LAST_STATUS'] ?? ($userStarCrownObj[$userId]['LAST_STATUS'] ?? 1),
+                'LAST_CROWN_LV' => $userStarCrownObj[$userId]['LAST_CROWN_LV'] ?? '',
+                'AMOUNT' => 0,
+                'POINT' => $bonusPoint,
+                'PERIOD_NUM' => $this->_periodNum,
+                'CALC_YEAR' => $this->_calcYear,
+                'CALC_MONTH' => $this->_calcYearMonth,
+                'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                'CREATED_AT' => Date::nowTime(),
+                'PERF' => $monthTotalPV,
+                'TRANSFER_RATE' => $mate,
+                'TRANSFER_AMOUNT' => Tool::formatPrice($transferAmount),
+                'CAP_AMOUNT' => 0,
+                'POINT_COMPLEX' => 0,
+            ];
+
+            $bonusPointComplex += $bonusPoint;
+        }
+
+        // 数据写入总表
+        if ($insertBonusData) {
+            foreach ($insertBonusData as &$bonusData) {
+                // 计算奖金
+                $amount = Tool::formatPrice($transferAmount * ($bonusData['POINT'] / $bonusPointComplex));
+                if ($amount <= 0) {
+                    continue;
+                }
+                // 封顶前奖金数
+                $capAmount = $amount;
+                // 奖金数不能大于封顶值
+                $amount = ($amount > $capBonus) ? $capBonus : $amount;
+
+                $bonusData['AMOUNT'] = $amount;
+                $bonusData['CAP_AMOUNT'] = $capAmount;
+                $bonusData['POINT_COMPLEX'] = $bonusPointComplex;
+
+                // 放入缓存
+                CalcCache::garageBonus($bonusData['USER_ID'], $this->_periodNum, $amount);
+                // 加入月奖的会员
+                CalcCache::addHasMonthBonusUsers($bonusData['USER_ID'], $this->_periodNum);
+            }
+
+            CalcBonusGarage::batchInsert($insertBonusData);
+        }
+
+        return true;
+    }
+
+    /**
+     * 季度奖写用户缓存
+     *
+     */
+    public function calcQuarterUser(int $offset = 0) {
+        if( !$this->_isCalcMonth || !in_array($this->_calcMonth, [3,6,9,12])) {
+            // 不是结算月,则不进行计算
+            return false;
+        }
+        $allData = CalcBonusQuarter::finduseDbCalc()
+                ->where('PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum])
+                ->groupBy('USER_ID')
+                ->offset($offset)
+                ->limit($this->_limit)
+                ->asArray()
+                ->all();
+        if ($allData){
+            // 达标条件:会员级别:钻卡
+            $config = json_decode($this->_sysConfig['openQuarter']['OPTIONS'], true);
+            $minDecLevel = $config['declarationLevel'] ?? [];
+
+            foreach ($allData as $user) {
+                // 扣除相应的复消积分和管理费
+                $deductData = $this->deduct($user['USER_ID'], $user['ORI_BONUS']);
+                $realBonusBs = $deductData['surplus']; // 扣除管理费和复消积分后的实发蓝星奖金
+                $manageTax = $deductData['manageTax']; // 管理费
+                $point = $deductData['reConsumePoints'] + $user['RECONSUME_POINTS'];// 复消积分
+                // 管理奖钻卡发放
+                if ($user['LAST_DEC_LV'] == $minDecLevel) {
+                    // 把对碰后的奖金存入缓存中
+                    CalcCache::bonus($user['USER_ID'], $this->_periodNum, 'BONUS_QUARTER', $user['ORI_BONUS'], $deductData);
+                    // 加入月奖的会员
+                    CalcCache::addHasMonthBonusUsers($user['USER_ID'], $this->_periodNum);
+                }
+
+                // 更新奖金存储过程的实发金额数据
+                CalcBonusQuarter::updateAll([
+                    'RECONSUME_POINTS' => $point,
+                    'AMOUNT' => $realBonusBs,
+                    'MANAGE_TAX' => $manageTax],
+                    'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM',
+                    [':USER_ID' => $user['USER_ID'], ':PERIOD_NUM' => $this->_periodNum]);
+            }
+            return $this->calcQuarterUser($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 蓝星管理奖金
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusBsGL(int $offset = 0) {
+        if( !$this->_isCalcMonth ) {
+            // 不是结算月,则不进行计算
+            return false;
+        }
+        // 从缓存获取分页有收入的会员信息
+        $allData = CalcBonusBS::findUseDbCalc()
+            ->where('PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum])
+            ->groupBy('USER_ID')
+            ->offset($offset)
+            ->limit($this->_limit)
+            ->asArray()
+            ->all();
+        if ($allData) {
+            // 达标条件:会员级别:钻卡
+            $config = json_decode($this->_sysConfig['openGL']['OPTIONS'], true);
+            $minDecLevel = $config['mntDec'] ?? [];
+
+            foreach ($allData as $user) {
+                //扣除相应的复消积分和管理费
+                $deductData = $this->deduct($user['USER_ID'], $user['ORI_BONUS_MNT']);
+                $realBonusBs = $deductData['surplus']; // 扣除管理费和复消积分后的实发蓝星奖金
+                $manageTax = $deductData['manageTax']; // 管理费
+                $point = $deductData['reConsumePoints'] + $user['RECONSUME_POINTS'];// 复消积分
+
+                // 管理奖钻卡发放
+//                if (in_array($user['LAST_DEC_LV'], $minDecLevel)) {
+                    // 把对碰后的奖金存入缓存中
+                    CalcCache::bonus($user['USER_ID'], $this->_periodNum, 'BONUS_BS_MNT', $user['ORI_BONUS_MNT'], $deductData);
+                    // 加入月奖的会员
+                    CalcCache::addHasMonthBonusUsers($user['USER_ID'], $this->_periodNum);
+//                }
+
+                // 更新蓝星奖金存储过程的实发金额数据
+                CalcBonusBS::updateAll([
+                    'RECONSUME_POINTS' => $point,
+                    'AMOUNT_MNT' => $realBonusBs,
+                    'MANAGE_TAX_MNT' => $manageTax],
+                'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM', 
+                [':USER_ID' => $user['USER_ID'], ':PERIOD_NUM' => $this->_periodNum]);
+            }
+            return $this->calcBonusBsGL($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 蓝星业绩奖金
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusBsYJ(int $offset = 0) {
+        if( !$this->_isCalcMonth ) {
+            // 不是结算月,则不进行计算
+            return false;
+        }
+        // 从缓存获取分页有收入的会员信息
+        $allData = CalcBonusBS::findUseDbCalc()
+            ->where('PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum])
+            ->groupBy('USER_ID')
+            ->offset($offset)
+            ->limit($this->_limit)
+            ->asArray()
+            ->all();
+        if ($allData) {
+            // 达标条件:会员级别:金卡、钻卡
+            $config = json_decode($this->_sysConfig['openGL']['OPTIONS'], true);
+            $minDecLevel = $config['abbrDec'] ?? [];
+
+            foreach ($allData as $user) {
+                //扣除相应的复消积分和管理费
+                $deductData = $this->deduct($user['USER_ID'], $user['ORI_BONUS_ABBR']);
+                $realBonusBs = $deductData['surplus']; // 扣除管理费和复消积分后的实发蓝星奖金
+                $manageTax = $deductData['manageTax']; // 管理费
+                $point = $deductData['reConsumePoints'] + $user['RECONSUME_POINTS'];// 复消积分
+
+                // 业绩奖金卡、钻卡发放
+//                if (in_array($user['LAST_DEC_LV'], $minDecLevel)) {
+                    // 把对碰后的奖金存入缓存中
+                    CalcCache::bonus($user['USER_ID'], $this->_periodNum, 'BONUS_BS_ABBR', $user['ORI_BONUS_ABBR'], $deductData);
+                    // 加入月奖的会员
+                    CalcCache::addHasMonthBonusUsers($user['USER_ID'], $this->_periodNum);
+//                }
+
+                // 更新蓝星业绩奖金存储过程的实发金额数据
+                CalcBonusBS::updateAll([
+                    'AMOUNT_ABBR' => $realBonusBs,
+                    'MANAGE_TAX_ABBR' => $manageTax,
+                    'RECONSUME_POINTS' => $point],
+            'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM',
+                    [':USER_ID' => $user['USER_ID'], ':PERIOD_NUM' => $this->_periodNum]);
+            }
+            return $this->calcBonusBsYJ($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 报单管理奖
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusBdGL(int $offset = 0) {
+        // 从缓存获取分页有收入的会员信息
+        $allData = CalcCache::getHasIncomeUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                $bonusUserData = [];
+                //会员信息
+                $validDeep = 1;
+                $this->loopRelationParentDo($userId, function ($parent) use ($userId, &$validDeep, &$bonusUserData) {
+                    //判断$parent是否有首单团队奖
+                    $bonusUserId = $parent['PARENT_UID'];
+                    $bonusUserBaseInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+
+                    $nowDecLevelConfig = $this->_decLevelConfig[$bonusUserBaseInfo['DEC_LV']];
+                    $recNum = $bonusUserBaseInfo['REC_NUM'];
+                    if( $recNum <= 0 ) {
+                        $maxOddLayer = 0;
+                    }else if( $recNum == 1 ) {
+                        $maxOddLayer = $nowDecLevelConfig['GL_ODD_DEEP_ONE'] ?? 0;
+                    }else if( $recNum == 2 ) {
+                        $maxOddLayer = $nowDecLevelConfig['GL_ODD_DEEP_TWO'] ?? 0;
+                    }else {
+                        $maxOddLayer = $nowDecLevelConfig['GL_ODD_DEEP_THREE'] ?? 0;
+                    }
+
+                    if ( $validDeep < 2 * $maxOddLayer && $validDeep % 2 === 1 ) {//奇数层
+                        $bonusUserData[] =  [
+                            'bonusUid' => $parent['PARENT_UID'],
+                            'validDeep' => $validDeep
+                        ];
+                    }
+
+                    $parentBonus = CalcCache::bonus($bonusUserId, $this->_periodNum);
+                    if( $parentBonus['ORI_BONUS_QY_BD'] > 0 ) {//有效层
+                        $validDeep += 1;
+                    }
+
+                    unset($parent, $parentBonus, $bonusUserId, $bonusUserBaseInfo, $nowDecLevelConfig, $recNum, $maxOddLayer);
+
+                    //超过代数上限则结束
+                    if ( $validDeep >= 18 ) {
+                        return self::LOOP_FINISH;
+                    }
+                });
+
+                //发奖
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                $userBonus = CalcCache::bonus($userId, $this->_periodNum);
+                foreach ($bonusUserData as $everyData) {
+                    $bonusUserId = $everyData['bonusUid'];
+                    //判断 是否本期小市场(安置关系)新增980pv 能否得奖
+                    if ( !$this->checkSmallMarketPerf($bonusUserId) ) {
+//                        unset($bonusUserId);
+//                        continue;
+                        //得奖人不满足980就找一个满足980的人来得这个奖
+                        $bonusUserId = $this->getMinBdPvRelationParent($bonusUserId);
+                        if ( !$bonusUserId ) {
+                            unset($bonusUserId);
+                            continue;
+                        }
+                    }
+
+                    $parentOddPercentValue = floor($everyData['validDeep']/6);
+                    $parentOddPercentName = sprintf('parentOddPercent_%s', $parentOddPercentValue);
+                    if( !isset($this->_sysConfig[$parentOddPercentName]) ) continue;
+                    $bonusPercent = $this->_sysConfig[$parentOddPercentName]['VALUE'];
+
+                    $manageBonus = Tool::formatPrice($userBonus['ORI_BONUS_QY_BD'] * $bonusPercent / 100);
+                    if ($manageBonus <= 0) continue;
+
+                    //总金额限制
+                    $bonusUserBaseInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+                    $recNum = $bonusUserBaseInfo['REC_NUM'];
+                    $manageBonus = $this->bonusTotalLimit($manageBonus, $bonusUserId, $recNum, $bonusUserBaseInfo['ZC_AMOUNT']);
+                    if( $manageBonus <= 0 ) continue;
+
+                    //扣除相应的复消积分和管理费
+                    $deductData = $this->deduct($bonusUserId, $manageBonus);
+
+                    // 钻卡会员奖金记入缓存
+                    $decLevelConfig = $this->_decLevelConfig;
+                    $nowDecLevelConfig = $decLevelConfig[$userBaseInfo['DEC_LV']];
+                    if ($nowDecLevelConfig == '67ABCE0ECE705575E055736AECE8644D') {
+                        CalcCache::bonus($bonusUserId, $this->_periodNum, 'BONUS_GL', $manageBonus, $deductData, CalcCache::FROM_MEANS_BD);
+                        CalcCache::addHasMonthBonusUsers($bonusUserId, $this->_periodNum);
+                    }
+
+                    //管理奖流水
+                    $insertBonusData[] = [
+                        'ID' => SnowFake::instance()->generateId(),
+                        'USER_ID' => $bonusUserId,
+                        'LAST_DEC_LV' => $bonusUserBaseInfo['DEC_LV'],
+                        'LAST_EMP_LV' => $bonusUserBaseInfo['EMP_LV'],
+                        'LAST_STATUS' => $bonusUserBaseInfo['STATUS'],
+                        'FROM_USER_ID' => $userId,
+                        'LAST_FROM_DEC_LV' => $userBaseInfo['DEC_LV'],
+                        'LAST_FROM_EMP_LV' => $userBaseInfo['EMP_LV'],
+                        'LAST_FROM_STATUS' => $userBaseInfo['STATUS'],
+                        'AMOUNT' => $deductData['surplus'],
+                        'ORI_BONUS' => $manageBonus,
+                        'RECONSUME_POINTS' => $deductData['reConsumePoints'],
+                        'MANAGE_TAX' => $deductData['manageTax'],
+                        'PERIOD_NUM' => $this->_periodNum,
+                        'CALC_YEAR' => $this->_calcYear,
+                        'CALC_MONTH' => $this->_calcYearMonth,
+                        'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                        'CREATED_AT' => Date::nowTime(),
+                        'LOGS' => json_encode([
+                            $parentOddPercentName => $this->_sysConfig[$parentOddPercentName]['VALUE'],
+                            'validDeep' => $everyData['validDeep'],
+                            'recNum' => $bonusUserBaseInfo['REC_NUM'],
+                            'decAmount' => $bonusUserBaseInfo['ZC_AMOUNT'],
+                            'fromUserId' => $userId,
+                            'decLevel' => $bonusUserBaseInfo['DEC_LV'],
+                            'bonusTotalLimit' => [
+                                $this->_sysConfig['bonusTotalZeroLimit']['VALUE'],
+                                $this->_sysConfig['bonusTotalOneLimit']['VALUE'],
+                                $this->_sysConfig['bonusTotalTwoLimit']['VALUE'],
+                            ],
+                        ]),
+                    ];
+
+                    unset($everyData, $bonusUserId, $parentOddPercentValue, $parentOddPercentName, $bonusPercent, $manageBonus, $deductData);
+                }
+
+                unset($userId, $validDeep, $userBaseInfo, $userBonus, $bonusUserData);
+            }
+            if($insertBonusData) CalcBonusGL::batchInsert($insertBonusData);
+            unset($allData, $insertBonusData);
+            return $this->calcBonusBdGL($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 复消管理奖
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusFxGL(int $offset = 0) {
+        $periodNum = $this->_periodNum;
+        // 从缓存获取分页有收入的会员信息
+        $allData = CalcCache::getHasIncomeUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                //会员信息
+                $decLevelConfig = $this->_decLevelConfig;
+                $netWorkParents = Cache::getAllRelationParents($userId);
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                $userEmpLevel = $userBaseInfo['EMP_LV'];
+                foreach ($netWorkParents as $netWorkParent) {
+                    //自已距顶端的深度减去父级距顶端的深度
+                    $diffLayer = $netWorkParent['TOP_DEEP'] - $netWorkParent['PARENT_DEEP'];
+                    if( $diffLayer <= 0) continue;
+
+                    //@todo 超过最大代数就跳出本次循环
+
+                    //偶数代跳过
+                    if( $diffLayer % 2 == 0 ) continue;
+
+                    // 获取会员的报单级别
+                    $bonusUserId = $netWorkParent['PARENT_UID'];
+                    if( !$bonusUserId ) continue;
+                    $bonusUserBaseInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+
+                    $nowDecLevelConfig = $decLevelConfig[$bonusUserBaseInfo['DEC_LV']];
+                    if( !$nowDecLevelConfig ) continue;
+                    //推荐人数
+                    $recNum = $bonusUserBaseInfo['REC_NUM'];
+                    if( $recNum <= 0 ) continue;
+                    if( $recNum == 1 ) {
+                        $maxOddLayer = $nowDecLevelConfig['GL_ODD_DEEP_ONE'];
+                    }else if( $recNum == 2 ) {
+                        $maxOddLayer = $nowDecLevelConfig['GL_ODD_DEEP_TWO'];
+                    }else {
+                        $maxOddLayer = $nowDecLevelConfig['GL_ODD_DEEP_THREE'];
+                    }
+
+                    //超过代数上限也跳过
+                    if( $diffLayer >= 2 * $maxOddLayer ) continue;
+
+//                    $everyLayer = floor(2 * $nowDecLevelConfig['GL_ODD_DEEP_THREE'] / $nowDecLevelConfig['SORT']);
+                    //多少代
+//                    $theLayer = $diffLayer % $everyLayer;
+//                    $theLayerLevel = floor($diffLayer/$everyLayer);
+                    $theLayerLevel = floor($diffLayer/6);
+                    $parentOddPercentName = sprintf('parentOddPercent_%s', $theLayerLevel);
+                    if( !isset($this->_sysConfig[$parentOddPercentName]) ) continue;
+                    $bonusPercent = $this->_sysConfig[$parentOddPercentName]['VALUE'];
+
+                    // 从缓存中获取会员的收入信息
+                    $incomeBonus = CalcCache::bonus($userId, $periodNum);
+                    $parentOddIncomeFromName = sprintf('parentOddIncomeFrom_%s', $theLayerLevel);
+                    $parentOddIncomeFrom = explode(',', $this->_sysConfig[$parentOddIncomeFromName]['VALUE']);
+                    $parentOddIncome = 0;
+                    foreach ($parentOddIncomeFrom as $incomeType) {
+                        if( $incomeType === 'XF' ) {
+                            $incomeTypeName = sprintf('ORI_BONUS_%s', $incomeType);
+                        }else {
+                            $incomeTypeName = sprintf('ORI_BONUS_%s_FX', $incomeType);
+                        }
+                        $incomeTypeValue = $incomeBonus[$incomeTypeName] ?? 0;
+                        $parentOddIncome += $incomeTypeValue;
+                        unset($incomeType, $incomeTypeName, $incomeTypeValue);
+                    }
+                    unset($parentOddIncomeFrom);
+                    $manageBonus = Tool::formatPrice($parentOddIncome * $bonusPercent / 100);
+                    if ($manageBonus <= 0) continue;
+
+                    //总金额限制
+                    $manageBonus = $this->bonusTotalLimit($manageBonus, $bonusUserId, $recNum, $bonusUserBaseInfo['ZC_AMOUNT']);
+                    if( $manageBonus <= 0 ) continue;
+
+                    //扣除相应的复消积分和管理费
+                    $deductData = $this->deduct($bonusUserId, $manageBonus);
+
+                    CalcCache::bonus($bonusUserId, $periodNum, 'BONUS_GL', $manageBonus, $deductData, CalcCache::FROM_MEANS_FX);
+
+                    //管理奖流水
+                    $insertBonusData[] = [
+                        'ID' => SnowFake::instance()->generateId(),
+                        'USER_ID' => $bonusUserId,
+                        'LAST_DEC_LV' => $bonusUserBaseInfo['DEC_LV'],
+                        'LAST_EMP_LV' => $bonusUserBaseInfo['EMP_LV'],
+                        'LAST_STATUS' => $bonusUserBaseInfo['STATUS'],
+                        'FROM_USER_ID' => $userId,
+                        'LAST_FROM_DEC_LV' => $userBaseInfo['DEC_LV'],
+                        'LAST_FROM_EMP_LV' => $userEmpLevel,
+                        'LAST_FROM_STATUS' => $userBaseInfo['STATUS'],
+                        'AMOUNT' => $deductData['surplus'],
+                        'ORI_BONUS' => $manageBonus,
+                        'RECONSUME_POINTS' => $deductData['reConsumePoints'],
+                        'MANAGE_TAX' => $deductData['manageTax'],
+                        'PERIOD_NUM' => $this->_periodNum,
+                        'CALC_YEAR' => $this->_calcYear,
+                        'CALC_MONTH' => $this->_calcYearMonth,
+                        'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                        'CREATED_AT' => Date::nowTime(),
+                        'LOGS' => json_encode([
+                            $parentOddPercentName => $this->_sysConfig[$parentOddPercentName]['VALUE'],
+                            $parentOddIncomeFromName => $this->_sysConfig[$parentOddIncomeFromName]['VALUE'],
+                            'decLevelConfig' => $nowDecLevelConfig,
+                            'recNum' => $bonusUserBaseInfo['REC_NUM'],
+                            'decAmount' => $bonusUserBaseInfo['ZC_AMOUNT'],
+                            'fromUserId' => $userId,
+                            'decLevel' => $bonusUserBaseInfo['DEC_LV'],
+                            'bonusTotalLimit' => [
+                                $this->_sysConfig['bonusTotalZeroLimit']['VALUE'],
+                                $this->_sysConfig['bonusTotalOneLimit']['VALUE'],
+                                $this->_sysConfig['bonusTotalTwoLimit']['VALUE'],
+                            ],
+                        ]),
+                    ];
+
+                    unset($netWorkParent, $diffLayer, $bonusUserId, $bonusUserBaseInfo, $bonusPercent, $nowDecLevelConfig, $manageBonus, $incomeBonus, $maxOddLayer, $theLayerLevel, $recNum, $parentOddPercentName, $parentOddIncomeFromName, $deductData);
+
+                }
+
+                unset($userId, $userBaseInfo, $userEmpLevel, $netWorkParents, $decLevelConfig);
+
+            }
+            CalcBonusGL::batchInsert($insertBonusData);
+            unset($allData, $insertBonusData);
+            return $this->calcBonusFxGL($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+
+    /**
+     * 结算荣衔奖第一步
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     * @throws \Exception
+     */
+    public function calcBonusYCStepOne(int $offset = 0) {
+        // 月结,如果不是月结点,则直接退出
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+        echo sprintf("时间:[%s]荣衔奖第【1】步,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        //从缓存获取分页有本月业绩的会员
+        $allData = CalcCache::getHasMonthPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $maxLevelId = EmployLevel::SHX_ZJ_LEVEL_ID;
+            $maxLevel = $this->_empLevelConfig[$maxLevelId];
+            $maxLevelPercent = $maxLevel['RX_PERCENT'] ?? 0;
+            unset($maxLevelId, $maxLevel);
+
+            foreach ($allData as $userId) {
+//                echo sprintf("时间:[%s]荣衔奖,当前用户ID为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $userId);
+                $nowMonthPerf = CalcCache::nowMonthPerf($userId, $this->_periodNum);
+                $monthPvPcs = $nowMonthPerf['PV_PCS'] ?? 0;
+                if( $monthPvPcs <= 0 ) continue;
+
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+
+                //个人业绩的奖金
+                $empLevelId = $userBaseInfo['EMP_LV'];
+                $empLevel = $this->_empLevelConfig[$empLevelId];
+                $rxPercent = $empLevel['RX_PERCENT'] ?? 0;
+                $empBonus = $monthPvPcs * $rxPercent / 100;
+                if( $empBonus > 0 ) {
+                    //给本人添加聘级比例
+                    CalcCache::ycMaxBonusPercent($userId, $this->_periodNum, $rxPercent);
+                    //记录奖金和奖金来源到缓存 并实现在缓存中奖金累加
+                    CalcCache::saveYCBonusList($userId, $this->_periodNum, $empBonus, ['fromUid'=>$userId, 'fromPvPcs'=>$monthPvPcs]);
+                    CalcCache::addHasYcBonusUsers($userId, $this->_periodNum);
+                    //达到最大的比例其它人就得不到了
+                    if( $rxPercent >= $maxLevelPercent ) continue;
+                }
+                unset($empLevelId, $empLevel, $rxPercent, $empBonus);
+
+                //获取所有的父级
+                $this->loopRelationParentDo($userId, function ($parent) use ($userId, $monthPvPcs, $maxLevelPercent) {
+                    $bonusUserId = $parent['PARENT_UID'];
+                    //计算级别之后更新过userInfo的缓存,缓存中级别发生了变化
+                    $bonusUserInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+                    $parentEmpLevelId = $bonusUserInfo['EMP_LV'];
+                    $parentEmpLevel = $this->_empLevelConfig[$parentEmpLevelId];
+                    $parentRxPercent = $parentEmpLevel['RX_PERCENT'] ?? 0;
+                    $cacheMaxPercent = CalcCache::ycMaxBonusPercent($userId, $this->_periodNum);
+                    $diffPercent = $parentRxPercent - $cacheMaxPercent;
+                    if( $diffPercent <= 0 ) return self::LOOP_CONTINUE;
+
+                    $empBonus = $monthPvPcs * $diffPercent / 100;
+                    if( $empBonus <= 0  ) return self::LOOP_CONTINUE;
+
+                    //给本人添加聘级比例
+                    CalcCache::ycMaxBonusPercent($userId, $this->_periodNum, $parentRxPercent);
+                    //记录奖金和奖金来源到缓存 并实现在缓存中奖金累加
+                    CalcCache::saveYCBonusList($bonusUserId, $this->_periodNum, $empBonus, ['fromUid'=>$userId, 'fromPvPcs'=>$monthPvPcs]);
+                    CalcCache::addHasYcBonusUsers($bonusUserId, $this->_periodNum);
+
+                    unset($cacheMaxPercent, $diffPercent, $parentEmpLevel, $bonusUserId, $bonusUserInfo, $parentEmpLevelId, $empBonus);
+
+                    //达到最大的比例就不在向上找了
+                    if( $parentRxPercent >= $maxLevelPercent ) return self::LOOP_FINISH;
+                    unset($parentRxPercent);
+
+                });
+
+                unset($nowMonthPerf, $monthPvPcs);
+            }
+
+            unset($allData, $maxLevelPercent);
+            $this->calcBonusYCStepOne($offset + $this->_limit);
+        }
+        unset($periodNum, $allData);
+        return true;
+    }
+
+
+    /**
+     * 结算荣衔奖第二步
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusYCStepTwo(int $offset = 0) {
+        // 月结,如果不是月结点,则直接退出
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+        echo sprintf("时间:[%s]荣衔奖第【2】步,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+
+        $allData = CalcCache::getHasYcBonusUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                $monthPerfData = CalcCache::nowMonthPerf($userId, $this->_periodNum);
+                $fxPvStatus = $monthPerfData['PV_PCS_FX'] >= $this->_sysConfig['monthPcsPvFxCondition']['VALUE'];
+                unset($monthPerfData);
+                if( !$fxPvStatus ) continue;
+                unset($fxPvStatus);
+
+                $ycBonusData = CalcCache::getYCBonusList($userId, $this->_periodNum);
+                if( !$ycBonusData ) continue;
+                $empBonus = $ycBonusData['empBonus'] ?? 0;
+                if( $empBonus <=0  ) continue;
+                //总金额限制
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                $empBonus = $this->bonusTotalLimit($empBonus, $userId, $userBaseInfo['REC_NUM'], $userBaseInfo['ZC_AMOUNT']);
+
+                if( $empBonus <= 0 ) continue;
+
+                //扣除相应的复消积分和管理费
+                $empBonus = Tool::formatPrice($empBonus);
+                $deductData = $this->deduct($userId, $empBonus);
+
+                CalcCache::bonus($userId, $this->_periodNum, 'BONUS_YC', $empBonus, $deductData);
+
+                $empLevelId = $userBaseInfo['EMP_LV'];
+                $insertBonusData[] = [
+                    'ID' => SnowFake::instance()->generateId(),
+                    'USER_ID' => $userId,
+                    'LAST_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $userBaseInfo['EMP_LV'],
+                    'LAST_STATUS' => $userBaseInfo['STATUS'],
+                    'FROM_USER_ID' => $userId,
+                    'LAST_FROM_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_FROM_EMP_LV' => $userBaseInfo['EMP_LV'],
+                    'LAST_FROM_STATUS' => $userBaseInfo['STATUS'],
+                    'DEEP' => 0,
+                    'AMOUNT' => $deductData['surplus'],
+                    'ORI_BONUS' => $empBonus,
+                    'RECONSUME_POINTS' => $deductData['reConsumePoints'],
+                    'MANAGE_TAX' => $deductData['manageTax'],
+                    'BONUS_TYPE' => CalcBonusYC::BONUS_TYPE_YC,
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                    'CREATED_AT' => Date::nowTime(),
+                    'LOGS' => json_encode([
+                        'empLevelId' => $empLevelId,
+                    ])
+//                    'FROM_DATA' => json_encode($ycBonusData['fromData']),
+                ];
+
+                unset($ycBonusData, $deductData);
+
+                //如果是总监以上拿平级2代
+                $empLevel = $this->_empLevelConfig[$empLevelId];
+                $userEmpLevelSort = $empLevel['SORT'] ?? EmployLevel::EMP_LEVEL_SORT['NO_LEVEL'];
+//                if( $userEmpLevelSort >= EmployLevel::EMP_LEVEL_SORT['JX_ZR_LEVEL'] ) {}
+                $newInsertBonusData = $this->calcParentYC($userId, $userBaseInfo, $userEmpLevelSort, $empBonus);
+                $insertBonusData = array_merge($insertBonusData, $newInsertBonusData);
+                unset($newInsertBonusData);
+
+                unset($userId, $userBaseInfo, $empLevel, $empLevelId, $userEmpLevelSort, $empBonus);
+
+            }
+
+            CalcBonusYC::batchInsert($insertBonusData);
+            unset($insertBonusData, $allData);
+            $this->calcBonusYCStepTwo($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * VIP奖
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusVIP(int $offset = 0)
+    {
+        // 月结,如果不是月结点,则直接退出
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+        echo sprintf("时间:[%s]VIP奖,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()), $offset);
+        //从缓存获取分页有本月业绩的会员
+        $allData = CalcCache::getHasMonthPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                //条件一:级别必须为VIP
+                $isVip = false;
+                if( $userBaseInfo['DEC_LV'] === DeclarationLevel::VIP_LEVEL_ID ) {
+                    $isVip= true;
+                }
+                if( $this->_sysConfig['vipBonusGoldDecLevel']['VALUE'] && $userBaseInfo['DEC_LV'] === DeclarationLevel::JIN_ZUAN_LEVEL_ID ) {
+                    $isVip = true;
+                }
+                if ( !$isVip ) {
+                    unset($userId, $userBaseInfo);
+                    continue;
+                }
+
+                //判断其它两个条件,满足则计算月团队奖,不满足则留下大区剩余业绩,清除其它区的剩余业绩
+                //获取本月的业绩
+                $monthPerfData = CalcCache::nowMonthPerf($userId, $this->_periodNum);
+                // 从缓存中获取会员的上期结余业绩信息
+                $pervSurplusPerf = CalcCache::surplusPerf($userId, $this->_periodNum);
+                // 本期 + 上期结余
+                $perfArr = [
+                    'VIP_SURPLUS_1L_ZC' => $monthPerfData['VIP_PV_1L_ZC'] + $pervSurplusPerf['VIP_SURPLUS_1L_ZC'],
+                    'VIP_SURPLUS_2L_ZC' => $monthPerfData['VIP_PV_2L_ZC'] + $pervSurplusPerf['VIP_SURPLUS_2L_ZC'],
+                    'VIP_SURPLUS_3L_ZC' => $monthPerfData['VIP_PV_3L_ZC'] + $pervSurplusPerf['VIP_SURPLUS_3L_ZC'],
+                    'VIP_SURPLUS_4L_ZC' => $monthPerfData['VIP_PV_4L_ZC'] + $pervSurplusPerf['VIP_SURPLUS_4L_ZC'],
+                    'VIP_SURPLUS_5L_ZC' => $monthPerfData['VIP_PV_5L_ZC'] + $pervSurplusPerf['VIP_SURPLUS_5L_ZC'],
+                ];
+                //条件二:本月复消金额达到300元
+                //fix-2021-03-26修改 条件二:本月个人复消PV300
+                $fxPvStatus = $monthPerfData['PV_PCS_FX'] >= $this->_sysConfig['monthPcsPvFxCondition']['VALUE'];
+                unset($monthPerfData, $pervSurplusPerf);
+                if( !$fxPvStatus ) {
+                    $this->dealWithMaxPerf($userId, $perfArr);
+                    unset($userBaseInfo, $userId, $perfArr, $fxPvStatus);
+                    continue;
+                }
+                //条件三:其它市场业绩
+                $isCanOtherDepartPerf = $this->_isCanVipBonusOtherAreaPerf($perfArr);
+                if ( !$isCanOtherDepartPerf ) {
+                    $this->dealWithMaxPerf($userId, $perfArr);
+                    unset($userBaseInfo, $userId, $perfArr, $isCanOtherDepartPerf);
+                    continue;
+                }
+
+                $oriPerfArr = [
+                    'perfArr' => $perfArr,
+                    'touchBonus' => 0,
+                ];
+                // 对碰
+                $touchBonusArr = $this->touchPerf($oriPerfArr, $perfArr, $this->_sysConfig['vipBonusPercent']['VALUE']/100);
+                unset($oriPerfArr, $perfArr);
+                $touchPerfArr = [];
+                foreach ($touchBonusArr['perfArr'] as $keyR => $perfR) {
+                    $touchPerfArr[$keyR] = $perfR;
+                }
+                // 对碰完成后把结余的业绩存入本期业绩缓存中
+//                CalcCache::nowPeriodPerf($userId, $this->_periodNum, $touchPerfArr);
+                //更新数据库
+                PerfMonth::updateAll($touchPerfArr, 'USER_ID=:USER_ID AND CALC_MONTH=:CALC_MONTH', [
+                    'USER_ID' => $userId,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                ]);
+                if ($touchBonusArr['touchBonus'] <= 0) continue;
+
+                $vipBonus = $touchBonusArr['touchBonus'];
+
+                //判断级别上限
+                $vipBonus = $this->declarationLevelCap($vipBonus, $userId, $userBaseInfo['DEC_LV']);
+                if( $vipBonus <= 0 ) continue;
+
+                $vipBonus = $this->bonusTotalLimit($vipBonus, $userId, $userBaseInfo['REC_NUM'], $userBaseInfo['ZC_AMOUNT']);
+
+                if( $vipBonus <= 0 ) continue;
+
+                //扣除相应的复消积分和管理费
+                $deductData = $this->deduct($userId, $vipBonus);
+
+                CalcCache::bonus($userId, $this->_periodNum, 'BONUS_VIP', $vipBonus, $deductData);
+
+                $insertBonusData[] = [
+                    'ID' => SnowFake::instance()->generateId(),
+                    'USER_ID' => $userId,
+                    'LAST_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $userBaseInfo['EMP_LV'],
+                    'LAST_STATUS' => $userBaseInfo['STATUS'],
+                    'FROM_USER_ID' => $userId,
+                    'LAST_FROM_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_FROM_EMP_LV' => $userBaseInfo['EMP_LV'],
+                    'LAST_FROM_STATUS' => $userBaseInfo['STATUS'],
+                    'AMOUNT' => $deductData['surplus'],
+                    'ORI_BONUS' => $vipBonus,
+                    'RECONSUME_POINTS' => $deductData['reConsumePoints'],
+                    'MANAGE_TAX' => $deductData['manageTax'],
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_YEAR' => $this->_calcYear,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                    'CREATED_AT' => Date::nowTime(),
+                    'LOGS' => json_encode($touchPerfArr)
+                ];
+
+                unset($userBaseInfo, $userId, $perfArr, $fxAmountStatus, $isCanOtherDepartPerf, $touchBonusArr, $touchPerfArr, $vipBonus, $deductData);
+            }
+
+            if( $insertBonusData ) CalcBonusVIP::batchInsert($insertBonusData);
+            unset($insertBonusData, $allData);
+            $this->calcBonusVIP($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 见习达标奖
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusStandard(int $offset = 0)
+    {
+        // 月结,如果不是月结点,则直接退出
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+        echo sprintf("时间:[%s]见习达标奖,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()), $offset);
+        //从缓存获取分页有本月业绩的会员
+        $allData = CalcCache::getHasStandardMonthPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $minSaleCondition = $this->_jxStandardMinSaleCondition();
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                //条件一:聘级必须为见习主任
+                if( $userBaseInfo['EMP_LV'] != EmployLevel::JX_ZR_LEVEL_ID ) {
+                    unset($userId, $userBaseInfo);
+                    continue;
+                }
+
+                $standardMonthPerf = CalcCache::nowStandardMonthPerf($userId, $this->_periodNum);
+                $totalAmount = $standardMonthPerf['AMOUNT_PCS'] + $standardMonthPerf['AMOUNT_PSS'];
+                if( $totalAmount < $minSaleCondition ) {
+                    unset($userId, $userBaseInfo, $standardMonthPerf, $totalAmount);
+                    continue;
+                }
+
+                $standardBonus = $this->_jxStandardAwardAmount($totalAmount);
+                if( $standardBonus <= 0 ) {
+                    unset($userId, $userBaseInfo, $standardMonthPerf, $totalAmount, $standardBonus);
+                    continue;
+                }
+
+                CalcCache::standardBonus($userId, $this->_periodNum, $standardBonus);
+
+                $insertBonusData[] = [
+                    'ID' => SnowFake::instance()->generateId(),
+                    'USER_ID' => $userId,
+                    'LAST_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $userBaseInfo['EMP_LV'],
+                    'LAST_STATUS' => $userBaseInfo['STATUS'],
+                    'FROM_USER_ID' => $userId,
+                    'LAST_FROM_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_FROM_EMP_LV' => $userBaseInfo['EMP_LV'],
+                    'LAST_FROM_STATUS' => $userBaseInfo['STATUS'],
+                    'AMOUNT' => $standardBonus,
+                    'ORI_BONUS' => $standardBonus,
+                    'RECONSUME_POINTS' => 0,
+                    'MANAGE_TAX' => 0,
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_YEAR' => $this->_calcYear,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                    'CREATED_AT' => Date::nowTime(),
+                    'LOGS' => json_encode($standardMonthPerf)
+                ];
+
+                unset($userId, $userBaseInfo, $standardMonthPerf, $totalAmount);
+            }
+
+            if( $insertBonusData ) CalcBonusStandard::batchInsert($insertBonusData);
+
+            unset($insertBonusData, $minSaleCondition, $insertBonusData, $allData);
+            $this->calcBonusStandard($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * === 运算相关的辅助功能开始 ===
+     */
+
+    /**
+     * 获取上2代的得奖人信息
+     * @param $userId
+     * @param $validDeep
+     * @param $shareBonusOne
+     * @return array
+     */
+    protected function gxLastTwoBonusData($userId, $validDeep, $shareBonusOne) {
+        $bonusUserList = CalcCache::getShareBonusOneRelation($userId, $this->_periodNum, $validDeep);
+        if( !$bonusUserList ) {
+            unset($bonusUserList);
+            return [];
+        }
+
+        $bonusUserData = [];
+        $bonusUserTotalPerf = 0;
+        $bonusUserRealList = [];
+        foreach ($bonusUserList as $bonusUserId) {
+            //查看该用户的个人业绩和团队新增业绩总和
+            $nowPeriodData = CalcCache::nowPeriodPerf($bonusUserId, $this->_periodNum);
+            //本期新增业绩
+            $bonusUserPerf = $nowPeriodData['PV_PCS'] + $nowPeriodData['PV_PSS'];
+            if ($bonusUserPerf <= 0) {
+                unset($bonusUserId, $nowPeriodData, $bonusUserPerf);
+                continue;
+            }
+
+            $bonusUserTotalPerf += $bonusUserPerf;
+            $bonusUserRealList[] = [
+                'bonusUserId' => $bonusUserId,
+                'bonusUserPerf' => $bonusUserPerf,
+            ];
+
+            unset($bonusUserId, $nowPeriodData, $bonusUserPerf);
+        }
+        unset($bonusUserList);
+
+        foreach ($bonusUserRealList as $everyData) {
+            $bonusUserData[] = [
+                'bonusUid' => $everyData['bonusUserId'],
+                'bonus' => $shareBonusOne * $everyData['bonusUserPerf'] / $bonusUserTotalPerf,
+                'validMinPv' => false
+
+            ];
+            unset($everyData);
+        }
+
+        unset($bonusUserRealList);
+
+        return $bonusUserData;
+    }
+
+    /**
+     * 获取达标奖的金额
+     * @param $amount
+     * @return int|mixed
+     */
+    private function _jxStandardAwardAmount($amount) {
+//        $jxStandardConfig = Json::decode($this->_sysConfig['jxStandard']['VALUE']);
+//        $awardAmount = 0;
+//        foreach ($jxStandardConfig as $everyData) {
+//            if( $amount >= $everyData['salesCondition'] ) {
+//                $awardAmount = $everyData['awardAmount'];
+//            }
+//            unset($everyData);
+//        }
+//
+//        return $awardAmount;
+        $realTimes = floor($amount/self::JX_STANDARD_BASE_AMOUNT);
+        $calcTimes = min($realTimes, self::JX_STANDARD_MAX_TIMES);
+        $awardAmount = $calcTimes * self::JX_STANDARD_BASE_AMOUNT * self::JX_STANDARD_BONUS_PERCENT / 100;
+
+        unset($amount, $realTimes, $calcTimes);
+        return $awardAmount;
+    }
+
+    /**
+     * 获取达标奖的最小条件
+     * @return int|mixed
+     */
+    private function _jxStandardMinSaleCondition() {
+        return self::JX_STANDARD_BASE_AMOUNT;
+    }
+
+    /**
+     * 处理只留最大业绩的逻辑
+     * @param $userId
+     * @param $perfArr
+     */
+    private function dealWithMaxPerf($userId, $perfArr) {
+        $maxPerf = max($perfArr);
+        $hitMaxPerf = false;
+        foreach ($perfArr as $key=>$everyPerf) {
+            if( !$hitMaxPerf && $everyPerf == $maxPerf ) {
+                $hitMaxPerf = true;
+            }else {
+                $perfArr[$key] = 0;
+            }
+
+            unset($key, $everyPerf);
+        }
+
+        //更新数据库
+        PerfMonth::updateAll($perfArr, 'USER_ID=:USER_ID AND CALC_MONTH=:CALC_MONTH', [
+            'USER_ID' => $userId,
+            'CALC_MONTH' => $this->_calcYearMonth
+        ]);
+
+        unset($userId, $maxPerf, $hitMaxPerf, $perfArr);
+    }
+
+    /**
+     * VIP奖其它市场的报单业绩是否能满足
+     * @param $perfArr
+     * @return bool
+     */
+    private function _isCanVipBonusOtherAreaPerf($perfArr) {
+        $maxAreaPerf = max($perfArr);
+        $totalAreaPerf = array_sum($perfArr);
+
+        $diffAreaPerf = strval($totalAreaPerf - $maxAreaPerf);
+        if( $diffAreaPerf >= $this->_sysConfig['vipBonusOtherDepartPvCondition']['VALUE'] ) {
+            unset($maxAreaPerf, $totalAreaPerf, $perfArr);
+            return true;
+        }else {
+            unset($maxAreaPerf, $totalAreaPerf, $perfArr);
+            return false;
+        }
+    }
+
+    /**
+     * 给父级计算的荣衔奖
+     * @param $userId
+     * @param $userBaseInfo
+     * @param $userEmpLevelSort
+     * @param $qyBonus
+     * @return array
+     */
+    public function calcParentYC($userId, $userBaseInfo, $userEmpLevelSort, $qyBonus) {
+        if( $userEmpLevelSort <= EmployLevel::EMP_LEVEL_SORT['NO_LEVEL'] ) return [];
+
+        $validDeep = 0;
+        $bonusDataList = [];
+        $this->loopRelationParentDo($userId, function ($parent) use ($userId, $userEmpLevelSort, &$validDeep, &$bonusDataList) {
+            $bonusUserId = $parent['PARENT_UID'];
+            $bonusUserInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+            $bonusUserEmpLevel = $this->_empLevelConfig[$bonusUserInfo['EMP_LV']];
+            $bonusUserEmpLevelSort = $bonusUserEmpLevel['SORT'] ?? EmployLevel::EMP_LEVEL_SORT['NO_LEVEL'];
+            unset($bonusUserInfo, $bonusUserEmpLevel);
+
+            //必须是有聘级的用户
+            if( $bonusUserEmpLevelSort <= EmployLevel::EMP_LEVEL_SORT['NO_LEVEL'] ) return self::LOOP_CONTINUE;
+
+            //超过级别就停止
+            if( $bonusUserEmpLevelSort > $userEmpLevelSort) return self::LOOP_FINISH;
+
+            //级别小于就跳过
+            if( $bonusUserEmpLevelSort < $userEmpLevelSort ) return self::LOOP_CONTINUE;
+
+            $validDeep++;
+            array_push($bonusDataList, [
+                'bonusUserId' => $bonusUserId,
+                'bonusType' => $validDeep,
+            ]);
+            if( $validDeep >= 2 ) return self::LOOP_FINISH;
+
+            unset($bonusUserId, $bonusUserEmpLevelSort);
+        });
+
+        $insertBonusData = [];
+        foreach ($bonusDataList as $bonusUserData) {
+            $bonusUserId = $bonusUserData['bonusUserId'];
+
+            $bonusMonthPerfData = CalcCache::nowMonthPerf($bonusUserId, $this->_periodNum);
+            $fxPvStatus = $bonusMonthPerfData['PV_PCS_FX'] >= $this->_sysConfig['monthPcsPvFxCondition']['VALUE'];
+            unset($bonusMonthPerfData);
+            if( !$fxPvStatus ) continue;
+            unset($fxPvStatus);
+
+            $bonusUserInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+            $bonusEmpLevelId = $bonusUserInfo['EMP_LV'];
+
+            $newEmpBonus = Tool::formatPrice($qyBonus * $this->_sysConfig['sameEmpLevelPercent']['VALUE'] / 100);
+
+
+            //总金额限制
+            $newEmpBonus = $this->bonusTotalLimit($newEmpBonus, $bonusUserId, $bonusUserInfo['REC_NUM'], $bonusUserInfo['ZC_AMOUNT']);
+            if( $newEmpBonus <= 0 ) continue;
+
+            //扣除相应的复消积分和管理费
+            $newEmpBonus = Tool::formatPrice($newEmpBonus);
+            $deductData = $this->deduct($bonusUserId, $newEmpBonus);
+
+            CalcCache::bonus($bonusUserId, $this->_periodNum, 'BONUS_YC_EXTRA', $newEmpBonus, $deductData);
+
+
+            // 把育成津贴追加到育成津贴结算表,以便奖金追溯育成津贴用
+            $insertBonusData[] = [
+                'ID' => SnowFake::instance()->generateId(),
+                'USER_ID' => $bonusUserId,
+                'LAST_DEC_LV' => $bonusUserInfo['DEC_LV'],
+                'LAST_EMP_LV' => $bonusUserInfo['EMP_LV'],
+                'LAST_STATUS' => $bonusUserInfo['STATUS'],
+                'FROM_USER_ID' => $userId,
+                'LAST_FROM_DEC_LV' => $userBaseInfo['DEC_LV'],
+                'LAST_FROM_EMP_LV' => $userBaseInfo['EMP_LV'],
+                'LAST_FROM_STATUS' => $userBaseInfo['STATUS'],
+                'DEEP' => 1,
+                'AMOUNT' => $deductData['surplus'],
+                'ORI_BONUS' => $newEmpBonus,
+                'RECONSUME_POINTS' => $deductData['reConsumePoints'],
+                'MANAGE_TAX' => $deductData['manageTax'],
+                'BONUS_TYPE' => $bonusUserData['bonusType'],
+                'PERIOD_NUM' => $this->_periodNum,
+                'CALC_MONTH' => $this->_calcYearMonth,
+                'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                'CREATED_AT' => Date::nowTime(),
+                'LOGS' => json_encode([
+                    'empLevelId' => $bonusEmpLevelId,
+                    'qyBonus' => $qyBonus,
+                    'sameEmpLevelPercent' => $this->_sysConfig['sameEmpLevelPercent']['VALUE'],
+                ]),
+            ];
+
+            unset($newEmpBonus, $bonusEmpLevelId, $bonusUserId, $bonusUserInfo, $deductData);
+        }
+
+        unset($userId, $userBaseInfo, $userEmpLevelSort, $qyBonus, $bonusDataList);
+
+
+        return $insertBonusData;
+    }
+
+    /**
+     * 对碰
+     * @param array $oriPerfArr
+     * @param array $perfArr
+     * @param $percent
+     * @param $loopTimes
+     * @return array
+     */
+    public function touchPerf(array $oriPerfArr, array $perfArr, $percent, $loopTimes=1) {
+        $resultArr = $oriPerfArr;
+        foreach ($perfArr as $keyT => $perfT) {
+            if ($perfT <= 0) {
+                unset($perfArr[$keyT]);
+            }
+        }
+        if (count($perfArr) >= 2 && $loopTimes < 6) {
+            arsort($perfArr, SORT_NUMERIC);
+            $onePerf = null;
+            $oneKey = null;
+            // $touchSurplusAmount = 0;
+            $touchAmount = 0;
+            // 前两个进行对碰
+            foreach ($perfArr as $key => $perf) {
+                if ($onePerf === null) {
+                    $oneKey = $key;
+                    $onePerf = $perf;
+                } else {
+                    $touchSurplusAmount = $perf - $onePerf;
+                    if ($touchSurplusAmount > 0) {
+                        unset($perfArr[$oneKey]);
+                        $resultArr['perfArr'][$oneKey] = 0;
+                        $perfArr[$key] = $touchSurplusAmount;
+                        $touchAmount = $onePerf;
+                    } elseif ($touchSurplusAmount < 0) {
+                        unset($perfArr[$key]);
+                        $resultArr['perfArr'][$key] = 0;
+                        $perfArr[$oneKey] = abs($touchSurplusAmount);
+                        $touchAmount = $perf;
+                    } else {
+                        unset($perfArr[$oneKey], $perfArr[$key]);
+                        $resultArr['perfArr'][$oneKey] = 0;
+                        $resultArr['perfArr'][$key] = 0;
+                        $touchAmount = $perf;
+                    }
+                    break;
+                }
+            }
+            /*if ($touchAmount > $nowDecLevelConfig['QY_TOUCH_CAP']) {
+                $touchAmount = $nowDecLevelConfig['QY_TOUCH_CAP'];
+            }*/
+            $touchBonus = Tool::formatPrice($touchAmount * $percent);
+//            if ($touchBonus > $nowDecLevelConfig['QY_TOUCH_CAP']) {
+//                $resultArr['touchBonus'] += $nowDecLevelConfig['QY_TOUCH_CAP'];
+//            } else {
+//                $resultArr['touchBonus'] += $touchBonus;
+//            }
+            $resultArr['touchBonus'] += $touchBonus;
+
+            foreach ($perfArr as $keyR => $perfR) {
+                $resultArr['perfArr'][$keyR] = $perfR;
+            }
+            return $this->touchPerf($resultArr, $perfArr, $percent, $loopTimes+1);
+
+        }
+        return $resultArr;
+    }
+
+    /**
+     * 对碰
+     * @param array $oriPerfArr
+     * @param array $perfArr
+     * @param $decLevel
+     * @param $decLevelConfig
+     * @param $loopTimes
+     * @return array
+     */
+    public function touchPerfBefore(array $oriPerfArr, array $perfArr, $decLevel, $decLevelConfig, $loopTimes=1) {
+        $resultArr = $oriPerfArr;
+        foreach ($perfArr as $keyT => $perfT) {
+            if ($perfT <= 0) {
+                unset($perfArr[$keyT]);
+            }
+        }
+        if (count($perfArr) >= 2 && $loopTimes < 6) {
+            $nowDecLevelConfig = $decLevelConfig[$decLevel];
+            arsort($perfArr, SORT_NUMERIC);
+            $onePerf = null;
+            $oneKey = null;
+            // $touchSurplusAmount = 0;
+            $touchAmount = 0;
+            // 前两个进行对碰
+            foreach ($perfArr as $key => $perf) {
+                if ($onePerf === null) {
+                    $oneKey = $key;
+                    $onePerf = $perf;
+                } else {
+                    $touchSurplusAmount = $perf - $onePerf;
+                    if ($touchSurplusAmount > 0) {
+                        unset($perfArr[$oneKey]);
+                        $resultArr['perfArr'][$oneKey] = 0;
+                        $perfArr[$key] = $touchSurplusAmount;
+                        $touchAmount = $onePerf;
+                    } elseif ($touchSurplusAmount < 0) {
+                        unset($perfArr[$key]);
+                        $resultArr['perfArr'][$key] = 0;
+                        $perfArr[$oneKey] = abs($touchSurplusAmount);
+                        $touchAmount = $perf;
+                    } else {
+                        unset($perfArr[$oneKey], $perfArr[$key]);
+                        $resultArr['perfArr'][$oneKey] = 0;
+                        $resultArr['perfArr'][$key] = 0;
+                        $touchAmount = $perf;
+                    }
+                    break;
+                }
+            }
+            /*if ($touchAmount > $nowDecLevelConfig['QY_TOUCH_CAP']) {
+                $touchAmount = $nowDecLevelConfig['QY_TOUCH_CAP'];
+            }*/
+            $touchBonus = Tool::formatPrice($touchAmount * 2 / 100);
+//            if ($touchBonus > $nowDecLevelConfig['QY_TOUCH_CAP']) {
+//                $resultArr['touchBonus'] += $nowDecLevelConfig['QY_TOUCH_CAP'];
+//            } else {
+//                $resultArr['touchBonus'] += $touchBonus;
+//            }
+            $resultArr['touchBonus'] += $touchBonus;
+
+            foreach ($perfArr as $keyR => $perfR) {
+                $resultArr['perfArr'][$keyR] = $perfR;
+            }
+            return $this->touchPerfBefore($resultArr, $perfArr, $decLevel, $decLevelConfig, $loopTimes+1);
+
+        }
+        return $resultArr;
+    }
+
+    /**
+     * 循环父级并执行回调函数
+     * @param $userId
+     * @param callable $callbackFunc
+     * @param int $offset
+     * @return bool
+     */
+    public function loopNetworkParentDo($userId, callable $callbackFunc, int $offset = 0) {
+        $allParents = Cache::getAllNetworkParents($userId);
+        $allData = array_slice($allParents, $offset, $this->_limit);
+        unset($allParents);
+        if ($allData) {
+            foreach ($allData as $data) {
+                $funcResult = $callbackFunc($data);
+                if ($funcResult === self::LOOP_FINISH) {
+                    return true;
+                } elseif ($funcResult === self::LOOP_CONTINUE) {
+                    continue;
+                }
+                unset($data, $funcResult);
+            }
+            unset($allData);
+            return $this->loopNetworkParentDo($userId, $callbackFunc, $offset + $this->_limit);
+        }
+        return true;
+    }
+
+    /**
+     * 循环推荐网络的父级
+     * @param $userId
+     * @param callable $callbackFunc
+     * @param int $offset
+     * @return bool
+     */
+    public function loopRelationParentDo($userId, callable $callbackFunc, int $offset = 0) {
+        $allParents = Cache::getAllRelationParents($userId);
+        $allData = array_slice($allParents, $offset, $this->_limit);
+        unset($allParents);
+        if ($allData) {
+            foreach ($allData as $data) {
+                $funcResult = $callbackFunc($data);
+                if ($funcResult === self::LOOP_FINISH) {
+                    return true;
+                } elseif ($funcResult === self::LOOP_CONTINUE) {
+                    continue;
+                }
+                unset($data, $funcResult);
+            }
+
+            unset($allData);
+            return $this->loopRelationParentDo($userId, $callbackFunc, $offset + $this->_limit);
+        }
+        return true;
+    }
+
+    /**
+     * 共享奖,判断当期此用户,是否有原报单团队奖金
+     */
+    public function checkHasOriBonusQyBd($userId) {
+        //判断$parent是否有首单团队奖
+        $bonus = CalcCache::bonus($userId, $this->_periodNum);
+        if( isset($bonus['ORI_BONUS_QY_BD']) && $bonus['ORI_BONUS_QY_BD'] > 0 ) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 判断除大市场外 其它市场的报单业绩
+     * @param $userId
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function checkSmallMarketPerf($userId) {
+        // 从缓存中获取会员的上期结余业绩信息
+        $pervSurplusPerf = CalcCache::surplusPerf($userId, $this->_periodNum);
+        //判断出大市场
+        $areaNums = [1, 2, 3];
+        $maxArea = 0;
+        $maxSurplusPerf = 0;
+        foreach ($areaNums as $area) {
+            $areaKey = sprintf('SURPLUS_%dL', $area);
+            if ( isset($pervSurplusPerf[$areaKey]) && $pervSurplusPerf[$areaKey] > $maxSurplusPerf ) {
+                $maxArea = $area;
+                $maxSurplusPerf = $pervSurplusPerf[$areaKey];
+            }
+
+            unset($area, $areaKey);
+        }
+        unset($pervSurplusPerf, $maxSurplusPerf);
+
+
+        $otherAreaZcPv = 0;
+        $nowPeriodPerf = CalcCache::nowPeriodPerf($userId, $this->_periodNum);
+        foreach ($areaNums as $area) {
+            if( $maxArea > 0 && $area === $maxArea ) continue;
+            $areaKey = sprintf('PV_%dL_ZC', $area);
+            if ( isset($nowPeriodPerf[$areaKey]) && $nowPeriodPerf[$areaKey] > 0 ) {
+                $otherAreaZcPv += $nowPeriodPerf[$areaKey];
+            }
+
+            unset($area, $areaKey);
+        }
+        unset($nowPeriodPerf, $areaNums, $maxArea);
+
+        return $otherAreaZcPv >= self::MIN_BD_PV;
+    }
+
+    /**
+     * 获取有最小报单PV的用效父级
+     * @param $userId
+     * @return string
+     */
+    public function getMinBdPvRelationParent($userId) {
+        $validParentId = '';
+        $this->loopRelationParentDo($userId, function ($parent) use(&$validParentId) {
+            if ( $this->checkSmallMarketPerf($parent['PARENT_UID']) ) {
+                $validParentId = $parent['PARENT_UID'];
+                unset($parent);
+                return self::LOOP_FINISH;
+            }
+            unset($parent);
+        });
+
+        return$validParentId;
+    }
+
+    /**
+     * 获取有最小报单PV的用效父级 20220407修改成是否此期此用户有原报单团队奖金
+     * @param $userId
+     * @return string
+     */
+    public function getMinBdPvNetworkParent($userId, $openFindLimit = 1, $findLimitTimes = 5) {
+        $validParentId = '';
+        $validDeep = 1;
+        $this->loopNetworkParentDo($userId, function ($parent) use(&$validParentId,&$validDeep,$openFindLimit,$findLimitTimes) {
+            if ( $this->checkHasOriBonusQyBd($parent['PARENT_UID']) ) {
+                $validParentId = $parent['PARENT_UID'];
+                unset($parent);
+                return self::LOOP_FINISH;
+            }
+            // 只找5层
+            if ( $openFindLimit && $validDeep >= $findLimitTimes ) {
+                unset($parent);
+                return self::LOOP_FINISH;
+            }
+
+            $validDeep += 1;
+            unset($parent);
+        });
+
+        return$validParentId;
+    }
+    // public function getMinBdPvNetworkParent($userId, $openFindLimit = 1, $findLimitTimes = 5) {
+    //     $validParentId = '';
+    //     $validDeep = 1;
+    //     $this->loopNetworkParentDo($userId, function ($parent) use(&$validParentId,&$validDeep,$openFindLimit,$findLimitTimes) {
+    //         if ( $this->checkSmallMarketPerf($parent['PARENT_UID']) ) {
+    //             $validParentId = $parent['PARENT_UID'];
+    //             unset($parent);
+    //             return self::LOOP_FINISH;
+    //         }
+    //         // 只找5层
+    //         if ( $openFindLimit && $validDeep >= $findLimitTimes ) {
+    //             unset($parent);
+    //             return self::LOOP_FINISH;
+    //         }
+
+    //         $validDeep += 1;
+    //         unset($parent);
+    //     });
+
+    //     return$validParentId;
+    // }
+
+    /**
+     * 按级别的收入上限
+     * 新的需求调整: 改成不按月进行限制,并且去掉vip奖
+     * @param $bonus
+     * @param $userId
+     * @param $declarationLevel
+     * @return float
+     */
+    public function declarationLevelCap($bonus, $userId, $declarationLevel) {
+        $decLevelConfig = $this->_decLevelConfig;
+        $nowDecLevelConfig = $decLevelConfig[$declarationLevel];
+        unset($decLevelConfig);
+        $maxGetBonus = $nowDecLevelConfig['INCOME_CAP'];
+        if( $bonus <= $maxGetBonus) {
+            return $bonus;
+        }else {
+            return $maxGetBonus;
+        }
+    }
+
+    // public function declarationLevelCap($bonus, $userId, $declarationLevel) {
+    //     $decLevelConfig = $this->_decLevelConfig;
+    //     $nowDecLevelConfig = $decLevelConfig[$declarationLevel];
+    //     unset($decLevelConfig);
+
+    //     //本月往期奖金 这里执行速度可能慢一些,但需求如此没有办法
+    //     $lastData = CalcCache::lastPeriodMonthCalcBonus($userId, $this->_periodNum, $this->_calcMonth);
+    //     $lastTotal = $lastData['ORI_BONUS_QY_SUM'] + $lastData['ORI_BONUS_VIP_SUM'];
+
+    //     // 从缓存中获取用户的奖金
+    //     $bonusData = CalcCache::bonus($userId, $this->_periodNum);
+    //     $thisTotal = $bonusData['ORI_BONUS_QY'] + $bonusData['ORI_BONUS_VIP'];
+    //     $maxGetBonus = $nowDecLevelConfig['INCOME_CAP'] * $this->_calcMonthPeriodNumCount - $lastTotal - $thisTotal;
+    //     unset($lastData, $lastTotal, $bonusData, $thisTotal);
+    //     if( $maxGetBonus <= 0 ) return 0.00;
+
+    //     if( $bonus <= $maxGetBonus) {
+    //         return $bonus;
+    //     }else {
+    //         return $maxGetBonus;
+    //     }
+    // }
+
+    /**
+     * 总奖金限制
+     * @param $bonus
+     * @param $userId
+     * @param $recNum
+     * @param $decAmount
+     * @return float
+     */
+    public function bonusTotalLimit($bonus, $userId, $recNum, $decAmount) {
+        //@todo 临时不做总奖金限制
+        return $bonus;
+    }
+
+    /**
+     * 扣除复消积分和管理费
+     * @param $userId
+     * @param $bonus
+     * @return array
+     * @throws \yii\db\Exception
+     */
+    public function deductNoReconsumePoints($userId, $bonus): array
+    {
+        $manageTax = $bonus * $this->_sysConfig['manageTaxPercent']['VALUE'] / 100;
+        $surplus = $bonus - $manageTax;
+        return [
+            'reConsumePoints' => 0.00,
+            'manageTax' => Tool::formatPrice($manageTax),
+            'surplus' => Tool::formatPrice($surplus),
+        ];
+    }
+
+
+    /**
+     * 扣除复消积分和管理费
+     * @param $userId
+     * @param $bonus
+     * @return array
+     * @throws \yii\db\Exception
+     */
+    public function deduct($userId, $bonus) {
+        //判断是否达到了本月扣除复消的上限
+        $cacheData = CalcCache::monthLastPeriodReconsumePoints($userId, $this->_periodNum, $this->_calcYearMonth);
+        $bonusCache = CalcCache::bonus($userId, $this->_periodNum);
+
+        $reConsumePointsTotal = $bonusCache['RECONSUME_POINTS'] + $cacheData['RECONSUME_POINTS_SUM'];
+        $reConsumePointsCap = $this->_sysConfig['reConsumePointsMonthCap']['VALUE'];
+        unset($cacheData, $bonusCache);
+
+        $reConsumePoints = 0;
+        if( $reConsumePointsTotal < $reConsumePointsCap ) {
+            $reConsumePoints = $bonus * $this->_sysConfig['reConsumePointsPercent']['VALUE'] / 100;
+
+            $reConsumePoints = min($reConsumePoints, $reConsumePointsCap-$reConsumePointsTotal);
+        }
+        unset($reConsumePointsTotal, $reConsumePointsCap);
+
+        $manageTax = $bonus * $this->_sysConfig['manageTaxPercent']['VALUE'] / 100;
+        $surplus = $bonus - $reConsumePoints - $manageTax;
+        return [
+            'reConsumePoints' => Tool::formatPrice($reConsumePoints),//复效积分
+            'manageTax' => Tool::formatPrice($manageTax),//管理费
+            'surplus' => Tool::formatPrice($surplus),//真实奖金
+        ];
+    }
+
+    /**
+     * 更新百分比并发送
+     * @param $percent
+     */
+    private function _updatePercent($percent) {
+        // 把数据写入数据库中
+        Period::updateAll(['CALC_PERCENT' => $percent], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        \Yii::$app->swooleAsyncTimer->pushAsyncPercentToAdmin($percent, ['MODEL' => 'PERIOD', 'ID' => $this->_periodId, 'FIELD' => 'CALC_PERCENT']);
+    }
+
+    /**
+     * 把往期有月奖金的会员加到本期有奖会员缓存列表中
+     * @param int $offset
+     * @return bool
+     */
+    public function loopMonthBonusUserFromDbToCache($offset = 0) {
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+
+        $allData = CalcMonthBonusUser::findUseDbCalc()->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])->offset($offset)->limit($this->_limit)->asArray()->all();
+        if ( $allData ) {
+            foreach ($allData as $everyData) {
+                CalcCache::addHasBonusUsers($everyData['USER_ID'], $this->_periodNum);
+
+                unset($everyData);
+            }
+
+            unset($allData);
+            $this->loopMonthBonusUserFromDbToCache($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 把缓存中的月奖用户信息存到数据库
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function loopMonthBonusUserToDb($offset = 0) {
+        echo sprintf("时间:[%s]月奖会员入库,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        $allData = CalcCache::getHasMonthBonusUsers($this->_periodNum, $offset, $this->_limit);
+        if($allData) {
+            $insertDataBonusUser = [];
+            foreach($allData as $userId){
+                $insertDataBonusUser[] = [
+                    'ID' => SnowFake::instance()->generateId(),
+                    'USER_ID' => $userId,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CREATED_AT' => Date::nowTime()
+                ];
+
+                unset($userId);
+            }
+
+            if( $insertDataBonusUser ) CalcMonthBonusUser::batchInsert($insertDataBonusUser);
+
+            unset($insertDataBonus, $allData);
+            return $this->loopMonthBonusUserToDb($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    //奖金入库
+    /**
+     * 循环奖服务奖金会员,并入库
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function loopBonusUsers($offset = 0) {
+        echo sprintf("时间:[%s]缓存奖金数据入库,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        // 从缓存列表里面从底层往上倒序获取会员
+
+        // 这里有问题,因为蓝星奖,是存储过程计算的,并没有加入缓存,这里需要修改.
+        // 给用户发送兑换积分,也需要进行调整查看。
+        // CalcCache::addHasBonusUsers($everyData['USER_ID'], $this->_periodNum);
+        $allData = CalcCache::getHasBonusUsers($this->_periodNum, $offset, $this->_limit);
+        if($allData){
+            $insertDataBonus = [];
+            foreach($allData as $userId){
+                $tempBonusData = $this->bonusData($userId);
+                if(!empty($tempBonusData['result'])){
+                    $insertDataBonus[] = $tempBonusData['result'];
+                }
+
+                unset($userId, $tempBonusData);
+            }
+            CalcBonus::batchInsert($insertDataBonus);
+
+            unset($insertDataBonus, $allData);
+            return $this->loopBonusUsers($offset + $this->_limit);
+        }
+        return true;
+    }
+
+    // 奖金入库完成,将各个奖金计算流水会员聘级,更新成蓝星奖当时计算的聘级
+    public function loopCalcBlueEmpLv($offset = 0) {
+        if( !$this->_isCalcMonth ) {
+            // 不是结算月,则不需要进行聘级调整
+            return false;
+        }
+        // 从缓存获取分页有收入的会员信息
+        $allData = CalcBonusBS::findUseDbCalc()
+            ->where('PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum])
+            ->groupBy('USER_ID')
+            ->offset($offset)
+            ->limit($this->_limit)
+            ->asArray()
+            ->all();
+        if ($allData) {
+            foreach ($allData as $data) {
+                $nowBsEmpLv = $data['LEVEL_ID']; // 当前蓝星奖计算(即管理奖) 的等级
+                // 修改AR_CALC_BONUS的LAST_EMP_LV聘级,修改奖金流水的聘级AR_FLOW_BONUS表的LAST_EMP_LV
+                // 期结算结果
+                CalcBonus::updateAll(['LAST_EMP_LV' => $nowBsEmpLv], 'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM', 
+                    [
+                        ':USER_ID' => $data['USER_ID'],
+                        ':PERIOD_NUM' => $this->_periodNum
+                    ]
+                );
+                // 奖金流水
+                FlowBonus::updateAll(['LAST_EMP_LV' => $nowBsEmpLv], 'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM', 
+                    [
+                        ':USER_ID' => $data['USER_ID'],
+                        ':PERIOD_NUM' => $this->_periodNum
+                    ]
+                );
+//                // 共享奖流水
+//                CalcBonusGX::updateAll(['LAST_EMP_LV' => $nowBsEmpLv], 'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM',
+//                    [
+//                        ':USER_ID' => $data['USER_ID'],
+//                        ':PERIOD_NUM' => $this->_periodNum
+//                    ]
+//                );
+                // 推广奖流水
+                CalcBonusTG::updateAll(['LAST_EMP_LV' => $nowBsEmpLv], 'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM',
+                    [
+                        ':USER_ID' => $data['USER_ID'],
+                        ':PERIOD_NUM' => $this->_periodNum
+                    ]
+                );
+                // 团队奖流水
+                CalcBonusQY::updateAll(['LAST_EMP_LV' => $nowBsEmpLv], 'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM', 
+                    [
+                        ':USER_ID' => $data['USER_ID'],
+                        ':PERIOD_NUM' => $this->_periodNum
+                    ]
+                );
+//                // 服务奖流水
+//                CalcBonusBD::updateAll(['LAST_EMP_LV' => $nowBsEmpLv], 'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM',
+//                    [
+//                        ':USER_ID' => $data['USER_ID'],
+//                        ':PERIOD_NUM' => $this->_periodNum
+//                    ]
+//                );
+//                // 更新form的聘级
+//                CalcBonusGX::updateAll(['LAST_FROM_EMP_LV' => $nowBsEmpLv], 'FROM_USER_ID=:FROM_USER_ID AND PERIOD_NUM=:PERIOD_NUM',
+//                    [
+//                        ':FROM_USER_ID' => $data['USER_ID'],
+//                        ':PERIOD_NUM' => $this->_periodNum
+//                    ]
+//                );
+                CalcBonusTG::updateAll(['LAST_FROM_EMP_LV' => $nowBsEmpLv], 'FROM_USER_ID=:FROM_USER_ID AND PERIOD_NUM=:PERIOD_NUM',
+                    [
+                        ':FROM_USER_ID' => $data['USER_ID'],
+                        ':PERIOD_NUM' => $this->_periodNum
+                    ]
+                );
+//                CalcBonusBD::updateAll(['LAST_FROM_EMP_LV' => $nowBsEmpLv], 'FROM_USER_ID=:FROM_USER_ID AND PERIOD_NUM=:PERIOD_NUM',
+//                    [
+//                        ':FROM_USER_ID' => $data['USER_ID'],
+//                        ':PERIOD_NUM' => $this->_periodNum
+//                    ]
+//                );
+            }
+            unset($allData);
+            return $this->loopCalcBlueEmpLv($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 奖金
+     * @param $userId
+     * @return array
+     * @throws \yii\db\Exception
+     */
+    public function bonusData($userId) {
+        // 从缓存中获取用户的奖金
+        $bonus = CalcCache::bonus($userId, $this->_periodNum);
+        $standardBonus = CalcCache::standardBonus($userId, $this->_periodNum);
+        $baseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+        $perfData = CalcCache::nowPeriodPerf($userId, $this->_periodNum);
+        $tourismBonus = CalcCache::tourismBonus($userId, $this->_periodNum);
+        $garageBonus = CalcCache::garageBonus($userId, $this->_periodNum);
+        $villaBonus = CalcCache::villaBonus($userId, $this->_periodNum);
+        $empLv = $baseInfo['EMP_LV'];
+//        if($this->_isCalcMonth){
+//            $empLv = $this->nowMonthPerfData($userId)['LAST_EMP_LV'];
+//        }
+        $pervSurplusPerf = CalcCache::surplusPerf($userId, $this->_periodNum);
+        // 星级
+        $starCrownLv = CalcCache::getUserStarCrown($userId, $this->_periodNum);
+
+        // //没有共享和管理奖 以前的管理奖和共享奖逻辑
+        // $bonusReal = $bonus['BONUS_BD'] + $bonus['BONUS_TG'] + $bonus['BONUS_XF'] + $bonus['BONUS_YJ'] + $bonus['BONUS_QY'] + $bonus['BONUS_YC'] + $bonus['BONUS_YC_EXTRA'] + $bonus['BONUS_VIP'] + $standardBonus;
+        // $realBonusGx = 0;
+        // $realBonusGl = 0;
+        // if( $this->_isCalcMonth ) {
+        //     //查看是否复消300
+        //     $monthPerfData = CalcCache::nowMonthPerf($userId, $this->_periodNum);
+        //     $fxPvStatus = $monthPerfData['PV_PCS_FX'] >= $this->_sysConfig['monthPcsPvFxCondition']['VALUE'];
+        //     if ( $fxPvStatus ) {//加上本期和往期的共享和管理奖
+        //         $monthSumData = CalcBonus::findUseSlaves()->select('SUM(BONUS_GX) AS BONUS_GX_SUM, SUM(BONUS_GL) AS BONUS_GL_SUM')->where('USER_ID=:USER_ID AND CALC_MONTH=:CALC_MONTH', ['USER_ID'=>$userId, 'CALC_MONTH'=>$this->_calcYearMonth])->asArray()->one();
+        //         $bonusGxSum = $monthSumData['BONUS_GX_SUM'] ?? 0;
+        //         $bonusGlSum = $monthSumData['BONUS_GL_SUM'] ?? 0;
+        //         $realBonusGx += $bonusGxSum + $bonus['BONUS_GX'];
+        //         $realBonusGl += $bonusGlSum + $bonus['BONUS_GL'];
+        //         $bonusReal += $realBonusGx + $realBonusGl;
+        //         unset($monthSumData, $bonusGxSum, $bonusGlSum);
+        //     }
+        // }
+
+        //没有共享和管理奖
+        $bonusReal = $bonus['BONUS_BD'] + $bonus['BONUS_TG'] + $bonus['BONUS_XF'] + $bonus['BONUS_YJ'] + $bonus['BONUS_QY'] + $bonus['BONUS_YC'] + $bonus['BONUS_YC_EXTRA'] + $bonus['BONUS_VIP'] + $standardBonus + $bonus['BONUS_BS_MNT'] + $bonus['BONUS_BS_ABBR'] + $bonus['BONUS_QUARTER'];
+        $realBonusGx = 0;
+        $realBonusGl = 0;
+        $realBonusBs = 0; // 蓝星管理奖. BlueStar
+        $blueStartOriBonus = 0;
+        $blueStartManageTax = 0;
+        $exchangePoints = 0; // 蓝星奖管理奖. 产生的兑换积分
+
+        $realBonusBsMnt = 0; // 蓝星管理奖——实发奖金
+        $blueStartOriBonusMnt = 0;  // 蓝星管理奖——原奖金
+        $blueStartManageTaxMnt = 0;   // 蓝星管理奖——管理费
+
+        $realBonusBsAbbr = 0; // 蓝星业绩奖——实发奖金
+        $blueStartOriBonusAbbr = 0; // 蓝星业绩奖——原奖金
+        $blueStartManageTaxAbbr = 0;   // 蓝星业绩奖——管理费
+        if( $this->_isCalcMonth ) {
+            // 个人月消费PV大于配置值,才会计算发放蓝星奖
+            $fxPvStatus = $this->_isMonthPerfLimit($userId);
+            // BONUS_REAL 字段是发到用户的真实奖金
+            if ( $fxPvStatus ) {
+                // 管理奖改成了蓝星奖,但是对于用户来说依旧叫管理奖.字段改成bs.
+                // 因为管理奖(即蓝星奖,是用存储过程计算的,则此处直接取AR_CALC_BONUS_BS表中计算的结果数据使用,
+                // 并将管理奖结果同步到当前结算月的CalcBonus中)
+                // 由于单独奖金流水记录每次都插入,所以倒叙找最新的数据.
+                $userBS = CalcBonusBS::find()
+                ->where(
+                    'PERIOD_NUM=:PERIOD_NUM AND USER_ID=:USER_ID',
+                    [
+                        ':PERIOD_NUM' => $this->_periodNum,
+                        ':USER_ID' => $userId
+                    ]
+                )
+                ->select('AMOUNT,ORI_BONUS,MANAGE_TAX,LEVEL_ID,PRODUCT_POINT,AMOUNT_MNT,ORI_BONUS_MNT,MANAGE_TAX_MNT,AMOUNT_ABBR,ORI_BONUS_ABBR,MANAGE_TAX_ABBR')
+                ->limit(1)
+                ->orderBy('CREATED_AT DESC')
+                ->asArray()
+                ->all();
+
+                $userBS = is_array($userBS) ? reset($userBS) : [];
+                $blueStartAmount = isset($userBS['AMOUNT']) && !empty($userBS['AMOUNT']) ? $userBS['AMOUNT'] : 0; // 奖金
+                $blueStartOriBonus = isset($userBS['ORI_BONUS']) && !empty($userBS['ORI_BONUS']) ? $userBS['ORI_BONUS'] : 0; // 原奖金
+                $blueStartManageTax = isset($userBS['MANAGE_TAX']) && !empty($userBS['MANAGE_TAX']) ? $userBS['MANAGE_TAX'] : 0; // 管理费
+
+                $realBonusBsMnt = $userBS['AMOUNT_MNT'] ?? 0; // 蓝星管理奖. 实发奖金
+                $blueStartOriBonusMnt = $userBS['ORI_BONUS_MNT'] ?? 0; // 蓝星管理奖. 原奖金
+                $blueStartManageTaxMnt = $userBS['MANAGE_TAX_MNT'] ?? 0; // 蓝星管理奖. 管理费
+
+                $realBonusBsAbbr = $userBS['AMOUNT_ABBR'] ?? 0; // 蓝星业绩奖. 奖金
+                $blueStartOriBonusAbbr = $userBS['ORI_BONUS_ABBR'] ?? 0; // 蓝星业绩奖. 原奖金
+                $blueStartManageTaxAbbr = $userBS['MANAGE_TAX_ABBR'] ?? 0; // 蓝星业绩奖. 管理费
+
+                $blueStartManageTax += $blueStartManageTaxMnt + $blueStartManageTaxAbbr; // 管理费
+
+                $monthSumData = CalcBonus::findUseSlaves()
+                ->select('SUM(BONUS_GX) AS BONUS_GX_SUM, SUM(BONUS_GL) AS BONUS_GL_SUM')
+                ->where('USER_ID=:USER_ID AND CALC_MONTH=:CALC_MONTH',
+                    [
+                        'USER_ID' => $userId,
+                        'CALC_MONTH' => $this->_calcYearMonth
+                    ]
+                )
+                ->asArray()
+                ->one();
+                $realBonusBs = $blueStartAmount; // 蓝星奖直接取数据库中算好的值PRODUCT_POINT
+                $exchangePoints = isset($userBS['PRODUCT_POINT']) && !empty($userBS['PRODUCT_POINT']) ? $userBS['PRODUCT_POINT'] : 0; // 兑换积分
+
+                // 蓝星奖总奖金:管理奖+业绩奖
+//                $blueStartOriBonus = $blueStartOriBonusMnt + $blueStartOriBonusAbbr;
+//                $bonus['BONUS_TOTAL'] = $bonus['BONUS_TOTAL'] + $blueStartOriBonus; // 管理奖在存储过程计算,这里单独加上管理奖(即蓝星奖)
+                unset($monthSumData, $bonusGxSum, $bonusGlSum);
+            }
+        }
+
+        if( $this->_isCalcMonth ) { //季度奖
+            if(in_array($this->_calcMonth, [3,6,9,12])){ // 季度奖
+            }
+        }
+
+        $result = [
+            'USER_ID' => $userId,
+            'LAST_USER_NAME' => $baseInfo['USER_NAME'],
+            'LAST_REAL_NAME' => $baseInfo['REAL_NAME'],
+            'LAST_DEC_LV' => $baseInfo['DEC_LV'],
+            'LAST_EMP_LV' => $empLv,
+            'LAST_CROWN_LV' => $starCrownLv ?? StarCrownLevel::getDefaultLevelId(),
+            'LAST_STATUS' => $baseInfo['STATUS'],
+            'LAST_MOBILE' => $baseInfo['MOBILE'],
+            'LAST_PERIOD_AT' => $baseInfo['PERIOD_NUM'],
+            'LAST_CREATED_AT' => $baseInfo['CREATED_AT'],
+            'LAST_SUB_COM_ID' => $baseInfo['SUB_COM_ID'],
+            'LAST_PROVINCE' => $baseInfo['PROVINCE'],
+            'LAST_CITY' => $baseInfo['CITY'],
+            'LAST_COUNTY' => $baseInfo['COUNTY'],
+            'LAST_SYSTEM_ID' => $baseInfo['SYSTEM_ID'] ? $baseInfo['SYSTEM_ID'] : '',
+            'LAST_IS_DIRECT_SELLER' => $baseInfo['IS_DIRECT_SELLER'],
+            'LAST_REC_USER_NAME' => $baseInfo['REC_USER_NAME'],
+            'LAST_REC_REAL_NAME' => $baseInfo['REC_REAL_NAME'],
+            'LAST_CON_USER_NAME' => $baseInfo['CON_USER_NAME'],
+            'LAST_CON_REAL_NAME' => $baseInfo['CON_REAL_NAME'],
+            'EXCHANGE_POINTS' => $exchangePoints, // 兑换积分
+//            'LAST_LOCATION' => $baseInfo['LOCATION'] ? $baseInfo['LOCATION'] : 1,
+            //@todo
+            'LAST_LOCATION' => 1,
+            'BONUS_BD' => $bonus['BONUS_BD'],
+            'BONUS_TG' => $bonus['BONUS_TG'],
+            'BONUS_XF' => $bonus['BONUS_XF'],
+            'BONUS_YJ' => $bonus['BONUS_YJ'],
+            'BONUS_GX' => $bonus['BONUS_GX'],
+            'BONUS_GL' => $bonus['BONUS_GL'],
+            'BONUS_QY' => $bonus['BONUS_QY'],
+            'BONUS_YC' => $bonus['BONUS_YC'] + $bonus['BONUS_YC_EXTRA'],
+            'BONUS_VIP' => $bonus['BONUS_VIP'],
+            'RECONSUME_POINTS' => $bonus['RECONSUME_POINTS'],
+            'MANAGE_TAX' => $blueStartManageTax,    // 管理费
+            'BONUS_INCOME'=>$bonus['INCOME_TOTAL'],
+            'BONUS_REAL'=>  $bonusReal,
+            'BONUS_TOTAL'=>$bonus['BONUS_TOTAL'],
+            'ORI_BONUS_BD' => $bonus['ORI_BONUS_BD'],
+            'ORI_BONUS_TG' => $bonus['ORI_BONUS_TG'],
+            'ORI_BONUS_XF' => $bonus['ORI_BONUS_XF'],
+            'ORI_BONUS_YJ' => $bonus['ORI_BONUS_YJ'],
+            'ORI_BONUS_YJ_BD' => $bonus['ORI_BONUS_YJ_BD'],
+            'ORI_BONUS_YJ_FX' => $bonus['ORI_BONUS_YJ_FX'],
+            'ORI_BONUS_GX' => $bonus['ORI_BONUS_GX'],
+            'REAL_BONUS_GX' => $realBonusGx,
+            'ORI_BONUS_GL' => $bonus['ORI_BONUS_GL'],
+            'REAL_BONUS_GL' => $realBonusGl,
+
+            'BONUS_BS' => $realBonusBs, // 新的管理奖金,即蓝星管理奖
+            'ORI_BONUS_BS' => $blueStartOriBonus, // 蓝星管理奖金原奖金,即包含管理费
+            'REAL_BONUS_BS' => $realBonusBs, // 实发蓝星管理奖金
+
+            'BONUS_BS_MNT' => $realBonusBsMnt, // 蓝星管理奖
+            'ORI_BONUS_BS_MNT' => $blueStartOriBonusMnt, // 蓝星管理奖金原奖金,即包含管理费
+            'REAL_BONUS_BS_MNT' => $realBonusBsMnt, // 实发蓝星管理奖金
+            'MANAGE_TAX_MNT' => $blueStartManageTaxMnt,  // 实发蓝星管理——管理费
+
+            'BONUS_BS_ABBR' => $realBonusBsAbbr, // 蓝星业绩奖
+            'ORI_BONUS_BS_ABBR' => $blueStartOriBonusAbbr, // 蓝星业绩奖金原奖金,即包含管理费
+            'REAL_BONUS_BS_ABBR' => $realBonusBsAbbr, // 实发蓝星业绩奖金
+            'MANAGE_TAX_ABBR' => $blueStartManageTaxAbbr, // 实发蓝星业绩奖——管理费
+
+            'ORI_BONUS_GL_BD' => $bonus['ORI_BONUS_GL_BD'],
+            'ORI_BONUS_GL_FX' => $bonus['ORI_BONUS_GL_FX'],
+            'ORI_BONUS_QY' => $bonus['ORI_BONUS_QY'],
+            'ORI_BONUS_QY_BD' => $bonus['ORI_BONUS_QY_BD'],
+            'ORI_BONUS_QY_FX' => $bonus['ORI_BONUS_QY_FX'],
+            'ORI_BONUS_YC' => $bonus['ORI_BONUS_YC'] + $bonus['ORI_BONUS_YC_EXTRA'],
+            'ORI_BONUS_VIP' => $bonus['ORI_BONUS_VIP'],
+            'ORI_BONUS_STANDARD' => $standardBonus,
+            'ORI_CAPPED_BONUS_QY' => $bonus['ORI_CAPPED_BONUS_QY'], // 团队奖封顶前的奖金
+            'BONUS_QUARTER' => $bonus['BONUS_QUARTER'],
+            'ORI_BONUS_QUARTER' => $bonus['ORI_BONUS_QUARTER'],
+
+            'BONUS_TOURISM' => $tourismBonus, // 旅游奖
+            'BONUS_VILLA' => $villaBonus, // 房奖
+            'BONUS_GARAGE' => $garageBonus, // 车奖
+
+            //以下没有用
+            'BONUS_FW' => 0,
+            'BONUS_FX' => $bonus['BONUS_FX'],
+            'BONUS_LS' => $bonus['BONUS_LS'],
+            'BONUS_BT' => $bonus['BONUS_BT'],
+            'BONUS_BT_PROD' => $bonus['BONUS_BT_PROD'],
+            'BONUS_BT_TOOL' => $bonus['BONUS_BT_TOOL'],
+            'DEDUCT_ZR' => $bonus['DEDUCT_ZR'],
+            'BONUS_FL' => $bonus['BONUS_FL'],
+            'BONUS_CF' => $bonus['BONUS_CF'],
+            'BONUS_LX' => $bonus['BONUS_LX'],
+            'SHOULD_QY' => $bonus['BONUS_QY'],
+            'SHOULD_DEDUCT_ZR' => $bonus['DEDUCT_ZR'],
+
+            'PV_1L' => $perfData['PV_1L_TOUCH'],//TOUCH为碰业绩
+            'QY_1L' => $perfData['PV_1L_TOUCH'] + $pervSurplusPerf['SURPLUS_1L'],
+            'SURPLUS_1L' => $perfData['SURPLUS_1L'],
+            'PV_2L' => $perfData['PV_2L_TOUCH'],
+            'QY_2L' => $perfData['PV_2L_TOUCH'] + $pervSurplusPerf['SURPLUS_2L'],
+            'SURPLUS_2L' => $perfData['SURPLUS_2L'],
+            'PV_3L' => $perfData['PV_3L_TOUCH'],
+            'QY_3L' => $perfData['PV_3L_TOUCH'] + $pervSurplusPerf['SURPLUS_3L'],
+            'SURPLUS_3L' => $perfData['SURPLUS_3L'],
+            'PV_4L' => $perfData['PV_4L_TOUCH'],
+            'QY_4L' => $perfData['PV_4L_TOUCH'] + $pervSurplusPerf['SURPLUS_4L'],
+            'SURPLUS_4L' => $perfData['SURPLUS_4L'],
+            'PV_5L' => $perfData['PV_5L_TOUCH'],
+            'QY_5L' => $perfData['PV_5L_TOUCH'] + $pervSurplusPerf['SURPLUS_5L'],
+            'SURPLUS_5L' => $perfData['SURPLUS_5L'],
+            'PV_PCS' => $perfData['PV_PCS'],
+            'PV_LS_TOUCH' => 0.00,
+            'SURPLUS_LS' => 0.00,
+            'QY_LS' => 0.00,
+            'PV_TOUCH' => Tool::formatPrice($perfData['PV_1L_TOUCH'] + $perfData['PV_2L_TOUCH'] + $perfData['PV_3L_TOUCH'] + $perfData['PV_4L_TOUCH'] + $perfData['PV_5L_TOUCH'] + $perfData['PV_LS_TOUCH']),
+            'PERIOD_NUM' => $this->_periodNum,
+            'CALC_YEAR' => $this->_calcYear,
+            'CALC_MONTH' => $this->_calcYearMonth,
+            'CALCULATED_AT' => Date::nowTime(),
+            'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+            'CREATED_AT' => Date::nowTime(),
+        ];
+        $resend = [];
+
+        unset($bonus, $realBonusGx, $realBonusGl, $bonusReal);
+        return ['result'=>$result,'resend'=>$resend];
+    }
+
+    /**
+     * 根据本有收入计算基础积分
+     * @param int $offset
+     * @return bool
+     */
+    public function calcBaseScore(int $offset=0) {
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+        echo sprintf("时间:[%s]基础积分,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+
+        //查询本月的,每人的累计总收入
+        $allData = CalcBonus::findUseDbCalc()->select('USER_ID, SUM(BONUS_TOTAL) AS TOTAL_SUM, SUM(ORI_BONUS_BD) AS BD_TOTAL_SUM')->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])->groupBy('USER_ID')->orderBy('USER_ID DESC')->offset($offset)->limit($this->_limit)->asArray()->all();
+        if ($allData) {
+            foreach ($allData as $everyData) {
+                $validTotalSum = $everyData['TOTAL_SUM'] - $everyData['BD_TOTAL_SUM'];
+                if( $validTotalSum <=0 ) continue;
+
+                //达不到 见习主任级别,不产生基础积分
+                $userBaseInfo = CalcCache::getUserInfo($everyData['USER_ID'], $this->_periodNum);
+                $userEmpLevel = $this->_empLevelConfig[$userBaseInfo['EMP_LV']];
+                $userEmpLevelSort = $userEmpLevel['SORT'] ?? EmployLevel::EMP_LEVEL_SORT['NO_LEVEL'];
+                unset($userBaseInfo, $userEmpLevel);
+                if( $userEmpLevelSort < EmployLevel::EMP_LEVEL_SORT['JX_ZR_LEVEL'] ) {
+                    unset($userEmpLevelSort);
+                    continue;
+                }
+                unset($userEmpLevelSort);
+
+
+                //根据总收入获取积分
+                $baseScore = $this->_getBaseScoreByTotalBonus($validTotalSum);
+                //根据用户的级别判断 能否得到级别积分
+                if( $baseScore > 0 ) {
+                    CalcCache::nowMonthScore($everyData['USER_ID'], $this->_periodNum, [
+                        'BASE_SCORE' => $baseScore,
+                    ]);
+
+                    CalcCache::addHasScoreUsers($everyData['USER_ID'], $this->_periodNum);
+                }
+
+                unset($everyData, $validTotalSum, $baseScore);
+            }
+
+            unset($allData);
+            return $this->calcBaseScore($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+
+    /**
+     * 循环积分入库
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function loopWriteScore(int $offset = 0) {
+        if(!$this->_isCalcMonth){
+            return true;
+        }
+        echo sprintf("时间:[%s]积分入库,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        // 从缓存列表里面从底层往上倒序获取会员
+        $allData = CalcCache::getHasScoreUsers($this->_periodNum, $offset, $this->_limit);
+        if($allData){
+            $insertDataMonthScore = [];
+            foreach($allData as $userId){
+                $monthScoreData = CalcCache::nowMonthScore($userId, $this->_periodNum);
+                $totalScore = $monthScoreData['BASE_SCORE'] + $monthScoreData['LEVEL_SCORE'] + $monthScoreData['UPGRADE_SCORE'];
+                if( $totalScore <= 0 ) continue;
+
+                $insertDataMonthScore[] = [
+                    'USER_ID' => $userId,
+                    'BASE_SCORE' => $monthScoreData['BASE_SCORE'],
+                    'LEVEL_SCORE' => $monthScoreData['LEVEL_SCORE'],
+                    'UPGRADE_SCORE' => $monthScoreData['UPGRADE_SCORE'],
+                    'TOTAL_SCORE' => $totalScore,
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'CREATED_AT' => Date::nowTime(),
+                ];
+
+                unset($userId, $monthScoreData, $totalScore);
+            }
+
+            ScoreMonth::batchInsert($insertDataMonthScore);
+            unset($insertDataMonthScore, $allData);
+            return $this->loopWriteScore($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 根据总收入获取相应的基础积分
+     * @param $totalBonus
+     * @return int|mixed
+     */
+    private function _getBaseScoreByTotalBonus($totalBonus) {
+        $baseScoreConfig = Json::decode($this->_sysConfig['baseScore']['VALUE']);
+        $baseScore = 0;
+        foreach ($baseScoreConfig as $everyScoreData) {
+            if( $totalBonus < $everyScoreData['monthTotalPvMin'] ) continue;
+
+            if( $everyScoreData['monthTotalPvMax'] != 0 && $totalBonus >= $everyScoreData['monthTotalPvMax'] ) continue;
+
+            $baseScore = $everyScoreData['score'];
+            unset($everyScoreData);
+            break;
+        }
+
+        unset($baseScoreConfig, $totalBonus);
+        return $baseScore;
+    }
+
+    // 判断是否满足月最低消费
+    public function _isMonthPerfLimit($userId) {
+        $userMonthTotal = PerfMonth::find()->where(
+            'USER_ID=:USER_ID AND CALC_MONTH=:CALC_MONTH', 
+            ['USER_ID'=>$userId, 'CALC_MONTH'=>$this->_calcYearMonth]
+        )
+        ->asArray()
+        ->one();
+        $fxPvStatus = false;
+        if (isset($userMonthTotal['PV_PCS']) && $userMonthTotal['PV_PCS'] >= $this->_sysConfig['monthPcsPvFxCondition']['VALUE']) {
+            $fxPvStatus = true;
+        }
+        
+        return $fxPvStatus;
+    }
+}

+ 1157 - 0
common/helpers/bonus/BonusSend.php

@@ -0,0 +1,1157 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/3/18
+ * Time: 上午11:06
+ */
+
+namespace common\helpers\bonus;
+
+use common\components\ActiveRecord;
+use common\helpers\DataBak;
+use common\helpers\Form;
+use common\helpers\Tool;
+use common\helpers\user\Balance;
+use common\helpers\user\Info;
+use common\helpers\user\Reconsume;
+use common\helpers\user\Status;
+use common\libs\api\sms\SmsApi;
+use common\libs\swoole\Process;
+use common\models\DealType;
+use common\models\FlowRemainPv;
+use common\models\PerfPeriod;
+use common\models\DecOrder;
+use common\models\EmployLevel;
+use common\models\StarCrownLevel;
+use common\models\UserBonus;
+use common\models\UserPerfMonthUpdate;
+use common\models\UserPeriodPoints;
+use common\models\UserWallet;
+use common\models\UserInfo;
+use common\models\UserPerf;
+use common\models\UserPerfUpdate;
+use common\models\UserTeamwork;
+use common\models\YearHighestEmpLv;
+use common\models\Order;
+use common\models\RemainPv;
+use common\models\forms\OrderForm;
+use yii\base\BaseObject;
+use yii\base\StaticInstanceTrait;
+use common\helpers\Cache;
+use common\helpers\Date;
+use common\models\CalcBonus;
+use common\models\CalcBonusBD;
+use common\models\CalcBonusBS;
+use common\models\CalcBonusGX;
+use common\models\CalcBonusQY;
+use common\models\CalcBonusTG;
+use common\models\FlowBonus;
+use common\models\PerfActiveUser;
+use common\models\PerfMonth;
+use common\models\User;
+use Yii;
+use common\models\Period;
+use yii\base\Exception;
+use yii\db\Expression;
+
+class BonusSend extends BaseObject {
+    use StaticInstanceTrait;
+
+    private $_limit = 1000;
+    private $_appUserId = null;
+    private $_sysConfig = [];
+    private $_decLevelConfig = [];
+    private $_empLevelConfig = [];
+    private $_decRoleConfig = [];
+    private $_errors = [];
+    private $_periodNum = 0;
+    private $_periodId;
+    private $_isCalcMonth = 0;
+    private $_isCalcYear = 0;
+    private $_calcYear;
+    private $_calcMonth;
+    private $_calcYearMonth;
+    private $_lastCalcYear;
+    private $_lastCalcMonth;
+    private $_lastCalcYearMonth;
+    private $_workerId;
+    private $_workerNum;
+
+    public function init(int $periodNum = 0, $appUserId = null, $workerId = null, $workerNum = null) {
+        parent::init();
+        $this->_sysConfig = Cache::getSystemConfig();
+        $this->_decLevelConfig = Cache::getDecLevelConfig();
+        $this->_empLevelConfig = Cache::getEmpLevelConfig();
+        $this->_decRoleConfig = CalcCache::getDecRoleConfig($this->_periodNum);
+        $this->_periodNum = $periodNum;
+        $this->_appUserId = $appUserId;
+        $this->_workerId = $workerId;
+        $this->_workerNum = $workerNum;
+    }
+
+    /**
+     * 设置期数
+     * @param int $periodNum
+     * @return int
+     */
+    public function setPeriodNum(int $periodNum) {
+        return $this->_periodNum = $periodNum;
+    }
+
+    /**
+     * 获取期数
+     * @return int
+     */
+    public function getPeriodNum() {
+        return $this->_periodNum;
+    }
+
+    /**
+     * 加入错误错误
+     * @param $attr
+     * @param $error
+     */
+    public function addError($attr, $error) {
+        $this->_errors[$attr][] = $error;
+    }
+
+    /**
+     * 获取错误信息
+     * @return array
+     */
+    public function getErrors() {
+        return $this->_errors;
+    }
+
+    /**
+     * 挂网时处理虚假订单
+     *
+     */
+    public function putFakeOrder() {
+        echo('开始处理-假订单' . PHP_EOL);
+        $fakeOrder= Order::find()->where(['PERIOD_NUM'=>$this->_periodNum, 'IS_AUTO'=>'1'])->asArray()->all();
+//        print_r($fakeOrder);exit;
+        foreach($fakeOrder as $fOrder){
+            $oRemainPv=RemainPv::findOne(["USER_ID"=>$fOrder['USER_ID']]);
+            $transactionRemain = \Yii::$app->db->beginTransaction();
+            try{
+                $flowRemainPvModel = new FlowRemainPv();
+                $flowRemainPvModel->ID = $this->_generateSn();
+                $flowRemainPvModel->USER_ID = $fOrder['USER_ID'];
+                $flowRemainPvModel->REMAIN_PV_FLOW = -30;
+                $flowRemainPvModel->REMAIN_PV_TOTAL = $oRemainPv->REMAIN_PV - 30;
+                $flowRemainPvModel->PERIOD_NUM = $this->_periodNum;
+                $flowRemainPvModel->UPDATED_AT = Date::nowTime();
+                $flowRemainPvModel->ORDER_SN = $fOrder['SN'];
+                if(!$flowRemainPvModel->save()){
+                    $this->addErrors($flowRemainPvModel->getErrors());
+                    return false;
+                }
+                $oRemainPv->updateCounters(['REMAIN_PV'=>-30]);
+                $transactionRemain->commit();
+            } catch (Exception $e) {
+                $transactionRemain->rollBack();
+                $this->addError('add', $e->getMessage());
+                return null;
+            }
+        }
+        echo('假订单处理完' . PHP_EOL);
+        return true; // $flowRemainPvModel;
+    }
+    /**
+     * 生成流水号
+     * @return string
+     */
+    private function _generateSn() {
+        return Date::today('Ymd') . $this->_random(10, 1);
+    }
+
+    /**
+     * 生成随机数
+     * @param $length
+     * @param int $numeric
+     * @return string
+     */
+    private function _random($length, $numeric = 0) {
+        $seed = base_convert(md5(microtime() . $_SERVER['DOCUMENT_ROOT']), 16, $numeric ? 10 : 35);
+        $seed = $numeric ? (str_replace('0', '', $seed) . '012340567890') : ($seed . 'zZ' . strtoupper($seed));
+        $hash = '';
+        $max = strlen($seed) - 1;
+        for ($i = 0; $i < $length; $i++) {
+            $hash .= $seed[mt_rand(0, $max)];
+        }
+        return $hash;
+    }
+
+
+    /**
+     * 进行奖金发放步骤
+     * @return bool
+     */
+    public function sendStep() {
+        try {
+            $t1 = microtime(true);
+            // 初始化
+            $this->initTask();
+            echo('挂网开始');
+            $this->putFakeOrder();
+            // 先把有remainPv的订单处理一下,将remainPv加入到remain_pv及流水表
+            echo('处理当期REMAIN PV ' . date('Y-m-d  H:i:s', time()) . PHP_EOL);
+            $this->_calcRemainPv();
+            $t2 = microtime(true);
+            echo('初始化完成,当前期数【' . $this->_periodNum . '】,耗时:' . round($t2 - $t1, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            // 改变状态
+            $this->setSendStatus('start');
+            $t3 = microtime(true);
+            echo('改变状态完成,耗时:' . round($t3 - $t2, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(20);
+            //Yii::$app->db->close();
+            //Yii::$app->dbShop->close();
+            // 更新StarDirectory
+            $this->updateEmpLevel();
+            $this->_updatePercent(40);
+            $t4 = microtime(true);
+            echo('更新聘级完成,耗时:' . round($t4 - $t3, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+
+            // 更新StarCrown
+            $this->updateCrownLevel();
+            $this->_updatePercent(50);
+            $t41 = microtime(true);
+            echo('更新星级完成,耗时:' . round($t41 - $t4, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+
+            // 发放奖金
+            $this->sendBonusLoop();
+            $this->_updatePercent(60);
+            $t5 = microtime(true);
+            echo('发放奖金完成,耗时:' . round($t5 - $t4, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            // 更新会员上次报单级别
+//            $this->updateUserDevLv();
+//            $this->_updatePercent(80);
+            $t6 = microtime(true);
+//            echo('更新会员上次报单级别完成,耗时:' . round($t6 - $t5, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            // 更新会员累计业绩
+            $this->updateUserPerf();
+            $this->_updatePercent(80);
+            $t7 = microtime(true);
+            echo('更新会员累计业绩完成,耗时:' . round($t7 - $t6, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+
+            $this->updateUserPerfMonth();
+            $this->_updatePercent(90);
+            $t8 = microtime(true);
+            echo('更新会员累计月业绩完成,耗时:' . round($t8 - $t7, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+
+            // 更新活跃用户状态,更新为已处理
+            // $this->updateActiveUser();
+            // $this->_updatePercent(95);
+            // $t9 = microtime(true);
+            // echo('更新会员累计月业绩完成,耗时:' . round($t9 - $t8, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            
+            // 开启子进程去完成下面的循环发放和循环改聘级和循环更新累计业绩
+            /*$process = new Process('sendBonus', 3);
+            $process->run(function($workId, $pmid){
+                $this->processStep($workId);
+            });
+            while (true){
+                if($process->isFinish()) break;
+            }
+            unset($process);
+            $this->_updatePercent(90);
+            $t4 = microtime(true);
+            echo('所有子进程的任务完成,耗时:'.round($t4 - $t3, 3).',内存使用:'.(round(memory_get_usage()/1024/1024, 3)).'MB'.PHP_EOL);*/
+            echo('全部奖金发放完成,耗时:'.round($t7 - $t1, 3).',内存使用:'.(round(memory_get_usage()/1024/1024, 3)).'MB'.PHP_EOL);
+            $this->_updatePercent(100);
+        } catch (\Exception $e) {
+            $this->addError('sendBonus', sprintf('File【%s】, Line【%s】, Msg【%s】', $e->getFile(), $e->getLine(), $e->getMessage()));
+            return false;
+        }
+        if (count($this->_errors) > 0) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 处理order表中有remain_pv的订单
+     * 将结果写入到remainPv相关表中
+     *
+     */
+    private function _calcRemainPv(){
+        $orders = Order::find()->where('PERIOD_NUM=:PERIOD_NUM AND REMAIN_PV>0',[':PERIOD_NUM'=>$this->_periodNum])->asArray()->all();
+        foreach($orders as $order){
+            $oRemainPv = RemainPv::find()->where(['USER_ID' => $order['USER_ID']])->one();
+
+            $transactionRemain = \Yii::$app->db->beginTransaction();
+            try{
+                $flowRemainPvModel = new FlowRemainPv();
+                $flowRemainPvModel->ID = $this->_generateSn();
+                $flowRemainPvModel->USER_ID = $order['USER_ID'];
+                $flowRemainPvModel->REMAIN_PV_FLOW = $order['REMAIN_PV'];
+                $flowRemainPvModel->REMAIN_PV_TOTAL = $oRemainPv['REMAIN_PV'] + $order['REMAIN_PV'];
+                $flowRemainPvModel->PERIOD_NUM = $this->_periodNum;
+                $flowRemainPvModel->UPDATED_AT = Date::nowTime();
+                $flowRemainPvModel->ORDER_SN = $order['SN'];
+                if(!$flowRemainPvModel->save()){
+                    $this->addErrors($flowRemainPvModel->getErrors());
+                    return false;
+                }
+
+                $oRemainPv = RemainPv::find()->where(['USER_ID' => $order['USER_ID']])->one();
+                if($oRemainPv){
+                    $oRemainPv->updateCounters(['REMAIN_PV'=>$order['REMAIN_PV']]);
+                }else{
+                    $remainPvModel = new RemainPv();
+                    $remainPvModel->ID = $this->_generateSn();
+                    $remainPvModel->USER_ID = $order['USER_ID'];
+                    $remainPvModel->UPDATED_AT = Date::nowTime();
+                    $remainPvModel->REMAIN_PV = $order['REMAIN_PV'];
+                    $remainPvModel->STATUS = 1;
+                    if(!$remainPvModel->save()){
+                        $this->addErrors($remainPvModel->getErrors());
+                        return false;
+                    }
+                }
+                $transactionRemain->commit();
+            } catch (Exception $e) {
+                $transactionRemain->rollBack();
+                $this->addError('add', $e->getMessage());
+                return null;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 需要多进程执行的任务
+     * @param $workId
+     * @throws \yii\db\Exception
+     */
+    public function processStep($workId = null) {
+        if ($workId == 1 || $workId === null) {
+            // 发放奖金
+            $this->sendBonusLoop();
+        }
+        if ($workId == 2 || $workId === null) {
+            // 更新聘级
+            $this->updateEmpLevel();
+        }
+        if ($workId == 3 || $workId === null) {
+            // 更新会员累计业绩
+            $this->updateUserPerf();
+        }
+        Yii::$app->db->close();
+        Yii::$app->dbShop->close();
+    }
+
+    /**
+     * 初始化发放任务
+     * @throws Exception
+     */
+    public function initTask() {
+        $this->_errors = [];
+        $periodNum = $this->_periodNum;
+        // 获取传入期数的相关信息
+        $periodObj = Period::instance();
+        $periodDataArr = $periodObj->setPeriodNum($periodNum);
+        $this->_periodId = $periodDataArr['ID'];
+        $this->_isCalcMonth = $periodObj->isCalcMonth($periodNum);
+        $this->_calcYear = $periodObj->getYear($periodNum);
+        $this->_calcMonth = $periodObj->getMonth($periodNum);
+        $this->_calcYearMonth = $periodObj->getYearMonth($periodNum);
+        $lastYearMonthArr = $periodObj->getLastMonth($periodNum);
+        $this->_lastCalcYear = $lastYearMonthArr['year'];
+        $this->_lastCalcMonth = $lastYearMonthArr['month'];
+        $this->_lastCalcYearMonth = $lastYearMonthArr['yearMonth'];
+    }
+
+    /**
+     * 设置结算状态
+     * @param $type
+     * start|end|fail
+     */
+    public function setSendStatus($type) {
+        Yii::$app->db->close();
+        if ($type == 'start') {
+            Period::updateAll(['IS_SENDING' => 1, 'SEND_STARTED_AT' => Date::nowTime()], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        } elseif ($type == 'end') {
+            Period::updateAll(['IS_SENDING' => 0, 'IS_SENT' => Period::SEND_FINISH, 'SENT_AT' => Date::nowTime()], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        } elseif ($type == 'fail') {
+            Period::updateAll(['IS_SENDING' => 0, 'IS_SENT' => Period::SEND_FAIL, 'SENT_AT' => 0], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        }
+    }
+
+    /**
+     * 结束计算任务
+     * @throws \yii\db\Exception
+     */
+    public function endTask() {
+        CalcCache::clearAll($this->_periodNum);
+        $this->setSendStatus('end');
+        //@todo 先不备份数据
+//        DataBak::backup($this->_periodNum);
+        //发送复销短信
+//        if ($this->_isCalcMonth && $this->_sysConfig['smsOpen']['VALUE']) {
+//            $this->sendSmsLoop();
+//        }
+    }
+
+    /**
+     * 出现错误
+     */
+    public function errorTask() {
+        $this->setSendStatus('fail');
+    }
+
+    /**
+     * 发放奖金
+     * @param int $page
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function sendBonusLoop($page=1) {
+        echo sprintf("时间:[%s]数据库发奖,当前page为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()), $page);
+        $periodNum = $this->_periodNum;
+        // 从奖金结算表中获取当期未发放的所有数据
+        $allData = CalcBonus::findUseDbCalc()
+        ->yearMonth($this->_calcYearMonth)
+        ->where(
+            '(IS_SENT=0 OR IS_SENT=2) AND CALC_MONTH=:CALC_MONTH AND PERIOD_NUM=:PERIOD_NUM', 
+            [':CALC_MONTH' => $this->_calcYearMonth, ':PERIOD_NUM' => $periodNum]
+        )
+        ->limit($this->_limit)
+        ->asArray()
+        ->all();
+        if ($allData) {
+            $transaction = Yii::$app->db->beginTransaction();
+            try {
+                foreach ($allData as $key => $data) {
+                    // 期奖金
+//                    $periodAmount = $data['BONUS_QY'] + $data['BONUS_YC'] + $data['BONUS_XF'] + $data['BONUS_BD'] + $data['BONUS_TG'] + $data['BONUS_YJ'] + $data['BONUS_GX'] + $data['BONUS_GL'];
+                    $periodAmount = $data['BONUS_REAL'];
+                    // 获取本期结算的管理员
+//                    $period = Period::findOneAsArray(['PERIOD_NUM'=>$periodNum]);
+//                    $updateAminId = $period['CALC_ADMIN_ID'];
+                    if ($periodAmount > 0) {
+                        Balance::changeUserBonus($data['USER_ID'], 'bonus', $periodAmount, [
+                            'CALC_ID' => $data['ID'],
+                            'REMARK' => 'From Period ' . $periodNum,
+                            'PERIOD_NUM' => $periodNum,
+                            'QY' => $data['BONUS_QY'],
+                            'FW' => $data['BONUS_FW'],
+                            'YC' => $data['BONUS_YC'],
+                            'VIP' => $data['BONUS_VIP'],
+                            'BD' => $data['BONUS_BD'],
+                            'TG' => $data['BONUS_TG'],
+                            'YJ' => $data['BONUS_YJ'],
+                            'GX' => $data['BONUS_GX'],
+                            'GL' => $data['BONUS_GL'],
+                            'BS' => $data['BONUS_BS'],
+                            'BS_MNT' => $data['BONUS_BS_MNT'],
+                            'BS_ABBR' => $data['BONUS_BS_ABBR'],
+
+                            'ORI_QY' => $data['ORI_BONUS_QY'],
+                            'ORI_YC' => $data['ORI_BONUS_YC'],
+                            'ORI_VIP' => $data['ORI_BONUS_VIP'],
+                            'ORI_STANDARD' => $data['ORI_BONUS_STANDARD'],
+                            'ORI_BD' => $data['ORI_BONUS_BD'],
+                            'ORI_TG' => $data['ORI_BONUS_TG'],
+                            'ORI_YJ' => $data['ORI_BONUS_YJ'],
+                            'ORI_GX' => $data['ORI_BONUS_GX'],
+                            'ORI_GL' => $data['ORI_BONUS_GL'],
+                            'ORI_BS' => $data['ORI_BONUS_BS'],
+                            'ORI_BS_MNT' => $data['ORI_BONUS_BS_MNT'],
+                            'ORI_BS_ABBR' => $data['ORI_BONUS_BS_ABBR'],
+
+                            'RECONSUME_POINTS_TOTAL' => $data['RECONSUME_POINTS'],
+                            'EXCHANGE_POINTS_TOTAL' => $data['EXCHANGE_POINTS'],
+                            'MANAGE_TAX' => $data['MANAGE_TAX'],
+
+                            'BONUS_TOTAL' => $data['BONUS_TOTAL'],
+
+                            'DEAL_TYPE_ID' => DealType::BONUS_SEND,
+                            'SORT' => $key * 10,
+                            'BONUS_ISSUE' => true,
+                        ]);
+//                        $this->_teamworkBonus($data['USER_ID'], $periodAmount, $key);
+                    }
+
+                    // 旅游奖
+                    if ($data['BONUS_TOURISM'] > 0) {
+                        Balance::changeUserBonus($data['USER_ID'], 'tourism_points', $data['BONUS_TOURISM'], [
+                            'CALC_ID' => $data['ID'],
+                            'REMARK' => 'From Period ' . $periodNum,
+                            'PERIOD_NUM' => $periodNum,
+                            'TOURISM_POINTS' => $data['BONUS_TOURISM'],
+                            'DEAL_TYPE_ID' => DealType::TOURISM_SEND,
+                            'SORT' => $key * 10,
+                            'BONUS_ISSUE' => true,
+                        ]);
+                    }
+                    // 车奖
+                    if ($data['BONUS_GARAGE'] > 0) {
+                        Balance::changeUserBonus($data['USER_ID'], 'garage_points', $data['BONUS_GARAGE'], [
+                            'CALC_ID' => $data['ID'],
+                            'REMARK' => 'From Period ' . $periodNum,
+                            'PERIOD_NUM' => $periodNum,
+                            'GARAGE_POINTS' => $data['BONUS_GARAGE'],
+                            'DEAL_TYPE_ID' => DealType::GARAGE_SEND,
+                            'SORT' => $key * 10,
+                            'BONUS_ISSUE' => true,
+                        ]);
+                    }
+                    // 房奖
+                    if ($data['BONUS_VILLA'] > 0) {
+                        Balance::changeUserBonus($data['USER_ID'], 'villa_points', $data['BONUS_VILLA'], [
+                            'CALC_ID' => $data['ID'],
+                            'REMARK' => 'From Period ' . $periodNum,
+                            'PERIOD_NUM' => $periodNum,
+                            'VILLA_POINTS' => $data['BONUS_VILLA'],
+                            'DEAL_TYPE_ID' => DealType::VILLA_SEND,
+                            'SORT' => $key * 10,
+                            'BONUS_ISSUE' => true,
+                        ]);
+                    }
+
+                    //发放重消积分
+//                    if ($data['RECONSUME_POINTS'] > 0) {
+//                        Balance::changeUserBonus($data['USER_ID'], 'reconsume_points', $data['RECONSUME_POINTS'], [
+//                            'CALC_ID' => $data['ID'],
+//                            'REMARK' => 'From ' . $periodNum . '期',
+//                            'PERIOD_NUM' => $periodNum,
+//                            'RECONSUME_POINTS' => $data['RECONSUME_POINTS'],
+//                            'DEAL_TYPE_ID' => DealType::RECONSUME_POINTS_SEND,
+//                        ]);
+//                    }
+//
+//                    //发放兑换积分
+//                    if ($data['EXCHANGE_POINTS'] > 0) {
+//                        Balance::changeUserBonus($data['USER_ID'], 'exchange_points', $data['EXCHANGE_POINTS'], [
+//                            'CALC_ID' => $data['ID'],
+//                            'REMARK' => 'From ' . $periodNum . '期',
+//                            'PERIOD_NUM' => $periodNum,
+//                            'EXCHANGE_POINTS' => $data['EXCHANGE_POINTS'],
+//                            'DEAL_TYPE_ID' => DealType::EXCHANGE_POINTS_SEND,
+//                            'BONUS_ISSUE' => true,
+//                        ]);
+//                    }
+                    // 把记录标记为已发放状态
+                    CalcBonus::updateAll(['IS_SENT' => 1, 'SENT_AT' => Date::nowTime()], 'ID=:ID', [':ID' => $data['ID']]);
+
+                    unset($periodAmount, $key, $data);
+                }
+                $transaction->commit();
+            } catch (Exception $e) {
+                $transaction->rollBack();
+                $this->addError('sendBonus', '奖金发放失败,原因:' . $e->getFile() . ' ' . $e->getLine() . '  ' . $e->getMessage());
+                return false;
+            }
+
+            unset($allData, $transaction);
+            $page++;
+            return $this->sendBonusLoop($page);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 蓝星奖金(即新的管理奖),更新会员聘级
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function updateEmpLevel(int $offset = 0) {
+        if ($this->_isCalcMonth) {
+            $empLv = EmployLevel::getIdConvertLevelSortCache();
+            $allData = CalcBonus::findUseDbCalc()
+            ->yearMonth($this->_calcYearMonth)
+            ->where(
+                'CALC_MONTH=:CALC_MONTH AND PERIOD_NUM=:PERIOD_NUM', 
+                [
+                    ':CALC_MONTH' => $this->_calcYearMonth,
+                    ':PERIOD_NUM' =>$this->_periodNum
+                ]
+            )
+            ->orderBy('CREATED_AT DESC')
+            ->offset($offset)
+            ->limit($this->_limit)
+            ->all();
+            //@todo 用户级别不变则不更新
+            $defaultEmpLv = EmployLevel::getDefaultLevelId();
+            if ($allData) {
+                $transaction = Yii::$app->db->beginTransaction();
+                try {
+                    foreach ($allData as $data) {
+                        //@todo 用户级别不变则不更新
+                        if( $data['LAST_EMP_LV'] === $defaultEmpLv ) continue;
+                        $nowBsEmpLv = $data['LAST_EMP_LV']; // 当前蓝星奖计算(即管理奖) 的等级
+                        $user = CalcCache::getUserInfo($data['USER_ID'], $this->_periodNum);
+                        $userEmpLv = $user['EMP_LV']; // 用户的历史最高聘级
+                        $userEmpLvSort = $empLv[$userEmpLv]; // 历史最高聘级的 级别值
+                        $nowBsEmpLvSort = $empLv[$nowBsEmpLv]; // 当前蓝星计算的聘级 级别值
+                        if ($nowBsEmpLvSort > $userEmpLvSort) {
+                            // 如果当前期的级别值大于历史最高级别,则更新用户表里的最高聘级
+                            User::updateAll(['EMP_LV' => $nowBsEmpLv], 'ID=:USER_ID', [':USER_ID' => $data['USER_ID']]);
+                            User::deleteBaseInfoFromRedis($data['USER_ID']);
+                            unset($data);
+                        } else {
+                            continue;
+                        }
+                    }
+                    $transaction->commit();
+                } catch (Exception $e) {
+                    $transaction->rollBack();
+                    $this->addError('updateEmpLevel', '更新聘级失败,原因:' . $e->getMessage());
+                    return false;
+                }
+                unset($transaction, $allData, $defaultEmpLv);
+                return $this->updateEmpLevel($offset + $this->_limit);
+            }
+            unset($allData);
+        }
+        return true;
+    }
+
+    /**
+     * 更新用户星级
+     * @param int $offset
+     * @return bool
+     */
+    public function updateCrownLevel(int $offset = 0)
+    {
+        $starCrownLv = StarCrownLevel::getIdConvertLevelSortCache();
+        $allData = CalcBonusQY::findUseDbCalc()
+            ->yearMonth($this->_calcYearMonth)
+            ->where(
+                'CALC_MONTH=:CALC_MONTH AND PERIOD_NUM=:PERIOD_NUM',
+                [
+                    ':CALC_MONTH' => $this->_calcYearMonth,
+                    ':PERIOD_NUM' =>$this->_periodNum
+                ]
+            )
+            ->orderBy('CREATED_AT DESC')
+            ->groupBy('USER_ID')
+            ->offset($offset)
+            ->limit($this->_limit)
+            ->all();
+
+        $defaultEmpLv = StarCrownLevel::getDefaultLevelId();
+        if ($allData) {
+            $transaction = Yii::$app->db->beginTransaction();
+            try {
+                foreach ($allData as $data) {
+                    // 默认级别不更新
+                    if( $data['LAST_CROWN_LV'] === $defaultEmpLv ) continue;
+
+                    $modernCrownLv = $data['LAST_CROWN_LV'];   // 本期计算出的最新级别
+
+                    $user = CalcCache::getUserInfo($data['USER_ID'], $this->_periodNum);
+                    $originCrownLv = $user['CROWN_LV']; // 用户的历史最高crown级别
+                    $originCrownLvSort = $starCrownLv[$originCrownLv]; // 历史最高crown级别值
+                    $modernCrownLvSort = $starCrownLv[$modernCrownLv]; // 当前计算的crown级别值
+                    if ($modernCrownLvSort > $originCrownLvSort) {
+                        $columns = [
+                            'CROWN_LV' => $data['LAST_CROWN_LV'],
+                            'LAST_CROWN_LV' => $data['LAST_CROWN_LV'],
+                            'LAST_CROWN_LV_UPDATED_AT' => time(),
+                            'LAST_CROWN_LV_UPDATED_PERIOD' => $this->_periodNum,
+                        ];
+                    } else {
+                        $columns = [
+                            'LAST_CROWN_LV' => $data['LAST_CROWN_LV'],
+                            'LAST_CROWN_LV_UPDATED_AT' => time(),
+                            'LAST_CROWN_LV_UPDATED_PERIOD' => $this->_periodNum,
+                        ];
+                    }
+
+                    User::updateAll($columns, 'ID = :USER_ID', [':USER_ID' => $data['USER_ID']]);
+                    User::deleteBaseInfoFromRedis($data['USER_ID']);
+                    unset($data);
+                }
+                $transaction->commit();
+            } catch (Exception $e) {
+                $transaction->rollBack();
+                $this->addError('updateStarCrownLevel', '更新StartCrown失败,原因:' . $e->getMessage());
+                return false;
+            }
+            unset($transaction, $allData);
+            return $this->updateCrownLevel($offset + $this->_limit);
+        }
+        unset($allData);
+
+        return true;
+    }
+
+    // /**
+    //  * 更新会员聘级
+    //  * @param int $offset
+    //  * @return bool
+    //  * @throws \yii\db\Exception
+    //  */
+    // public function updateEmpLevel(int $offset = 0) {
+    //     if ($this->_isCalcMonth) {
+    //         $allData = PerfMonth::findUseDbCalc()->yearMonth($this->_calcYearMonth)->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])->offset($offset)->limit($this->_limit)->all();
+    //         //@todo 用户级别不变则不更新
+    //         $defaultEmpLv = EmployLevel::getDefaultLevelId();
+    //         if ($allData) {
+    //             $transaction = Yii::$app->db->beginTransaction();
+    //             try {
+    //                 foreach ($allData as $data) {
+    //                     //@todo 用户级别不变则不更新
+    //                     if( $data['LAST_EMP_LV'] === $defaultEmpLv ) continue;
+
+    //                     User::updateAll(['EMP_LV' => $data['LAST_EMP_LV']], 'ID=:USER_ID', [':USER_ID' => $data['USER_ID']]);
+    //                     User::deleteBaseInfoFromRedis($data['USER_ID']);
+    //                     unset($data);
+    //                 }
+    //                 $transaction->commit();
+    //             } catch (Exception $e) {
+    //                 $transaction->rollBack();
+    //                 $this->addError('updateEmpLevel', '更新聘级失败,原因:' . $e->getMessage());
+    //                 return false;
+    //             }
+    //             unset($transaction, $allData, $defaultEmpLv);
+    //             return $this->updateEmpLevel($offset + $this->_limit);
+    //         }
+    //         unset($allData);
+    //     }
+    //     return true;
+    // }
+
+    // 更活跃会员,将is_send改成1
+    public function updateActiveUser() {
+        try {
+            $ret = PerfActiveUser::updateAll(
+                ['IS_SENT' => 1], 
+                'PERIOD_NUM=:PERIOD_NUM AND IS_SENT=:IS_SENT', 
+                ['IS_SENT'=>0, 'PERIOD_NUM'=>$this->_periodNum]
+            );
+
+            return $ret;
+        } catch(Exception $e) {
+
+            $this->addError('updateActiveUser', '更新活跃会员业绩期处理状态失败,原因:' . $e->getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 更新会员的累计业绩
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function updateUserPerf(int $offset = 0) {
+        $allData = PerfPeriod::findUseDbCalc()->yearMonth($this->_calcYearMonth)->where('PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum])->orderBy('ID ASC')->offset($offset)->limit($this->_limit)->all();
+        if ($allData) {
+            $transaction = Yii::$app->db->beginTransaction();
+            try {
+                foreach ($allData as $data) {
+                    $isUpdate = false;
+                    if (UserPerf::findUseDbCalc()->where('USER_ID=:USER_ID', [':USER_ID' => $data['USER_ID']])->exists()) {
+                        // 判断本期是否已经更新过业绩
+                        if (!UserPerfUpdate::findUseDbCalc()->yearMonth($this->_calcYearMonth)->where('USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM', [':USER_ID' => $data['USER_ID'], ':PERIOD_NUM' => $this->_periodNum])->exists()) {
+                            $isUpdate = true;
+                            // 更新业绩
+                            UserPerf::updateAll([
+                                'PV_PCS_ZC' => new Expression('PV_PCS_ZC+' . $data['PV_PCS_ZC']),
+                                'PV_PCS_YH' => new Expression('PV_PCS_YH+' . $data['PV_PCS_YH']),
+                                'PV_PCS_ZG' => new Expression('PV_PCS_ZG+' . $data['PV_PCS_ZG']),
+                                'PV_PCS_LS' => new Expression('PV_PCS_LS+' . $data['PV_PCS_LS']),
+                                'PV_PCS_FX' => new Expression('PV_PCS_FX+' . $data['PV_PCS_FX']),
+                                'PV_PSS' => new Expression('PV_PSS+' . $data['PV_PSS']),
+                                'PV_PSS_TOTAL' => new Expression('PV_PSS_TOTAL+' . $data['PV_PSS']),
+                                'PV_1L' => new Expression('PV_1L+' . $data['PV_1L']),
+                                'PV_2L' => new Expression('PV_2L+' . $data['PV_2L']),
+                                'PV_3L' => new Expression('PV_3L+' . $data['PV_3L']),
+                                'PV_4L' => new Expression('PV_4L+' . $data['PV_4L']),
+                                'PV_5L' => new Expression('PV_5L+' . $data['PV_5L']),
+                                'SURPLUS_1L' => $data['SURPLUS_1L'],
+                                'SURPLUS_2L' => $data['SURPLUS_2L'],
+                                'SURPLUS_3L' => $data['SURPLUS_3L'],
+                                'SURPLUS_4L' => $data['SURPLUS_4L'],
+                                'SURPLUS_5L' => $data['SURPLUS_5L'],
+                                'SURPLUS_1L_ZC' => $data['SURPLUS_1L_ZC'],
+                                'SURPLUS_2L_ZC' => $data['SURPLUS_2L_ZC'],
+                                'SURPLUS_3L_ZC' => $data['SURPLUS_3L_ZC'],
+                                'SURPLUS_4L_ZC' => $data['SURPLUS_4L_ZC'],
+                                'SURPLUS_5L_ZC' => $data['SURPLUS_5L_ZC'],
+                                'SURPLUS_1L_FX' => $data['SURPLUS_1L_FX'],
+                                'SURPLUS_2L_FX' => $data['SURPLUS_2L_FX'],
+                                'SURPLUS_3L_FX' => $data['SURPLUS_3L_FX'],
+                                'SURPLUS_4L_FX' => $data['SURPLUS_4L_FX'],
+                                'SURPLUS_5L_FX' => $data['SURPLUS_5L_FX'],
+                            ], 'USER_ID=:USER_ID', [':USER_ID' => $data['USER_ID']]);
+                        }
+                    } else {
+                        $isUpdate = true;
+                        UserPerf::insertOne([
+                            'USER_ID' => $data['USER_ID'],
+                            'PV_PCS_ZC' => $data['PV_PCS_ZC'],
+                            'PV_PCS_YH' => $data['PV_PCS_YH'],
+                            'PV_PCS_ZG' => $data['PV_PCS_ZG'],
+                            'PV_PCS_LS' => $data['PV_PCS_LS'],
+                            'PV_PCS_FX' => $data['PV_PCS_FX'],
+                            'PV_PSS' => $data['PV_PSS'],
+                            'PV_PSS_TOTAL' => $data['PV_PSS'],
+                            'PV_1L' => $data['PV_1L'],
+                            'PV_2L' => $data['PV_2L'],
+                            'PV_3L' => $data['PV_3L'],
+                            'PV_4L' => $data['PV_4L'],
+                            'PV_5L' => $data['PV_5L'],
+                            'SURPLUS_1L' => $data['SURPLUS_1L'],
+                            'SURPLUS_2L' => $data['SURPLUS_2L'],
+                            'SURPLUS_3L' => $data['SURPLUS_3L'],
+                            'SURPLUS_4L' => $data['SURPLUS_4L'],
+                            'SURPLUS_5L' => $data['SURPLUS_5L'],
+                            'SURPLUS_1L_ZC' => $data['SURPLUS_1L_ZC'],
+                            'SURPLUS_2L_ZC' => $data['SURPLUS_2L_ZC'],
+                            'SURPLUS_3L_ZC' => $data['SURPLUS_3L_ZC'],
+                            'SURPLUS_4L_ZC' => $data['SURPLUS_4L_ZC'],
+                            'SURPLUS_5L_ZC' => $data['SURPLUS_5L_ZC'],
+                            'SURPLUS_1L_FX' => $data['SURPLUS_1L_FX'],
+                            'SURPLUS_2L_FX' => $data['SURPLUS_2L_FX'],
+                            'SURPLUS_3L_FX' => $data['SURPLUS_3L_FX'],
+                            'SURPLUS_4L_FX' => $data['SURPLUS_4L_FX'],
+                            'SURPLUS_5L_FX' => $data['SURPLUS_5L_FX'],
+                            'CREATED_AT' => Date::nowTime(),
+                        ]);
+                    }
+                    if ($isUpdate) {
+                        // 变为已更新
+                        UserPerfUpdate::insertOne(['USER_ID' => $data['USER_ID'], 'PERIOD_NUM' => $this->_periodNum, 'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH), 'CREATED_AT' => Date::nowTime()]);
+                    }
+
+                    unset($data, $isUpdate);
+                }
+                $transaction->commit();
+            } catch (Exception $e) {
+                $transaction->rollBack();
+                $this->addError('updateUserPerf', '更新会员业绩失败,原因:' . $e->getMessage());
+                return false;
+            }
+            unset($allData, $transaction);
+            return $this->updateUserPerf($offset + $this->_limit);
+
+        }
+        unset($allData);
+        return true;
+    }
+
+
+    /**
+     * 更新会员的月剩余业绩
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function updateUserPerfMonth(int $offset = 0) {
+        // 月结,如果不是月结点,则直接退出
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+        $allData = PerfMonth::findUseDbCalc()->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])->orderBy('ID ASC')->offset($offset)->limit($this->_limit)->all();
+        if ($allData) {
+            $transaction = Yii::$app->db->beginTransaction();
+            try {
+                foreach ($allData as $data) {
+                    $isUpdate = false;
+                    if (UserPerf::findUseDbCalc()->where('USER_ID=:USER_ID', [':USER_ID' => $data['USER_ID']])->exists()) {
+                        // 判断本期是否已经更新过业绩
+                        if (!UserPerfMonthUpdate::findUseDbCalc()->where('USER_ID=:USER_ID AND CALC_MONTH=:CALC_MONTH', [':USER_ID' => $data['USER_ID'], ':CALC_MONTH' => $this->_calcYearMonth])->exists()) {
+                            $isUpdate = true;
+                            // 更新业绩
+                            UserPerf::updateAll([
+                                'VIP_SURPLUS_1L_ZC' => $data['VIP_SURPLUS_1L_ZC'],
+                                'VIP_SURPLUS_2L_ZC' => $data['VIP_SURPLUS_2L_ZC'],
+                                'VIP_SURPLUS_3L_ZC' => $data['VIP_SURPLUS_3L_ZC'],
+                                'VIP_SURPLUS_4L_ZC' => $data['VIP_SURPLUS_4L_ZC'],
+                                'VIP_SURPLUS_5L_ZC' => $data['VIP_SURPLUS_5L_ZC']
+                            ], 'USER_ID=:USER_ID', [':USER_ID' => $data['USER_ID']]);
+                        }
+                    } else {
+                        $isUpdate = true;
+                        UserPerf::insertOne([
+                            'USER_ID' => $data['USER_ID'],
+                            'VIP_SURPLUS_1L_ZC' => $data['VIP_SURPLUS_1L_ZC'],
+                            'VIP_SURPLUS_2L_ZC' => $data['VIP_SURPLUS_2L_ZC'],
+                            'VIP_SURPLUS_3L_ZC' => $data['VIP_SURPLUS_3L_ZC'],
+                            'VIP_SURPLUS_4L_ZC' => $data['VIP_SURPLUS_4L_ZC'],
+                            'VIP_SURPLUS_5L_ZC' => $data['VIP_SURPLUS_5L_ZC'],
+                            'CREATED_AT' => Date::nowTime(),
+                        ]);
+                    }
+                    if ($isUpdate) {
+                        // 变为已更新
+                        UserPerfMonthUpdate::insertOne(['USER_ID' => $data['USER_ID'], 'CALC_MONTH' => $this->_calcYearMonth, 'CREATED_AT' => Date::nowTime()]);
+                    }
+
+                    unset($data, $isUpdate);
+                }
+                $transaction->commit();
+            } catch (Exception $e) {
+                $transaction->rollBack();
+                $this->addError('updateUserPerf', '更新会员月业绩失败,原因:' . $e->getMessage());
+                return false;
+            }
+            unset($allData, $transaction);
+            return $this->updateUserPerfMonth($offset + $this->_limit);
+
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 检测复消积分是否过期
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function checkReconsumePointsExpired(int $offset = 0) {
+        $periodNum = $this->_periodNum;
+        //一期为7天,那么365天为52.14即53期
+        $expiredPeriodNum = $periodNum - 53;
+        if( $expiredPeriodNum <= 0 ) return true;
+
+        //查询需要过期的期数
+        $allData = UserPeriodPoints::find()->select('ID,USER_ID,REMAINDER_POINTS')->where('PERIOD_NUM<=:PERIOD_NUM AND EXPIRED=0', ['PERIOD_NUM'=>$expiredPeriodNum])->orderBy('ID ASC')->offset($offset)->limit($this->_limit)->asArray()->all();
+        if( $allData ) {
+            $transaction = Yii::$app->db->beginTransaction();
+            try{
+                //扣除钱包的复消积分
+                foreach ($allData as $everyData) {
+                    //过期
+                    UserPeriodPoints::updateAll([
+                        'REMAINDER_POINTS' => 0,
+                        'EXPIRED' => 1,
+                        'EXPIRED_PERIOD' => $periodNum,
+                        'EXPIRED_AT' => Date::nowTime(),
+                    ], 'ID=:ID', ['ID'=>$everyData['ID']]);
+
+                    if( !isset($everyData['REMAINDER_POINTS']) || !$everyData['REMAINDER_POINTS'] ) continue;
+
+                    UserBonus::updateAllCounters([
+                        'RECONSUME_POINTS' => (-1) * $everyData['REMAINDER_POINTS'],
+                    ], 'USER_ID=:USER_ID', $everyData['USER_ID']);
+
+                    unset($everyData);
+                }
+                unset($periodNum, $expiredPeriodNum, $allData);
+
+                $transaction->commit();
+            }catch (\Exception $e){
+                $transaction->rollBack();
+                $this->addError('checkReconsumePointsExpired', '检测过期在复消积分失败,原因:' . $e->getMessage());
+                return false;
+            }
+
+            return $this->checkReconsumePointsExpired($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 更新会员上次报单级别
+     * @return bool
+     */
+    public function updateUserDevLv() {
+        $transaction = Yii::$app->dbShop->beginTransaction();
+        try {
+            \Yii::$app->dbShop->createCommand()->update(User::tableName(), ['LAST_DEC_LV' => new Expression('DEC_LV'), 'LAST_DEC_LV_UPDATED_AT' => Date::nowTime(), 'LAST_DEC_LV_UPDATED_PERIOD' => $this->_periodNum], 'LAST_DEC_LV!=DEC_LV')->execute();
+            $transaction->commit();
+        } catch (Exception $e) {
+            $transaction->rollBack();
+            $this->addError('updateUserDevLv', '更新会员上次报单级别失败,原因:' . $e->getMessage());
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 给商城会员增加货款
+     * @param $userId
+     * @param $amount
+     * @param $remark
+     * @throws Exception
+     */
+    private function _shopAddPaymentForGoods($userId, $amount, $remark) {
+        $db = \Yii::$app->dbShop;
+        $transaction = $db->beginTransaction();
+        try {
+            ActiveRecord::batchUpdate(['PAYMENT_FOR_GOODS' => new Expression('PAYMENT_FOR_GOODS+' . abs($amount))], 'USER_ID=:USER_ID', [':USER_ID' => $userId], '{{%USER_WALLET}}', 'dbShop');
+            // 增加流水
+            $flowInsertData[] = [
+                'USER_ID' => $userId,
+                'USER_NAME' => Cache::getUserBaseInfo($userId)['USER_NAME'],
+                'DEC_LV' => Cache::getUserBaseInfo($userId)['DEC_LV'],
+                'ORDER_SN' => null,
+                'AMOUNT' => $amount,
+                'CREATED_AT' => Date::nowTime(),
+                'PERIOD_AT' => $this->_periodNum,
+                'IS_INCR' => 1,
+                'REMARK' => $remark,
+                'PARTITION_DATE' => Date::ociToDate(),
+                'WALLET_TYPE' => 'payment_for_goods',
+                'FROM_TYPE' => 'incr',
+            ];
+            ActiveRecord::batchInsert($flowInsertData, '{{%FLOW_WALLET}}', 'dbShop');
+            $transaction->commit();
+        } catch (Exception $e) {
+            $transaction->rollBack();
+            throw new Exception($e->getMessage());
+        }
+    }
+
+    /**
+     * 更新百分比并发送
+     * @param $percent
+     */
+    private function _updatePercent($percent) {
+        // 把数据写入数据库中
+        Period::updateAll(['SENT_PERCENT' => $percent], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        \Yii::$app->swooleAsyncTimer->pushAsyncPercentToAdmin($percent, ['MODEL' => 'PERIOD', 'ID' => $this->_periodId, 'FIELD' => 'SENT_PERCENT']);
+    }
+
+    /**
+     * 更新最高聘级
+     * @param $user_id
+     * @return bool
+     * @throws Exception
+     */
+    private function _updateHighestEmpLv($user_id) {
+        $empLv = Info::getEmpLv($user_id);
+        $highEmpLv = Info::getHighEmpLv($user_id);
+        $empLvSort = EmployLevel::getSortById($empLv);
+        $empLvHighSort = EmployLevel::getSortById($highEmpLv);
+        if ($empLvHighYear = YearHighestEmpLv::findOneAsArray('USER_ID=:USER_ID AND YEAR=:YEAR', [':USER_ID' => $user_id, ':YEAR' => $this->_calcYear], 'HIGHEST_EMP_LV_SORT')) {
+            if ($empLvSort > $empLvHighYear['HIGHEST_EMP_LV_SORT']) {
+                YearHighestEmpLv::updateAll(['HIGHEST_EMP_LV' => $empLv, 'HIGHEST_EMP_LV_SORT' => $empLvSort, 'HIGHEST_EMP_LV_PERIOD' => $this->_periodNum, 'UPDATED_AT' => Date::nowTime()], 'USER_ID=:USER_ID AND YEAR=:YEAR', [':USER_ID' => $user_id, ':YEAR' => $this->_calcYear]);
+            }
+        } else {
+            $yearHighest = new YearHighestEmpLv();
+            $yearHighest->USER_ID = $user_id;
+            $yearHighest->YEAR = $this->_calcYear;
+            $yearHighest->HIGHEST_EMP_LV = $empLv;
+            $yearHighest->HIGHEST_EMP_LV_SORT = $empLvSort;
+            $yearHighest->HIGHEST_EMP_LV_PERIOD = $this->_periodNum;
+            $yearHighest->CREATED_AT = Date::nowTime();
+            if (!$yearHighest->save()) {
+                throw new \yii\db\Exception(Form::formatErrorsForApi($yearHighest->getErrors()));
+            }
+        }
+        if ($empLvSort > $empLvHighSort) {
+            UserInfo::updateAll(['HIGHEST_EMP_LV' => $empLv, 'HIGHEST_EMP_LV_PERIOD' => $this->_periodNum], 'USER_ID=:USER_ID', [':USER_ID' => $user_id]);
+            //发送历史最高聘级短信
+            if($this->_sysConfig['smsEmpOpen']['VALUE']){
+                if(in_array($empLvSort,explode(",",$this->_sysConfig['smsEmp']['VALUE']))){
+                    $realName = Info::getUserRealNameByUserId($user_id);
+                    $empLvDate = Date::convert();
+                    $empLvName = EmployLevel::getNameById($empLv);
+                    $content = str_replace(['{%REAL_NAME%}', '{%EMP_LV_DATE%}', '{%EMP_LV%}'], [$realName, $empLvDate, $empLvName], $this->_sysConfig['smsContent']['VALUE']);
+                    $params = [
+                        'mobiles' => Info::getUserMobileByUserId($user_id),
+                        'content' => $content,
+                    ];
+                    SmsApi::instance()->clearError();
+                    SmsApi::instance()->goSend($params);
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 点位合作奖金
+     * @param $userId
+     * @param $amount
+     * @param null $type
+     * @return bool
+     * @throws Exception
+     * @throws \yii\db\Exception
+     */
+    private function _teamworkBonus($userId, $amount, $key) {
+        if (!$teamwork = UserTeamwork::findAllAsArray('USER_ID!=:MAIN_UID AND MAIN_UID=:MAIN_UID AND IS_DEL=0', [':MAIN_UID' => $userId], 'USER_ID,DIVIDE_PERCENT')) return false;
+        $fromUserInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+        foreach ($teamwork as $value) {
+            $bonus = Tool::formatPrice($amount * $value['DIVIDE_PERCENT'] * 0.01);
+            if ($bonus <= 0) continue;
+            $toUserInfo = CalcCache::getUserInfo($value['USER_ID'], $this->_periodNum);
+            Balance::changeUserBonus($userId, 'bonus', -abs($bonus), ['SORT' => $key*10+1, 'DEAL_TYPE_ID' => DealType::TEAMWORK_TRANSFER_OUT, 'REMARK' => 'To:' . $toUserInfo['USER_NAME'] . ' Per:' . $value['DIVIDE_PERCENT'] . '%']);
+            Balance::changeUserBonus($value['USER_ID'], 'bonus', abs($bonus), ['SORT' => $key*10+2, 'DEAL_TYPE_ID' => DealType::TEAMWORK_TRANSFER_IN, 'REMARK' => 'From: ' . $fromUserInfo['USER_NAME'] . ' Per:' . $value['DIVIDE_PERCENT'] . '%']);
+        }
+    }
+
+    /**
+     * 发送短信
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function sendSmsLoop(int $offset = 0) {
+        $allData = UserInfo::findUseDbCalc()->select('USER_ID,ALLOW_RECONSUME_SMS_TO')->where('ALLOW_RECONSUME_SMS=1')->offset($offset)->limit($this->_limit)->all();
+        if ($allData) {
+            $smsWallet = explode(",", $this->_sysConfig['smsWallet']['VALUE']);
+            $smsFee = $this->_sysConfig['smsFee']['VALUE'];
+            //获取剩余月份
+            $period = Period::instance();
+            $year = $period->getYear($this->_periodNum);
+            $monthLeft = Period::getMonthLeft($this->_periodNum);
+            $smsFee = Tool::formatPrice($smsFee * $monthLeft);
+            $to = Date::yearEnd();
+            $transaction = Yii::$app->db->beginTransaction();
+            try {
+                foreach ($allData as $data) {
+                    $userId = $data['USER_ID'];
+                    //过期续费
+                    if ($data['ALLOW_RECONSUME_SMS_TO'] < Date::nowTime()) {
+                        foreach ($smsWallet as $item) {
+                            //1奖金钱包2现金钱包
+                            if ($item == 1) {
+                                //看余额是否充足
+                                if (Balance::getAvailableBalance($userId) < $smsFee){
+                                    UserInfo::updateAll(['ALLOW_RECONSUME_SMS' => 0], 'USER_ID=:USER_ID', [':USER_ID' => $userId]);
+                                    continue;
+                                };
+                                Balance::changeUserBonus($userId, 'bonus', -abs($smsFee), ['DEAL_TYPE_ID' => DealType::SMS, 'REMARK' => $year . '年复销提醒短信服务费'], false);
+                                UserInfo::updateAll(['ALLOW_RECONSUME_SMS_TO' => $to], 'USER_ID=:USER_ID', [':USER_ID' => $userId]);
+                                $data['ALLOW_RECONSUME_SMS_TO'] = $to;
+                                break;
+                            } elseif ($item == 2) {
+                                throw new Exception('不存在此方式');
+                                break;
+                            }
+                        }
+                    }
+                    if ($data['ALLOW_RECONSUME_SMS_TO'] > Date::nowTime() && $mobile = Info::getUserMobileByUserId($userId)) {
+                        $realName = Info::getUserRealNameByUserId($userId);
+                        $reconsumPool = Reconsume::getUserReconsumePool($userId);
+                        $lastRechargeDate = $reconsumPool['toRechargeDate'];
+                        $isPass = $reconsumPool['isPass'] == 1 ? '合格' : '不合格';
+                        $month = $period->getNowMonth();
+                        $content = str_replace(['{%REAL_NAME%}', '{%LAST_RECHARGE_DATE%}', '{%MONTH%}', '{%IS_PASS%}'], [$realName, $lastRechargeDate, $month, $isPass], $this->_sysConfig['smsContent']['VALUE']);
+                        //todo 发短信函数 待测试
+                        $params = [
+                            'mobiles' => $mobile,
+                            'content' => $content,
+                        ];
+                        SmsApi::instance()->clearError();
+                        SmsApi::instance()->goSend($params);
+                    }
+                    unset($userId,$mobile,$realName,$reconsumPool,$lastRechargeDate,$isPass,$month,$content);
+                }
+                $transaction->commit();
+            } catch (Exception $e) {
+                $transaction->rollBack();
+                return false;
+            }
+            unset($data);
+            return $this->sendSmsLoop($offset + $this->_limit);
+
+        }
+        unset($allData);
+        return true;
+    }
+}

+ 1113 - 0
common/helpers/bonus/CalcCache.php

@@ -0,0 +1,1113 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/8/2
+ * Time: 上午10:38
+ */
+namespace common\helpers\bonus;
+
+use common\helpers\Cache;
+use common\models\Config;
+use common\models\PerfMonth;
+use common\models\Period;
+use common\models\DeclarationLevel;
+use common\models\DecRole;
+use common\models\DeductReconsume;
+use common\models\EmployLevel;
+use common\models\StarCrownLevel;
+use common\models\User;
+use common\models\UserPerf;
+use Yii;
+use common\models\UserInfo;
+use yii\helpers\Json;
+
+class CalcCache {
+    const LIMIT = 10000;
+
+    const REDIS_KEY_PREFIX_USER = 'calc:user_';
+    const REDIS_KEY_PREFIX_USER_ACTIVE = 'calc:userActive_';
+    const REDIS_KEY_PREFIX_USER_INFO = 'calc:userInfo_';
+    const REDIS_KEY_PREFIX_USER_BONUS = 'calc:userBonus_';
+    const REDIS_KEY_PREFIX_PERIOD_MONTH_CALC_BONUS = 'calc:periodMonthCalcBonus_';
+    const REDIS_KEY_PREFIX_USER_PERF = 'calc:userPerf_';
+    const REDIS_KEY_PREFIX_SURPLUS_PERF = 'calc:spPerf_';
+    const REDIS_KEY_PREFIX_NOW_PERIOD_PERF = 'calc:nowPeriodPerf_';
+    const REDIS_KEY_PREFIX_LAST_MONTH_PERF = 'calc:lastMonthPerf_';
+    const REDIS_KEY_PREFIX_NOW_MONTH_PERF = 'calc:nowMonthPerf_';
+    const REDIS_KEY_PREFIX_NOW_MONTH_LAST_PERIOD_RECONSUME_POINTS = 'calc:nowMonthLastPeriodReconsumePoints_';
+    const REDIS_KEY_PREFIX_EMP_NUM_PERF = 'calc:empLevelNum_';
+    const REDIS_KEY_PREFIX_BONUS = 'calc:bonus_';
+    const REDIS_KEY_PREFIX_FW_BONUS = 'calc:fw:bonus_';
+    const REDIS_KEY_PREFIX_TOURISM_BONUS = 'calc:tourism:bonus_';
+    const REDIS_KEY_PREFIX_VILLA_BONUS = 'calc:villa:bonus_';
+    const REDIS_KEY_PREFIX_GARAGE_BONUS = 'calc:garage:bonus_';
+    const REDIS_KEY_PREFIX_HAS_PERF_USER = 'calc:hasPerfUser_';
+    const REDIS_KEY_PREFIX_HAS_MONTH_PERF_USER = 'calc:hasMonthPerfUser_';
+    const REDIS_KEY_PREFIX_HAS_STANDARD_MONTH_PERF_USER = 'calc:hasStandardMonthPerfUser_';
+    const REDIS_KEY_PREFIX_HAS_BD_USER = 'calc:hasBDUser_';
+    const REDIS_KEY_PREFIX_HAS_INCOME_USER = 'calc:hasIncomeUser_';
+    const REDIS_KEY_PREFIX_HAS_BONUS_USER = 'calc:hasBonusUser_';
+    const REDIS_KEY_PREFIX_HAS_FW_BONUS_USER = 'calc:hasFwBonusUser_';
+    const REDIS_KEY_PREFIX_HAS_MONTH_BONUS_USER = 'calc:hasMonthBonusUser_';
+    const REDIS_KEY_PREFIX_HAS_PERF_USER_POOL = 'calc:hasPerfUserPool_';
+    const REDIS_KEY_PREFIX_HAS_MONTH_PERF_USER_POOL = 'calc:hasMonthPerfUserPool_';
+    const REDIS_KEY_PREFIX_HAS_STANDARD_MONTH_PERF_USER_POOL = 'calc:hasStandardMonthPerfUserPool_';
+    const REDIS_KEY_PREFIX_HAS_BD_USER_POOL = 'calc:hasBDUserPool_';
+    const REDIS_KEY_PREFIX_HAS_INCOME_USER_POOL = 'calc:hasIncomeUserPool_';
+    const REDIS_KEY_PREFIX_HAS_BONUS_USER_POOL = 'calc:hasBonusUserPool_';
+    const REDIS_KEY_PREFIX_HAS_FW_BONUS_USER_POOL = 'calc:hasFwBonusUserPool_';
+    const REDIS_KEY_PREFIX_HAS_MONTH_BONUS_USER_POOL = 'calc:hasMonthBonusUserPool_';
+    const REDIS_KEY_PREFIX_HAS_LS_PCS_USER = 'calc:hasLSPCSUser_';
+    const REDIS_KEY_PREFIX_HAS_LS_PCS_USER_POOL = 'calc:hasLSPCSUserPool_';
+    const REDIS_KEY_PREFIX_HAS_CF_PERCENT_USER = 'calc:hasCFPercentPCSUser_';
+    const REDIS_KEY_PREFIX_HAS_CF_PERCENT_USER_POOL = 'calc:hasCFPercentPCSUserPool_';
+    const REDIS_KEY_PREFIX_HAS_LX_PERCENT_USER = 'calc:hasLXPercentPCSUser_';
+    const REDIS_KEY_PREFIX_HAS_LX_PERCENT_USER_POOL = 'calc:hasLXPercentPCSUserPool_';
+    const REDIS_KEY_PREFIX_DEC_ROLE_CONFIG = 'calc:decRoleConfig_';
+    const REDIS_KEY_PREFIX_USER_INFO_CHILD_ONE_DEEP = 'calc:userInfo:ChildOneDeep_';
+    const REDIS_KEY_PREFIX_FW_BONUS_LIST_DATA = 'calc:fwBonusListData_';
+    const REDIS_KEY_PREFIX_BONUS_CROWN_CROWN_LIST_DATA = 'calc:bonusStarCrownListData_';
+
+    //@todo
+    const REDIS_KEY_PREFIX_REPAIR_SURPLUS_PERF = 'calc:repairSurplusPerf_';
+    const REDIS_KEY_PREFIX_HAS_REPAIR_PERF_USER = 'calc:hasRepairPerfUser_';
+    const REDIS_KEY_PREFIX_HAS_REPAIR_PERF_USER_POOL = 'calc:hasRepairPerfPool_';
+
+    const INCOME_QY_BONUS_BD = 'BONUS_QY_BD';
+    const INCOME_BONUS_TRAVEL = 'BONUS_TRAVEL';
+    const INCOME_BONUS_CAR = 'BONUS_CAR';
+    const INCOME_BONUS_HOUSE = 'BONUS_HOUSE';
+    const CAPPED_BONUS_QY = 'CAPPED_BONUS_QY'; // 团队奖封顶前的金额
+    const INCOME_BONUS_LIST = [
+        self::INCOME_QY_BONUS_BD,
+    ];
+    const NOT_SEND_BONUS_LIST = [
+        self::INCOME_QY_BONUS_BD,
+        self::INCOME_BONUS_TRAVEL,
+        self::INCOME_BONUS_CAR,
+        self::INCOME_BONUS_HOUSE,
+        self::CAPPED_BONUS_QY,
+    ];
+
+    const FROM_MEANS_BD = 'BD';
+
+    /**
+     * 结算奖金时要清空的缓存
+     * @param $periodNum
+     */
+    public static function clearCalcBonusCache($periodNum) {
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_BONUS . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_BONUS_USER . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_BONUS_USER_POOL . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_FW_BONUS . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_FW_BONUS_USER . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_FW_BONUS_USER_POOL . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_MONTH_BONUS_USER . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_MONTH_BONUS_USER_POOL . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_FW_BONUS_LIST_DATA . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_BONUS_CROWN_CROWN_LIST_DATA . $periodNum);
+        DeclarationLevel::updateToCache();
+        Config::updateToCache();
+        DecRole::updateToCache();
+        EmployLevel::updateToCache();
+        StarCrownLevel::updateToCache();
+    }
+
+    /**
+     * 清网络缓存
+     */
+    public static function clearNetCache(){
+        \Yii::$app->redis->del(Cache::USER_NETWORK_PARENTS);
+        \Yii::$app->redis->del(Cache::USER_RELATION_PARENTS);
+    }
+
+    /**
+     * 清空所有临时计算用到的缓存
+     * @param $periodNum
+     */
+    public static function clearAll($periodNum) {
+        //Yii::$app->redis->del(Yii::$app->redis->keys('calc*'));
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_USER . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_USER_ACTIVE . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_USER_INFO . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_USER_BONUS . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_PERIOD_MONTH_CALC_BONUS . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_SURPLUS_PERF . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_USER_PERF . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_NOW_PERIOD_PERF . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_LAST_MONTH_PERF . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_NOW_MONTH_PERF . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_NOW_MONTH_LAST_PERIOD_RECONSUME_POINTS . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_EMP_NUM_PERF . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_BONUS . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_FW_BONUS . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_PERF_USER . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_PERF_USER_POOL . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_MONTH_PERF_USER . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_MONTH_PERF_USER_POOL . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_BD_USER . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_BD_USER_POOL . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_INCOME_USER . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_INCOME_USER_POOL . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_BONUS_USER . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_BONUS_USER_POOL . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_FW_BONUS_USER . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_FW_BONUS_USER_POOL . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_MONTH_BONUS_USER . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_MONTH_BONUS_USER_POOL . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_DEC_ROLE_CONFIG . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_USER_INFO_CHILD_ONE_DEEP . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_FW_BONUS_LIST_DATA . $periodNum);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_BONUS_CROWN_CROWN_LIST_DATA . $periodNum);
+        DeclarationLevel::updateToCache();
+        Config::updateToCache();
+        DecRole::updateToCache();
+        EmployLevel::updateToCache();
+        StarCrownLevel::updateToCache();
+    }
+
+    /**
+     * 会员信息加入缓存
+     * @param $periodNum
+     * @param int $offset
+     * @param int $limit
+     * @return bool
+     */
+    public static function addUsers($periodNum, $offset = 0, $limit = self::LIMIT) {
+        $allData = UserInfo::findUseDbCalc()
+            ->from(UserInfo::tableName(). 'AS UI')
+            ->select('UI.USER_ID,U.STATUS')
+            ->leftJoin(User::tableName() . 'AS U', 'U.ID=UI.USER_ID')
+            ->where('1=1')
+            ->orderBy('UI.ID DESC')
+            ->offset($offset)
+            ->limit($limit)
+            ->asArray()
+            ->all();
+        if ($allData) {
+            foreach ($allData as $data) {
+                $cacheKey = self::REDIS_KEY_PREFIX_USER . $periodNum;
+                $value = $data['USER_ID'];
+                Yii::$app->redis->rpush($cacheKey, $value);
+                if( $data['STATUS'] == 1 ) {
+                    $activeCacheKey = self::REDIS_KEY_PREFIX_USER_ACTIVE . $periodNum;
+                    Yii::$app->redis->rpush($activeCacheKey, $value);
+                    unset($activeCacheKey);
+                }
+                unset($data, $cacheKey, $value);
+            }
+            unset($allData);
+            return self::addUsers($periodNum, $offset + $limit, $limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 获取会员从缓存中
+     * @param $periodNum
+     * @param int $offset
+     * @param int $limit
+     * @return mixed
+     */
+    public static function getUsers($periodNum, $offset = 0, $limit = self::LIMIT) {
+        return Yii::$app->redis->lrange(self::REDIS_KEY_PREFIX_USER . $periodNum, $offset, ($offset + $limit - 1));
+    }
+
+    /**
+     * 获取激活会员从缓存中
+     * @param $periodNum
+     * @param int $offset
+     * @param int $limit
+     * @return mixed
+     */
+    public static function getActiveUsers($periodNum, $offset = 0, $limit = self::LIMIT) {
+        return Yii::$app->redis->lrange(self::REDIS_KEY_PREFIX_USER_ACTIVE . $periodNum, $offset, ($offset + $limit - 1));
+    }
+
+    /**
+     * 加入有业绩的会员
+     * @param $userId
+     * @param $periodNum
+     */
+    public static function addHasPerfUsers($userId, $periodNum) {
+        // 先从已存在的会员池里面获取
+        $isset = Yii::$app->redis->hget(self::REDIS_KEY_PREFIX_HAS_PERF_USER_POOL . $periodNum, $userId);
+        if (!$isset) {
+            Yii::$app->redis->hset(self::REDIS_KEY_PREFIX_HAS_PERF_USER_POOL . $periodNum, $userId, 1);
+            Yii::$app->redis->rpush(self::REDIS_KEY_PREFIX_HAS_PERF_USER . $periodNum, $userId);
+        }
+        unset($userId, $periodNum, $isset);
+    }
+
+    /**
+     * 获取有业绩的会员
+     * @param $periodNum
+     * @param int $offset
+     * @param int $limit
+     * @return mixed
+     */
+    public static function getHasPerfUsers($periodNum, $offset = 0, $limit = self::LIMIT) {
+        return Yii::$app->redis->lrange(self::REDIS_KEY_PREFIX_HAS_PERF_USER . $periodNum, $offset, ($offset + $limit - 1));
+    }
+
+    /**
+     * 加入有月业绩的会员
+     * @param $userId
+     * @param $periodNum
+     */
+    public static function addHasMonthPerfUsers($userId, $periodNum) {
+        // 先从已存在的会员池里面获取
+        $isset = Yii::$app->redis->hget(self::REDIS_KEY_PREFIX_HAS_MONTH_PERF_USER_POOL . $periodNum, $userId);
+        if (!$isset) {
+            Yii::$app->redis->hset(self::REDIS_KEY_PREFIX_HAS_MONTH_PERF_USER_POOL . $periodNum, $userId, 1);
+            Yii::$app->redis->rpush(self::REDIS_KEY_PREFIX_HAS_MONTH_PERF_USER . $periodNum, $userId);
+        }
+        unset($userId, $periodNum, $isset);
+    }
+
+    /**
+     * 获取有业绩的会员
+     * @param $periodNum
+     * @param int $offset
+     * @param int $limit
+     * @return mixed
+     */
+    public static function getHasMonthPerfUsers($periodNum, $offset = 0, $limit = self::LIMIT) {
+        return Yii::$app->redis->lrange(self::REDIS_KEY_PREFIX_HAS_MONTH_PERF_USER . $periodNum, $offset, ($offset + $limit - 1));
+    }
+
+    /**
+     * 加入被报单的会员
+     * @param $userId
+     * @param $periodNum
+     * @param $saveData
+     */
+    public static function addHasBDUsers($userId, $periodNum, $saveData) {
+        // 先从已存在的会员池里面获取
+        $data = Yii::$app->redis->hget(self::REDIS_KEY_PREFIX_HAS_BD_USER_POOL . $periodNum, $userId);
+        if (!$data) {
+            Yii::$app->redis->hset(self::REDIS_KEY_PREFIX_HAS_BD_USER_POOL . $periodNum, $userId, Json::encode($saveData));
+            Yii::$app->redis->rpush(self::REDIS_KEY_PREFIX_HAS_BD_USER . $periodNum, $userId);
+        }
+        unset($userId, $periodNum, $saveData, $isset);
+    }
+
+    /**
+     * 获取被报单的会员信息
+     * @param $userId
+     * @param $periodNum
+     * @return array
+     */
+    public static function getBDUsersInfo($userId, $periodNum) {
+        $data = Yii::$app->redis->hget(self::REDIS_KEY_PREFIX_HAS_BD_USER_POOL . $periodNum, $userId);
+        if( !$data ) return [];
+
+        return Json::decode($data, true);
+    }
+
+    /**
+     * 获取被报单的会员
+     * @param $periodNum
+     * @param int $offset
+     * @param int $limit
+     * @return mixed
+     */
+    public static function getHasBDUsers($periodNum, $offset = 0, $limit = self::LIMIT) {
+        return Yii::$app->redis->lrange(self::REDIS_KEY_PREFIX_HAS_BD_USER . $periodNum, $offset, ($offset + $limit - 1));
+    }
+
+    /**
+     * 加入有收入的会员
+     * @param $userId
+     * @param $periodNum
+     */
+    public static function addHasIncomeUsers($userId, $periodNum) {
+        // 先从已存在的会员池里面获取
+        $isset = Yii::$app->redis->hget(self::REDIS_KEY_PREFIX_HAS_INCOME_USER_POOL . $periodNum, $userId);
+        if (!$isset) {
+            Yii::$app->redis->hset(self::REDIS_KEY_PREFIX_HAS_INCOME_USER_POOL . $periodNum, $userId, 1);
+            Yii::$app->redis->rpush(self::REDIS_KEY_PREFIX_HAS_INCOME_USER . $periodNum, $userId);
+        }
+
+        unset($userId, $periodNum, $isset);
+    }
+
+    /**
+     * 获取有收入的会员
+     * @param $periodNum
+     * @param int $offset
+     * @param int $limit
+     * @return mixed
+     */
+    public static function getHasIncomeUsers($periodNum, $offset = 0, $limit = self::LIMIT) {
+        return Yii::$app->redis->lrange(self::REDIS_KEY_PREFIX_HAS_INCOME_USER . $periodNum, $offset, ($offset + $limit - 1));
+    }
+
+    /**
+     * 加入有奖金的会员
+     * @param $userId
+     * @param $periodNum
+     */
+    public static function addHasBonusUsers($userId, $periodNum) {
+        // 先从已存在的会员池里面获取
+        $isset = Yii::$app->redis->hget(self::REDIS_KEY_PREFIX_HAS_BONUS_USER_POOL . $periodNum, $userId);
+        if (!$isset) {
+            Yii::$app->redis->hset(self::REDIS_KEY_PREFIX_HAS_BONUS_USER_POOL . $periodNum, $userId, 1);
+            Yii::$app->redis->rpush(self::REDIS_KEY_PREFIX_HAS_BONUS_USER . $periodNum, $userId);
+        }
+
+        unset($userId, $periodNum, $isset);
+    }
+
+    /**
+     * 获取有奖金的会员
+     * @param $periodNum
+     * @param int $offset
+     * @param int $limit
+     * @return mixed
+     */
+    public static function getHasBonusUsers($periodNum, $offset = 0, $limit = self::LIMIT) {
+        return Yii::$app->redis->lrange(self::REDIS_KEY_PREFIX_HAS_BONUS_USER . $periodNum, $offset, ($offset + $limit - 1));
+    }
+
+    /**
+     * 获取有服务奖的会员
+     * @param $periodNum
+     * @param int $offset
+     * @param int $limit
+     * @return mixed
+     */
+    public static function getHasFwBonusUsers($periodNum, $offset = 0, $limit = self::LIMIT) {
+        return Yii::$app->redis->lrange(self::REDIS_KEY_PREFIX_HAS_FW_BONUS_USER . $periodNum, $offset, ($offset + $limit - 1));
+    }
+
+    /**
+     * 加入服务奖的会员
+     * @param $userId
+     * @param $periodNum
+     */
+    public static function addHasFwBonusUsers($userId, $periodNum) {
+        // 先从已存在的会员池里面获取
+        $isset = Yii::$app->redis->hget(self::REDIS_KEY_PREFIX_HAS_FW_BONUS_USER_POOL . $periodNum, $userId);
+        if (!$isset) {
+            Yii::$app->redis->hset(self::REDIS_KEY_PREFIX_HAS_FW_BONUS_USER_POOL . $periodNum, $userId, 1);
+            Yii::$app->redis->rpush(self::REDIS_KEY_PREFIX_HAS_FW_BONUS_USER . $periodNum, $userId);
+        }
+
+        unset($userId, $periodNum, $isset);
+    }
+
+    /**
+     * 获取有月奖的会员
+     * @param $periodNum
+     * @param int $offset
+     * @param int $limit
+     * @return mixed
+     */
+    public static function getHasMonthBonusUsers($periodNum, $offset = 0, $limit = self::LIMIT) {
+        return Yii::$app->redis->lrange(self::REDIS_KEY_PREFIX_HAS_MONTH_BONUS_USER . $periodNum, $offset, ($offset + $limit - 1));
+    }
+
+    /**
+     * 保存服务奖奖金
+     * @param $userId
+     * @param $periodNum
+     * @param $fwBonus
+     * @param array $fromData
+     */
+    public static function saveFwBonusList($userId, $periodNum, $fwBonus, $fromData=[]) {
+        $userFwBonusData = Yii::$app->redis->hget(self::REDIS_KEY_PREFIX_FW_BONUS_LIST_DATA . $periodNum, $userId);
+        if( $userFwBonusData ) {
+            $fwBonusData = Json::decode($userFwBonusData, true);
+            $fwBonusData['fwBonus'] += $fwBonus;
+        }else {
+            $fwBonusData = [
+                'fwBonus' => $fwBonus,
+            ];
+        }
+        unset($userFwBonusData);
+
+        Yii::$app->redis->hset(self::REDIS_KEY_PREFIX_FW_BONUS_LIST_DATA . $periodNum, $userId, Json::encode($fwBonusData));
+        unset($fwBonusData, $userId, $periodNum, $empBonus, $fromData);
+    }
+
+    /**
+     * 返回服务奖信息
+     * @param $userId
+     * @param $periodNum
+     * @return array
+     */
+    public static function getFwBonusList($userId, $periodNum) {
+        $userYcBonusData = Yii::$app->redis->hget(self::REDIS_KEY_PREFIX_FW_BONUS_LIST_DATA . $periodNum, $userId);
+
+        return $userYcBonusData ? Json::decode($userYcBonusData, true) : [];
+    }
+
+    /**
+     * 会员星级加入缓存
+     * @param $userId
+     * @param $periodNum
+     * @param $crownCrown
+     */
+    public static function addUserStarCrown($userId, $periodNum, $starCrown) {
+        // 先从已存在的会员池里面获取
+        $data = Yii::$app->redis->hget(self::REDIS_KEY_PREFIX_BONUS_CROWN_CROWN_LIST_DATA . $periodNum, $userId);
+        if (!$data) {
+            Yii::$app->redis->hset(self::REDIS_KEY_PREFIX_BONUS_CROWN_CROWN_LIST_DATA . $periodNum, $userId, $starCrown);
+        }
+
+        unset($userId, $periodNum, $saveData, $isset);
+    }
+
+    /**
+     * 获取会员星级信息
+     * @param $userId
+     * @param $periodNum
+     * @return array
+     */
+    public static function getUserStarCrown($userId, $periodNum) {
+        return Yii::$app->redis->hget(self::REDIS_KEY_PREFIX_BONUS_CROWN_CROWN_LIST_DATA . $periodNum, $userId);
+    }
+
+    /**
+     * 有车房补贴比例的人
+     * @param $userId
+     * @param $periodNum
+     */
+    public static function addHasCFPercentUsers($userId, $periodNum) {
+        $isset = Yii::$app->redis->hget(self::REDIS_KEY_PREFIX_HAS_CF_PERCENT_USER_POOL . $periodNum, $userId);
+        if (!$isset) {
+            Yii::$app->redis->hset(self::REDIS_KEY_PREFIX_HAS_CF_PERCENT_USER_POOL . $periodNum, $userId, 1);
+            $key = self::REDIS_KEY_PREFIX_HAS_CF_PERCENT_USER . $periodNum;
+            $value = $userId;
+            Yii::$app->redis->rpush($key, $value);
+        }
+    }
+
+    /**
+     * 获取有车房补贴的人
+     * @param $periodNum
+     * @param int $offset
+     * @param int $limit
+     * @return mixed
+     */
+    public static function getHasCFPercentUsers($periodNum, $offset = 0, $limit = self::LIMIT) {
+        return Yii::$app->redis->lrange(self::REDIS_KEY_PREFIX_HAS_CF_PERCENT_USER . $periodNum, $offset, ($offset + $limit - 1));
+    }
+
+    /**
+     * 通过创建时间获取指定长度的用户列表
+     * @param $userId
+     * @param $limit
+     * @return array|\yii\db\ActiveRecord[]
+     */
+    public static function getUserListByCreatedAtFromDb($userId, $limit)
+    {
+        //查找这个人的CREATED_AT
+        $user = User::findUseDbCalc()->select('ID,CREATED_AT')->where('ID=:ID', ['ID'=>$userId])->asArray()->one();
+        if( !$user ) return [];
+
+        return User::findUseDbCalc()->select('ID,CREATED_AT')->where('ID<:ID AND CREATED_AT<=:CREATED_AT', [
+            'ID'=>$userId,
+            'CREATED_AT' => $user['CREATED_AT']
+        ])->orderBy('CREATED_AT DESC,ID DESC')->limit($limit)->asArray()->all();
+    }
+
+    /**
+     * 通过创建时间获取指定长度的用户列表
+     * @param $userId
+     * @param $limit
+     * @return array|\yii\db\ActiveRecord[]
+     */
+    public static function getAfterUserListByCreatedAtFromDb($userId, $limit)
+    {
+        $cacheKey = Cache::USER_CREATED_AT_LIST . $limit;
+        $value = Yii::$app->redis->hGet($cacheKey, $userId);
+        if( $value ) {
+            unset($cacheKey);
+            return json_decode($value, true);
+        }
+        unset($value);
+
+        $list = UserInfo::findUseDbCalc()->select('USER_ID')->where('USER_ID>:USER_ID', [
+            'USER_ID'=>$userId,
+        ])->orderBy('USER_ID ASC')->limit($limit)->asArray()->all();
+        if ( $limit == count($list) ) {
+            Yii::$app->redis->hSet($cacheKey, $userId, json_encode($list));
+        }
+
+        unset($cacheKey);
+        return $list;
+    }
+
+    // 从缓存获取哪些奖金需要扣除管理费和复消积分
+    public static function deductBonus($periodNum) {
+
+    }
+
+    /**
+     * 获取会员信息从缓存
+     * @param $userId
+     * @param $periodNum
+     * @return array
+     * @throws \Exception
+     */
+    public static function getUserInfo($userId, $periodNum) {
+        $key = self::REDIS_KEY_PREFIX_USER_INFO . $periodNum;
+        $data = Yii::$app->redis->hget($key, $userId);
+        $data = [];
+        if (empty($data)) {
+            $userInfo = UserInfo::findUseDbCalc()
+            ->select('USER_ID,USER_NAME,CON_UID,REC_UID')
+            ->where('USER_ID=:USER_ID', [':USER_ID' => $userId])
+            ->asArray()
+            ->one();
+            if( !$userInfo ) {
+                throw new \Exception('用户信息表数据不存在,userId:'.$userId);
+            }
+            $userShopInfo = User::find()
+            ->select('LAST_DEC_LV,REAL_NAME,DEC_LV,EMP_LV,CROWN_LV,IS_DEC,STATUS,IS_STUDIO,DEC_ID,DEC_ROLE_ID,LAST_EMP_LV')
+            ->where('ID=:ID', [':ID' => $userId])
+            ->asArray()
+            ->one();
+            if ( !$userShopInfo ) {
+                throw new \Exception('用户表数据不存在,userId:'.$userId);
+            }
+            //$bsEmpLv = Period::userLastEmpLv($userId, $periodNum);
+            // 用户的最新蓝星级别,需要修改调整因为计算服务这里并没有以前的级别数据表数据,所以无法查询出以前的最近月节点数据
+            // 获取用户的最新级别数据,根据蓝星最近一期月结数据进行查询
+            $userInfo['REAL_NAME'] = $userShopInfo['REAL_NAME'];
+            $userInfo['DEC_LV'] = $userShopInfo['LAST_DEC_LV'];
+            $userInfo['EMP_LV'] = $userShopInfo['EMP_LV'];
+            $userInfo['LAST_EMP_LV'] = $userShopInfo['LAST_EMP_LV'];// 最新的聘级
+            $userInfo['CROWN_LV'] = $userShopInfo['CROWN_LV'];
+            $userInfo['IS_DEC'] = $userShopInfo['IS_DEC'];
+            $userInfo['STATUS'] = $userShopInfo['STATUS'];
+            $userInfo['DEC_ID'] = $userShopInfo['DEC_ID'];
+            $userInfo['DEC_ROLE_ID'] = $userShopInfo['DEC_ROLE_ID'];
+            unset($userShopInfo);
+            if (!$userInfo['DEC_LV']) {
+                $userInfo['DEC_LV'] = DeclarationLevel::getDefaultLevelId();
+            }
+            if (!$userInfo['EMP_LV']) {
+                $userInfo['EMP_LV'] = EmployLevel::getDefaultLevelId();
+            }
+            if (!$userInfo['CROWN_LV']) {
+                $userInfo['CROWN_LV'] = StarCrownLevel::getDefaultLevelId();
+            }
+            $data = Json::encode($userInfo);
+            Yii::$app->redis->hset($key, $userId, $data);
+            unset($key, $userId, $periodNum);
+            return $userInfo;
+        } else {
+            if ($data) {
+                $ret = Json::decode($data, true);
+                //$bsEmpLv = Period::userLastEmpLv($userId, $periodNum);
+                $ret['LAST_EMP_LV'] = EmployLevel::NO_LEVEL_ID; // 最新的聘级
+                return $ret;
+            } else {
+                return [];
+            }
+        }
+    }
+
+    public static function setUserInfo($userId, $periodNum, $userInfo) {
+        $key = self::REDIS_KEY_PREFIX_USER_INFO . $periodNum;
+        $data = Json::encode($userInfo);
+        Yii::$app->redis->hset($key, $userId, $data);
+        unset($userId, $key, $data, $userInfo, $periodNum);
+        return true;
+    }
+
+    /**
+     * 本月往期的复消积分的数据
+     * @param $userId
+     * @param $periodNum
+     * @param $calcYearMonth
+     * @return array|mixed|null|\yii\db\ActiveRecord
+     * @throws \yii\db\Exception
+     */
+    public static function monthLastPeriodReconsumePoints($userId, $periodNum, $calcYearMonth) {
+        $cacheKey = self::REDIS_KEY_PREFIX_NOW_MONTH_LAST_PERIOD_RECONSUME_POINTS . $periodNum;
+        $cacheValue = \Yii::$app->redis->hget($cacheKey, $userId);
+        if ($cacheValue) {
+            $value = Json::decode($cacheValue, true);
+        } else {
+            $value = DeductReconsume::findUseDbCalc()
+            ->select(["RECONSUME_POINTS_SUM"])
+            ->where('USER_ID=:USER_ID AND CALC_MONTH=:CALC_MONTH', [':USER_ID' => $userId, ':CALC_MONTH'=>$calcYearMonth])
+            ->asArray()
+            ->one();
+            //$value = CalcBonus::findUseDbCalc()->select(["SUM(RECONSUME_POINTS) as RECONSUME_POINTS_SUM"])->where('USER_ID=:USER_ID AND CALC_MONTH=:CALC_MONTH', [':USER_ID' => $userId, ':CALC_MONTH'=>$calcYearMonth])->asArray()->one();
+            \Yii::$app->redis->hset($cacheKey, $userId, Json::encode($value));
+        }
+        return $value;
+    }
+
+    /**
+     * 获取结算时这一期的报单中心级别的配置
+     * @param $periodNum
+     * @return array|\yii\db\ActiveRecord[]
+     */
+    public static function getDecRoleConfig($periodNum) {
+        $key = self::REDIS_KEY_PREFIX_DEC_ROLE_CONFIG . $periodNum;
+        Yii::$app->redis->del($key);
+        $data = Yii::$app->redis->get($key);
+        if (!$data) {
+            $data = DecRole::find()->where('1=1')->indexBy('ID')->asArray()->all();
+            $data = Json::encode($data);
+            Yii::$app->redis->set($key, $data);
+        }
+        return $data ? Json::decode($data) : [];
+    }
+
+    /**
+     * 上一期结余业绩
+     * @param $userId
+     * @param $periodNum
+     * @return array|mixed|\yii\db\ActiveRecord|null
+     * @throws \yii\db\Exception
+     */
+    public static function surplusPerf($userId, $periodNum) {
+        $userPerf = self::userPerf($userId, $periodNum);
+        return [
+            'SURPLUS_1L' => $userPerf['SURPLUS_1L'],
+            'SURPLUS_2L' => $userPerf['SURPLUS_2L'],
+            'SURPLUS_3L' => $userPerf['SURPLUS_3L'],
+            'SURPLUS_4L' => $userPerf['SURPLUS_4L'],
+            'SURPLUS_5L' => $userPerf['SURPLUS_5L'],
+            'SURPLUS_1L_ZC' => $userPerf['SURPLUS_1L_ZC'],
+            'SURPLUS_2L_ZC' => $userPerf['SURPLUS_2L_ZC'],
+            'SURPLUS_3L_ZC' => $userPerf['SURPLUS_3L_ZC'],
+            'SURPLUS_4L_ZC' => $userPerf['SURPLUS_4L_ZC'],
+            'SURPLUS_5L_ZC' => $userPerf['SURPLUS_5L_ZC'],
+            'SURPLUS_1L_FX' => $userPerf['SURPLUS_1L_FX'],
+            'SURPLUS_2L_FX' => $userPerf['SURPLUS_2L_FX'],
+            'SURPLUS_3L_FX' => $userPerf['SURPLUS_3L_FX'],
+            'SURPLUS_4L_FX' => $userPerf['SURPLUS_4L_FX'],
+            'SURPLUS_5L_FX' => $userPerf['SURPLUS_5L_FX'],
+        ];
+    }
+
+    /**
+     * 获取repairSurplusPerf
+     * @param $userId
+     * @return int[]|mixed|null
+     */
+    public static function getRepairSurplusPerf($userId) {
+        $cacheKey = self::REDIS_KEY_PREFIX_REPAIR_SURPLUS_PERF;
+        $cacheValue = \Yii::$app->redis->hget($cacheKey, $userId);
+        if ($cacheValue) {
+            $value = Json::decode($cacheValue);
+        }else {
+            $value = [
+                'SURPLUS_1L' => 0,
+                'SURPLUS_2L' => 0,
+                'SURPLUS_3L' => 0,
+                'SURPLUS_4L' => 0,
+                'SURPLUS_5L' => 0,
+            ];
+        }
+
+        return $value;
+    }
+
+    /**
+     * 累加repairSurplusPerf
+     * @param $userId
+     * @param $surplusPerf
+     * @return int[]|mixed|null
+     */
+    public static function setRepairSurplusPerf($userId, $surplusPerf) {
+        $surplusPerfList = self::getRepairSurplusPerf($userId);
+        foreach ($surplusPerfList as $key => $perf) {
+            $thisPerf = $surplusPerf[$key] ?? 0;
+            $surplusPerfList[$key] = $perf + $thisPerf;
+
+            unset($key, $perf, $thisPerf);
+        }
+
+        $cacheKey = self::REDIS_KEY_PREFIX_REPAIR_SURPLUS_PERF;
+        \Yii::$app->redis->hset($cacheKey, $userId, Json::encode($surplusPerfList));
+        self::addHasRepairPerfUsers($userId);
+
+        unset($userId, $cacheKey, $surplusPerf);
+
+        return $surplusPerfList;
+    }
+
+    /**
+     * 加入有往期结余业绩的会员
+     * @param $userId
+     */
+    public static function addHasRepairPerfUsers($userId) {
+        // 先从已存在的会员池里面获取
+        $isset = Yii::$app->redis->hget(self::REDIS_KEY_PREFIX_HAS_REPAIR_PERF_USER_POOL, $userId);
+        if (!$isset) {
+            Yii::$app->redis->hset(self::REDIS_KEY_PREFIX_HAS_REPAIR_PERF_USER_POOL, $userId, 1);
+            Yii::$app->redis->rpush(self::REDIS_KEY_PREFIX_HAS_REPAIR_PERF_USER, $userId);
+        }
+        unset($userId, $isset);
+    }
+
+    /**
+     * 获取有往期结余业绩的会员
+     * @param int $offset
+     * @param int $limit
+     * @return mixed
+     */
+    public static function getHasRepairPerfUsers($offset = 0, $limit = self::LIMIT) {
+        return Yii::$app->redis->lrange(self::REDIS_KEY_PREFIX_HAS_REPAIR_PERF_USER, $offset, ($offset + $limit - 1));
+    }
+
+    /**
+     * 修正业绩缓存清空
+     */
+    public static function clearRepairAllCache() {
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_REPAIR_SURPLUS_PERF);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_REPAIR_PERF_USER);
+        Yii::$app->redis->del(self::REDIS_KEY_PREFIX_HAS_REPAIR_PERF_USER_POOL);
+    }
+
+    /**
+     * 上一期结余业绩
+     * @param $userId
+     * @param $periodNum
+     * @return array|mixed|\yii\db\ActiveRecord|null
+     * @throws \yii\db\Exception
+     */
+    public static function userPerf($userId, $periodNum) {
+        $cacheKey = self::REDIS_KEY_PREFIX_USER_PERF . $periodNum;
+        $field = $userId;
+        $cacheValue = \Yii::$app->redis->hget($cacheKey, $field);
+        if ($cacheValue) {
+            $value = Json::decode($cacheValue);
+        } else {
+            $value = UserPerf::getOneUserPerf($userId);
+            \Yii::$app->redis->hset($cacheKey, $field, Json::encode($value));
+        }
+        return $value;
+    }
+
+    /**
+     * 当前期数的的业绩
+     * @param $userId
+     * @param $periodNum
+     * @param null $perf
+     * @return array|mixed
+     */
+    public static function nowPeriodPerf($userId, $periodNum, $perf = null) {
+        $cacheKey = self::REDIS_KEY_PREFIX_NOW_PERIOD_PERF . $periodNum;
+        $field = $userId;
+        $cacheValue = \Yii::$app->redis->hget($cacheKey, $field);
+        if ($cacheValue) {
+            $value = Json::decode($cacheValue);
+        } else {
+            $value = [
+                'FX_AMOUNT_CASH' => 0,
+                'PV_PCS' => 0,
+                'PV_PSS' => 0,//本期的团队业绩
+                'PV_PCS_ZC' => 0,//注册
+                'PV_PCS_FX' => 0,
+                'PV_PCS_FX_CASH' => 0,//现金复消
+                'PV_PCS_FX_POINT' => 0,//积分复消
+                'PV_1L' => 0,
+                'PV_1L_TOUCH' => 0,
+                'PV_1L_ZC' => 0,
+                'PV_1L_FX' => 0,
+                'PV_2L' => 0,
+                'PV_2L_TOUCH' => 0,
+                'PV_2L_ZC' => 0,
+                'PV_2L_FX' => 0,
+                'PV_3L' => 0,
+                'PV_3L_TOUCH' => 0,
+                'PV_3L_ZC' => 0,
+                'PV_3L_FX' => 0,
+                'PV_4L' => 0,
+                'PV_4L_TOUCH' => 0,
+                'PV_4L_ZC' => 0,
+                'PV_4L_FX' => 0,
+                'PV_5L' => 0,
+                'PV_5L_TOUCH' => 0,
+                'PV_5L_ZC' => 0,
+                'PV_5L_FX' => 0,
+                'SURPLUS_1L' => 0,
+                'SURPLUS_2L' => 0,
+                'SURPLUS_3L' => 0,
+                'SURPLUS_4L' => 0,
+                'SURPLUS_5L' => 0,
+                'SURPLUS_1L_ZC' => 0,
+                'SURPLUS_2L_ZC' => 0,
+                'SURPLUS_3L_ZC' => 0,
+                'SURPLUS_4L_ZC' => 0,
+                'SURPLUS_5L_ZC' => 0,
+                'SURPLUS_1L_FX' => 0,
+                'SURPLUS_2L_FX' => 0,
+                'SURPLUS_3L_FX' => 0,
+                'SURPLUS_4L_FX' => 0,
+                'SURPLUS_5L_FX' => 0,
+            ];
+        }
+        if ($perf !== null) {
+            foreach ($perf as $key => $pv) {
+                if (strpos($key, 'SURPLUS') !== false) {
+                    $value[$key] = $pv;
+                } else {
+                    $value[$key] = $pv + $value[$key];
+                }
+            }
+            Yii::$app->redis->hset($cacheKey, $field, Json::encode($value));
+        }
+        return $value;
+    }
+
+    /**
+     * 上个月的业绩
+     * @param $userId
+     * @param $periodNum
+     * @return array|mixed|null|\yii\db\ActiveRecord
+     */
+    public static function lastMonthPerf($userId, $periodNum) {
+        $period = Period::instance();
+        $last = $period->getLastMonth($periodNum);
+        $lastYearMonth = $last['yearMonth'];
+        $cacheKey = self::REDIS_KEY_PREFIX_LAST_MONTH_PERF . $periodNum;
+        $field = $userId;
+        $cacheValue = \Yii::$app->redis->hget($cacheKey, $field);
+        if ($cacheValue) {
+            $value = Json::decode($cacheValue);
+        } else {
+            $value = PerfMonth::getMonthPerf($lastYearMonth, $userId);
+            \Yii::$app->redis->hset($cacheKey, $field, Json::encode($value));
+        }
+        return $value;
+    }
+
+    /**
+     * 当前月的业绩
+     * @param $userId
+     * @param $periodNum
+     * @param $perf
+     * @return array|mixed
+     */
+    public static function nowMonthPerf($userId, $periodNum, $perf = null) {
+        $cacheKey = self::REDIS_KEY_PREFIX_NOW_MONTH_PERF . $periodNum;
+        $field = $userId;
+        $cacheValue = \Yii::$app->redis->hget($cacheKey, $field);
+        if ($cacheValue) {
+            $value = Json::decode($cacheValue);
+        } else {
+            $baseInfo = self::getUserInfo($userId, $periodNum);
+            $value = [
+                'USER_ID' => $userId,
+                'FX_AMOUNT_CASH' => 0,
+                'PV_PCS' => 0,
+                'PV_PCS_FX' => 0,
+                'PV_PSS' => 0,
+                'PV_1L' => 0,
+                'PV_2L' => 0,
+                'PV_3L' => 0,
+                'PV_4L' => 0,
+                'PV_5L' => 0,
+                'VIP_PV_1L_ZC' => 0,
+                'VIP_PV_2L_ZC' => 0,
+                'VIP_PV_3L_ZC' => 0,
+                'VIP_PV_4L_ZC' => 0,
+                'VIP_PV_5L_ZC' => 0,
+                'PV_1L_TOTAL' => 0,
+                'PV_2L_TOTAL' => 0,
+                'PV_3L_TOTAL' => 0,
+                'PV_4L_TOTAL' => 0,
+                'PV_5L_TOTAL' => 0,
+                'PV_PSS_TOTAL' => 0,
+                'DEC_LEVEL' => $baseInfo['DEC_LV'],
+                'EMP_LEVEL' => EmployLevel::getDefaultLevelId(),
+            ];
+        }
+        if ($perf !== null) {
+            foreach ($perf as $key => $item) {
+                $value[$key] = $item;
+            }
+        }
+        Yii::$app->redis->hset($cacheKey, $field, Json::encode($value));
+        return $value;
+    }
+
+    /**
+     * 获取直推的所有子会员
+     * @param $userId
+     * @param $periodNum
+     * @return array
+     */
+    public static function getChildrenOneDeepFromRedis($userId, $periodNum){
+        $key = self::REDIS_KEY_PREFIX_USER_INFO_CHILD_ONE_DEEP . $periodNum;
+        $data = Yii::$app->redis->hget($key, $userId);
+        if(!$data){
+            $list = UserInfo::findUseDbCalc()->select('USER_ID')->where('REC_UID=:REC_UID', [
+                'REC_UID' => $userId
+            ])->asArray()->all();
+            $data = Json::encode($list);
+            unset($list);
+            Yii::$app->redis->hset($key, $userId, $data);
+        }
+        unset($key, $userId, $periodNum);
+        return $data ? Json::decode($data, true) : [];
+    }
+
+    /**
+     * 服务奖最大比例
+     * @param $userId
+     * @param $periodNum
+     * @param int $percent
+     * @return float|int
+     */
+    public static function fwMaxBonusPercent($userId, $periodNum, $percent=0) {
+        $cacheKey = self::REDIS_KEY_PREFIX_FW_BONUS . $periodNum;
+        $value = Yii::$app->redis->hget($cacheKey, $userId);
+        $maxPercent = floatval($value)>0 ? floatval($value) : 0;
+        if ( $percent > 0 && $percent > $maxPercent ) {
+            $maxPercent = $percent;
+            unset($periodNum, $percent);
+            Yii::$app->redis->hset($cacheKey, $userId, $maxPercent);
+
+            return $maxPercent;
+        }
+
+        unset($userId, $periodNum, $oriBonus, $cacheKey, $value);
+        return $maxPercent;
+    }
+
+    /**
+     * 奖金缓存
+     * @param $userId
+     * @param $periodNum
+     * @param string $bonusType
+     * @param float $oriBonus
+     * @param array $deductData
+     * @return array|mixed
+     */
+    public static function bonus($userId, $periodNum, $bonusType = null, $oriBonus = 0.00, $deductData=[], $fromMeans='') {
+        $cacheKey = self::REDIS_KEY_PREFIX_BONUS . $periodNum;
+        $value = [
+            'BONUS_TG' => 0,
+            'BONUS_QY' => 0,
+            'BONUS_BS' => 0,
+            'BONUS_QUARTER' => 0,
+            'BONUS_BD' => 0,
+            'ORI_BONUS_BS' => 0,
+            'ORI_BONUS_BD' => 0,
+            'ORI_BONUS_QUARTER' => 0,
+            'ORI_BONUS_TG' => 0,
+            'ORI_BONUS_QY' => 0,
+            'INCOME_TOTAL' => 0,
+            'BONUS_TOTAL' => 0,
+            'RECONSUME_POINTS' => 0,
+            'MANAGE_TAX' => 0,
+            'ORI_CAPPED_BONUS_QY' => 0,// 团队奖,封顶前金额
+        ];
+        // 从 redis 中获取当前的结果
+        $cacheValue = \Yii::$app->redis->hget($cacheKey, $userId);
+        if ($cacheValue) {
+            $cacheValue = Json::decode($cacheValue);
+            $value = $cacheValue;
+        }
+        unset($cacheValue);
+        if ($oriBonus > 0) {
+            $oriBonusType = sprintf('ORI_%s', $bonusType);
+            $value[$oriBonusType] += $oriBonus;
+            if( $fromMeans !== '' ) {
+                $oriBonusMeansType = sprintf('ORI_%s_%s', $bonusType, $fromMeans);
+                $value[$oriBonusMeansType] += $oriBonus;
+            }
+
+            if( !in_array($bonusType, self::NOT_SEND_BONUS_LIST) ) {
+                if( $deductData ) {
+                    $value[$bonusType] += $deductData['surplus'];
+                    $value['RECONSUME_POINTS'] += $deductData['reConsumePoints'];
+                    $value['MANAGE_TAX'] += $deductData['manageTax'];
+                }else {
+                    $value[$bonusType] += $oriBonus;
+                }
+                $value['BONUS_TOTAL'] += $oriBonus;
+            }
+
+            if( in_array($bonusType, self::INCOME_BONUS_LIST) ) {
+                self::addHasIncomeUsers($userId, $periodNum);
+//                if( !in_array($bonusType, self::NOT_SEND_BONUS_LIST) ) {
+//                    $value['INCOME_TOTAL'] += $oriBonus;
+//                }
+            }
+
+            Yii::$app->redis->hset($cacheKey, $userId, Json::encode($value));
+            unset($oriBonusType);
+            self::addHasBonusUsers($userId, $periodNum);
+        }
+        unset($userId, $periodNum, $bonusType, $oriBonus, $deductData, $cacheKey);
+        return $value;
+    }
+
+    public static function tourismBonus($userId, $periodNum, $bonus = 0.00) {
+        $cacheKey = self::REDIS_KEY_PREFIX_TOURISM_BONUS . $periodNum;
+
+        $value = 0.00;
+        if( $bonus > 0 ) {
+            Yii::$app->redis->hset($cacheKey, $userId, $bonus);
+            $value = $bonus;
+            //加入有奖金的会员中
+            self::addHasBonusUsers($userId, $periodNum);
+        }else {
+            $cacheValue = \Yii::$app->redis->hget($cacheKey, $userId);
+            if ($cacheValue) {
+                $value = $cacheValue;
+            }
+            unset($cacheValue);
+        }
+
+        unset($cacheKey, $userId, $periodNum, $bonus);
+        return $value;
+    }
+
+    public static function villaBonus($userId, $periodNum, $bonus = 0.00) {
+        $cacheKey = self::REDIS_KEY_PREFIX_VILLA_BONUS . $periodNum;
+
+        $value = 0.00;
+        if( $bonus > 0 ) {
+            Yii::$app->redis->hset($cacheKey, $userId, $bonus);
+            $value = $bonus;
+            //加入有奖金的会员中
+            self::addHasBonusUsers($userId, $periodNum);
+        }else {
+            $cacheValue = \Yii::$app->redis->hget($cacheKey, $userId);
+            if ($cacheValue) {
+                $value = $cacheValue;
+            }
+            unset($cacheValue);
+        }
+
+        unset($cacheKey, $userId, $periodNum, $bonus);
+        return $value;
+    }
+
+    public static function garageBonus($userId, $periodNum, $bonus = 0.00) {
+        $cacheKey = self::REDIS_KEY_PREFIX_GARAGE_BONUS . $periodNum;
+
+        $value = 0.00;
+        if( $bonus > 0 ) {
+            Yii::$app->redis->hset($cacheKey, $userId, $bonus);
+            $value = $bonus;
+            //加入有奖金的会员中
+            self::addHasBonusUsers($userId, $periodNum);
+        }else {
+            $cacheValue = \Yii::$app->redis->hget($cacheKey, $userId);
+            if ($cacheValue) {
+                $value = $cacheValue;
+            }
+            unset($cacheValue);
+        }
+
+        unset($cacheKey, $userId, $periodNum, $bonus);
+        return $value;
+    }
+}

+ 1388 - 0
common/helpers/bonus/CalcServeBonusCalc.php

@@ -0,0 +1,1388 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/8/2
+ * Time: 上午10:32
+ */
+
+namespace common\helpers\bonus;
+
+use common\helpers\Cache;
+use common\helpers\Date;
+use common\helpers\snowflake\SnowFake;
+use common\helpers\Tool;
+use common\models\CalcBonus;
+use common\models\CalcBonusBD;
+use common\models\CalcBonusBS;
+use common\models\CalcBonusQuarter;
+use common\models\CalcBonusGarage;
+use common\models\CalcBonusQY;
+use common\models\CalcBonusTG;
+use common\models\CalcBonusTourism;
+use common\models\CalcBonusVilla;
+use common\models\Config;
+use common\models\DeclarationLevel;
+use common\models\EmployLevel;
+use common\models\PerfMonth;
+use common\models\PerfPeriod;
+use common\models\Period;
+use common\models\ServeProcess;
+use common\models\StarCrownLevel;
+use Exception;
+use Yii;
+use yii\base\BaseObject;
+use yii\base\StaticInstanceTrait;
+use yii\db\Query;
+
+class CalcServeBonusCalc extends BaseObject {
+    use StaticInstanceTrait;
+
+    private $_limit = 3000;
+    private $_handleUserId;
+    private $_sysConfig = [];
+    private $_decLevelConfig = [];
+    private $_empLevelConfig = [];
+    private $_starCrownLevelConfig = [];
+    private $_decRoleConfig = [];
+    private $_errors = [];
+    private $_periodNum = 0;
+    private $_periodId;
+    private $_isCalcMonth = 0;
+    private $_calcYear;
+    private $_calcMonth;
+    private $_calcYearMonth;
+    private $_calcMonthPeriodNumCount = 0;
+    private $_isPerpare;
+    //pv
+    private $_pvRatio;
+    private $_calcZone = ['openTravel', 'openCar', 'openHouse'];
+
+    const LOOP_FINISH = 1;
+    const LOOP_CONTINUE = 2;
+
+    const ORDER_TYPE_TO_FW_COEFFICIENT = [
+        'ZC' => 'fwCoefficientFromZc',
+        'FX_CASH' => 'fwCoefficientFromFxCash',
+        'FX_POINT' => 'fwCoefficientFromFxPoint',
+    ];
+
+    //最小报单pv
+    const MIN_BD_PV = 980;
+
+    public function init() {
+        parent::init();
+    }
+
+    /**
+     * 设置期数
+     * @param int $periodNum
+     * @return int
+     */
+    public function setPeriodNum(int $periodNum) {
+        return $this->_periodNum = $periodNum;
+    }
+
+    /**
+     * 获取期数
+     * @return int
+     */
+    public function getPeriodNum() {
+        return $this->_periodNum;
+    }
+
+    /**
+     * 加入错误错误
+     * @param $attr
+     * @param $error
+     */
+    public function addError($attr, $error) {
+        $this->_errors[$attr][] = $error;
+    }
+
+    /**
+     * 获取错误信息
+     * @return array
+     */
+    public function getErrors() {
+        return $this->_errors;
+    }
+
+    /**
+     * 开始执行结算步骤
+     * @param $periodNum
+     * @param null $handleUserId
+     * @return bool
+     */
+    public function calcStep() {
+        try {
+            $this->_errors = [];
+            $this->setPeriodNum(0);
+            $this->_handleUserId = '';
+            $t1 = microtime(true);
+            // 初始化结算任务
+            $this->initCalcTask();
+            // 判断是否能开始进行计算奖金
+            $checkStart = $this->checkStart();
+            if (!$checkStart) {
+                return false;
+            } else {
+                // 更新状态为,奖金计算中
+                Period::updateCalcProcess(4, $this->_periodNum);
+            }
+            $t2 = microtime(true);
+            ServeProcess::recordProcess($t1, $t2, $this->_periodNum, '奖金计算初始化配置', 'bonus');
+            // 设置结算状态
+            $this->setCalcStatus('start');
+            // 清空所有本期结算用到的缓存
+            CalcCache::clearCalcBonusCache($this->_periodNum);
+            $t3 = microtime(true);
+            ServeProcess::recordProcess($t2, $t3, $this->_periodNum, '设置结算状态,清空缓存', 'bonus');
+            // 清空相关表数据
+            $this->clearCalcTableData();
+            $t4 = microtime(true);
+            ServeProcess::recordProcess($t3, $t4, $this->_periodNum, '清空相关表数据', 'bonus');
+            echo('初始化、清空缓存及相关数据表完成,耗时:' . round($t4 - $t1, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(10);
+            // 蓝星奖放到最前面 奖金计算开始
+            if($this->_sysConfig['openGL']['VALUE']) {
+                echo('计算蓝星奖开始,' . date('Y-m-d H:i:s', $t4) . PHP_EOL);
+                // 调用存储过程,计算蓝星管理奖金
+                $this->calcBsProcedure();
+                // 将有蓝星管理奖金的用户加入到有奖金缓存用户中
+                $this->calcBonusBsGL();
+                if ($this->_isCalcMonth) {
+                    ServeProcess::recordProcess($t4, time(), $this->_periodNum, '计算蓝星奖', 'bonus');
+                }
+            }
+            $t5 = microtime(true);
+            echo('计算蓝星奖'.($this->_sysConfig['openGL']['VALUE']?'完成':'关闭').',耗时:' . round($t5 - $t4, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+
+            if($this->_sysConfig['openFW']['VALUE']) {
+                $this->calcBonusBDStepOne();
+                $this->calcBonusBDStepTwo();
+                ServeProcess::recordProcess($t5, time(), $this->_periodNum, '计算服务奖', 'bonus');
+            }
+            $t6 = microtime(true);
+            echo('计算服务奖'.($this->_sysConfig['openFW']['VALUE']?'完成':'关闭').',耗时:' . round($t6 - $t5, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(15);
+
+            // 销售奖/推广奖
+            if($this->_sysConfig['openTG']['VALUE']) {
+                $this->calcBonusTG();
+                ServeProcess::recordProcess($t6, time(), $this->_periodNum, '计算推广奖', 'bonus');
+            }
+            $t7 = microtime(true);
+            echo('计算推广奖'.($this->_sysConfig['openTG']['VALUE']?'完成':'关闭').',耗时:' . round($t7 - $t6, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(20);
+
+            // 绩效奖/团队奖
+            if($this->_sysConfig['openQY']['VALUE']) {
+                $this->calcBonusQY();
+                ServeProcess::recordProcess($t7, time(), $this->_periodNum, '计算团队奖', 'bonus');
+            }
+            $t8 = microtime(true);
+            echo('计算团队奖'.($this->_sysConfig['openQY']['VALUE']?'完成':'关闭').',耗时:' . round($t8 - $t7, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(35);
+
+//            $this->calcBonusTourism($this->_sysConfig['openTourism']);
+            // $t21 = microtime(true);
+//            echo('计算旅游奖' . ($this->_sysConfig['openTourism']['VALUE'] ? '完成' : '关闭') . ',耗时:' . round($t21 - $t20, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+//            $this->_updatePercent(68);
+            if($this->_sysConfig['openVilla']['VALUE']) {
+                $this->calcBonusVilla();
+                ServeProcess::recordProcess($t8, time(), $this->_periodNum, '计算房奖', 'bonus');
+            }
+            $t22 = microtime(true);
+            echo('计算房奖' . ($this->_sysConfig['openVilla']['VALUE'] ? '完成' : '关闭').',耗时:' . round($t22 - $t8, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(45);
+
+            if($this->_sysConfig['openGarage']['VALUE']) {
+                $this->calcBonusGarage();
+                ServeProcess::recordProcess($t22, time(), $this->_periodNum, '计算车奖', 'bonus');
+            }
+            $t23 = microtime(true);
+            echo('计算车奖' . ($this->_sysConfig['openGarage']['VALUE'] ? '完成' : '关闭').',耗时:' . round($t23 - $t22, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(55);
+
+            // 计算季度奖
+            if($this->_sysConfig['openQuarter']['VALUE']) {
+                $this->calcQuarter();
+                ServeProcess::recordProcess($t23, time(), $this->_periodNum, '计算季度奖-调用存储过程', 'bonus');
+            }
+            $t24 = microtime(true);
+            echo('计算季度奖' . ($this->_sysConfig['openQuarter']['VALUE'] ? '开启调用存储过程' : '关闭').',耗时:' . round($t24 - $t23, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            // 将用户写入缓存
+            if($this->_sysConfig['openQuarter']['VALUE']) {
+                $this->calcQuarterUser();
+                ServeProcess::recordProcess($t24, time(), $this->_periodNum, '计算季度奖-存入奖金会员', 'bonus');
+            }
+            $this->_updatePercent(65);
+            $t25 = microtime(true);
+            echo('计算季度奖' . ($this->_sysConfig['openQuarter']['VALUE'] ? '完成' : '关闭').',耗时:' . round($t25 - $t24, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+
+            // 奖金写库
+            $this->loopBonusUsers();
+            $this->_updatePercent(75);
+            $t30 = microtime(true);
+            ServeProcess::recordProcess($t25, $t30, $this->_periodNum, '奖金写库', 'bonus');
+            echo('奖金写库操作完成,耗时:' . round($t30 - $t25, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+
+            ServeProcess::recordProcess($t30, time(), $this->_periodNum, '标记为计算完成', 'bonus');
+            $this->endCalcTask();
+            $this->_updatePercent(100);
+            $t35 = microtime(true);
+            echo('结算全部完成,共耗时:' . round($t35 - $t1, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+        } catch (\Exception $e) {
+            $this->errorCalcTask();
+            $this->addError('calc', sprintf('File【%s】, Line【%s】, Msg【%s】', $e->getFile(), $e->getLine(), $e->getMessage()));
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 结算完成
+     */
+    public function endCalcTask() {
+        $this->setCalcStatus('end');// 更新结算状态
+    }
+
+    /**
+     * 结算错误
+     */
+    public function errorCalcTask() {
+        CalcCache::clearCalcBonusCache($this->_periodNum); // 清空所有本期结算用到的缓存
+        $this->setCalcStatus('fail'); // 更新结算状态
+    }
+
+    /**
+     * 初始化结算任务
+     * @throws \yii\db\Exception
+     */
+    public function initCalcTask() {
+        $periodObj = Period::instance();
+        $periodDataArr = $periodObj->setPeriodNum($this->_periodNum);
+        if (empty($this->_periodNum)) {
+            $this->_periodNum = $periodDataArr['PERIOD_NUM'];
+        }
+        $this->_sysConfig = Cache::getSystemConfig();
+        $this->_decLevelConfig = Cache::getDecLevelConfig();
+        $this->_empLevelConfig = Cache::getEmpLevelConfig();
+        $this->_starCrownLevelConfig = Cache::getStarCrownLevelConfig();
+        $this->_decRoleConfig = CalcCache::getDecRoleConfig($this->_periodNum);
+        $this->_periodId = $periodDataArr['ID'];
+        $this->_isPerpare = $periodDataArr['IS_PREPARE'];
+        $this->_isCalcMonth = $periodObj->isCalcMonth($this->_periodNum);
+        $this->_calcYear = $periodObj->getYear($this->_periodNum);
+        $this->_calcMonth = $periodObj->getMonth($this->_periodNum);
+        $this->_calcYearMonth = $periodObj->getYearMonth($this->_periodNum);
+    }
+
+    // 校验是否能开始进行计算
+    public function checkStart() {
+        if ($this->_isPerpare == 3) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 设置结算状态
+     * @param $type
+     * start|end|fail
+     */
+    public function setCalcStatus($type) {
+        if ($type == 'start') {
+            Period::updateAll(['IS_CALCING' => 1, 'IS_CALCULATED' => Period::CALCULATE_NONE, 'CALCULATE_STARTED_AT' => Date::nowTime()], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        } elseif ($type == 'end') {
+            Period::updateAll(['IS_CALCING' => 0, 'IS_CALCULATED' => Period::CALCULATE_FINISH, 'CALCULATED_AT' => Date::nowTime()], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        } elseif ($type == 'fail') {
+            Period::updateAll(['IS_CALCING' => 0, 'IS_CALCULATED' => Period::CALCULATE_FAIL, 'CALCULATED_AT' => 0], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        }
+    }
+
+    /**
+     * 清空相关表数据
+     */
+    public function clearCalcTableData() {
+        // 奖金表
+        CalcBonus::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+        CalcBonusQY::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+        CalcBonusBD::pageDeleteAll('PERIOD_NUM='.$this->_periodNum); // 实际上是服务奖流水表
+        CalcBonusTG::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+        // 月结时要清空的数据
+        if ($this->_isCalcMonth) {
+            CalcBonusTourism::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+            CalcBonusGarage::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+            CalcBonusVilla::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+        }
+    }
+
+    /**
+     * 推广奖
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusTG(int $offset = 0) {
+        $periodNum = $this->_periodNum;
+        // 从缓存获取分页有业绩的会员信息
+        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                // 从缓存中获取会员的业绩信息
+                $perfData = CalcCache::nowPeriodPerf($userId, $periodNum);
+                if( !$perfData ) continue;
+                //个人业绩都算推荐奖,包括报单和复消、二次购物
+                $perfPv = $perfData['PV_PCS_ZC'] ?? 0;
+                if( $perfPv <= 0 ) continue;
+
+                //推广奖使用个人PCS
+                $recBonus = Tool::formatPrice($perfPv * $this->_sysConfig['recPercent']['VALUE'] / 100);
+                if ($recBonus <= 0) continue;
+                // 把对碰后的奖金存入缓存中
+                $perfUserInfo = CalcCache::getUserInfo($userId, $periodNum);
+                $bonusUserId = $perfUserInfo['REC_UID'] ?? '';
+                if( !$bonusUserId ) continue;
+
+                // 获取会员的报单级别
+                $userBaseInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+                CalcCache::bonus($bonusUserId, $this->_periodNum, 'BONUS_TG', $recBonus);                
+
+                //来源会员信息
+                $fromUserInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                //推广奖流水
+                $insertBonusData[] = [
+                    'ID' => SnowFake::instance()->generateId(),
+                    'USER_ID' => $bonusUserId,
+                    'LAST_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $userBaseInfo['LAST_EMP_LV'],
+                    'LAST_STATUS' => $userBaseInfo['STATUS'],
+                    'FROM_USER_ID' => $userId,
+                    'LAST_FROM_DEC_LV' => $fromUserInfo['DEC_LV'],
+                    'LAST_FROM_EMP_LV' => $fromUserInfo['EMP_LV'],
+                    'LAST_FROM_STATUS' => $fromUserInfo['STATUS'],
+                    'ORI_BONUS' => $recBonus,
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_YEAR' => $this->_calcYear,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'CREATED_AT' => Date::nowTime(),
+                    'LOGS' => json_encode([
+                        'perfPv' => $perfPv,
+                        'recPercentConfig' => $this->_sysConfig['recPercent']['VALUE'],
+                        'decLevel' => $userBaseInfo['DEC_LV'],
+                    ]),
+                ];
+
+                unset($perfData, $perfPv, $perfUserInfo, $recBonus, $bonusUserId, $userBaseInfo, $userId, $deductData, $fromUserInfo);
+            }
+            CalcBonusTG::batchInsert($insertBonusData);
+            unset($allData, $insertBonusData);
+            return $this->calcBonusTG($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 服务奖第一步
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusBDStepOne(int $offset = 0) {
+        echo sprintf("时间:[%s]服务奖第【1】步,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        $periodNum = $this->_periodNum;
+        // 从缓存获取分页有业绩的会员信息
+        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                // 从缓存中获取会员的业绩信息
+                $perfData = CalcCache::nowPeriodPerf($userId, $periodNum);
+                if( !$perfData ) continue;
+                $decRoleBonusFrom = explode(',', $this->_sysConfig['decRoleBonusFrom']['VALUE']);
+                $validPvPcs = 0;
+                foreach ($decRoleBonusFrom as $orderType) {
+                    $orderTypeName = sprintf('PV_PCS_%s', $orderType);
+                    $orderTypeValue = $perfData[$orderTypeName] ?? 0;
+
+                    $coefficientName = self::ORDER_TYPE_TO_FW_COEFFICIENT[$orderType];
+                    $coefficient = $this->_sysConfig[$coefficientName]['VALUE'] ?? 1;
+                    $validPvPcs += $orderTypeValue * $coefficient;
+
+                    unset($orderType, $orderTypeName, $orderTypeValue, $coefficientName, $coefficient);
+                }
+                unset($perfData, $decRoleBonusFrom);
+                if ( $validPvPcs <= 0 ) continue;
+
+                $this->loopRelationParentDo($userId, function ($parent) use($userId, $validPvPcs){
+                    try {
+                    //判断parent的报单中心级别 和 服务奖比例
+                    $bonusUserId = $parent['PARENT_UID'];
+                    //计算级别之后更新过userInfo的缓存,缓存中级别发生了变化
+                    $bonusUserInfo = CalcCache::getUserInfo($bonusUserId, $this->_periodNum);
+                    $isDec = $bonusUserInfo['IS_DEC'];
+                    if($isDec == 0) return self::LOOP_CONTINUE;
+                    $decRoleId = $bonusUserInfo['DEC_ROLE_ID'];
+                    if( !$decRoleId ) return self::LOOP_CONTINUE;
+                    if( !isset($this->_decRoleConfig[$decRoleId]) ) return self::LOOP_CONTINUE;
+
+                    $parentDecRoleLevel = $this->_decRoleConfig[$decRoleId];
+                    $parentFwBonusPercent = $parentDecRoleLevel['FW_BONUS_PERCENT'] ?? 0;
+                    $cacheMaxPercent = CalcCache::fwMaxBonusPercent($userId, $this->_periodNum);
+                    $diffPercent = $parentFwBonusPercent - $cacheMaxPercent;
+                    if( $diffPercent <= 0 ) return self::LOOP_CONTINUE;
+
+                    $fwBonus = $validPvPcs * $diffPercent / 100;
+                    if( $fwBonus <= 0  ) return self::LOOP_CONTINUE;
+
+                    //给本人添加服务奖比例
+                    CalcCache::fwMaxBonusPercent($userId, $this->_periodNum, $parentFwBonusPercent);
+                    //记录奖金和奖金来源到缓存 并实现在缓存中奖金累加
+                    CalcCache::saveFwBonusList($bonusUserId, $this->_periodNum, $fwBonus, ['fromUid'=>$userId, 'fromPvPcs'=>$validPvPcs]);
+                    CalcCache::addHasFwBonusUsers($bonusUserId, $this->_periodNum);
+                    } catch(Exception $e) {
+                        var_dump($e->getMessage(), '------------');
+                    }
+                    unset($bonusUserId, $bonusUserInfo, $isDec, $decRoleId, $parentDecRoleLevel, $parentFwBonusPercent, $cacheMaxPercent, $diffPercent, $fwBonus);
+                });
+
+                unset($userId, $validPvPcs);
+            }
+            unset($allData, $insertBonusData);
+            return $this->calcBonusBDStepOne($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 服务奖第二步
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusBDStepTwo(int $offset = 0) {
+        echo sprintf("时间:[%s]服务奖第【2】步,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+
+        $allData = CalcCache::getHasFwBonusUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                $fwBonusData = CalcCache::getFwBonusList($userId, $this->_periodNum);
+                if( !$fwBonusData ) continue;
+                $fwBonus = $fwBonusData['fwBonus'] ?? 0;
+                if( $fwBonus <=0  ) continue;
+                //总金额限制
+                try {
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                CalcCache::bonus($userId, $this->_periodNum, 'BONUS_BD', $fwBonus);
+                 
+                $decRoleId = $userBaseInfo['DEC_ROLE_ID'];
+                $insertBonusData[] = [
+                    'ID' => SnowFake::instance()->generateId(),
+                    'USER_ID' => $userId,
+                    'LAST_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $userBaseInfo['LAST_EMP_LV'],
+                    'LAST_STATUS' => $userBaseInfo['STATUS'],
+                    'FROM_USER_ID' => $userId,
+                    'LAST_FROM_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_FROM_EMP_LV' => $userBaseInfo['EMP_LV'],
+                    'LAST_FROM_STATUS' => $userBaseInfo['STATUS'],
+                    'AMOUNT' => $fwBonus,
+                    'ORI_BONUS' => $fwBonus,
+                    'RECONSUME_POINTS' => 0,
+                    'MANAGE_TAX' => 0,
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_YEAR' => $this->_calcYear,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'CREATED_AT' => Date::nowTime(),
+                    'LOGS' => json_encode([
+                        'decRoleId' => $decRoleId,
+                    ])
+                ];
+                } catch(Exception $e) {
+                    var_dump('---->>>', $e->getMessage());exit;
+                }
+
+                unset($userId, $fwBonusData, $userBaseInfo, $decRoleId, $fwBonus);
+            }
+
+            CalcBonusBD::batchInsert($insertBonusData);
+            unset($insertBonusData, $allData);
+            $this->calcBonusBDStepTwo($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 团队奖
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function calcBonusQY(int $offset = 0) {
+        echo sprintf("时间:[%s]团队奖,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        $periodNum = $this->_periodNum;
+        // 从缓存获取分页有业绩的会员信息
+        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            $insertBonusData = [];
+            foreach ($allData as $userId) {
+                // 从缓存中获取会员的业绩信息
+                $perfData = CalcCache::nowPeriodPerf($userId, $periodNum);
+                // 从缓存中获取会员的上期结余业绩信息
+                $pervSurplusPerf = CalcCache::surplusPerf($userId, $periodNum);
+                // 本期 + 上期结余
+                $perfArr = [
+                    'SURPLUS_1L' => $perfData['PV_1L_TOUCH'] + $pervSurplusPerf['SURPLUS_1L'],
+                    'SURPLUS_2L' => $perfData['PV_2L_TOUCH'] + $pervSurplusPerf['SURPLUS_2L'],
+                    'SURPLUS_3L' => $perfData['PV_3L_TOUCH'] + $pervSurplusPerf['SURPLUS_3L'],
+                    'SURPLUS_4L' => $perfData['PV_4L_TOUCH'] + $pervSurplusPerf['SURPLUS_4L'],
+                    'SURPLUS_5L' => $perfData['PV_5L_TOUCH'] + $pervSurplusPerf['SURPLUS_5L'],
+                ];
+                $oriPerfArr = [
+                    'perfArr' => $perfArr,
+                    'touchBonus' => 0,
+                ];
+
+                // 获取会员的报单级别
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                $decLevelConfig = $this->_decLevelConfig;
+                $nowDecLevelConfig = $decLevelConfig[$userBaseInfo['DEC_LV']];
+                // 对碰
+                $touchBonusArr = $this->touchPerf($oriPerfArr, $perfArr, $nowDecLevelConfig['QY_PERCENT']/100);
+                $touchPerfArr = [];
+                foreach ($touchBonusArr['perfArr'] as $keyR => $perfR) {
+                    $touchPerfArr[$keyR] = $perfR;
+                }
+                // 对碰完成后把结余的业绩存入本期业绩缓存中
+                CalcCache::nowPeriodPerf($userId, $periodNum, $touchPerfArr);
+                //更新数据库
+                PerfPeriod::updateAll($touchPerfArr, 'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM', [
+                    'USER_ID' => $userId,
+                    'PERIOD_NUM' => $periodNum,
+                ]);
+                if ($touchBonusArr['touchBonus'] <= 0) continue;
+
+                $teamBonus = $touchBonusArr['touchBonus'];
+                $capBonusQy = $teamBonus; // 封顶前的奖金
+                //判断级别上限,个人奖金封顶限制
+                $teamBonus = $this->declarationLevelCap($teamBonus, $userId, $userBaseInfo['DEC_LV']);
+                if( $teamBonus <= 0 ) continue;
+                // 将封顶前的金额加入用户奖金缓存中,此金额不能发放(总奖金,总实际奖金) 
+                CalcCache::bonus($userId, $periodNum, 'CAPPED_BONUS_QY', $capBonusQy); 
+
+                // TODO:取小腿值
+                $payLeg = min([$perfArr['SURPLUS_1L'], $perfArr['SURPLUS_2L']]);
+                // 计算荣衔星级
+                $starCrown = StarCrownLevel::getStarCrown($payLeg);
+
+                // 是否活跃
+                $isActive = $this->_isPerfActive($userId);
+                $oriBonus = $isActive ? $teamBonus : 0;
+                $lastCrownLv = $isActive ? $starCrown['ID'] : StarCrownLevel::getDefaultLevelId();
+                
+                //团队奖流水
+                $insertBonusData[] = [
+                    'ID' => SnowFake::instance()->generateId(),
+                    'USER_ID' => $userId,
+                    'ORI_CAPPED_BONUS_QY' => $capBonusQy,
+                    'LAST_DEC_LV' => $userBaseInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $userBaseInfo['LAST_EMP_LV'],
+                    'LAST_CROWN_LV' => $lastCrownLv,
+                    'LAST_STATUS' => $userBaseInfo['STATUS'],
+                    'AMOUNT' => $oriBonus,
+                    'ORI_BONUS' => $oriBonus,
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_YEAR' => $this->_calcYear,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'CREATED_AT' => Date::nowTime(),
+                    'LOGS' => json_encode([
+                        'perfArr' => $perfArr,
+                        'touchPerfArrOri' => $touchBonusArr['perfArr'],
+                        'touchPerfArr' => $touchPerfArr,
+                        'nowDecLevelConfig' => $nowDecLevelConfig,
+                        'decLevel' => $userBaseInfo['DEC_LV'],
+                    ]),
+                    'IS_ACTIVE' => (int)$isActive,
+                    'HOPE_CROWN_LV' => $starCrown['ID'],
+                    'HOPE_BONUS' => $teamBonus,
+                ];
+                // 星级放入缓存
+                CalcCache::addUserStarCrown($userId, $periodNum, $lastCrownLv);
+                if ($oriBonus > 0) {
+                    // 把对碰后的奖金存入缓存中
+                    // CalcCache::bonus($userId, $periodNum, 'BONUS_QY', $oriBonus, $deductData);
+                    CalcCache::bonus($userId, $periodNum, 'BONUS_QY', $teamBonus);
+                }
+
+                unset($perfData, $pervSurplusPerf, $perfArr, $oriPerfArr, $touchPerfArr, $userBaseInfo, $decLevelConfig, $touchBonusArr, $userId, $nowDecLevelConfig, $teamBonus, $deductData);
+            }
+            CalcBonusQY::batchInsert($insertBonusData);
+            unset($allData, $insertBonusData);
+            return $this->calcBonusQY($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 季度奖计算
+     */
+    public function calcQuarter() {
+        if( !$this->_isCalcMonth || !in_array($this->_calcMonth, [3,6,9,12])) {
+        // echo('不是季结点,进这里,不计算季度奖'. PHP_EOL);
+            return false;
+        }
+        $result = \Yii::$app->db->createCommand("CALL QtrCalc(:periodNum)")
+            ->bindValue(':periodNum' , $this->_periodNum )
+            ->execute();
+
+        return $result;
+    }
+
+    // 执行蓝星管理奖金的存储过程
+    public function calcBsProcedure() {
+        if( !$this->_isCalcMonth ) {
+            // 不是结算月,则不进行计算
+            return false;
+        }
+        $result = \Yii::$app->db->createCommand("CALL CalcBlue(:periodNum)") 
+                      ->bindValue(':periodNum' , $this->_periodNum )
+                      ->execute();
+                      
+        return $result;
+    }
+
+    // 执行旅游奖的计算
+    public function calcBonusTourism() {
+        // 月结,如果不是月结点,则直接退出
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+
+        $bonusConfig = $this->_sysConfig['openTourism'];
+        // 达标条件:聘级、级别、奖项比例
+        $config = json_decode($bonusConfig['OPTIONS'], true);
+        // 奖金总比例
+        $mate = $bonusConfig['VALUE'] / 100;
+        // 会员级别
+        $minDecLevel = $config['OPTIONS']['declarationLevel'] ?? [];
+        // 月度公司总PV
+        $monthTotalPV = PerfMonth::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
+            ->sum('PV_PCS');
+        // 用于分发的奖金总数
+        $transferAmount = $monthTotalPV * $mate;
+
+        // 基于蓝星奖结果计算符合获奖条件的会员StarDirector
+        $userStarDirector = CalcBonusBS::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
+            ->select('USER_ID,LEVEL_ID,LAST_DEC_LV,LAST_EMP_LV,LAST_STATUS')
+            ->groupBy('USER_ID')
+            ->asArray()
+            ->all();
+        $userStarDirectorObj = array_column($userStarDirector, NULL, 'USER_ID');
+
+        // 基于团队奖/绩效奖结果计算会员的StarCrown
+        $userStarCrown = CalcBonusQY::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
+            ->select('USER_ID,LAST_CROWN_LV')
+            ->groupBy('USER_ID')
+            ->asArray()
+            ->all();
+        $userStarCrownObj = array_column($userStarCrown, NULL, 'USER_ID');
+
+        // 合并用户ID,去重
+        $bonusUsers = array_unique(array_merge(array_keys($userStarDirectorObj), array_keys($userStarCrownObj)));
+
+        // 奖金点数综合
+        $bonusPointComplex = 0;
+        $insertBonusData = [];
+        foreach($bonusUsers as $userId) {
+            // 计算奖金:取starDirectorPoint和starCrownPoint的大个值
+            $starDirectorPoint = $this->_empLevelConfig[$userStarDirectorObj[$userId]['LEVEL_ID']]['TOURISM_PERCENT'] ?? 0;
+            $starCrownPoint = $this->_starCrownLevelConfig[$userStarCrownObj[$userId]['LAST_CROWN_LV']]['TOURISM_PERCENT'] ?? 0;
+            // 奖金比例:
+            $bonusPoint = max($starDirectorPoint, $starCrownPoint);
+            if ($bonusPoint <= 0) {
+                continue;
+            }
+            
+            $insertBonusData[] = [
+                'ID' => SnowFake::instance()->generateId(),
+                'USER_ID' => $userId,
+                'LAST_DEC_LV' => $userStarDirectorObj[$userId]['LAST_DEC_LV'],
+                'LAST_EMP_LV' => $userStarDirectorObj[$userId]['LEVEL_ID'],
+                'LAST_STATUS' => $userStarDirectorObj[$userId]['LAST_STATUS'],
+                'LAST_CROWN_LV' => $userStarCrownObj[$userId]['LAST_CROWN_LV'],
+                'AMOUNT_STANDARD' => 0,
+                'POINT' => $bonusPoint,
+                'PERIOD_NUM' => $this->_periodNum,
+                'CALC_YEAR' => $this->_calcYear,
+                'CALC_MONTH' => $this->_calcYearMonth,
+                'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                'CREATED_AT' => Date::nowTime(),
+                'PERF' => $monthTotalPV,
+                'TRANSFER_RATE' => $mate,
+                'TRANSFER_AMOUNT' => Tool::formatPrice($transferAmount),
+                'CAP_AMOUNT' => 0,
+                'POINT_COMPLEX' => 0,
+            ];
+
+            $bonusPointComplex += $bonusPoint;
+        }
+
+        // 数据写入总表
+        if ($insertBonusData) {
+            foreach ($insertBonusData as &$bonusData) {
+                // 计算奖金
+                $amount = Tool::formatPrice($transferAmount * ($bonusData['POINT'] / $bonusPointComplex));
+                if ($amount <= 0) {
+                    continue;
+                }
+
+                // 会员级别达到要求才会发放奖金
+                if ($bonusData['LAST_DEC_LV'] == $minDecLevel) {
+                    // 放入缓存
+                    CalcCache::tourismBonus($bonusData['USER_ID'], $this->_periodNum, $amount);
+                }
+                $bonusData['AMOUNT'] = $amount;
+                $bonusData['POINT_COMPLEX'] = $bonusPointComplex;
+            }
+
+            CalcBonusTourism::batchInsert($insertBonusData);
+        }
+
+        return true;
+    }
+
+    // 执行房奖的计算
+    public function calcBonusVilla() {
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+
+        $bonusConfig = $this->_sysConfig['openVilla'];
+        // 达标条件:聘级、级别、奖项比例
+        $config = json_decode($bonusConfig['OPTIONS'], true);
+        // 奖金总比例
+        $mate = $bonusConfig['VALUE'] / 100;
+        // 个人奖金封顶
+        $capBonus = intval($this->_sysConfig['openVillaCap']['VALUE'] ?? 0);
+        // 会员级别
+        $minDecLevel = $config['declarationLevel'] ?? [];
+
+        // 月度公司总PV
+        $monthTotalPV = PerfMonth::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
+            ->sum('PV_PCS');
+        // 用于分发的奖金总数
+        $transferAmount = $monthTotalPV * $mate;
+
+        // 基于团队奖/绩效奖结果计算会员的StarCrown.StarCrown基于周期计算,一个月会产生多次,取月周期中的最高星级
+        $subQuery = CalcBonusQY::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH = :CALC_MONTH AND LAST_CROWN_LV <> :NO_CROWN_LV', [':CALC_MONTH' => $this->_calcYearMonth, ':NO_CROWN_LV' => StarCrownLevel::NO_LEVEL_ID])
+            ->select('USER_ID,LAST_DEC_LV,LAST_CROWN_LV,LAST_STATUS,LEVEL_NAME,SORT')
+            ->joinWith(['starCrown' => function($query) {
+                $query->select(['LEVEL_NAME', 'SORT']);
+            }])
+            ->having(1)
+            ->orderBy('USER_ID ASC, SORT DESC');
+        $userStarCrownObj = (new Query())->from(['u' => $subQuery])->select('USER_ID,LAST_DEC_LV,LAST_CROWN_LV,LAST_STATUS,LEVEL_NAME,SORT')->groupBy('USER_ID')->indexBy('USER_ID')->all();
+
+        // 奖金点数综合
+        $bonusPointComplex = 0;
+        $insertBonusData = [];
+        foreach($userStarCrownObj as $item) {
+            // 奖金比例
+            $bonusPoint = $this->_starCrownLevelConfig[$item['LAST_CROWN_LV']]['VILLA_PERCENT'] ?? 0;
+            if (!$bonusPoint) {
+                continue;
+            }
+
+            // 会员级别达到要求才会发放奖金
+            if ($item['LAST_DEC_LV'] != $minDecLevel) {
+                continue;
+            }
+            $userBaseInfo = CalcCache::getUserInfo($item['USER_ID'], $this->_periodNum);
+            $insertBonusData[] = [
+                'ID' => SnowFake::instance()->generateId(),
+                'USER_ID' => $item['USER_ID'],
+                'LAST_DEC_LV' => $item['LAST_DEC_LV'] ?? '',
+                'LAST_EMP_LV' => $userBaseInfo['LAST_EMP_LV'],
+                'LAST_STATUS' => $item['LAST_STATUS'] ?? 0,
+                'LAST_CROWN_LV' => $item['LAST_CROWN_LV'] ?? '',
+                'AMOUNT' => 0,
+                'POINT' => $bonusPoint,
+                'PERIOD_NUM' => $this->_periodNum,
+                'CALC_YEAR' => $this->_calcYear,
+                'CALC_MONTH' => $this->_calcYearMonth,
+                'CREATED_AT' => Date::nowTime(),
+                'PERF' => $monthTotalPV,
+                'TRANSFER_RATE' => $mate,
+                'TRANSFER_AMOUNT' => Tool::formatPrice($transferAmount),
+                'CAP_AMOUNT' => 0,
+                'POINT_COMPLEX' => 0,
+            ];
+
+            $bonusPointComplex += $bonusPoint;
+        }
+
+        // 数据写入总表
+        if ($insertBonusData) {
+            // 计算个人奖金
+            foreach ($insertBonusData as &$bonusData) {
+                // 计算奖金
+                $amount = Tool::formatPrice($transferAmount * ($bonusData['POINT'] / $bonusPointComplex));
+                if ($amount <= 0) {
+                    continue;
+                }
+                // 封顶前奖金数
+                $capAmount = $amount;
+                // 奖金数不能大于封顶值
+                $amount = ($amount > $capBonus) ? $capBonus : $amount;
+
+                $bonusData['AMOUNT'] = $amount;
+                $bonusData['CAP_AMOUNT'] = $capAmount;
+                $bonusData['POINT_COMPLEX'] = $bonusPointComplex;
+
+                // 放入缓存
+                CalcCache::villaBonus($bonusData['USER_ID'], $this->_periodNum, $amount);
+            }
+
+            CalcBonusVilla::batchInsert($insertBonusData);
+        }
+
+        return true;
+    }
+
+    // 执行车奖的计算
+    public function calcBonusGarage() {
+        // 月结,如果不是月结点,则直接退出
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+
+        $bonusConfig = $this->_sysConfig['openGarage'];
+        // 达标条件:聘级、级别、奖项比例
+        $config = json_decode($bonusConfig['OPTIONS'], true);
+        // 奖金总比例
+        $mate = $bonusConfig['VALUE'] / 100;
+        // 会员级别
+        $minDecLevel = $config['declarationLevel'] ?? [];
+        // 个人奖金封顶
+        $capBonus = intval($this->_sysConfig['openGarageCap']['VALUE'] ?? 0);
+
+        // 月度公司总PV
+        $monthTotalPV = PerfMonth::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
+            ->sum('PV_PCS');
+        // 用于分发的奖金总数
+        $transferAmount = $monthTotalPV * $mate;
+
+        // 基于蓝星奖结果计算符合获奖条件的会员StarDirector
+        $userStarDirector = CalcBonusBS::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH = :CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
+            ->select('USER_ID,LEVEL_ID,LAST_DEC_LV,LAST_STATUS')
+            ->groupBy('USER_ID')
+            ->asArray()
+            ->all();
+        $userStarDirectorObj = array_column($userStarDirector, NULL, 'USER_ID');
+
+        // 基于团队奖/绩效奖结果计算会员的StarCrown.StarCrown基于周期计算,一个月会产生多次,取月周期中的最高星级
+        $subQuery = CalcBonusQY::find()
+            ->yearMonth($this->_calcYearMonth)
+            ->where('CALC_MONTH = :CALC_MONTH AND LAST_CROWN_LV <> :NO_CROWN_LV', [':CALC_MONTH' => $this->_calcYearMonth, ':NO_CROWN_LV' => StarCrownLevel::NO_LEVEL_ID])
+            ->select('USER_ID,LAST_DEC_LV,LAST_CROWN_LV,LAST_STATUS,LEVEL_NAME,SORT')
+            ->joinWith(['starCrown' => function($query) {
+                $query->select(['LEVEL_NAME', 'SORT']);
+            }])
+            ->having(1)
+            ->orderBy('USER_ID ASC, SORT DESC');
+        $userStarCrownObj = (new Query())->from(['u' => $subQuery])->select('USER_ID,LAST_DEC_LV,LAST_CROWN_LV,LAST_STATUS,LEVEL_NAME,SORT')->groupBy('USER_ID')->indexBy('USER_ID')->all();
+        
+        // 合并用户ID,去重
+        $bonusUsers = array_unique(array_merge(array_keys($userStarDirectorObj), array_keys($userStarCrownObj)));
+        sort($bonusUsers);
+
+        // 奖金点数综合
+        $bonusPointComplex = 0;
+        $insertBonusData = [];
+        foreach($bonusUsers as $userId) {
+            // 计算奖金:取starDirectorPoint和starCrownPoint的大个值
+            $starDirectorPoint = !isset($userStarDirectorObj[$userId]['LEVEL_ID']) ? 0 : ($this->_empLevelConfig[$userStarDirectorObj[$userId]['LEVEL_ID']]['GARAGE_PERCENT'] ?? 0);
+            $starCrownPoint = !isset($userStarCrownObj[$userId]['LAST_CROWN_LV']) ? 0: ($this->_starCrownLevelConfig[$userStarCrownObj[$userId]['LAST_CROWN_LV']]['GARAGE_PERCENT'] ?? 0);
+            // 奖金比例:
+            $bonusPoint = max($starDirectorPoint, $starCrownPoint);
+            if ($bonusPoint <= 0) {
+                continue;
+            }
+
+            // 会员级别达到要求才会发放奖金
+            $lastDecLv = $userStarDirectorObj[$userId]['LAST_DEC_LV'] ?? ($userStarCrownObj[$userId]['LAST_DEC_LV'] ?? '');
+            if ($lastDecLv != $minDecLevel) {
+                continue;
+            }
+
+            $insertBonusData[] = [
+                'ID' => SnowFake::instance()->generateId(),
+                'USER_ID' => $userId,
+                'LAST_DEC_LV' => $userStarDirectorObj[$userId]['LAST_DEC_LV'] ?? ($userStarCrownObj[$userId]['LAST_DEC_LV'] ?? ''),
+                'LAST_EMP_LV' => $userStarDirectorObj[$userId]['LEVEL_ID'] ?? '',
+                'LAST_STATUS' => $userStarDirectorObj[$userId]['LAST_STATUS'] ?? ($userStarCrownObj[$userId]['LAST_STATUS'] ?? 1),
+                'LAST_CROWN_LV' => $userStarCrownObj[$userId]['LAST_CROWN_LV'] ?? '',
+                'AMOUNT' => 0,
+                'POINT' => $bonusPoint,
+                'PERIOD_NUM' => $this->_periodNum,
+                'CALC_YEAR' => $this->_calcYear,
+                'CALC_MONTH' => $this->_calcYearMonth,
+                'CREATED_AT' => Date::nowTime(),
+                'PERF' => $monthTotalPV,
+                'TRANSFER_RATE' => $mate,
+                'TRANSFER_AMOUNT' => Tool::formatPrice($transferAmount),
+                'CAP_AMOUNT' => 0,
+                'POINT_COMPLEX' => 0,
+            ];
+
+            $bonusPointComplex += $bonusPoint;
+        }
+
+        // 数据写入总表
+        if ($insertBonusData) {
+            foreach ($insertBonusData as &$bonusData) {
+                // 计算奖金
+                $amount = Tool::formatPrice($transferAmount * ($bonusData['POINT'] / $bonusPointComplex));
+                if ($amount <= 0) {
+                    continue;
+                }
+                // 封顶前奖金数
+                $capAmount = $amount;
+                // 奖金数不能大于封顶值
+                $amount = ($amount > $capBonus) ? $capBonus : $amount;
+
+                $bonusData['AMOUNT'] = $amount;
+                $bonusData['CAP_AMOUNT'] = $capAmount;
+                $bonusData['POINT_COMPLEX'] = $bonusPointComplex;
+
+                // 放入缓存
+                CalcCache::garageBonus($bonusData['USER_ID'], $this->_periodNum, $amount);
+            }
+
+            CalcBonusGarage::batchInsert($insertBonusData);
+        }
+
+        return true;
+    }
+
+    /**
+     * 季度奖写用户缓存
+     */
+    public function calcQuarterUser(int $offset = 0) {
+        if( !$this->_isCalcMonth || !in_array($this->_calcMonth, [3,6,9,12])) {
+            // 不是结算月,则不进行计算
+            return false;
+        }
+        try{
+            $allData = CalcBonusQuarter::finduseDbCalc()
+                    ->where('PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum])
+                    ->groupBy('USER_ID')
+                    ->offset($offset)
+                    ->limit($this->_limit)
+                    ->asArray()
+                    ->all();
+            if ($allData){
+                // 达标条件:会员级别:钻卡
+                $config = json_decode($this->_sysConfig['openQuarter']['OPTIONS'], true);
+                $minDecLevel = $config['declarationLevel'] ?? [];
+                foreach ($allData as $user) {
+                    // 管理奖钻卡发放
+                    if ($user['LAST_DEC_LV'] == $minDecLevel) {
+                        CalcCache::bonus($user['USER_ID'], $this->_periodNum, 'BONUS_QUARTER', $user['ORI_BONUS']);
+                    }
+                }
+                return $this->calcQuarterUser($offset + $this->_limit);
+            }
+        } catch(Exception $e) {
+            var_dump($e->getMessage(),'--------------------');
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 蓝星管理奖金未拆分
+     */
+    public function calcBonusBsGL(int $offset = 0) {
+        if( !$this->_isCalcMonth ) {
+            // 不是结算月,则不进行计算
+            return false;
+        }
+        // 从缓存获取分页有收入的会员信息
+        $allData = CalcBonusBS::findUseDbCalc()
+            ->where('PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum])
+            ->groupBy('USER_ID')
+            ->offset($offset)
+            ->limit($this->_limit)
+            ->asArray()
+            ->all();
+        if ($allData) {
+            foreach ($allData as $user) {
+                // 是否活跃会员
+                $isActive = $this->_isPerfActive($user['USER_ID']);
+                $oriBonus = $isActive ? $user['ORI_BONUS'] : 0;
+                $lastEmpLv = $isActive ? $user['LEVEL_ID'] : EmployLevel::getDefaultLevelId();
+                if ($oriBonus > 0) {
+                    CalcCache::bonus($user['USER_ID'], $this->_periodNum, 'BONUS_BS', $user['ORI_BONUS']);
+                }
+                // 如果不活跃,则不发放奖金,积分,级别
+                // 更新蓝星奖金存储过程的实发金额数据
+                CalcBonusBS::updateAll(
+                    [
+                        'IS_ACTIVE' => (int)$isActive,
+                        'HOPE_EMP_LV' => $user['LEVEL_ID'],
+                        'LEVEL_ID' => $lastEmpLv,
+                    ],
+                'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM', 
+                [':USER_ID' => $user['USER_ID'], ':PERIOD_NUM' => $this->_periodNum]);
+                // 修改用户缓存中记录的用户的最新级别
+                $userInfo = CalcCache::getUserInfo($user['USER_ID'], $this->_periodNum);
+                $userInfo['LAST_EMP_LV'] = $lastEmpLv;
+                CalcCache::setUserInfo($user['USER_ID'], $this->_periodNum, $userInfo);
+            }
+            return $this->calcBonusBsGL($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 对碰
+     */
+    public function touchPerf(array $oriPerfArr, array $perfArr, $percent, $loopTimes=1) {
+        $resultArr = $oriPerfArr;
+        foreach ($perfArr as $keyT => $perfT) {
+            if ($perfT <= 0) {
+                unset($perfArr[$keyT]);
+            }
+        }
+        if (count($perfArr) >= 2 && $loopTimes < 6) {
+            arsort($perfArr, SORT_NUMERIC);
+            $onePerf = null;
+            $oneKey = null;
+            // $touchSurplusAmount = 0;
+            $touchAmount = 0;
+            // 前两个进行对碰
+            foreach ($perfArr as $key => $perf) {
+                if ($onePerf === null) {
+                    $oneKey = $key;
+                    $onePerf = $perf;
+                } else {
+                    $touchSurplusAmount = $perf - $onePerf;
+                    if ($touchSurplusAmount > 0) {
+                        unset($perfArr[$oneKey]);
+                        $resultArr['perfArr'][$oneKey] = 0;
+                        $perfArr[$key] = $touchSurplusAmount;
+                        $touchAmount = $onePerf;
+                    } elseif ($touchSurplusAmount < 0) {
+                        unset($perfArr[$key]);
+                        $resultArr['perfArr'][$key] = 0;
+                        $perfArr[$oneKey] = abs($touchSurplusAmount);
+                        $touchAmount = $perf;
+                    } else {
+                        unset($perfArr[$oneKey], $perfArr[$key]);
+                        $resultArr['perfArr'][$oneKey] = 0;
+                        $resultArr['perfArr'][$key] = 0;
+                        $touchAmount = $perf;
+                    }
+                    break;
+                }
+            }
+            $touchBonus = Tool::formatPrice($touchAmount * $percent);
+            $resultArr['touchBonus'] += $touchBonus;
+
+            foreach ($perfArr as $keyR => $perfR) {
+                $resultArr['perfArr'][$keyR] = $perfR;
+            }
+            return $this->touchPerf($resultArr, $perfArr, $percent, $loopTimes+1);
+
+        }
+        return $resultArr;
+    }
+
+    /**
+     * 循环父级并执行回调函数
+     * @param $userId
+     * @param callable $callbackFunc
+     * @param int $offset
+     * @return bool
+     */
+    public function loopNetworkParentDo($userId, callable $callbackFunc, int $offset = 0) {
+        $allParents = Cache::getAllNetworkParents($userId);
+        $allData = array_slice($allParents, $offset, $this->_limit);
+        unset($allParents);
+        if ($allData) {
+            foreach ($allData as $data) {
+                $funcResult = $callbackFunc($data);
+                if ($funcResult === self::LOOP_FINISH) {
+                    return true;
+                } elseif ($funcResult === self::LOOP_CONTINUE) {
+                    continue;
+                }
+                unset($data, $funcResult);
+            }
+            unset($allData);
+            return $this->loopNetworkParentDo($userId, $callbackFunc, $offset + $this->_limit);
+        }
+        return true;
+    }
+
+    /**
+     * 循环推荐网络的父级
+     * @param $userId
+     * @param callable $callbackFunc
+     * @param int $offset
+     * @return bool
+     */
+    public function loopRelationParentDo($userId, callable $callbackFunc, int $offset = 0) {
+        $allParents = Cache::getAllRelationParents($userId);
+        $allData = array_slice($allParents, $offset, $this->_limit);
+        unset($allParents);
+        if ($allData) {
+            foreach ($allData as $data) {
+                $funcResult = $callbackFunc($data);
+                if ($funcResult === self::LOOP_FINISH) {
+                    return true;
+                } elseif ($funcResult === self::LOOP_CONTINUE) {
+                    continue;
+                }
+                unset($data, $funcResult);
+            }
+
+            unset($allData);
+            return $this->loopRelationParentDo($userId, $callbackFunc, $offset + $this->_limit);
+        }
+        return true;
+    }
+
+    /**
+     * 按级别的收入上限
+     * 新的需求调整: 改成不按月进行限制,并且去掉vip奖
+     * @param $bonus
+     * @param $userId
+     * @param $declarationLevel
+     * @return float
+     */
+    public function declarationLevelCap($bonus, $userId, $declarationLevel) {
+        $decLevelConfig = $this->_decLevelConfig;
+        $nowDecLevelConfig = $decLevelConfig[$declarationLevel];
+        unset($decLevelConfig);
+        $maxGetBonus = $nowDecLevelConfig['INCOME_CAP'];
+        if( $bonus <= $maxGetBonus) {
+            return $bonus;
+        }else {
+            return $maxGetBonus;
+        }
+    }
+
+    /**
+     * 扣除管理费 返回管理费
+     */
+    public function deductManageTax($bonus) {
+        return $bonus * $this->_sysConfig['manageTaxPercent']['VALUE'] / 100;
+    }
+
+    // 扣除复消积分 返回应扣除的复消费积分
+    public function deductReconsumePonits($userId, $bonus) {
+        //判断是否达到了本月扣除复消的上限
+        $cacheData = CalcCache::monthLastPeriodReconsumePoints($userId, $this->_periodNum, $this->_calcYearMonth);
+        $bonusCache = CalcCache::bonus($userId, $this->_periodNum);
+        $lastPeriodPoints = '0.00';
+        if (!empty($cacheData)) {
+            $lastPeriodPoints =  $cacheData['RECONSUME_POINTS_SUM'];
+        }
+        $reConsumePointsTotal = $bonusCache['RECONSUME_POINTS'] + $lastPeriodPoints;
+        $reConsumePointsCap = $this->_sysConfig['reConsumePointsMonthCap']['VALUE'];
+        unset($cacheData, $bonusCache, $lastPeriodPoints);
+        $reConsumePoints = 0;
+        if( $reConsumePointsTotal < $reConsumePointsCap ) {
+            $reConsumePoints = $bonus * $this->_sysConfig['reConsumePointsPercent']['VALUE'] / 100;
+
+            $reConsumePoints = min($reConsumePoints, $reConsumePointsCap-$reConsumePointsTotal);
+        }
+        unset($reConsumePointsTotal, $reConsumePointsCap);
+
+        return $reConsumePoints;
+    }
+
+    /**
+     * 更新百分比并发送
+     * @param $percent
+     */
+    private function _updatePercent($percent) {
+        // 把数据写入数据库中
+        Period::updateAll(['CALC_PERCENT' => $percent], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        \Yii::$app->swooleAsyncTimer->pushAsyncPercentToAdmin($percent, ['MODEL' => 'PERIOD', 'ID' => $this->_periodId, 'FIELD' => 'CALC_PERCENT']);
+    }
+
+    /**
+     * 循环奖服务奖金会员,并入库
+     */
+    public function loopBonusUsers($offset = 0) {
+        echo sprintf("时间:[%s]缓存奖金数据入库,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        // 从缓存列表里面从底层往上倒序获取会员
+        $allData = CalcCache::getHasBonusUsers($this->_periodNum, $offset, $this->_limit);
+        
+            if($allData){
+                $insertDataBonus = [];
+                foreach($allData as $userId){
+                    try {
+                    $tempBonusData = $this->bonusData($userId);
+                    if(!empty($tempBonusData['result'])){
+                        $insertDataBonus[] = $tempBonusData['result'];
+                    }
+                    }catch(Exception $e) {
+                        var_dump($e->getMessage(),'ssssssssssssssss',$userId);
+                    }
+                    unset($userId, $tempBonusData);
+                }
+                CalcBonus::batchInsert($insertDataBonus);
+
+                unset($insertDataBonus, $allData);
+                return $this->loopBonusUsers($offset + $this->_limit);
+            }
+         
+        return true;
+    }
+
+    /**
+     * 奖金入库
+     */
+    public function bonusData($userId) {
+        // 车奖和房奖是积分,不参加到奖金累计中.季度奖季度发放,无最低消费限制
+        try{
+            $bonus = CalcCache::bonus($userId, $this->_periodNum);
+            $baseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+            $perfData = CalcCache::nowPeriodPerf($userId, $this->_periodNum);
+            $tourismBonus = CalcCache::tourismBonus($userId, $this->_periodNum);
+            $garageBonus = CalcCache::garageBonus($userId, $this->_periodNum);
+            $villaBonus = CalcCache::villaBonus($userId, $this->_periodNum);
+            $pervSurplusPerf = CalcCache::surplusPerf($userId, $this->_periodNum);
+            $starCrownLv = CalcCache::getUserStarCrown($userId, $this->_periodNum);// 星级
+            $bonusReal = '0.000'; // 总实发   
+            $deductManageTaxItems = Config::DEDUCT_MANAGE_TAX; // 扣除管理费的列表  
+            $deductReconsumePointsItems = Config::DEDUCT_RECONSUME_POINTS;// 扣除复消积分的列表
+            $totalReconsumePointSum = $totalManageSum = '0.000'; // 需要扣除的奖金项,扣除完之后的实发金额
+            $deductManageTax = '0.000'; //  扣除的管理费
+            $deductReconsumePoints = '0.000'; //  扣除的复消积分 
+            foreach($deductManageTaxItems as $v) {
+                $totalManageSum+= $bonus[$v]; // 需要扣除管理费的累计之和
+            }
+            foreach($deductReconsumePointsItems as $v) {
+                $totalReconsumePointSum+= $bonus[$v]; // 需要扣除复消积分的累计之和 
+            }
+
+            // 扣除管理费
+            if ($totalManageSum > 0) {
+                $deductManageTax = $this->deductManageTax($totalManageSum);
+            }
+            // 扣除复消积分
+            if ($totalReconsumePointSum > 0) {
+                $deductReconsumePoints = $this->deductReconsumePonits($userId, $totalReconsumePointSum);
+            }
+            // 总实发=总原奖金-扣除的总管理费-总复消积分
+            $bonusReal = $bonus['BONUS_TOTAL'] - $deductManageTax - $deductReconsumePoints;
+            // NG系统,不发放兑换积分
+            // 因为是最后统一发奖金,每个奖金得实发是和原奖金一样得.只不过要扣除管理费和复消积分.
+            // 当然,ng现在不扣除管理费和复消积分.所以都是0
+            $result = [
+                'USER_ID' => $userId,
+                'LAST_USER_NAME' => $baseInfo['USER_NAME'],
+                'LAST_REAL_NAME' => $baseInfo['REAL_NAME'],
+                'LAST_DEC_LV' => $baseInfo['DEC_LV'],
+                'LAST_EMP_LV' => $baseInfo['LAST_EMP_LV'],
+                'LAST_CROWN_LV' => $starCrownLv ?? StarCrownLevel::getDefaultLevelId(),
+                'RECONSUME_POINTS' => $deductReconsumePoints,
+                'MANAGE_TAX' => $deductManageTax,    // 管理费
+                'BONUS_REAL'=>  $bonusReal,
+                'BONUS_TOTAL'=>$bonus['BONUS_TOTAL'],
+                'ORI_BONUS_BD' => $bonus['ORI_BONUS_BD'],
+                'ORI_BONUS_TG' => $bonus['ORI_BONUS_TG'],
+                'ORI_BONUS_BS' => $bonus['ORI_BONUS_BS'], // 蓝星管理奖金原奖金
+                'ORI_BONUS_QY' => $bonus['ORI_BONUS_QY'],
+                'ORI_CAPPED_BONUS_QY' => $bonus['ORI_CAPPED_BONUS_QY'], // 团队奖封顶前的奖金
+                'ORI_BONUS_QUARTER' => $bonus['ORI_BONUS_QUARTER'],
+                'ORI_BONUS_TOURISM' => $tourismBonus, // 旅游奖
+                'ORI_BONUS_VILLA' => $villaBonus, // 房奖
+                'ORI_BONUS_GARAGE' => $garageBonus, // 车奖
+                'PV_1L' => $perfData['PV_1L_TOUCH'],//TOUCH为碰业绩
+                'QY_1L' => $perfData['PV_1L_TOUCH'] + $pervSurplusPerf['SURPLUS_1L'],
+                'SURPLUS_1L' => $perfData['SURPLUS_1L'],
+                'PV_2L' => $perfData['PV_2L_TOUCH'],
+                'QY_2L' => $perfData['PV_2L_TOUCH'] + $pervSurplusPerf['SURPLUS_2L'],
+                'SURPLUS_2L' => $perfData['SURPLUS_2L'],
+                'PV_3L' => $perfData['PV_3L_TOUCH'],
+                'QY_3L' => $perfData['PV_3L_TOUCH'] + $pervSurplusPerf['SURPLUS_3L'],
+                'SURPLUS_3L' => $perfData['SURPLUS_3L'],
+                'PV_4L' => $perfData['PV_4L_TOUCH'],
+                'QY_4L' => $perfData['PV_4L_TOUCH'] + $pervSurplusPerf['SURPLUS_4L'],
+                'SURPLUS_4L' => $perfData['SURPLUS_4L'],
+                'PV_5L' => $perfData['PV_5L_TOUCH'],
+                'QY_5L' => $perfData['PV_5L_TOUCH'] + $pervSurplusPerf['SURPLUS_5L'],
+                'SURPLUS_5L' => $perfData['SURPLUS_5L'],
+                'PV_PCS' => $perfData['PV_PCS'],
+                'PV_TOUCH' => Tool::formatPrice($perfData['PV_1L_TOUCH'] + $perfData['PV_2L_TOUCH'] + $perfData['PV_3L_TOUCH'] + $perfData['PV_4L_TOUCH'] + $perfData['PV_5L_TOUCH']),
+                'PERIOD_NUM' => $this->_periodNum,
+                'CALC_YEAR' => $this->_calcYear,
+                'CALC_MONTH' => $this->_calcYearMonth,
+                'CREATED_AT' => Date::nowTime(),
+            ];
+            
+        } catch(Exception $e) {
+            var_dump($e->getMessage(),'-----------------------------------------------',$userId);
+        }
+        $resend = [];
+        unset($bonus, $bonusReal);
+        return ['result'=>$result,'resend'=>$resend];
+    }
+
+    // 判断是否满足月最低消费
+    public function _isMonthPerfLimit($userId) {
+        $userMonthTotal = PerfMonth::find()->where(
+            'USER_ID=:USER_ID AND CALC_MONTH=:CALC_MONTH', 
+            ['USER_ID'=>$userId, 'CALC_MONTH'=>$this->_calcYearMonth]
+        )
+        ->asArray()
+        ->one();
+        $fxPvStatus = false;
+        if (isset($userMonthTotal['PV_PCS']) && $userMonthTotal['PV_PCS'] >= $this->_sysConfig['monthPcsPvFxCondition']['VALUE']) {
+            $fxPvStatus = true;
+        }
+        
+        return $fxPvStatus;
+    }
+
+    // 判断会员是否活跃
+    public function _isPerfActive($userId): bool
+    {
+        $pv = PerfPeriod::find()->where('USER_ID=:USER_ID AND CALC_MONTH=:CALC_MONTH AND PERIOD_NUM<=:PERIOD_NUM',
+            ['USER_ID'=>$userId, 'CALC_MONTH'=>$this->_calcYearMonth, 'PERIOD_NUM'=>$this->_periodNum])
+            ->SUM('PV_PCS');
+
+        return $pv >= $this->_sysConfig['monthPcsPvFxCondition']['VALUE'];
+    }
+}

+ 640 - 0
common/helpers/bonus/CalcServePerfCalc.php

@@ -0,0 +1,640 @@
+<?php
+/**
+
+ */
+namespace common\helpers\bonus;
+
+use common\helpers\Cache;
+use common\helpers\Date;
+use common\helpers\snowflake\SnowFake;
+use common\models\PerfOrder;
+use common\models\PerfPeriod;
+use common\models\Period;
+use common\models\PerfMonth;
+use common\models\ServeProcess;
+use common\models\UserNetwork;
+use yii\base\Exception;
+use yii\base\StaticInstanceTrait;
+
+class CalcServePerfCalc {
+    use StaticInstanceTrait;
+
+    private $_limit = 3000;
+    private $_companyMonthPerf = 0;
+    private $_cfTotalPercent = 0;
+    private $_lxTotalPercent = 0;
+    private $_sysConfig = [];
+    private $_decLevelConfig = [];
+    private $_empLevelConfig = [];
+    private $_decRoleConfig = [];
+    private $_periodNum = 0;
+    private $_isCalcMonth = 0;
+    private $_calcYear;
+    private $_calcMonth;
+    private $_calcYearMonth;
+    private $_lastCalcYear;
+    private $_lastCalcMonth;
+    private $_lastCalcYearMonth;
+    private $_lastPeriodNum;
+    private $_periodObj;
+
+    const LOOP_FINISH = 1;
+    const LOOP_CONTINUE = 2;
+
+    const ORDER_PAY_TYPE_CASH = 'cash';
+
+    /**
+     * 获取期数
+     * @return int
+     */
+    public function getPeriodNum() {
+        return $this->_periodNum;
+    }
+
+    // 校验是否可进行计算
+    public function isCalcIng($periodNum) {
+        $periodObj = Period::instance();
+        $periodDataArr = $periodObj->setPeriodNum($periodNum);
+        if (empty($periodDataArr)) {
+            return false;
+        }
+        if ($periodDataArr['IS_PREPARE'] != 1) {
+            return false;
+        }
+        $this->_periodNum = $periodDataArr['PERIOD_NUM'];
+        $this->_periodObj = $periodObj;
+
+        return true;
+    }
+
+    /**
+     * 累计业绩数据
+     * @param $periodNum
+     * @return bool
+     */
+    public function calcStep($periodNum = null) {
+        try {
+            $requestTime = date('Y-m-d H:i:s', time());
+            // if (empty($periodNum)) {
+            //     echo('触发时间:【'.$requestTime.'】'.'定时器执行累计业绩计算 ,内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            // } else {
+            //     echo('触发时间:【'.$requestTime.'】'.'手动触发累计业绩计算 ,内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            // }
+            //一、查询此业绩状态,是否能进行计算
+            $calcIng = $this->isCalcIng($periodNum);
+            ServeProcess::recordRequest($requestTime, $this->_periodNum, '', true);
+            if ($calcIng !== true) {
+                //echo('触发时间:【'.date('Y-m-d H:i:s', time()).'】'.'业绩期表中,此期状态不正确');
+                //ServeProcess::recordRequest($requestTime, $this->_periodNum, '业绩期表中,此期状态不正确');
+                return false;
+            } else {
+                // 将IS_PREPARE改成2,计算中
+                Period::updateCalcProcess(2, $this->_periodNum);
+                ServeProcess::recordRequest($requestTime, $this->_periodNum, '调整IS_PREPARE值为2,计算开始');
+            }
+            
+            // 记录初始化数据,用户总数,业绩单业绩总pv值.
+            ServeProcess::recordDataInfo(date('Y-m-d H:i:s',time()), $this->_periodNum);
+
+            $t1 = microtime(true);
+            //二、初始化
+            $this->initCalcTask();
+            echo '业绩期为:'.$this->_periodNum;
+            
+            Period::updatePercent(10, $this->_periodNum);
+            ServeProcess::recordProcess($t1, time(), $this->_periodNum, '初始化---初始化配置');
+
+            $initT2 = microtime(true);
+            //三、 设置结算状态
+            $this->setCalcStatus('start', $this->_periodNum);
+            ServeProcess::recordProcess($initT2, time(), $this->_periodNum, '初始化---设置结算状态');
+
+            $initT3 = microtime(true);
+            //四、 清空所有本期结算用到的缓存
+            CalcCache::clearAll($this->_periodNum);
+            ServeProcess::recordProcess($initT3, time(), $this->_periodNum, '初始化---清空业绩缓存');
+
+            $initT4 = microtime(true);
+            //五、 清空会员推荐和接点关系缓存
+            CalcCache::clearNetCache();
+            ServeProcess::recordProcess($initT4, time(), $this->_periodNum, '初始化---清空会员推荐和接点关系缓存');
+
+            $initT5 = microtime(true);
+            //六、 清空相关表数据
+            $this->clearTableData();
+            $this->_updatePercent(15);
+            ServeProcess::recordProcess($initT5, time(), $this->_periodNum, '初始化---清空相关表数据');
+
+            $t2 = microtime(true);
+            echo(date('Y-m-d H:i:s',time()).'初始化、清空缓存及相关数据表完成,耗时:' . round($t2 - $t1, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            ServeProcess::recordProcess($t1, $t2, $this->_periodNum, '初始化---初始化、清空缓存及相关数据表完成');
+            
+            //七、 添加缓存中用户数据
+            CalcCache::addUsers($this->_periodNum);
+            $t3 = microtime(true);
+            echo(date('Y-m-d H:i:s',time()).'计算业绩向缓存中加入用户完成,耗时:' . round($t3 - $t2, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            ServeProcess::recordProcess($t2, $t3, $this->_periodNum, '计算业绩向缓存中加入用户完成');
+            $this->_updatePercent(20);
+
+            // 八、循环累计用户各项业绩数据
+            $this->loopGrandPerf();
+            $t4 = microtime(true);
+            echo(date('Y-m-d H:i:s',time()).'累计用户业绩完成' . round($t4 - $t3, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            ServeProcess::recordProcess($t3, $t4, $this->_periodNum, '累计用户业绩完成');
+            $this->_updatePercent(60);
+            
+            // 九、本期业绩入库
+            $this->loopWriteNowPerf();
+            $t6 = microtime(true);
+            echo(date('Y-m-d H:i:s',time()).'本期业绩入库完成,耗时:' . round($t6 - $t4, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            ServeProcess::recordProcess($t4, $t6, $this->_periodNum, '本期业绩入库完成');
+            $this->_updatePercent(70);
+
+            //十、计算月业绩表中的数据
+            $this->loopCalcMonthPerfTableData();
+            $t7 = microtime(true);
+            echo(date('Y-m-d H:i:s',time()).'计算月业绩表中的数据完成,耗时:' . round($t7 - $t6, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            if($this->_isCalcMonth) {
+                ServeProcess::recordProcess($t6, $t7, $this->_periodNum, '计算月业绩表中的数据完成');
+            }
+            $this->_updatePercent(80);
+
+            //十一、本月业绩入库
+            $this->loopWriteMonthPerf();
+            $t8 = microtime(true);
+            echo(date('Y-m-d H:i:s',time()).'本月业绩入库完成,耗时:' . round($t8 - $t7, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            if($this->_isCalcMonth) {
+                ServeProcess::recordProcess($t7, $t8, $this->_periodNum, '本月业绩入库完成');
+            }
+            $this->_updatePercent(100);
+            $t9 = microtime(true);
+            ServeProcess::recordProcess($t6, $t7, $this->_periodNum, '计算业绩业绩结算全部完成');
+            echo(date('Y-m-d H:i:s',time()).'计算业绩业绩结算全部完成,共耗时:' . round($t9 - $t1, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+        } catch (\Exception $e) {
+            $this->errorCalcTask();
+            var_dump($e->getMessage());
+            return false;
+        }
+
+        $this->endCalcTask();
+
+        return true;
+    }
+
+    /**
+     * 结算完成
+     * @return bool
+     */
+    public function endCalcTask() {
+        // 更新结算状态
+        $this->setCalcStatus('end');
+    }
+
+    /**
+     * 结算错误
+     */
+    public function errorCalcTask() {
+        // 清空所有本期结算用到的缓存
+        CalcCache::clearAll($this->_periodNum);
+        // 更新结算状态
+        $this->setCalcStatus('fail');
+    }
+
+    /**
+     * 设置生成业绩单状态
+     * @param $type
+     * start|end|fail
+     */
+    public function setCalcStatus($type, $periodNum = null) {
+        if ($type == 'start') {
+            Period::updateAll(['IS_PERFING' => 1, 'IS_PERFED' => Period::PERF_NONE, 'PERF_STARTED_AT' => Date::nowTime()], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        } elseif ($type == 'end') {
+            Period::updateAll(['IS_PERFING' => 0, 'IS_PERFED' => Period::PERF_FINISH, 'PERFED_AT' => Date::nowTime()], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        } elseif ($type == 'fail') {
+            Period::updateAll(['IS_PERFING' => 0, 'IS_PERFED' => Period::PERF_FAIL, 'PERFED_AT' => 0], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        }
+    }
+
+    /**
+     * 初始化结算任务
+     * @throws \yii\db\Exception
+     */
+    public function initCalcTask() {
+        try {
+            $periodObj = Period::instance();
+            $this->_sysConfig = Cache::getSystemConfig();
+            $this->_decLevelConfig = Cache::getDecLevelConfig();
+            $this->_empLevelConfig = Cache::getEmpLevelConfig();
+            $this->_decRoleConfig = CalcCache::getDecRoleConfig($this->_periodNum);
+            $this->_isCalcMonth = $periodObj->isCalcMonth($this->_periodNum);
+            $this->_calcYear = $periodObj->getYear($this->_periodNum);
+            $this->_calcMonth = $periodObj->getMonth($this->_periodNum);
+            $this->_calcYearMonth = $periodObj->getYearMonth($this->_periodNum);
+        } catch(Exception $e) {
+            var_dump($e->getMessage());
+        }
+    }
+
+    /**
+     * 清空相关表数据
+     */
+    public function clearTableData() {
+        PerfPeriod::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);// 周业绩
+        if ($this->_isCalcMonth) {
+            PerfMonth::pageDeleteAll("CALC_MONTH='{$this->_calcYearMonth}'");// 月业绩表
+        }
+    }
+
+    /**
+     * 累计用户业绩
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function loopGrandPerf($offset = 0) {
+        $allData = PerfOrder::findUseDbCalc()
+        ->select('ORDER_AMOUNT,DEC_TYPE,USER_ID,PV,PERIOD_NUM,DEC_USER_ID,PAY_TYPE')
+        ->where(
+            "PERIOD_NUM=:PERIOD_NUM", 
+            [':PERIOD_NUM' => $this->_periodNum]
+        )
+        ->orderBy('CREATED_AT DESC,ID DESC')
+        ->offset($offset)
+        ->limit($this->_limit)
+        ->asArray()
+        ->all();
+        if (!empty($allData)) {
+            foreach ($allData as $data) {
+                // 循环累计报单业绩
+                try{
+                    echo "开始累计用户业绩,用户ID为:".$data['USER_ID'].PHP_EOL;
+                    if ($data['DEC_TYPE'] == PerfOrder::ZC_TYPE) {
+                        // 给自己增加PCS(个人消费)
+                        CalcCache::nowPeriodPerf($data['USER_ID'], $this->_periodNum, [
+                            'PV_PCS' => $data['PV'],
+                            'PV_PCS_ZC' => $data['PV'],
+                        ]);
+                        // 把该会员加入到能拿到业绩的会员缓存中
+                        CalcCache::addHasPerfUsers($data['USER_ID'], $this->_periodNum);
+                        //加入到报单会员中
+                        $toInfo = CalcCache::getUserInfo($data['USER_ID'], $this->_periodNum);
+                        CalcCache::addHasBDUsers($data['USER_ID'], $this->_periodNum, [
+                            'TO_USER_ID' => $data['USER_ID'],
+                            'USER_ID' => $toInfo['DEC_ID'],
+                            'DEC_ID' => $toInfo['DEC_ID'],
+                            //考虑可能会移网的情况
+                            'REC_USER_ID' => $toInfo['REC_UID'] ?? '',
+                            'CON_USER_ID' => $toInfo['CON_UID'] ?? '',
+                            'DEC_AMOUNT' => $data['ORDER_AMOUNT'],
+                            'DEC_PV' => $data['PV'],
+                        ]);
+                        // 给上追加业绩
+                        $this->loopNetworkParentDo($data['USER_ID'], function ($parent) use (&$data) {
+                            // 给上级会员追加本期业绩到缓存中
+                            CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
+                                'PV_' . $parent['LOCATION'] . 'L' => $data['PV'],
+                                'PV_' . $parent['LOCATION'] . 'L_TOUCH' => $data['PV'],
+                                'PV_' . $parent['LOCATION'] . 'L_' . $data['DEC_TYPE'] => $data['PV'],
+                            ]);
+                            // 把该会员加入到能拿到业绩的会员缓存中
+                            CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
+                            unset($parent);
+                        });
+                        //给推荐关系累计增加业绩
+                        $this->loopRelationParentDo($data['USER_ID'], function ($parent) use (&$data) {
+                            // 给上级会员追加本期业绩到缓存中
+                            CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
+                                'PV_PSS' => $data['PV'],
+                            ]);
+                            // 把该会员加入到能拿到业绩的会员缓存中
+                            CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
+                            unset($parent);
+                        });
+                    }
+                } catch(Exception $e) {
+                    var_dump(__LINE__,__FILE__,$e->getMessage(), $data);
+                }
+                
+                // 循环累计复消业绩
+                try{
+                    if ($data['DEC_TYPE'] == PerfOrder::FX_TYPE) {
+                        if( $data['PAY_TYPE'] === self::ORDER_PAY_TYPE_CASH ) {
+                            $orderCashAmount = $data['ORDER_AMOUNT'];
+                            $payPv = $data['PV'] * $this->_sysConfig['cashReconsumeBonusPercent']['VALUE'] / 100;
+                            $cacheDataKey = 'PV_PCS_FX_CASH';
+                        } else {
+                            $orderCashAmount = 0;
+                            $payPv = $data['PV'];
+                            $cacheDataKey = 'PV_PCS_FX_POINT';
+                        }
+
+                        if( $payPv <= 0 ) continue;
+                        // 给自己增加PCS(个人消费)
+                        CalcCache::nowPeriodPerf($data['USER_ID'], $this->_periodNum, [
+                            'FX_AMOUNT_CASH' => $orderCashAmount,
+                            'PV_PCS' => $payPv,
+                            'PV_PCS_FX' => $payPv,
+                            $cacheDataKey => $payPv,
+                        ]);
+                        // 把该会员加入到能拿到业绩的会员缓存中
+                        CalcCache::addHasPerfUsers($data['USER_ID'], $this->_periodNum);
+                        // 给上追加业绩
+                        $this->loopNetworkParentDo($data['USER_ID'], function ($parent) use (&$data, $payPv) {
+                            // 给上级会员追加本期业绩到缓存中
+                            CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
+                                'PV_' . $parent['LOCATION'] . 'L' => $payPv,
+                                'PV_' . $parent['LOCATION'] . 'L_TOUCH' => $payPv,
+                                'PV_' . $parent['LOCATION'] . 'L_FX' => $payPv,
+                            ]);
+                            // 把该会员加入到能拿到业绩的会员缓存中
+                            CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
+                        });
+                        //给推荐关系累计增加业绩
+                        $this->loopRelationParentDo($data['USER_ID'], function ($parent) use ($data, $payPv) {
+                            // 给上级会员追加本期业绩到缓存中
+                            CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
+                                'PV_PSS' => $payPv,
+                            ]);
+                            // 把该会员加入到能拿到业绩的会员缓存中
+                            CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
+                        });
+                    }
+                }catch(Exception $e) {
+                    var_dump(__LINE__,__FILE__,$e->getMessage(), $data);
+                }
+            }
+
+            return $this->loopGrandPerf($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 计算月业绩表相关的数据并写入数据库
+     * @param int $offset
+     * @return bool
+     * @throws Exception
+     * @throws \yii\db\Exception
+     */
+    public function loopCalcMonthPerfTableData(int $offset = 0) {
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+        echo sprintf("时间:[%s]月业绩,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        $allData = PerfPeriod::findUseDbCalc()
+        ->select('USER_ID, SUM(FX_AMOUNT_CASH) AS FX_AMOUNT_CASH_SUM,SUM(PV_PCS) AS PV_PCS_SUM,SUM(PV_PCS_FX) AS PV_PCS_FX_SUM,
+        SUM(PV_PSS) AS PV_PSS_SUM,SUM(PV_1L) AS PV_1L_SUM,SUM(PV_2L) AS PV_2L_SUM,SUM(PV_3L) AS PV_3L_SUM,
+        SUM(PV_4L) AS PV_4L_SUM,SUM(PV_5L) AS PV_5L_SUM,SUM(PV_1L_ZC) AS PV_1L_ZC_SUM,SUM(PV_2L_ZC) AS PV_2L_ZC_SUM,
+        SUM(PV_3L_ZC) AS PV_3L_ZC_SUM,SUM(PV_4L_ZC) AS PV_4L_ZC_SUM,SUM(PV_5L_ZC) AS PV_5L_ZC_SUM')
+        ->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])
+        ->groupBy('USER_ID')
+        ->orderBy('USER_ID DESC')
+        ->offset($offset)
+        ->limit($this->_limit)
+        ->asArray()
+        ->all();
+        if ($allData) {
+            // 月度业绩表
+            foreach ($allData as $everyData) {
+                $userId = $everyData['USER_ID'];
+                //往期业绩
+                $userLastPerf = CalcCache::userPerf($userId, $this->_periodNum);
+                //本期业绩
+                $periodPerf = CalcCache::nowPeriodPerf($userId, $this->_periodNum);
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                $nowMonthPerf = [
+                    'USER_ID' => $userId,
+                    'FX_AMOUNT_CASH' => $everyData['FX_AMOUNT_CASH_SUM'],
+                    'PV_PCS' => $everyData['PV_PCS_SUM'],
+                    'PV_PCS_FX' => $everyData['PV_PCS_FX_SUM'],
+                    'PV_PSS' => $everyData['PV_PSS_SUM'],
+                    'PV_1L' => $everyData['PV_1L_SUM'],
+                    'PV_2L' => $everyData['PV_2L_SUM'],
+                    'PV_3L' => $everyData['PV_3L_SUM'],
+                    'PV_4L' => $everyData['PV_4L_SUM'],
+                    'PV_5L' => $everyData['PV_5L_SUM'],
+                    //总数据,历史+本期。不能用上月加本月,因为上月可能没业绩,上上个月有业绩。
+                    'PV_1L_TOTAL' => $periodPerf['PV_1L'] + $userLastPerf['PV_1L'],
+                    'PV_2L_TOTAL' => $periodPerf['PV_2L'] + $userLastPerf['PV_2L'],
+                    'PV_3L_TOTAL' => $periodPerf['PV_3L'] + $userLastPerf['PV_3L'],
+                    'PV_4L_TOTAL' => $periodPerf['PV_4L'] + $userLastPerf['PV_4L'],
+                    'PV_5L_TOTAL' => $periodPerf['PV_5L'] + $userLastPerf['PV_5L'],
+                    'PV_PSS_TOTAL' => $periodPerf['PV_PSS'] + $userLastPerf['PV_PSS_TOTAL'],
+                ];
+
+                // 把会员的月业绩写入缓存中,以便下面的奖金计算从缓冲中获取数据效率高
+                CalcCache::addHasMonthPerfUsers($userId, $this->_periodNum);
+                CalcCache::nowMonthPerf($userId, $this->_periodNum, $nowMonthPerf);
+
+                unset($userId, $everyData, $nowMonthPerf, $lastMonthData, $userBaseInfo, $isVip);
+            }
+            unset($allData);
+            $this->loopCalcMonthPerfTableData($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 循环有业绩会员,并入库
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function loopWriteNowPerf($offset = 0) {
+        echo sprintf("时间:[%s]缓存本期业绩数据入库,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        // 从缓存列表里面从底层往上倒序获取会员
+        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if($allData){
+            $insertDataPeriodPerf = [];
+            foreach($allData as $userId){
+                $insertDataPeriodPerf[] = $this->nowPeriodPerfData($userId);
+                unset($userId);
+            }
+            PerfPeriod::batchInsert($insertDataPeriodPerf);
+
+            unset($insertDataPeriodPerf, $allData);
+            return $this->loopWriteNowPerf($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 循环有月业绩会员,并入库
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function loopWriteMonthPerf($offset = 0) {
+        if(!$this->_isCalcMonth){
+            return true;
+        }
+        echo sprintf("时间:[%s]缓存本月业绩数据入库,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        // 从缓存列表里面从底层往上倒序获取会员
+        $allData = CalcCache::getHasMonthPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if($allData){
+            $insertDataMonthPerf = [];
+            foreach($allData as $userId){
+                $insertDataMonthPerf[] = $this->nowMonthPerfData($userId);
+                unset($userId);
+            }
+
+            PerfMonth::batchInsert($insertDataMonthPerf);
+            unset($insertDataMonthPerf, $allData);
+            return $this->loopWriteMonthPerf($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 本期业绩数据
+     * @param $userId
+     * @return array
+     */
+    public function nowPeriodPerfData($userId){
+        $data = CalcCache::nowPeriodPerf($userId, $this->_periodNum);
+        $result = [
+            'ID' => SnowFake::instance()->generateId(),
+            'USER_ID' => $userId,
+            'FX_AMOUNT_CASH' => $data['FX_AMOUNT_CASH'],
+            'PV_PCS' => $data['PV_PCS'],
+            'PV_PSS' => $data['PV_PSS'],
+            'PV_PCS_ZC' => $data['PV_PCS_ZC'],
+            'PV_PCS_FX' => $data['PV_PCS_FX'],
+            'PV_PCS_FX_CASH' => $data['PV_PCS_FX_CASH'],
+            'PV_PCS_FX_POINT' => $data['PV_PCS_FX_POINT'],
+            'PV_1L' => $data['PV_1L'],
+            'PV_1L_TOUCH' => $data['PV_1L_TOUCH'],
+            'PV_1L_ZC' => $data['PV_1L_ZC'],
+            'PV_1L_FX' => $data['PV_1L_FX'],
+            'PV_2L' => $data['PV_2L'],
+            'PV_2L_TOUCH' => $data['PV_2L_TOUCH'],
+            'PV_2L_ZC' => $data['PV_2L_ZC'],
+            'PV_2L_FX' => $data['PV_2L_FX'],
+            'PV_3L' => $data['PV_3L'],
+            'PV_3L_TOUCH' => $data['PV_3L_TOUCH'],
+            'PV_3L_ZC' => $data['PV_3L_ZC'],
+            'PV_3L_FX' => $data['PV_3L_FX'],
+            'PV_4L' => $data['PV_4L'],
+            'PV_4L_TOUCH' => $data['PV_4L_TOUCH'],
+            'PV_4L_ZC' => $data['PV_4L_ZC'],
+            'PV_4L_FX' => $data['PV_4L_FX'],
+            'PV_5L' => $data['PV_5L'],
+            'PV_5L_TOUCH' => $data['PV_5L_TOUCH'],
+            'PV_5L_ZC' => $data['PV_5L_ZC'],
+            'PV_5L_FX' => $data['PV_5L_FX'],
+            'SURPLUS_1L' => $data['SURPLUS_1L'],
+            'SURPLUS_2L' => $data['SURPLUS_2L'],
+            'SURPLUS_3L' => $data['SURPLUS_3L'],
+            'SURPLUS_4L' => $data['SURPLUS_4L'],
+            'SURPLUS_5L' => $data['SURPLUS_5L'],
+            'PERIOD_NUM' => $this->_periodNum,
+            'CALC_MONTH' => $this->_calcYearMonth,
+            'CREATED_AT' => Date::nowTime(),
+        ];
+        unset($data);
+        return $result;
+    }
+
+
+
+    /**
+     * 本月业绩
+     * @param $userId
+     * @return array
+     */
+    public function nowMonthPerfData($userId){
+        $data = CalcCache::nowMonthPerf($userId, $this->_periodNum);
+        $result = [
+            'ID' => SnowFake::instance()->generateId(),
+            'USER_ID' => $userId,
+            'FX_AMOUNT_CASH' => $data['FX_AMOUNT_CASH'],
+            'PV_PCS' => $data['PV_PCS'],
+            'PV_PCS_FX' => $data['PV_PCS_FX'],
+            'PV_PSS' => $data['PV_PSS'],
+            'PV_1L' => $data['PV_1L'],
+            'PV_2L' => $data['PV_2L'],
+            'PV_3L' => $data['PV_3L'],
+            'PV_4L' => $data['PV_4L'],
+            'PV_5L' => $data['PV_5L'],
+            'PV_1L_TOTAL' => $data['PV_1L_TOTAL'],
+            'PV_2L_TOTAL' => $data['PV_2L_TOTAL'],
+            'PV_3L_TOTAL' => $data['PV_3L_TOTAL'],
+            'PV_4L_TOTAL' => $data['PV_4L_TOTAL'],
+            'PV_5L_TOTAL' => $data['PV_5L_TOTAL'],
+            'PV_PSS_TOTAL' => $data['PV_PSS_TOTAL'],
+            'CALC_MONTH' => $this->_calcYearMonth,
+            'CREATED_AT' => Date::nowTime(),
+        ];
+        unset($data);
+        return $result;
+    }
+
+    /**
+     * 循环父级并执行回调函数
+     * @param $userId
+     * @param callable $callbackFunc
+     * @param int $offset
+     * @return bool
+     */
+    public function loopNetworkParentDo($userId, callable $callbackFunc, int $offset = 0) {
+        $allParents = Cache::getAllNetworkParents($userId, true);
+        $allData = array_slice($allParents, $offset, $this->_limit);
+        unset($allParents);
+        if ($allData) {
+            foreach ($allData as $data) {
+                $funcResult = $callbackFunc($data);
+                if ($funcResult === self::LOOP_FINISH) {
+                    return true;
+                } elseif ($funcResult === self::LOOP_CONTINUE) {
+                    continue;
+                }
+                unset($data, $funcResult);
+            }
+            unset($allData);
+            return $this->loopNetworkParentDo($userId, $callbackFunc, $offset + $this->_limit);
+        }
+        return true;
+    }
+
+    /**
+     * 循环推荐网络的父级
+     * @param $userId
+     * @param callable $callbackFunc
+     * @param int $offset
+     * @return bool
+     */
+    public function loopRelationParentDo($userId, callable $callbackFunc, int $offset = 0) {
+        $allParents = Cache::getAllRelationParents($userId,true);
+        $allData = array_slice($allParents, $offset, $this->_limit);
+        unset($allParents);
+        if ($allData) {
+            foreach ($allData as $data) {
+                $funcResult = $callbackFunc($data);
+                if ($funcResult === self::LOOP_FINISH) {
+                    return true;
+                } elseif ($funcResult === self::LOOP_CONTINUE) {
+                    continue;
+                }
+                unset($data, $funcResult);
+            }
+
+            unset($allData);
+            return $this->loopRelationParentDo($userId, $callbackFunc, $offset + $this->_limit);
+        }
+        return true;
+    }
+
+    /**
+     * 更新百分比并发送
+     * @param $percent
+     */
+    private function _updatePercent($percent) {
+        // 把数据写入数据库中
+        Period::updateAll(['PERF_PERCENT' => $percent], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+    }
+}

+ 1696 - 0
common/helpers/bonus/PerfCalc.php

@@ -0,0 +1,1696 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: liyunlong
+ * Date: 2019-01-11
+ * Time: 15:27
+ */
+
+namespace common\helpers\bonus;
+
+
+use common\helpers\Cache;
+use common\helpers\Date;
+use common\helpers\Form;
+use common\helpers\snowflake\SnowFake;
+use common\helpers\Tool;
+use common\helpers\user\Info;
+use common\helpers\user\Reconsume;
+use common\models\CalcBonus;
+use common\models\DeclarationLevel;
+use common\models\forms\DeclarationForm;
+use common\models\forms\OrderForm;
+use common\models\Order;
+use common\models\OrderDec;
+use common\models\OrderShop;
+use common\models\OrderStandard;
+use common\models\PerfCompany;
+use common\models\PerfMonth;
+use common\models\PerfOrder;
+use common\models\PerfPeriod;
+use common\models\PerfStandard;
+use common\models\Period;
+use common\models\DecOrder;
+use common\models\EmployLevel;
+use common\models\PerfActiveUser;
+use common\models\UserRelation;
+use common\models\RemainPv;
+use common\models\FlowRemainPv;
+use yii\base\Exception;
+use yii\base\StaticInstanceTrait;
+
+class PerfCalc {
+    use StaticInstanceTrait;
+
+    private $_limit = 10000;
+    private $_handleUserId;
+    private $_companyMonthPerf = 0;
+    private $_cfTotalPercent = 0;
+    private $_lxTotalPercent = 0;
+    private $_sysConfig = [];
+    private $_decLevelConfig = [];
+    private $_empLevelConfig = [];
+    private $_decRoleConfig = [];
+    private $_errors = [];
+    private $_periodNum = 0;
+    private $_periodId;
+    private $_isCalcMonth = 0;
+    private $_calcYear;
+    private $_calcMonth;
+    private $_calcYearMonth;
+    private $_lastCalcYear;
+    private $_lastCalcMonth;
+    private $_lastCalcYearMonth;
+    private $_lastPeriodNum;
+    private $_lastPeriodYear;
+    private $_lastPeriodMonth;
+    private $_lastPeriodYearMonth;
+    //pv
+    private $_pvRatio;
+
+    const LOOP_FINISH = 1;
+    const LOOP_CONTINUE = 2;
+
+    const ORDER_PAY_TYPE_CASH = 'cash';
+//    const ORDER_PAY_TYPE_POINT = 'point';
+
+    /**
+     * 设置期数
+     * @param int $periodNum
+     * @return int
+     */
+    public function setPeriodNum(int $periodNum) {
+        return $this->_periodNum = $periodNum;
+    }
+
+    /**
+     * 获取期数
+     * @return int
+     */
+    public function getPeriodNum() {
+        return $this->_periodNum;
+    }
+
+    /**
+     * 加入错误错误
+     * @param string $attr
+     * @param string $error
+     */
+    public function addError(string $attr, string $error) {
+        $this->_errors[$attr][] = $error;
+    }
+
+    /**
+     * 获取错误信息
+     * @return array
+     */
+    public function getErrors() {
+        return $this->_errors;
+    }
+
+    /**
+     * 生成流水号
+     * @return string
+     */
+    private function _generateSn() {
+        return Date::today('Ymd') . $this->_random(10, 1);
+    }
+
+    /**
+     * 生成随机数
+     * @param $length
+     * @param int $numeric
+     * @return string
+     */
+    private function _random($length, $numeric = 0) {
+        $seed = base_convert(md5(microtime() . $_SERVER['DOCUMENT_ROOT']), 16, $numeric ? 10 : 35);
+        $seed = $numeric ? (str_replace('0', '', $seed) . '012340567890') : ($seed . 'zZ' . strtoupper($seed));
+        $hash = '';
+        $max = strlen($seed) - 1;
+        for ($i = 0; $i < $length; $i++) {
+            $hash .= $seed[mt_rand(0, $max)];
+        }
+        return $hash;
+    }
+
+    /**
+     * 处理order表中有remain_pv的订单
+     * 将结果写入到remainPv相关表中
+     *
+     */
+    private function _calcRemainPv(){
+        $orders = Order::find()->where('PERIOD_NUM=:PERIOD_NUM AND REMAIN_PV>0',[':PERIOD_NUM'=>$this->_periodNum])->asArray()->all();
+        foreach($orders as $order){
+            $oRemainPv = RemainPv::find()->where(['USER_ID' => $order['USER_ID']])->one();
+
+            $transactionRemain = \Yii::$app->db->beginTransaction();
+            try{
+                $flowRemainPvModel = new FlowRemainPv();
+                $flowRemainPvModel->ID = $this->_generateSn();
+                $flowRemainPvModel->USER_ID = $order['USER_ID'];
+                $flowRemainPvModel->REMAIN_PV_FLOW = $order['REMAIN_PV'];
+                $flowRemainPvModel->REMAIN_PV_TOTAL = $oRemainPv['REMAIN_PV'] + $order['REMAIN_PV'];
+                $flowRemainPvModel->PERIOD_NUM = $this->_periodNum;
+                $flowRemainPvModel->UPDATED_AT = Date::nowTime();
+                $flowRemainPvModel->ORDER_SN = $order['SN'];
+                if(!$flowRemainPvModel->save()){
+                    $this->addErrors($flowRemainPvModel->getErrors());
+                    return false;
+                }
+
+                $oRemainPv = RemainPv::find()->where(['USER_ID' => $order['USER_ID']])->one();
+                if($oRemainPv){
+                    $oRemainPv->updateCounters(['REMAIN_PV'=>$order['REMAIN_PV']]);
+                }else{
+                    $remainPvModel = new RemainPv();
+                    $remainPvModel->ID = $this->_generateSn();
+                    $remainPvModel->USER_ID = $order['USER_ID'];
+                    $remainPvModel->UPDATED_AT = Date::nowTime();
+                    $remainPvModel->REMAIN_PV = $order['REMAIN_PV'];
+                    $remainPvModel->STATUS = 1;
+                    if(!$remainPvModel->save()){
+                        $this->addErrors($remainPvModel->getErrors());
+                        return false;
+                    }
+                }
+                $transactionRemain->commit();
+            } catch (Exception $e) {
+                $transactionRemain->rollBack();
+                $this->addError('add', $e->getMessage());
+                return null;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 生成假订单
+     *
+     */
+    public static function _createFakeOrder($periodNum){
+        echo('假假假'.$periodNum. PHP_EOL);
+        $userHaveRemain = RemainPv::findAllAsArray('REMAIN_PV >0');
+        $currentPeriod = Period::getInfoByPeriodNum($periodNum);
+        if($currentPeriod['IS_MONTH']){
+            print_r('是月结点'.PHP_EOL);
+            $periods = Period::getCurrentMonthPeriodByPeriodNum($periodNum);
+            //先清除本期的假订单
+            echo('首先,清除上次尝试生成业绩单时所创建的虚假订单'. date('Y-m-d  H:i:s', time()) . PHP_EOL);
+            $delFOrder = Order::deleteAll(['IS_AUTO'=>'1','PERIOD_NUM'=>$periodNum]);
+            echo('检查有结余PV的用户,如果他当月PV不足30,则为其创建假订单'. PHP_EOL);
+            foreach($userHaveRemain as $uR){
+                $myPv = Order::find()->where(['PERIOD_NUM'=>$periods, 'USER_ID'=>$uR['USER_ID']])->SUM('PV');
+                if($myPv<30){
+                    //制造虚拟订单
+                    echo('不足30了,生成假订单' . PHP_EOL);
+                    $newOrderForm = new OrderForm();
+                    $newOrderForm->addFakeOrder($uR['USER_ID'],$periodNum);
+                }
+            }
+        }
+    }
+
+    /**
+     * 计算步骤
+     * @param $periodNum
+     * @param null $handleUserId
+     * @return bool
+     */
+    public function calcStep($periodNum, $handleUserId = null) {
+        try {
+            $this->_errors = [];
+            $this->setPeriodNum($periodNum);
+            $this->_handleUserId = $handleUserId;
+            $t1 = microtime(true);
+            // 初始化结算任务
+            $this->initCalcTask();
+            // 设置结算状态
+            $this->setCalcStatus('start');
+            // 清空所有本期结算用到的缓存
+            CalcCache::clearAll($this->_periodNum);
+            // 清空会员推荐和接点关系缓存
+            CalcCache::clearNetCache();
+            // 清空相关表数据
+            $this->clearTableData();
+            $t2 = microtime(true);
+            echo(PHP_EOL . $periodNum. '期,生成业绩单,开始' . PHP_EOL);
+            echo('初始化、清空缓存及相关数据表完成,耗时:' . round($t2 - $t1, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(10);
+            // 计算月奖,才需要向缓存中加入按推荐深度的所有用户
+            echo('向缓存中加入用户开始 ' . date('Y-m-d  H:i:s', time()) . PHP_EOL);
+//            // 先把有remainPv的订单处理一下,将remainPv加入到remain_pv及流水表
+//            echo('处理当期REMAIN PV ' . date('Y-m-d  H:i:s', time()) . PHP_EOL);
+//            $this->_calcRemainPv();
+            echo('若需要,生成假订单' . date('Y-m-d  H:i:s', time()) . PHP_EOL);
+            $this->_createFakeOrder($periodNum);
+            echo('生成假订单完成,开始缓存用户'. date('Y-m-d  H:i:s', time()) . PHP_EOL);
+            //修改每一期都缓存所有用户
+            CalcCache::addUsers($this->_periodNum);
+            $t3 = microtime(true);
+            echo('向缓存中加入用户完成,耗时:' . round($t3 - $t2, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(20);
+            // 周结,循环向上级计入业绩并加入业绩单
+            $this->loopCalcPeriodPerfByDecOrder();
+            $this->loopCalcPeriodPerfByOrderDec();
+            $t4 = microtime(true);
+            echo('计算周业绩表中的数据完成,耗时:' . round($t4 - $t1, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(40);
+            // 从会员的复销订单会员计算复销业绩并加入业绩单
+            $this->loopCalcPerfByFXOrder();
+            $this->loopCalcPerfByShopFXOrder();
+            $t5 = microtime(true);
+            echo('计算复销业绩并写入业绩单完成,耗时:' . round($t5 - $t4, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(60);
+            //本期业绩入库
+            $this->loopWriteNowPerf();
+            $t6 = microtime(true);
+            echo('本期业绩入库完成,耗时:' . round($t6 - $t5, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(70);
+
+            //计算月业绩表中的数据
+            $this->loopCalcMonthPerfTableData();
+            $t7 = microtime(true);
+            echo('计算月业绩表中的数据完成,耗时:' . round($t7 - $t6, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(80);
+
+            //每月计算聘级
+            // modify 聘级字段改成蓝星奖的  奖衔级别(蓝星奖的等级).不在使用荣衔奖金.聘级的等级切换成蓝星奖等级,
+            // 此等级会影响团队将的封顶.
+            // $this->loopCalcEmpLevel();
+            $t8 = microtime(true);
+            // echo('计算聘级完成,耗时:' . round($t8 - $t7, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            //$this->_updatePercent(90);
+
+            //本月业绩入库
+            $this->loopWriteMonthPerf();
+            $t7 = microtime(true);
+            echo('本月业绩入库完成,耗时:' . round($t7 - $t6, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(90);
+
+            // //达标业绩
+            $this->loopCalcPerfByStandardFXOrder();
+            // //达标业绩入库
+            $this->loopWriteStandardPerf();
+            $t9 = microtime(true);
+            echo('本月业绩入库完成,耗时:' . round($t9 - $t7, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL);
+            $this->_updatePercent(95);
+
+            
+            // 通过插入到perforder中的业绩订单数据,生成此业绩期活跃用户数据
+            // $this->loopWriteActiveUser();
+            $this->_updatePercent(100);
+            $t10 = microtime(true);
+
+            echo('业绩结算全部完成,共耗时:' . round($t10 - $t9, 3) . ',内存使用:' . (round(memory_get_usage() / 1024 / 1024, 3)) . 'MB' . PHP_EOL . PHP_EOL);
+        } catch (\Exception $e) {
+            $this->errorCalcTask();
+            $this->addError('calc', sprintf('File【%s】, Line【%s】, Msg【%s】', $e->getFile(), $e->getLine(), $e->getMessage()));
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 结算完成
+     * @return bool
+     */
+    public function endCalcTask() {
+        // 更新结算状态
+        $this->setCalcStatus('end');
+        //如果自动结算
+        if(boolval($this->_sysConfig['autoCalcPeriod']['VALUE'])){
+            $period = Period::instance();
+            if($period->isLastSent($this->_periodNum)) {
+                $bonusCalc = BonusCalc::instance();
+                $asyncResult = $bonusCalc->calcStep($this->_periodNum);
+                if ($asyncResult) {
+                    $bonusCalc->endCalcTask();
+                } else {
+                    $bonusCalc->errorCalcTask();
+                }
+                return $asyncResult;
+            }
+        }
+    }
+
+    /**
+     * 结算错误
+     */
+    public function errorCalcTask() {
+        // 清空所有本期结算用到的缓存
+        CalcCache::clearAll($this->_periodNum);
+        // 更新结算状态
+        $this->setCalcStatus('fail');
+    }
+
+    /**
+     * 设置生成业绩单状态
+     * @param $type
+     * start|end|fail
+     */
+    public function setCalcStatus($type) {
+        if ($type == 'start') {
+            Period::updateAll(['IS_PERFING' => 1, 'IS_PERFED' => Period::PERF_NONE, 'PERF_STARTED_AT' => Date::nowTime()], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        } elseif ($type == 'end') {
+            Period::updateAll(['IS_PERFING' => 0, 'IS_PERFED' => Period::PERF_FINISH, 'PERFED_AT' => Date::nowTime()], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        } elseif ($type == 'fail') {
+            Period::updateAll(['IS_PERFING' => 0, 'IS_PERFED' => Period::PERF_FAIL, 'PERFED_AT' => 0], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        }
+    }
+
+    /**
+     * 初始化结算任务
+     * @throws \yii\db\Exception
+     */
+    public function initCalcTask() {
+        $this->_sysConfig = Cache::getSystemConfig();
+        $this->_decLevelConfig = Cache::getDecLevelConfig();
+        $this->_empLevelConfig = Cache::getEmpLevelConfig();
+        $this->_decRoleConfig = CalcCache::getDecRoleConfig($this->_periodNum);
+        $periodNum = $this->_periodNum;
+        // 获取本年月和上年月
+        $periodObj = Period::instance();
+        $periodDataArr = $periodObj->setPeriodNum($periodNum);
+        $this->_periodId = $periodDataArr['ID'];
+        $this->_isCalcMonth = $periodObj->isCalcMonth($periodNum);
+        $this->_calcYear = $periodObj->getYear($periodNum);
+        $this->_calcMonth = $periodObj->getMonth($periodNum);
+        $this->_calcYearMonth = $periodObj->getYearMonth($periodNum);
+        $lastYearMonthArr = $periodObj->getLastMonth($periodNum);
+        $this->_lastCalcYear = $lastYearMonthArr['year'];
+        $this->_lastCalcMonth = $lastYearMonthArr['month'];
+        $this->_lastCalcYearMonth = $lastYearMonthArr['yearMonth'];
+        $this->_lastPeriodNum = $periodNum - 1;
+        if (Period::isExistsPeriodNum($this->_lastPeriodNum)) {
+            $this->_lastPeriodYear = $periodObj->getYear($this->_lastPeriodNum);
+            $this->_lastPeriodMonth = $periodObj->getMonth($this->_lastPeriodNum);
+            $this->_lastPeriodYearMonth = $periodObj->getYearMonth($this->_lastPeriodNum);
+        } else {
+            $this->_lastPeriodYear = 0;
+            $this->_lastPeriodMonth = 0;
+            $this->_lastPeriodYearMonth = 0;
+        }
+        $this->_pvRatio = $this->_sysConfig['pvRatio']['VALUE'];
+    }
+
+    /**
+     * 清空相关表数据
+     */
+    public function clearTableData() {
+        // 周业绩
+        PerfPeriod::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+        // 业绩单
+        PerfOrder::pageDeleteAll('PERIOD_NUM='.$this->_periodNum);
+        // 删除活跃用户
+        // PerfActiveUser::pageDeleteAll('PERIOD_NUM='.$this->_periodNum.'  AND IS_SENT=0 ');
+        // 月结时要清空的数据
+        if ($this->_isCalcMonth) {
+            // 月业绩表
+            PerfMonth::pageDeleteAll("CALC_MONTH='{$this->_calcYearMonth}'");
+            //达标业绩表
+            PerfStandard::pageDeleteAll("CALC_MONTH='{$this->_calcYearMonth}'");
+        }
+    }
+
+    /**
+     * 循环判断活跃会员,并入库
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function loopWriteActiveUser($offset = 0) {
+        echo sprintf("时间:[%s]本期活跃用户PerfOrder表中,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        // 从PerfOrder表中获取此周期的用户数据
+        $data = PerfPeriod::findUseDbCalc()
+        ->select('USER_ID')
+        ->where('PERIOD_NUM=:PERIOD_NUM',[
+            ':PERIOD_NUM' => $this->_periodNum
+        ])->offset($offset)->limit($this->_limit)->asArray()->all();
+        if ($data) {
+            foreach ($data as $upv) {
+                $userId = $upv['USER_ID'];
+                // 判断用户每周是否是活跃用户
+                $allData = PerfOrder::findUseDbCalc()
+                ->select('USER_ID,PV,DEC_AMOUNT,PERIOD_NUM,CALC_MONTH,P_CALC_MONTH, DEC_SN,ORDER_CREATED_AT')
+                ->where('PERIOD_NUM=:PERIOD_NUM AND USER_ID=:USER_ID',[
+                    ':PERIOD_NUM' => $this->_periodNum,
+                    ':USER_ID' => $userId
+                ])
+                ->orderBy('ORDER_CREATED_AT DESC')
+                ->asArray()
+                ->all();
+               
+                if($allData) {
+                    $this->weekMonthOrder($allData);
+
+                    unset($insertDataPeriodPerf, $allData);
+                }
+
+                unset($allData);
+            }
+            
+            unset($data);
+            return $this->loopWriteActiveUser($offset + $this->_limit);
+        }
+
+        return true;
+    }
+
+    // 活跃判断
+    public function weekMonthOrder($data) {
+        // 获取每个订单是周几,把他们分别放到各自的周里面.
+        // 因为是自然月,所以需要知道是此月的几号,是此月的第几周.然后往前和往后推出时间戳范围内pv的累计之和是多少
+        $one = 24*60*60; // 一天的时间戳
+        $time_arr = [];
+        foreach ($data as $orders) {
+            $day = intval(date('d', $orders['ORDER_CREATED_AT'])); // 当月第多少号
+            $weekDay = (date('w', $orders['ORDER_CREATED_AT']) == 0) ? 7 : date('w', $orders['ORDER_CREATED_AT']);// 周几 1-7
+            $format = date('Y-m-d', $orders['ORDER_CREATED_AT']);
+            $orderDayStart = strtotime($format.' 0:0:0');
+            $orderDayEnd = strtotime($format.' 23:59:59');
+            $startCaluc = $weekDay-1;
+            $endCaluc = 7-$weekDay;
+            $calucWeekStart = $orderDayStart - $startCaluc*$one;
+            $calucWeekEnd = $orderDayEnd + $endCaluc*$one;
+            if (!isset($time_arr[$calucWeekStart])) {
+                $time_arr[$calucWeekStart] = array_merge($orders, [
+                    'temp_start' => $calucWeekStart,
+                    'temp_end' => $calucWeekEnd,
+                    'calculate_total_pv' => $orders['PV']
+                ]);
+            } else {
+                $time_arr[$calucWeekStart]['calculate_total_pv'] = $time_arr[$calucWeekStart]['calculate_total_pv'] + $orders['PV'];
+            }
+        }
+        unset($data);
+        $insert = [];
+        // 循环判断,当周pv累计是否达到400
+        foreach ($time_arr as $k => $v) {
+            if ($v['calculate_total_pv'] >= $this->_sysConfig['activeUserPvCondition']['VALUE']) {
+                // 如果个人业累计绩达400PV,取得连续四周活跃
+                // 取出周末再增加三周的时间戳
+                $insert[] = [
+                    'ID' => SnowFake::instance()->generateId(),
+                    'USER_ID' => $v['USER_ID'],
+                    'PERIOD_NUM' => $v['PERIOD_NUM'],
+                    'CALC_MONTH' => $v['CALC_MONTH'],
+                    'P_CALC_MONTH' => $v['P_CALC_MONTH'],
+                    'SRATR_AT' => $v['temp_start'],
+                    'END_AT' => $v['temp_end']+(3*7*24*60*60),
+                    'CREATED_AT' => time(),
+                    'PV' => $v['calculate_total_pv'],
+                ];
+            }
+        }
+        PerfActiveUser::batchInsert($insert);
+        
+        unset($insert);
+
+        return true;
+    }
+
+    /**
+     * 周结,向上级算业绩,并计入业绩单
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function loopCalcPeriodPerfByDecOrder($offset = 0) {
+        // 循环获取全部报单
+        $allData = DecOrder::findUseDbCalc()->select('ID,DEC_SN,ORDER_SN,USER_ID,TYPE,TO_USER_ID,IS_ADMIN,DEC_AMOUNT,DEC_PV,PERIOD_NUM,CALC_MONTH,IS_DEL,P_CALC_MONTH,CREATED_AT,DEC_ID')->where("PERIOD_NUM=:PERIOD_NUM AND IS_DEL=0 AND TYPE='ZC'", [':PERIOD_NUM' => $this->_periodNum])->orderBy('CREATED_AT DESC,ID DESC')->offset($offset)->limit($this->_limit)->asArray()->all();
+        if ($allData) {
+            $insertPerfOrderData = [];
+            foreach ($allData as $data) {
+                // 是否关停等状态不能拿业绩
+                if (!$this->isHasPerf($data['TO_USER_ID'])) {
+                    continue;
+                }
+                //零售单不累计业绩,仅存业绩单
+//                if($data['TYPE']!='LS') {
+                    // 给自己增加PCS(个人消费)
+                CalcCache::nowPeriodPerf($data['TO_USER_ID'], $this->_periodNum, [
+                    'PV_PCS' => $data['DEC_PV'],
+                    'PV_PCS_ZC' => $data['DEC_PV'],
+                ]);
+                // 把该会员加入到能拿到业绩的会员缓存中
+                CalcCache::addHasPerfUsers($data['TO_USER_ID'], $this->_periodNum);
+                //加入到报单会员中
+                $toInfo = CalcCache::getUserInfo($data['TO_USER_ID'], $this->_periodNum);
+                CalcCache::addHasBDUsers($data['TO_USER_ID'], $this->_periodNum, [
+                    'TO_USER_ID' => $data['TO_USER_ID'],
+                    'USER_ID' => $data['USER_ID'],
+                    'DEC_ID' => $data['DEC_ID'],
+                    //考虑可能会移网的情况
+                    'REC_USER_ID' => $toInfo['REC_UID'] ?? '',
+                    'CON_USER_ID' => $toInfo['CON_UID'] ?? '',
+                    'DEC_AMOUNT' => $data['DEC_AMOUNT'],
+                    'DEC_PV' => $data['DEC_PV'],
+                ]);
+                // 给上追加业绩
+                $this->loopNetworkParentDo($data['TO_USER_ID'], function ($parent) use (&$data) {
+                    // 给上级会员追加业绩到缓存中
+//                    CalcCache::addUserPerf($parent['PARENT_UID'], $this->_periodNum, [
+//                        'PV_'.$parent['LOCATION'].'L' => $data['DEC_PV'],
+//                    ]);
+                    // 给上级会员追加本期业绩到缓存中
+                    CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
+                        'PV_' . $parent['LOCATION'] . 'L' => $data['DEC_PV'],
+                        'PV_' . $parent['LOCATION'] . 'L_TOUCH' => $data['DEC_PV'],
+                        'PV_' . $parent['LOCATION'] . 'L_' . $data['TYPE'] => $data['DEC_PV'],
+                    ]);
+                    // 把该会员加入到能拿到业绩的会员缓存中
+                    CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
+                    unset($parent);
+                });
+                //给推荐关系累计增加业绩
+                $this->loopRelationParentDo($data['TO_USER_ID'], function ($parent) use (&$data) {
+                    // 给上级会员追加业绩到缓存中
+//                    CalcCache::addUserPerf($parent['PARENT_UID'], $this->_periodNum, [
+//                        'PV_'.$parent['LOCATION'].'L' => $data['DEC_PV'],
+//                    ]);
+                    // 给上级会员追加本期业绩到缓存中
+                    CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
+                        'PV_PSS' => $data['DEC_PV'],
+                    ]);
+                    // 把该会员加入到能拿到业绩的会员缓存中
+                    CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
+                    unset($parent);
+                });
+
+//                }
+                // 写入业绩单表
+                $decInfo = CalcCache::getUserInfo($data['USER_ID'], $this->_periodNum);
+                $sn = PerfOrder::generateSN();
+                $insertPerfOrderData[] = [
+                    'FROM_TABLES' => 'dec_order',
+                    'ID' => SnowFake::instance()->generateId(),
+                    'SN' => $data['ORDER_SN'],
+                    'DEC_SN' => $data['DEC_SN'],
+                    'DEC_TYPE' => strtoupper($data['TYPE']),
+                    'DEC_STATUS' => PerfOrder::STATUS_NORMAL,
+                    'USER_ID' => $data['TO_USER_ID'],
+                    'LAST_REC_USER_NAME' => $toInfo['REC_USER_NAME'],
+                    'LAST_REC_REAL_NAME' => $toInfo['REC_REAL_NAME'],
+                    'LAST_DEC_LV' => $toInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $toInfo['EMP_LV'],
+                    'LAST_STATUS' => $toInfo['STATUS'],
+                    'PV' => $data['DEC_PV'],
+                    'DEC_AMOUNT' => $data['DEC_AMOUNT'],
+                    'LAST_SUB_COM_ID' => $toInfo['SUB_COM_ID'],
+                    'LAST_PROVINCE' => $toInfo['PROVINCE'],
+                    'LAST_CITY' => $toInfo['CITY'],
+                    'LAST_COUNTY' => $toInfo['COUNTY'],
+                    'DEC_USER_ID' => $data['USER_ID'],
+                    'LAST_DEC_DEC_LV' => $decInfo['DEC_LV'],
+                    'LAST_DEC_SUB_COM_ID' => $decInfo['SUB_COM_ID'],
+                    'LAST_DEC_PROVINCE' => $decInfo['DEC_PROVINCE'],
+                    'LAST_DEC_CITY' => $decInfo['DEC_CITY'],
+                    'LAST_DEC_COUNTY' => $decInfo['DEC_COUNTY'],
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                    'CREATED_AT' => Date::nowTime(),
+                    'CLOSED_AT' => 0,
+                    'ORDER_CREATED_AT' => $data['CREATED_AT'],
+                ];
+                unset($data, $decInfo, $sn, $toInfo);
+            }
+            PerfOrder::batchInsert($insertPerfOrderData);
+            unset($insertPerfOrderData, $allData, $snArr);
+            return $this->loopCalcPeriodPerfByDecOrder($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 周结,向上级算业绩,并计入业绩单
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function loopCalcPeriodPerfByOrderDec($offset = 0) {
+        // 循环获取全部报单
+        $allData = OrderDec::findUseDbCalc()->select('CREATED_AT,ID,SN,USER_ID,ORDER_TYPE,ORDER_AMOUNT,PV,PAY_AMOUNT,PAY_PV,PERIOD_NUM,PAY_TYPE')->where("PERIOD_NUM=:PERIOD_NUM AND IS_DELETE=0 AND ORDER_TYPE=:ORDER_TYPE", [':PERIOD_NUM' => $this->_periodNum, ':ORDER_TYPE'=>'ZC'])->orderBy('ID DESC')->offset($offset)->limit($this->_limit)->asArray()->all();
+        if ($allData) {
+            $insertPerfOrderData = [];
+            foreach ($allData as $data) {
+                // 是否关停等状态不能拿业绩
+                if (!$this->isHasPerf($data['USER_ID'])) {
+                    continue;
+                }
+                // 给自己增加PCS(个人消费)
+                CalcCache::nowPeriodPerf($data['USER_ID'], $this->_periodNum, [
+                    'PV_PCS' => $data['PAY_PV'],
+                    'PV_PCS_ZC' => $data['PAY_PV'],
+                ]);
+                // 把该会员加入到能拿到业绩的会员缓存中
+                CalcCache::addHasPerfUsers($data['USER_ID'], $this->_periodNum);
+                //加入到报单会员中
+                $toInfo = CalcCache::getUserInfo($data['USER_ID'], $this->_periodNum);
+                CalcCache::addHasBDUsers($data['USER_ID'], $this->_periodNum, [
+                    'TO_USER_ID' => $data['USER_ID'],
+                    'USER_ID' => $toInfo['DEC_ID'],
+                    'DEC_ID' => $toInfo['DEC_ID'],
+                    //考虑可能会移网的情况
+                    'REC_USER_ID' => $toInfo['REC_UID'] ?? '',
+                    'CON_USER_ID' => $toInfo['CON_UID'] ?? '',
+                    'DEC_AMOUNT' => $data['ORDER_AMOUNT'],
+                    'DEC_PV' => $data['PAY_PV'],
+                ]);
+                // 给上追加业绩
+                $this->loopNetworkParentDo($data['USER_ID'], function ($parent) use (&$data) {
+                    // 给上级会员追加本期业绩到缓存中
+                    CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
+                        'PV_' . $parent['LOCATION'] . 'L' => $data['PAY_PV'],
+                        'PV_' . $parent['LOCATION'] . 'L_TOUCH' => $data['PAY_PV'],
+                        'PV_' . $parent['LOCATION'] . 'L_' . $data['ORDER_TYPE'] => $data['PAY_PV'],
+                    ]);
+                    // 把该会员加入到能拿到业绩的会员缓存中
+                    CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
+                    unset($parent);
+                });
+                //给推荐关系累计增加业绩
+                $this->loopRelationParentDo($data['USER_ID'], function ($parent) use (&$data) {
+                    // 给上级会员追加本期业绩到缓存中
+                    CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
+                        'PV_PSS' => $data['PAY_PV'],
+                    ]);
+                    // 把该会员加入到能拿到业绩的会员缓存中
+                    CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
+                    unset($parent);
+                });
+
+                // 写入业绩单表
+                $decInfo = CalcCache::getUserInfo($toInfo['DEC_ID'], $this->_periodNum);
+                $sn = PerfOrder::generateSN();
+                $insertPerfOrderData[] = [
+                    'FROM_TABLES' => 'order_dec',
+                    'ID' => SnowFake::instance()->generateId(),
+                    'SN' => $data['SN'],
+                    'DEC_SN' => $data['SN'],
+                    'DEC_TYPE' => strtoupper($data['ORDER_TYPE']),
+                    'DEC_STATUS' => PerfOrder::STATUS_NORMAL,
+                    'USER_ID' => $data['USER_ID'],
+                    'LAST_REC_USER_NAME' => $toInfo['REC_USER_NAME'],
+                    'LAST_REC_REAL_NAME' => $toInfo['REC_REAL_NAME'],
+                    'LAST_DEC_LV' => $toInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $toInfo['EMP_LV'],
+                    'LAST_STATUS' => $toInfo['STATUS'],
+                    'PV' => $data['PAY_PV'],
+                    'DEC_AMOUNT' => $data['ORDER_AMOUNT'],
+                    'LAST_SUB_COM_ID' => $toInfo['SUB_COM_ID'],
+                    'LAST_PROVINCE' => $toInfo['PROVINCE'],
+                    'LAST_CITY' => $toInfo['CITY'],
+                    'LAST_COUNTY' => $toInfo['COUNTY'],
+                    'DEC_USER_ID' => $toInfo['DEC_ID'],
+                    'LAST_DEC_DEC_LV' => $decInfo['DEC_LV'],
+                    'LAST_DEC_SUB_COM_ID' => $decInfo['SUB_COM_ID'],
+                    'LAST_DEC_PROVINCE' => $decInfo['DEC_PROVINCE'],
+                    'LAST_DEC_CITY' => $decInfo['DEC_CITY'],
+                    'LAST_DEC_COUNTY' => $decInfo['DEC_COUNTY'],
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                    'CREATED_AT' => Date::nowTime(),
+                    'CLOSED_AT' => 0,
+                    'ORDER_CREATED_AT' => $data['CREATED_AT']
+                ];
+                unset($data, $decInfo, $sn, $toInfo);
+            }
+            PerfOrder::batchInsert($insertPerfOrderData);
+            unset($insertPerfOrderData, $allData, $snArr);
+            return $this->loopCalcPeriodPerfByOrderDec($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+
+    /**
+     * 从会员的复销订单会员计算复销业绩并加入业绩单
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function loopCalcPerfByFXOrder(int $offset = 0) {
+        // 循环获取全部报单
+        $allData = Order::findUseDbCalc()->select('ID,SN,DEC_SN,USER_ID,ORDER_TYPE,ORDER_AMOUNT,PAY_AMOUNT,PAY_PV,PAY_TYPE,PERIOD_NUM,STATUS,IS_DELETE,P_CALC_MONTH,CREATED_AT')->where("PERIOD_NUM=:PERIOD_NUM AND IS_DELETE=0 AND ORDER_TYPE=:ORDER_TYPE", [':PERIOD_NUM' => $this->_periodNum, ':ORDER_TYPE'=>DeclarationForm::TYPE_FX])->orderBy('CREATED_AT DESC,ID DESC')->offset($offset)->limit($this->_limit)->asArray()->all();
+        if ($allData) {
+            $insertPerfOrderData = [];
+            foreach ($allData as $data) {
+                // 是否关停等状态不能拿业绩
+                if (!$this->isHasPerf($data['USER_ID'])) {
+                    continue;
+                }
+
+                //如果支付方式是现金,那么实际业绩是支付PV的50%
+                if( $data['PAY_TYPE'] === self::ORDER_PAY_TYPE_CASH ) {
+                    $orderCashAmount = $data['ORDER_AMOUNT'];
+                    //111期开始由50%改为60%-by 2020-04-30修改
+                    $payPv = $data['PAY_PV'] * $this->_sysConfig['cashReconsumeBonusPercent']['VALUE'] / 100;
+                    $cacheDataKey = 'PV_PCS_FX_CASH';
+                }else {
+                    $orderCashAmount = 0;
+                    $payPv = $data['PAY_PV'];
+                    $cacheDataKey = 'PV_PCS_FX_POINT';
+                }
+
+                if( $payPv <= 0 ) continue;
+
+                // 给自己增加PCS(个人消费)
+                CalcCache::nowPeriodPerf($data['USER_ID'], $this->_periodNum, [
+                    'FX_AMOUNT_CASH' => $orderCashAmount,
+                    'PV_PCS' => $payPv,
+                    'PV_PCS_FX' => $payPv,
+                    $cacheDataKey => $payPv,
+                ]);
+                // 把该会员加入到能拿到业绩的会员缓存中
+                CalcCache::addHasPerfUsers($data['USER_ID'], $this->_periodNum);
+                // 给上追加业绩
+                try {
+                    $this->loopNetworkParentDo($data['USER_ID'], function ($parent) use (&$data, $payPv) {
+                        // 给上级会员追加业绩到缓存中
+    //                    CalcCache::addUserPerf($parent['PARENT_UID'], $this->_periodNum, [
+    //                        'PV_'.$parent['LOCATION'].'L' => $data['DEC_PV'],
+    //                    ]);
+                        // 给上级会员追加本期业绩到缓存中
+                        CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
+                            'PV_' . $parent['LOCATION'] . 'L' => $payPv,
+                            'PV_' . $parent['LOCATION'] . 'L_TOUCH' => $payPv,
+                            'PV_' . $parent['LOCATION'] . 'L_FX' => $payPv,
+                        ]);
+                        // 把该会员加入到能拿到业绩的会员缓存中
+                        CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
+                    });
+                } catch(\Exception $e) {
+                    file_put_contents('loopNetworkParentDo_error.txt', var_export([
+                        'USER_ID' => $data['USER_ID'],
+                        '_periodNum' => $this->_periodNum,
+                        'error' => $e->getMessage()
+                    ],true));
+                }
+                //给推荐关系累计增加业绩
+                $this->loopRelationParentDo($data['USER_ID'], function ($parent) use ($data, $payPv) {
+                    // 给上级会员追加业绩到缓存中
+//                    CalcCache::addUserPerf($parent['PARENT_UID'], $this->_periodNum, [
+//                        'PV_'.$parent['LOCATION'].'L' => $data['DEC_PV'],
+//                    ]);
+                    // 给上级会员追加本期业绩到缓存中
+                    CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
+                        'PV_PSS' => $payPv,
+                    ]);
+                    // 把该会员加入到能拿到业绩的会员缓存中
+                    CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
+                });
+
+//                }
+                // 写入业绩单表
+                $baseInfo = CalcCache::getUserInfo($data['USER_ID'], $this->_periodNum);
+                $sn = PerfOrder::generateSN();
+                $insertPerfOrderData[] = [
+                    'FROM_TABLES' => 'order',
+                    'ID' => SnowFake::instance()->generateId(),
+                    'SN' => $data['SN'],
+                    'DEC_SN' => null,
+                    'DEC_TYPE' => 'FX',
+                    'DEC_STATUS' => PerfOrder::STATUS_NORMAL,
+                    'USER_ID' => $data['USER_ID'],
+                    'LAST_REC_USER_NAME' => $baseInfo['REC_USER_NAME'],
+                    'LAST_REC_REAL_NAME' => $baseInfo['REC_REAL_NAME'],
+                    'LAST_DEC_LV' => $baseInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $baseInfo['EMP_LV'],
+                    'LAST_STATUS' => $baseInfo['STATUS'],
+                    'PV' => $payPv,
+                    'DEC_AMOUNT' => $data['PAY_AMOUNT'],
+                    'LAST_SUB_COM_ID' => $baseInfo['SUB_COM_ID'],
+                    'LAST_PROVINCE' => $baseInfo['PROVINCE'],
+                    'LAST_CITY' => $baseInfo['CITY'],
+                    'LAST_COUNTY' => $baseInfo['COUNTY'],
+                    'DEC_USER_ID' => $data['USER_ID'],
+                    'LAST_DEC_DEC_LV' => $baseInfo['DEC_LV'],
+                    'LAST_DEC_SUB_COM_ID' => $baseInfo['SUB_COM_ID'],
+                    'LAST_DEC_PROVINCE' => $baseInfo['PROVINCE'],
+                    'LAST_DEC_CITY' => $baseInfo['CITY'],
+                    'LAST_DEC_COUNTY' => $baseInfo['COUNTY'],
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                    'CREATED_AT' => Date::nowTime(),
+                    'CLOSED_AT' => 0,
+                    'ORDER_CREATED_AT' => $data['CREATED_AT']
+                ];
+                unset($data, $baseInfo, $sn, $orderCashAmount, $payPv, $cacheDataKey);
+            }
+            PerfOrder::batchInsert($insertPerfOrderData);
+
+            unset($insertPerfOrderData, $allData, $snArr);
+            return $this->loopCalcPerfByFXOrder($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 从会员的商城复销订单会员计算复销业绩并加入业绩单
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function loopCalcPerfByShopFXOrder(int $offset = 0) {
+        // 循环获取全部报单
+        $allData = OrderShop::findUseDbCalc()->select('ID,SN,DEC_SN,USER_ID,ORDER_TYPE,ORDER_AMOUNT,PAY_AMOUNT,PAY_PV,PAY_TYPE,PERIOD_NUM,STATUS,IS_DELETE,P_CALC_MONTH,CREATED_AT')->where("PERIOD_NUM=:PERIOD_NUM AND IS_DELETE=0 AND ORDER_TYPE=:ORDER_TYPE", [':PERIOD_NUM' => $this->_periodNum, ':ORDER_TYPE'=>DeclarationForm::TYPE_FX])->orderBy('CREATED_AT DESC,ID DESC')->offset($offset)->limit($this->_limit)->asArray()->all();
+        if ($allData) {
+            $insertPerfOrderData = [];
+            foreach ($allData as $data) {
+                // 是否关停等状态不能拿业绩
+                if (!$this->isHasPerf($data['USER_ID'])) {
+                    continue;
+                }
+
+                //如果支付方式是现金,那么实际业绩是支付PV的50%
+                if( $data['PAY_TYPE'] === self::ORDER_PAY_TYPE_CASH ) {
+                    $orderCashAmount = $data['ORDER_AMOUNT'];
+                    //111期开始由50%改为60%-by 2020-04-30修改
+                    $payPv = $data['PAY_PV'] * $this->_sysConfig['cashReconsumeBonusPercent']['VALUE'] / 100;
+                    $cacheDataKey = 'PV_PCS_FX_CASH';
+                }else {
+                    $orderCashAmount = 0;
+                    $payPv = $data['PAY_PV'];
+                    $cacheDataKey = 'PV_PCS_FX_POINT';
+                }
+
+                if( $payPv <= 0 ) continue;
+
+                // 给自己增加PCS(个人消费)
+                CalcCache::nowPeriodPerf($data['USER_ID'], $this->_periodNum, [
+                    'FX_AMOUNT_CASH' => $orderCashAmount,
+                    'PV_PCS' => $payPv,
+                    'PV_PCS_FX' => $payPv,
+                    $cacheDataKey => $payPv,
+                ]);
+                // 把该会员加入到能拿到业绩的会员缓存中
+                CalcCache::addHasPerfUsers($data['USER_ID'], $this->_periodNum);
+                // 给上追加业绩
+                $this->loopNetworkParentDo($data['USER_ID'], function ($parent) use (&$data, $payPv) {
+                    // 给上级会员追加业绩到缓存中
+//                    CalcCache::addUserPerf($parent['PARENT_UID'], $this->_periodNum, [
+//                        'PV_'.$parent['LOCATION'].'L' => $data['DEC_PV'],
+//                    ]);
+                    // 给上级会员追加本期业绩到缓存中
+                    CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
+                        'PV_' . $parent['LOCATION'] . 'L' => $payPv,
+                        'PV_' . $parent['LOCATION'] . 'L_TOUCH' => $payPv,
+                        'PV_' . $parent['LOCATION'] . 'L_FX' => $payPv,
+                    ]);
+                    // 把该会员加入到能拿到业绩的会员缓存中
+                    CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
+                });
+                //给推荐关系累计增加业绩
+                $this->loopRelationParentDo($data['USER_ID'], function ($parent) use ($data, $payPv) {
+                    // 给上级会员追加业绩到缓存中
+//                    CalcCache::addUserPerf($parent['PARENT_UID'], $this->_periodNum, [
+//                        'PV_'.$parent['LOCATION'].'L' => $data['DEC_PV'],
+//                    ]);
+                    // 给上级会员追加本期业绩到缓存中
+                    CalcCache::nowPeriodPerf($parent['PARENT_UID'], $this->_periodNum, [
+                        'PV_PSS' => $payPv,
+                    ]);
+                    // 把该会员加入到能拿到业绩的会员缓存中
+                    CalcCache::addHasPerfUsers($parent['PARENT_UID'], $this->_periodNum);
+                });
+
+//                }
+                // 写入业绩单表
+                $baseInfo = CalcCache::getUserInfo($data['USER_ID'], $this->_periodNum);
+                $sn = PerfOrder::generateSN();
+                $insertPerfOrderData[] = [
+                    'FROM_TABLES' => 'order_shop',
+                    'ID' => SnowFake::instance()->generateId(),
+                    'SN' => $data['SN'],
+                    'DEC_SN' => null,
+                    'DEC_TYPE' => 'FX',
+                    'DEC_STATUS' => PerfOrder::STATUS_NORMAL,
+                    'USER_ID' => $data['USER_ID'],
+                    'LAST_REC_USER_NAME' => $baseInfo['REC_USER_NAME'],
+                    'LAST_REC_REAL_NAME' => $baseInfo['REC_REAL_NAME'],
+                    'LAST_DEC_LV' => $baseInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $baseInfo['EMP_LV'],
+                    'LAST_STATUS' => $baseInfo['STATUS'],
+                    'PV' => $payPv,
+                    'DEC_AMOUNT' => $data['PAY_AMOUNT'],
+                    'LAST_SUB_COM_ID' => $baseInfo['SUB_COM_ID'],
+                    'LAST_PROVINCE' => $baseInfo['PROVINCE'],
+                    'LAST_CITY' => $baseInfo['CITY'],
+                    'LAST_COUNTY' => $baseInfo['COUNTY'],
+                    'DEC_USER_ID' => $data['USER_ID'],
+                    'LAST_DEC_DEC_LV' => $baseInfo['DEC_LV'],
+                    'LAST_DEC_SUB_COM_ID' => $baseInfo['SUB_COM_ID'],
+                    'LAST_DEC_PROVINCE' => $baseInfo['PROVINCE'],
+                    'LAST_DEC_CITY' => $baseInfo['CITY'],
+                    'LAST_DEC_COUNTY' => $baseInfo['COUNTY'],
+                    'PERIOD_NUM' => $this->_periodNum,
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                    'CREATED_AT' => Date::nowTime(),
+                    'CLOSED_AT' => 0,
+                    'ORDER_CREATED_AT' => $data['CREATED_AT']
+                ];
+                unset($data, $baseInfo, $sn, $orderCashAmount, $payPv, $cacheDataKey);
+            }
+            PerfOrder::batchInsert($insertPerfOrderData);
+
+            unset($insertPerfOrderData, $allData, $snArr);
+            return $this->loopCalcPerfByShopFXOrder($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 达标复销订单
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function loopCalcPerfByStandardFXOrder(int $offset = 0) {
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+        $allData = OrderStandard::findUseDbCalc()->select('ID,SN,DEC_SN,USER_ID,ORDER_TYPE,ORDER_AMOUNT,PAY_AMOUNT,PAY_PV,PAY_TYPE,PERIOD_NUM,STATUS,IS_DELETE,CREATED_AT')->where("CALC_MONTH=:CALC_MONTH", [':CALC_MONTH' => $this->_calcYearMonth])->orderBy('ID DESC')->offset($offset)->limit($this->_limit)->asArray()->all();
+        if ($allData) {
+            $insertPerfOrderData = [];
+            foreach ($allData as $data) {
+                // 是否关停等状态不能拿业绩
+                if (!$this->isHasPerf($data['USER_ID'])) {
+                    continue;
+                }
+
+                //如果支付方式是现金,那么实际业绩是支付PV的50%
+                if( $data['PAY_TYPE'] === self::ORDER_PAY_TYPE_CASH ) {
+                    $orderCashAmount = $data['ORDER_AMOUNT'];
+//                    $payPv = $data['PAY_PV'] * $this->_sysConfig['cashReconsumeBonusPercent']['VALUE'] / 100;
+                }else {
+                    $orderCashAmount = 0;
+//                    $payPv = $data['PAY_PV'];
+                }
+
+                if( $orderCashAmount <= 0 ) continue;
+
+
+
+                // 给自己增加个人业绩
+                CalcCache::nowStandardMonthPerf($data['USER_ID'], $this->_periodNum, [
+                    'AMOUNT_PCS' => $orderCashAmount,
+                ]);
+                // 把该会员加入到能拿到业绩的会员缓存中
+                CalcCache::addHasStandardMonthPerfUsers($data['USER_ID'], $this->_periodNum);
+
+                //只有无聘级业绩才向上累加
+                $baseInfo = CalcCache::getUserInfo($data['USER_ID'], $this->_periodNum);
+                if( $baseInfo['EMP_LV'] !=  EmployLevel::NO_LEVEL_ID ) {
+                    continue;
+                }
+                //给推荐团队添加团队业绩 注:如果些订单特别多的情况,可以分2步。先只计算个人业绩、然后根据个人业绩再计算团队业绩。
+                //给推荐关系累计增加业绩
+                $this->loopRelationParentDo($data['USER_ID'], function ($parent) use ($data, $orderCashAmount) {
+                    // 给上级会员追加本期业绩到缓存中
+                    CalcCache::nowStandardMonthPerf($parent['PARENT_UID'], $this->_periodNum, [
+                        'AMOUNT_PSS' => $orderCashAmount,
+                    ]);
+                    // 把该会员加入到能拿到业绩的会员缓存中
+                    CalcCache::addHasStandardMonthPerfUsers($parent['PARENT_UID'], $this->_periodNum);
+
+                    //获取parent聘级
+                    $parentUserInfo = CalcCache::getUserInfo($parent['PARENT_UID'], $this->_periodNum);
+                    if( $parentUserInfo['EMP_LV'] !=  EmployLevel::NO_LEVEL_ID ) {//如果碰到有聘级的业绩就不在向上累加了
+                        return self::LOOP_FINISH;
+                    }
+                });
+
+                unset($data, $baseInfo, $orderCashAmount);
+            }
+
+            unset($insertPerfOrderData, $allData, $snArr);
+            return $this->loopCalcPerfByStandardFXOrder($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 计算月业绩表相关的数据并写入数据库
+     * @param int $offset
+     * @return bool
+     * @throws Exception
+     * @throws \yii\db\Exception
+     */
+    public function loopCalcMonthPerfTableData(int $offset = 0) {
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+        echo sprintf("时间:[%s]月业绩,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        // 从缓存列表里面从底层往上倒序获取会员
+
+//        $allData = PerfPeriod::findUseDbCalc()->from(PerfPeriod::tableName()  . ' AS PP')->select('PP.USER_ID AS USER_ID, SUM(PP.PV_PCS) AS PV_PCS_SUM,SUM(PP.PV_PSS) AS PV_PSS_SUM,SUM(PP.PV_1L) AS PV_1L_SUM,SUM(PP.PV_2L) AS PV_2L_SUM,SUM(PP.PV_3L) AS PV_3L_SUM,SUM(PP.PV_4L) AS PV_4L_SUM,SUM(PP.PV_5L) AS PV_5L_SUM, SUM(PM.PV_1L_TOTAL) AS PV_1L_TOTAL, SUM(PM.PV_2L_TOTAL) AS PV_2L_TOTAL, SUM(PM.PV_3L_TOTAL) AS PV_3L_TOTAL, SUM(PM.PV_4L_TOTAL) AS PV_4L_TOTAL,SUM(PM.PV_5L_TOTAL) AS PV_5L_TOTAL, SUM(PM.PV_PSS_TOTAL) AS PV_PSS_TOTAL')->leftJoin(PerfMonth::tableName()  . ' AS PM', 'PM.USER_ID=PP.USER_ID AND PM.CALC_MONTH=:LAST_CALC_MONTH', ['LAST_CALC_MONTH'=>$this->_lastCalcYearMonth])->where('PP.CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])->groupBy('PP.USER_ID')->orderBy('PP.USER_ID DESC')->offset($offset)->limit($this->_limit)->asArray()->all();
+        $allData = PerfPeriod::findUseDbCalc()->select('USER_ID, SUM(FX_AMOUNT_CASH) AS FX_AMOUNT_CASH_SUM,SUM(PV_PCS) AS PV_PCS_SUM,SUM(PV_PCS_FX) AS PV_PCS_FX_SUM,SUM(PV_PSS) AS PV_PSS_SUM,SUM(PV_1L) AS PV_1L_SUM,SUM(PV_2L) AS PV_2L_SUM,SUM(PV_3L) AS PV_3L_SUM,SUM(PV_4L) AS PV_4L_SUM,SUM(PV_5L) AS PV_5L_SUM,SUM(PV_1L_ZC) AS PV_1L_ZC_SUM,SUM(PV_2L_ZC) AS PV_2L_ZC_SUM,SUM(PV_3L_ZC) AS PV_3L_ZC_SUM,SUM(PV_4L_ZC) AS PV_4L_ZC_SUM,SUM(PV_5L_ZC) AS PV_5L_ZC_SUM')->where('CALC_MONTH=:CALC_MONTH', [':CALC_MONTH' => $this->_calcYearMonth])->groupBy('USER_ID')->orderBy('USER_ID DESC')->offset($offset)->limit($this->_limit)->asArray()->all();
+        if ($allData) {
+            // 月度业绩表
+            foreach ($allData as $everyData) {
+                $userId = $everyData['USER_ID'];
+//                $lastMonthData = PerfMonth::find()->select('PV_1L_TOTAL, PV_2L_TOTAL, PV_3L_TOTAL, PV_4L_TOTAL, PV_5L_TOTAL, PV_PSS_TOTAL')->where('USER_ID=:USER_ID AND CALC_MONTH=:LAST_CALC_MONTH', [
+//                    'USER_ID'=>$userId,
+//                    'LAST_CALC_MONTH'=>$this->_lastCalcYearMonth,
+//                ])->asArray()->one();
+                //往期业绩
+                $userLastPerf = CalcCache::userPerf($userId, $this->_periodNum);
+                //本期业绩
+                $periodPerf = CalcCache::nowPeriodPerf($userId, $this->_periodNum);
+
+                $userBaseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                //级别必须为VIP
+                $isVip = false;
+                if( $userBaseInfo['DEC_LV'] === DeclarationLevel::VIP_LEVEL_ID ) {
+                    $isVip= true;
+                }
+                if( $this->_sysConfig['vipBonusGoldDecLevel']['VALUE'] && $userBaseInfo['DEC_LV'] === DeclarationLevel::JIN_ZUAN_LEVEL_ID ) {
+                    $isVip = true;
+                }
+
+
+                $nowMonthPerf = [
+                    'USER_ID' => $userId,
+                    'FX_AMOUNT_CASH' => $everyData['FX_AMOUNT_CASH_SUM'],
+                    'PV_PCS' => $everyData['PV_PCS_SUM'],
+                    'PV_PCS_FX' => $everyData['PV_PCS_FX_SUM'],
+                    'PV_PSS' => $everyData['PV_PSS_SUM'],
+                    'PV_1L' => $everyData['PV_1L_SUM'],
+                    'PV_2L' => $everyData['PV_2L_SUM'],
+                    'PV_3L' => $everyData['PV_3L_SUM'],
+                    'PV_4L' => $everyData['PV_4L_SUM'],
+                    'PV_5L' => $everyData['PV_5L_SUM'],
+
+                    //VIP统计相关业绩
+                    'VIP_PV_1L_ZC' => $isVip ? $everyData['PV_1L_ZC_SUM'] : 0,
+                    'VIP_PV_2L_ZC' => $isVip ? $everyData['PV_2L_ZC_SUM'] : 0,
+                    'VIP_PV_3L_ZC' => $isVip ? $everyData['PV_3L_ZC_SUM'] : 0,
+                    'VIP_PV_4L_ZC' => $isVip ? $everyData['PV_4L_ZC_SUM'] : 0,
+                    'VIP_PV_5L_ZC' => $isVip ? $everyData['PV_5L_ZC_SUM'] : 0,
+
+                    //总数据,历史+本期。不能用上月加本月,因为上月可能没业绩,上上个月有业绩。
+                    'PV_1L_TOTAL' => $periodPerf['PV_1L'] + $userLastPerf['PV_1L'],
+                    'PV_2L_TOTAL' => $periodPerf['PV_2L'] + $userLastPerf['PV_2L'],
+                    'PV_3L_TOTAL' => $periodPerf['PV_3L'] + $userLastPerf['PV_3L'],
+                    'PV_4L_TOTAL' => $periodPerf['PV_4L'] + $userLastPerf['PV_4L'],
+                    'PV_5L_TOTAL' => $periodPerf['PV_5L'] + $userLastPerf['PV_5L'],
+                    'PV_PSS_TOTAL' => $periodPerf['PV_PSS'] + $userLastPerf['PV_PSS_TOTAL'],
+                ];
+
+                // 把会员的月业绩写入缓存中,以便下面的奖金计算从缓冲中获取数据效率高
+                CalcCache::addHasMonthPerfUsers($userId, $this->_periodNum);
+                CalcCache::nowMonthPerf($userId, $this->_periodNum, $nowMonthPerf);
+
+                unset($userId, $everyData, $nowMonthPerf, $lastMonthData, $userBaseInfo, $isVip);
+            }
+            unset($allData);
+            $this->loopCalcMonthPerfTableData($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 计算聘级
+     * @param int $offset
+     * @return bool
+     * @throws Exception
+     * @throws \yii\db\Exception
+     */
+    public function loopCalcEmpLevel(int $offset = 0) {
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+        echo sprintf("时间:[%s]计算聘级,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        // 从缓存列表里面从底层往上倒序获取会员
+        $allData = CalcCache::getUsers($this->_periodNum, $offset, $this->_limit);
+        if ($allData) {
+            // 月度业绩表
+            foreach ($allData as $userId) {
+                $userInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                $nowMonthPerf = CalcCache::nowMonthPerf($userId, $this->_periodNum);
+                $empLevel = $this->checkEmpLevel($userId, $nowMonthPerf, $userInfo['EMP_LV']);
+
+                //根据用户的级别判断 能否得到级别积分
+                if( $empLevel['LEVEL_SCORE'] > 0 ) {
+                    CalcCache::nowMonthScore($userId, $this->_periodNum, [
+                        'LEVEL_SCORE' => $empLevel['LEVEL_SCORE'],
+                    ]);
+                    CalcCache::addHasScoreUsers($userId, $this->_periodNum);
+                }
+
+
+                //更新月业绩的聘级和用户信息中的聘级
+                CalcCache::nowMonthPerf($userId, $this->_periodNum, [
+                    'EMP_LEVEL' => $empLevel['ID']
+                ]);
+
+                //为业绩单更新结算时聘级
+//                    PerfOrder::updateAll(['LAST_EMP_LV'=>$empLevel['ID']],'USER_ID=:USER_ID AND PERIOD_NUM=:PERIOD_NUM',[':USER_ID'=>$userId,':PERIOD_NUM'=>$this->_periodNum]);
+                $userEmpLevel = $this->_empLevelConfig[$userInfo['EMP_LV']];
+                $userEmpLevelSort = $userEmpLevel['SORT'] ?? EmployLevel::EMP_LEVEL_SORT['NO_LEVEL'];
+                //不降级
+                if( $empLevel['SORT'] <= $userEmpLevelSort ) continue;
+
+                $userInfo['EMP_LV'] = $empLevel['ID'];
+                CalcCache::setUserInfo($userId, $this->_periodNum, $userInfo);
+
+                //可以判断用户是否升级,能否得到升级积分
+                //因为默认是不降级,直接获得升级积分
+                $upgradeScore = 0;
+                foreach ($this->_empLevelConfig as $everyEmpLevel) {
+                    if( $everyEmpLevel['SORT'] <= $userEmpLevelSort ) continue;
+                    if( $everyEmpLevel['SORT'] > $empLevel['SORT'] ) continue;
+
+                    $upgradeScore += $everyEmpLevel['UPGRADE_SCORE'];
+
+                    unset($everyEmpLevel);
+                }
+
+                if( $upgradeScore > 0 ) {
+                    CalcCache::nowMonthScore($userId, $this->_periodNum, [
+                        'UPGRADE_SCORE' => $upgradeScore
+                    ]);
+                    CalcCache::addHasScoreUsers($userId, $this->_periodNum);
+                }
+
+                unset($userId, $empLevel, $nowMonthPerf, $userInfo, $userEmpLevel, $userEmpLevelSort, $upgradeScore);
+            }
+            unset($allData);
+            $this->loopCalcEmpLevel($offset + $this->_limit);
+        }
+
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 计算公司总业绩
+     * @return bool
+     * @throws Exception
+     */
+    public function calcPerfCompany() {
+        if (!$this->_isCalcMonth) {
+            return true;
+        }
+        $perfCompany = PerfCompany::findOne(['CALC_MONTH' => $this->_calcYearMonth]);
+        $db = \Yii::$app->db;
+        $transaction = $db->beginTransaction();
+        try {
+            if (!$perfCompany) {
+                $perfCompany = new PerfCompany();
+            }
+            $perfCompany->PV = $this->_companyMonthPerf;
+            $perfCompany->CREATED_AT = Date::nowTime();
+            $perfCompany->CALC_YEAR = $this->_calcYear;
+            $perfCompany->CALC_MONTH = $this->_calcYearMonth;
+            if (!$perfCompany->save()) {
+                throw new Exception(Form::formatErrorsForApi($perfCompany->getErrors()));
+            }
+            $transaction->commit();
+        } catch (Exception $e) {
+            $transaction->rollBack();
+            throw new Exception($e->getMessage());
+        }
+        unset($perfCompany);
+        return true;
+    }
+
+    /**
+     * 循环有业绩会员,并入库
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function loopWriteNowPerf($offset = 0) {
+        echo sprintf("时间:[%s]缓存本期业绩数据入库,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        // 从缓存列表里面从底层往上倒序获取会员
+        $allData = CalcCache::getHasPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if($allData){
+            $insertDataPeriodPerf = [];
+            foreach($allData as $userId){
+                $insertDataPeriodPerf[] = $this->nowPeriodPerfData($userId);
+                unset($userId);
+            }
+            PerfPeriod::batchInsert($insertDataPeriodPerf);
+
+            unset($insertDataPeriodPerf, $allData);
+            return $this->loopWriteNowPerf($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 循环有月业绩会员,并入库
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function loopWriteMonthPerf($offset = 0) {
+        if(!$this->_isCalcMonth){
+            return true;
+        }
+        echo sprintf("时间:[%s]缓存本月业绩数据入库,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        // 从缓存列表里面从底层往上倒序获取会员
+        $allData = CalcCache::getHasMonthPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if($allData){
+            $insertDataMonthPerf = [];
+            foreach($allData as $userId){
+                $insertDataMonthPerf[] = $this->nowMonthPerfData($userId);
+                unset($userId);
+            }
+
+            PerfMonth::batchInsert($insertDataMonthPerf);
+            unset($insertDataMonthPerf, $allData);
+            return $this->loopWriteMonthPerf($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+
+    /**
+     * 循环达标业绩会员,并入库
+     * @param int $offset
+     * @return bool
+     * @throws \yii\db\Exception
+     */
+    public function loopWriteStandardPerf($offset = 0) {
+        if(!$this->_isCalcMonth){
+            return true;
+        }
+        echo sprintf("时间:[%s]缓存达标业绩数据入库,当前offset为:【%s】" . PHP_EOL, date('Y-m-d H:i:s', time()) , $offset);
+        // 从缓存列表里面从底层往上倒序获取会员
+        $allData = CalcCache::getHasStandardMonthPerfUsers($this->_periodNum, $offset, $this->_limit);
+        if($allData){
+            $insertDataStandardPerf = [];
+            foreach($allData as $userId) {
+                $data = CalcCache::nowStandardMonthPerf($userId, $this->_periodNum);
+                $baseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+                $insertDataStandardPerf[] = [
+                    'ID' => SnowFake::instance()->generateId(),
+                    'USER_ID' => $userId,
+                    'LAST_DEC_LV' => $baseInfo['DEC_LV'],
+                    'LAST_EMP_LV' => $baseInfo['EMP_LV'],
+                    'LAST_STATUS' => $baseInfo['STATUS'],
+                    'AMOUNT_PCS' => $data['AMOUNT_PCS'],
+                    'AMOUNT_PSS' => $data['AMOUNT_PSS'],
+                    'CALC_MONTH' => $this->_calcYearMonth,
+                    'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+                    'CREATED_AT' => Date::nowTime(),
+                ];
+                unset($data, $baseInfo, $userId);
+            }
+
+            PerfStandard::batchInsert($insertDataStandardPerf);
+            unset($insertDataStandardPerf, $allData);
+            return $this->loopWriteStandardPerf($offset + $this->_limit);
+        }
+        unset($allData);
+        return true;
+    }
+
+    /**
+     * 本期业绩数据
+     * @param $userId
+     * @return array
+     */
+    public function nowPeriodPerfData($userId){
+        $data = CalcCache::nowPeriodPerf($userId, $this->_periodNum);
+        $baseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+        $result = [
+            'ID' => SnowFake::instance()->generateId(),
+            'USER_ID' => $userId,
+            'LAST_DEC_LV' => $baseInfo['DEC_LV'],
+            'LAST_EMP_LV' => $baseInfo['EMP_LV'],
+            'LAST_STATUS' => $baseInfo['STATUS'],
+            'FX_AMOUNT_CASH' => $data['FX_AMOUNT_CASH'],
+            'PV_PCS' => $data['PV_PCS'],
+            'PV_PSS' => $data['PV_PSS'],
+            'PV_PCS_ZC' => $data['PV_PCS_ZC'],
+            'PV_PCS_YH' => $data['PV_PCS_YH'],
+            'PV_PCS_ZG' => $data['PV_PCS_ZG'],
+            'PV_PCS_LS' => $data['PV_PCS_LS'],
+            'PV_PCS_FX' => $data['PV_PCS_FX'],
+            'PV_PCS_FX_CASH' => $data['PV_PCS_FX_CASH'],
+            'PV_PCS_FX_POINT' => $data['PV_PCS_FX_POINT'],
+            'PV_1L' => $data['PV_1L'],
+            'PV_1L_TOUCH' => $data['PV_1L_TOUCH'],
+            'PV_1L_ZC' => $data['PV_1L_ZC'],
+            'PV_1L_YH' => $data['PV_1L_YH'],
+            'PV_1L_ZG' => $data['PV_1L_ZG'],
+            'PV_1L_LS' => $data['PV_1L_LS'],
+            'PV_1L_FX' => $data['PV_1L_FX'],
+            'PV_2L' => $data['PV_2L'],
+            'PV_2L_TOUCH' => $data['PV_2L_TOUCH'],
+            'PV_2L_ZC' => $data['PV_2L_ZC'],
+            'PV_2L_YH' => $data['PV_2L_YH'],
+            'PV_2L_ZG' => $data['PV_2L_ZG'],
+            'PV_2L_LS' => $data['PV_2L_LS'],
+            'PV_2L_FX' => $data['PV_2L_FX'],
+            'PV_3L' => $data['PV_3L'],
+            'PV_3L_TOUCH' => $data['PV_3L_TOUCH'],
+            'PV_3L_ZC' => $data['PV_3L_ZC'],
+            'PV_3L_YH' => $data['PV_3L_YH'],
+            'PV_3L_ZG' => $data['PV_3L_ZG'],
+            'PV_3L_LS' => $data['PV_3L_LS'],
+            'PV_3L_FX' => $data['PV_3L_FX'],
+            'PV_4L' => $data['PV_4L'],
+            'PV_4L_TOUCH' => $data['PV_4L_TOUCH'],
+            'PV_4L_ZC' => $data['PV_4L_ZC'],
+            'PV_4L_YH' => $data['PV_4L_YH'],
+            'PV_4L_ZG' => $data['PV_4L_ZG'],
+            'PV_4L_LS' => $data['PV_4L_LS'],
+            'PV_4L_FX' => $data['PV_4L_FX'],
+            'PV_5L' => $data['PV_5L'],
+            'PV_5L_TOUCH' => $data['PV_5L_TOUCH'],
+            'PV_5L_ZC' => $data['PV_5L_ZC'],
+            'PV_5L_YH' => $data['PV_5L_YH'],
+            'PV_5L_ZG' => $data['PV_5L_ZG'],
+            'PV_5L_LS' => $data['PV_5L_LS'],
+            'PV_5L_FX' => $data['PV_5L_FX'],
+            'PV_LS_TOUCH' => $data['PV_LS_TOUCH'],
+            'SURPLUS_1L' => $data['SURPLUS_1L'],
+            'SURPLUS_2L' => $data['SURPLUS_2L'],
+            'SURPLUS_3L' => $data['SURPLUS_3L'],
+            'SURPLUS_4L' => $data['SURPLUS_4L'],
+            'SURPLUS_5L' => $data['SURPLUS_5L'],
+            'SURPLUS_LS' => $data['SURPLUS_LS'],
+            'PERIOD_NUM' => $this->_periodNum,
+            'CALC_MONTH' => $this->_calcYearMonth,
+            'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+            'CREATED_AT' => Date::nowTime(),
+        ];
+        unset($data);
+        return $result;
+    }
+
+
+
+    /**
+     * 本月业绩
+     * @param $userId
+     * @return array
+     */
+    public function nowMonthPerfData($userId){
+        $data = CalcCache::nowMonthPerf($userId, $this->_periodNum);
+        $baseInfo = CalcCache::getUserInfo($userId, $this->_periodNum);
+        $result = [
+            'ID' => SnowFake::instance()->generateId(),
+            'USER_ID' => $userId,
+            'LAST_DEC_LV' => $baseInfo['DEC_LV'],
+            'LAST_EMP_LV' => $data['EMP_LEVEL'],
+            'LAST_STATUS' => $baseInfo['STATUS'],
+            'FX_AMOUNT_CASH' => $data['FX_AMOUNT_CASH'],
+            'PV_PCS' => $data['PV_PCS'],
+            'PV_PCS_FX' => $data['PV_PCS_FX'],
+            'PV_PSS' => $data['PV_PSS'],
+            'PV_1L' => $data['PV_1L'],
+            'PV_2L' => $data['PV_2L'],
+            'PV_3L' => $data['PV_3L'],
+            'PV_4L' => $data['PV_4L'],
+            'PV_5L' => $data['PV_5L'],
+            'VIP_PV_1L_ZC' => $data['VIP_PV_1L_ZC'],
+            'VIP_PV_2L_ZC' => $data['VIP_PV_2L_ZC'],
+            'VIP_PV_3L_ZC' => $data['VIP_PV_3L_ZC'],
+            'VIP_PV_4L_ZC' => $data['VIP_PV_4L_ZC'],
+            'VIP_PV_5L_ZC' => $data['VIP_PV_5L_ZC'],
+            'PV_1L_TOTAL' => $data['PV_1L_TOTAL'],
+            'PV_2L_TOTAL' => $data['PV_2L_TOTAL'],
+            'PV_3L_TOTAL' => $data['PV_3L_TOTAL'],
+            'PV_4L_TOTAL' => $data['PV_4L_TOTAL'],
+            'PV_5L_TOTAL' => $data['PV_5L_TOTAL'],
+            'PV_PSS_TOTAL' => $data['PV_PSS_TOTAL'],
+            'CF_PERCENT' => $data['CF_PERCENT'],
+            'LX_PERCENT' => $data['LX_PERCENT'],
+            'FX_STATUS' => $data['FX_STATUS'],
+            'CALC_MONTH' => $this->_calcYearMonth,
+            'P_CALC_MONTH' => Date::ociToDate($this->_calcYearMonth, Date::OCI_TIME_FORMAT_SHORT_MONTH),
+            'CREATED_AT' => Date::nowTime(),
+        ];
+        unset($data);
+        return $result;
+    }
+
+    /**
+     * 循环父级并执行回调函数
+     * @param $userId
+     * @param callable $callbackFunc
+     * @param int $offset
+     * @return bool
+     */
+    public function loopNetworkParentDo($userId, callable $callbackFunc, int $offset = 0) {
+        $allParents = Cache::getAllNetworkParents($userId);
+        $allData = array_slice($allParents, $offset, $this->_limit);
+        unset($allParents);
+        if ($allData) {
+            foreach ($allData as $data) {
+                $funcResult = $callbackFunc($data);
+                if ($funcResult === self::LOOP_FINISH) {
+                    return true;
+                } elseif ($funcResult === self::LOOP_CONTINUE) {
+                    continue;
+                }
+                unset($data, $funcResult);
+            }
+            unset($allData);
+            return $this->loopNetworkParentDo($userId, $callbackFunc, $offset + $this->_limit);
+        }
+        return true;
+    }
+
+    /**
+     * 循环推荐网络的父级
+     * @param $userId
+     * @param callable $callbackFunc
+     * @param int $offset
+     * @return bool
+     */
+    public function loopRelationParentDo($userId, callable $callbackFunc, int $offset = 0) {
+        $allParents = Cache::getAllRelationParents($userId);
+        $allData = array_slice($allParents, $offset, $this->_limit);
+        unset($allParents);
+        if ($allData) {
+            foreach ($allData as $data) {
+                $funcResult = $callbackFunc($data);
+                if ($funcResult === self::LOOP_FINISH) {
+                    return true;
+                } elseif ($funcResult === self::LOOP_CONTINUE) {
+                    continue;
+                }
+                unset($data, $funcResult);
+            }
+
+            unset($allData);
+            return $this->loopRelationParentDo($userId, $callbackFunc, $offset + $this->_limit);
+        }
+        return true;
+    }
+
+    /**
+     * ==== 聘级计算开始 ====
+     */
+
+    /**
+     * 查看会员聘级
+     * @param $userId
+     * @param $nowMonthPerf
+     * @param $userEmpLevelId
+     * @return bool|mixed|\yii\db\ActiveRecord
+     */
+    public function checkEmpLevel($userId, $nowMonthPerf, $userEmpLevelId) {
+        $empLevel = $this->_empLevelConfig;
+        $userEmpLevel = $empLevel[$userEmpLevelId];
+        $userEmpLevelSort = $userEmpLevel['SORT'] ?? EmployLevel::EMP_LEVEL_SORT['NO_LEVEL'];
+        $childEmpLevelNumArr = CalcCache::hasEmpLevelNum($userId, $this->_periodNum);
+
+        // 判断主任到首席总监
+        $resultLevel = $this->isEmpLevelOther($empLevel, $childEmpLevelNumArr, $userEmpLevelSort);
+        unset($empLevel, $childEmpLevelNumArr);
+
+        if( empty($resultLevel) ) {
+            //不降级
+            if ( $userEmpLevelSort < EmployLevel::EMP_LEVEL_SORT['JX_ZR_LEVEL'] ) {
+                //主任
+                if ($checkLevel = $this->isEmpLevel1($userId, $nowMonthPerf)) {
+                    $resultLevel = $checkLevel;
+                } else {// 无聘级会员
+                    $resultLevel = EmployLevel::getLevelFromSort(EmployLevel::EMP_LEVEL_SORT['NO_LEVEL']);
+                }
+            }else {
+                $resultLevel = $userEmpLevel;
+            }
+        }
+        unset($userEmpLevel, $userEmpLevelSort);
+
+        // 获取到级别以后,给上级的相应人数中追加数量
+        $parentRecUserId = $userId;
+        $this->loopRelationParentDo($userId, function ($parent) use ($resultLevel, &$parentRecUserId) {
+            if( !$parentRecUserId ) return self::LOOP_FINISH;
+            //判断$parentRecUserId是否为$parent['PARENT_UID']的直推
+            $parentUid = $parent['PARENT_UID'];
+            $toInfo = CalcCache::getUserInfo($parentRecUserId, $this->_periodNum);
+            if( !isset($toInfo['REC_UID']) ) {
+                echo $parentRecUserId . PHP_EOL;
+            }
+            if( isset($toInfo['REC_UID']) && $parentUid !== $toInfo['REC_UID'] ) {
+                $parentUid = $toInfo['REC_UID'];
+            }
+            unset($toInfo);
+            if( !$parentUid ) self::LOOP_FINISH;
+
+            CalcCache::hasEmpLevelNum($parentUid, $this->_periodNum, [$parentRecUserId => [$resultLevel['ID'] => 1]]);
+            //每次记录上次的USER_ID
+            $parentRecUserId = $parentUid;
+        });
+        return $resultLevel;
+    }
+
+    /**
+     * 是否达到主任
+     * @param $userId
+     * @param $nowMonthPerf
+     * @return bool|mixed|\yii\db\ActiveRecord
+     * @throws \yii\db\Exception
+     */
+    public function isEmpLevel1($userId, $nowMonthPerf) {
+        $level1Option = EmployLevel::getLevelFromSort(EmployLevel::EMP_LEVEL_SORT['JX_ZR_LEVEL']);
+        //条件去除最大部门,其它部门累计50万
+        if( $nowMonthPerf['PV_PSS_TOTAL'] < $level1Option['OTHER_DEPART_PERF'] ) {//本身业绩就小于50万
+            unset($level1Option);
+            return false;
+        }
+
+        $oneDeepRelation = CalcCache::getChildrenOneDeepFromRedis($userId, $this->_periodNum);
+        if (  count($oneDeepRelation) <= 1 ) {//只有一个区就不判断了
+            unset($oneDeepRelation, $level1Option);
+            return false;
+        }
+        $maxPvPSS = 0;
+        $childPvPssTotalSum = 0;
+        foreach ($oneDeepRelation as $childData) {
+            //往期业绩
+            $userLastPerf = CalcCache::userPerf($childData['USER_ID'], $this->_periodNum);
+            //本期业绩
+            $periodPerf = CalcCache::nowPeriodPerf($childData['USER_ID'], $this->_periodNum);
+            $pvPcsTotal = $userLastPerf['PV_PCS_ZC'] + $userLastPerf['PV_PCS_FX'] + $periodPerf['PV_PCS'];
+
+            $pvPssTotal = $userLastPerf['PV_PSS_TOTAL'] + $periodPerf['PV_PSS'];
+            $childPvPssTotal = $pvPcsTotal + $pvPssTotal;
+            unset($userLastPerf, $periodPerf, $pvPcsTotal, $pvPssTotal);
+
+            $childPvPssTotalSum += $childPvPssTotal;
+            if( $childPvPssTotal >= $maxPvPSS ) {
+                $maxPvPSS = $childPvPssTotal;
+            }
+
+            unset($childData, $childPvPssTotal);
+        }
+        unset($oneDeepRelation);
+
+        if( $childPvPssTotalSum - $maxPvPSS >= $level1Option['OTHER_DEPART_PERF'] ) {
+            unset($maxPvPSS, $childPvPssTotalSum);
+            return $level1Option;
+        }else {
+            unset($maxPvPSS, $childPvPssTotalSum, $level1Option);
+            return false;
+        }
+    }
+
+    /**
+     * 是否达到其他级别
+     * @param $empLevel
+     * @param $childEmpLevelNumArr
+     * @param $userEmpLevelSort
+     * @return bool|mixed|\yii\db\ActiveRecord
+     */
+    public function isEmpLevelOther($empLevel, $childEmpLevelNumArr, $userEmpLevelSort) {
+        $resultLevel = [];
+        foreach ($empLevel as $level) {
+            if ($level['SORT'] < EmployLevel::EMP_LEVEL_SORT['JX_ZR_LEVEL']) continue;
+            //不降级
+            if ($level['SORT'] <= $userEmpLevelSort) continue;
+
+            // 级别要求有几个区
+            $locationNum = $level['LOCATION_NUM'];
+            // 级别要求这几个区分别存在级别会员的数量
+            $minEmpNum = $level['MIN_EMPLOY_NUM'];
+            $minEmpLevel = $level['MIN_EMPLOY_LEVEL'];
+            if( !$minEmpLevel || !isset($empLevel[$minEmpLevel]) || $minEmpNum < 1 ) continue;
+            $minEmpLevelSort = $empLevel[$minEmpLevel]['SORT'];
+            $resultEmpLevelNumArr = array_filter($childEmpLevelNumArr, function ($item, $departUserId) use ($empLevel, $minEmpLevelSort, $minEmpNum) {
+
+                $tempEmpLevelNum = 0;
+                foreach ($item as $levelIndex => $levelNum) {
+                    if ($empLevel[$levelIndex]['SORT'] >= $minEmpLevelSort && $levelNum >= $minEmpNum) {
+                        $tempEmpLevelNum += 1;
+                    }
+                }
+                return $tempEmpLevelNum >= 1;
+            }, ARRAY_FILTER_USE_BOTH);
+            if (count($resultEmpLevelNumArr) >= $locationNum) {
+                if( $level['SORT'] == EmployLevel::EMP_LEVEL_SORT['SHX_ZJ_LEVEL'] ) {
+                    //首席总监还要满足一个条件、任意部门1个高级总监
+                    $gjZjLevelNumArr = array_filter($childEmpLevelNumArr, function ($item, $departUserId) use ($empLevel) {
+
+                        $tempEmpLevelNum = 0;
+                        foreach ($item as $levelIndex => $levelNum) {
+                            if ($empLevel[$levelIndex]['SORT'] >= EmployLevel::EMP_LEVEL_SORT['GJ_ZJ_LEVEL'] && $levelNum >= 1) {
+                                $tempEmpLevelNum += 1;
+                            }
+                        }
+                        return $tempEmpLevelNum >= 1;
+                    }, ARRAY_FILTER_USE_BOTH);
+                    if( count($gjZjLevelNumArr) >= 1 ) {
+                        $resultLevel = $level;
+                    }
+                    unset($gjZjLevelNumArr);
+                }else {
+                    $resultLevel = $level;
+                }
+            }
+            unset($level, $minEmpLevel, $minEmpLevelSort, $resultEmpLevelNumArr);
+        }
+        unset($empLevel, $childEmpLevelNumArr, $userEmpLevelSort);
+        return $resultLevel;
+    }
+
+    /**
+     * 是否可拿业绩(即注销、关停、停发状态)
+     * @param $userId
+     * @return bool
+     */
+    public function isHasPerf($userId) {
+        //@todo 所有人都有业绩
+        return true;
+    }
+
+    /**
+     * ==== 聘级计算结束 ====
+     */
+
+    /**
+     * 更新百分比并发送
+     * @param $percent
+     */
+    private function _updatePercent($percent) {
+        // 把数据写入数据库中
+        Period::updateAll(['PERF_PERCENT' => $percent], 'PERIOD_NUM=:PERIOD_NUM', [':PERIOD_NUM' => $this->_periodNum]);
+        \Yii::$app->swooleAsyncTimer->pushAsyncPercentToAdmin($percent, ['MODEL' => 'PERIOD', 'ID' => $this->_periodId, 'FIELD' => 'PERF_PERCENT']);
+    }
+
+}

+ 101 - 0
common/helpers/http/BackendToFrontendApi.php

@@ -0,0 +1,101 @@
+<?php
+/**
+ * 内部API助手类
+ * Created by PhpStorm.
+ * User: Ming
+ * Date: 2018/3/13
+ * Time: 10:21
+ */
+
+namespace common\helpers\http;
+use common\helpers\Date;
+use Yii;
+class BackendToFrontendApi {
+    /**
+     * 通讯密钥
+     */
+    const AUTH_KEY = '';
+    /**
+     * 允许的时间差(秒)
+     */
+    const TIME_DIFF = '';
+    /**
+     * 请求方法
+     */
+    const HTTP_METHOD_POST = 'post';
+    const HTTP_METHOD_GET = 'get';
+    /**
+     * 返回成功的标记
+     */
+    const RETURN_SUCCESS = 'success';
+    /**
+     * http client
+     * @var null
+     */
+    /**
+     * http response
+     * @var null
+     */
+    protected static $response = null;
+    /**
+     * 获取密钥
+     * @return string
+     */
+    public static function getAuthKey(){
+        if(self::AUTH_KEY === ''){
+            return Yii::$app->params['http']['backendToFrontendApi']['authKey'];
+        }
+
+        return self::AUTH_KEY;
+    }
+
+    /**
+     * 获取允许的时间差
+     * @return string
+     */
+    public static function getTimeDiff(){
+        if(self::TIME_DIFF === ''){
+            return Yii::$app->params['http']['backendToFrontendApi']['timeDiff'];
+        }
+        return self::TIME_DIFF;
+    }
+    /**
+     * 生成签名
+     * @param array $params
+     * @return array
+     */
+    public static function paramsFormat(array $params){
+        if(!isset($params['timestamp'])){
+            $params['timestamp'] = time();
+        }
+        if(isset($params['signature'])){
+            unset($params['signature']);
+        }
+        ksort($params);
+        $string = '';
+        foreach($params as $key=>$value){
+            $string .= $key.'='.$value . '&';
+        }
+        $params['signature'] = sha1(trim($string,'&') . self::getAuthKey());
+        return $params;
+    }
+
+    /**
+     * 验证签名
+     * @param $signature
+     * @param array $params
+     * @return bool
+     */
+    public static function checkSignature($signature, array $params){
+        $params = self::paramsFormat($params);
+        if($params['signature'] !== $signature){
+            return false;
+        }
+        $timeDiff = (int)self::getTimeDiff();
+        if($timeDiff > 0 && (Date::nowTime() - $params['timestamp']) > $timeDiff){
+            return false;
+        }
+        return true;
+    }
+
+}

+ 156 - 0
common/helpers/http/LingYunGongApi.php

@@ -0,0 +1,156 @@
+<?php
+namespace common\helpers\http;
+
+use yii\base\BaseObject;
+use yii\httpclient\Client;
+
+class LingYunGongApi {
+
+    const CACHE_TOKEN_KEY = 'cashTokenKey_%s';
+
+    /**
+     * 请求方法
+     */
+    const HTTP_METHOD_POST = 'post';
+    const HTTP_METHOD_GET = 'get';
+
+    /**
+     * http client
+     * @var null
+     */
+    private static $_client = null;
+
+
+    /**
+     * 获取http client 对象
+     * @return null|Client
+     */
+    public static function client(){
+        if(is_null(self::$_client)){
+            self::$_client = new Client();
+        }
+        return self::$_client;
+    }
+
+    /**
+     * request
+     * @return \yii\httpclient\Request
+     */
+    public static function request(){
+        return self::client()->createRequest();
+    }
+
+    /**
+     * POST请求
+     * @param $url
+     * @param $data
+     * @param array $headers
+     * @return mixed
+     */
+    public static function post($url, $data, array $headers=[]){
+        $query = self::request()
+            ->setMethod(self::HTTP_METHOD_POST);
+        if( $headers ) {
+            $query->addHeaders($headers);
+        }
+
+        $query->setFormat(Client::FORMAT_JSON);
+        $query->setUrl($url)
+            ->setData($data);
+        unset($data);
+        return $query->send();
+    }
+
+    /**
+     * GET请求
+     * @param $url
+     * @param array $data
+     * @param array $headers
+     * @return \yii\httpclient\Response
+     */
+    public static function get($url, array $data = [], array $headers=[]){
+        $query = self::request()
+            ->setMethod(self::HTTP_METHOD_GET);
+        if( $headers ) {
+            $query->addHeaders($headers);
+        }
+        $query->setUrl($url);
+        if($data){
+            $query->setData($data);
+        }
+        unset($data);
+        return $query->send();
+    }
+
+    /**
+     * 获取token
+     * @return mixed|string
+     */
+    public static function getAuthToken() {
+        $authTokenClientId = \Yii::$app->params['http']['lingYunGongApi']['authToken']['clientId'];
+        $cacheKey = sprintf(self::CACHE_TOKEN_KEY, $authTokenClientId);
+        $cache = \Yii::$app->cache;
+        $token = $cache->get($cacheKey);
+        if( $token ) return $token;
+
+        $tokenData = self::_apiAuthToken();
+        if ( !$tokenData ) return '';
+
+        $token = $tokenData['access_token'] ?? '';
+        $expiresIn = $tokenData['expires_in'] ?? 0;
+//        $tokenType = $tokenData['token_type'];
+        $cacheTime  = $expiresIn - 60;
+        if( $cacheTime > 0 && $token ) {
+            \Yii::$app->cache->set($cacheKey, $token, $cacheTime);
+        }
+        return $token;
+    }
+
+
+    /**
+     * api接口获取token
+     * @return false
+     */
+    private static function _apiAuthToken() {
+        $host = \Yii::$app->params['http']['lingYunGongApi']['host'];
+        $authTokenPath = \Yii::$app->params['http']['lingYunGongApi']['authToken']['path'];
+        $authTokenClientId = \Yii::$app->params['http']['lingYunGongApi']['authToken']['clientId'];
+        $requestUrl = $host . $authTokenPath;
+
+        $data = [
+            'grant_type' => 'client_credential',
+            'client_secret' => 'secret!',
+            'client_id' => $authTokenClientId,
+        ];
+
+
+        $response = self::post($requestUrl, $data, ['content-type' => 'application/json']);
+        if($response->isOk){
+            return $response->data;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 判断是否上传了身份证信息
+     * @param string $idCard
+     */
+    public static function hasIdCardInfo(string $idCard) {
+        $token = self::getAuthToken();
+        if( !$token ) return false;
+
+        $host = \Yii::$app->params['http']['lingYunGongApi']['host'];
+        $authTokenPath = \Yii::$app->params['http']['lingYunGongApi']['hasIdCardInfoPath'];
+        $requestUrl = $host . sprintf($authTokenPath, $idCard);
+
+        $response = self::get($requestUrl, [], [
+            'Authorization' => sprintf('Bearer %s', $token)
+        ]);
+        if($response->isOk){
+            return $response->data;
+        } else {
+            return false;
+        }
+    }
+}

+ 152 - 0
common/helpers/http/RemoteUploadApi.php

@@ -0,0 +1,152 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/5/17
+ * Time: 下午2:44
+ */
+
+namespace common\helpers\http;
+
+use common\models\Uploads;
+use yii\base\BaseObject;
+use yii\base\StaticInstanceTrait;
+use yii\httpclient\Client;
+
+class RemoteUploadApi extends BaseObject
+{
+    use StaticInstanceTrait;
+
+    /**
+     * 通讯密钥
+     */
+    public $authKey;
+
+    /**
+     * 上传地址
+     * @var
+     */
+    public $uploadUrl;
+
+    /**
+     * action地址
+     * @var
+     */
+    public $actionUrl;
+
+    public $filePath;
+
+    /**
+     * 上传的用户ID
+     * @var
+     */
+    public $id;
+
+    const CATEGORY_EXPORT = 'export';
+    const FORBIDDEN_NOTIFY_URL = 'forbidden';
+
+    /**
+     * 初始化
+     * @throws \yii\base\Exception
+     */
+    public function init()
+    {
+        parent::init();
+        $this->authKey = \Yii::$app->params['http']['remoteUploadApi']['authKey'];
+        $this->uploadUrl = \Yii::$app->params['http']['remoteUploadApi']['host'].'/upload';
+        $this->actionUrl = \Yii::$app->params['http']['remoteUploadApi']['host'].'/action';
+        $this->filePath = \Yii::$app->params['http']['remoteUploadApi']['host'].'/files/';
+        $this->id = \Yii::$app->security->generateRandomString(32);
+    }
+
+    /**
+     * @param $uid
+     * @param array $params
+     * @return array
+     */
+    public function generateToken($uid, $params = []){
+        if(!isset($params['date']) || !$params['date']){
+            $params['date'] = date('YmdH');
+        }
+        if(!isset($params['authKey']) || !$params['authKey']){
+            $params['authKey'] = \Yii::$app->params['http']['remoteUploadApi']['authKey'];
+        }
+        $hash = 'uid:'.$uid.'&secretkey:'.(string)$params['authKey'].'&datetime:'.(string)$params['date'];
+        if(!isset($params['notifyUrl']) || !$params['notifyUrl']){
+            $params['notifyUrl'] = \Yii::$app->params['http']['remoteUploadApi']['remoteUploadNotifyUrl'];
+        }elseif($params['notifyUrl'] == self::FORBIDDEN_NOTIFY_URL){
+            $params['notifyUrl'] = '';
+        }
+        $hash .= '&notifyurl:' . (string)$params['notifyUrl'];
+        $md5 = md5($hash);
+        return [
+            'date' => $params['date'],
+            'token' => $md5,
+            'notifyUrl' => $params['notifyUrl'],
+        ];
+    }
+
+    /**
+     * 上传
+     * @param $filePath
+     * @return bool|mixed
+     * @throws \yii\base\InvalidConfigException
+     * @throws \yii\httpclient\Exception
+     */
+    public function upload($filePath){
+        $token = $this->generateToken($this->id);
+        // 上传
+        $client = new Client();
+        $response = $client->createRequest()
+            ->setMethod('POST')
+            ->addHeaders([
+                'UPLOAD-SERVER-TOKEN' => $token['token'],
+                'UPLOAD-SERVER-USER' => $this->id,
+                'UPLOAD-SERVER-NOTIFY-URL'=>$token['notifyUrl'],
+                'UPLOAD-SERVER-DATE'=>$token['date'],
+            ])
+            ->setUrl($this->uploadUrl)
+            ->addFile('ATTACHMENT', $filePath)
+            ->send();
+        if($response->isOk && isset($response->data['url'])){
+            return $response->data;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 删除
+     * @param $filePath
+     * @return bool
+     * @throws \yii\base\InvalidConfigException
+     */
+    public function delete($filePath){
+        // 处理$filePath
+        if(strpos($filePath, $this->filePath) !== false){
+            $filePath = str_replace($this->filePath, '', $filePath);
+        }
+        $token = $this->generateToken($this->id);
+        $client = new Client();
+        $response = $client->createRequest()
+            ->setMethod('POST')
+            ->addHeaders([
+                'UPLOAD-SERVER-TOKEN' => $token['token'],
+                'UPLOAD-SERVER-USER' => $this->id,
+                'UPLOAD-SERVER-NOTIFY-URL'=>$token['notifyUrl'],
+                'UPLOAD-SERVER-DATE'=>$token['date'],
+            ])
+            ->setUrl($this->actionUrl)
+            ->setData([
+                'action' => 'delete',
+                'post_data' => $filePath,
+            ])
+            ->send();
+        if($response->isOk && isset($response->data['success']) && $response->data['success'] == 1){
+            return $response->data;
+        } else {
+            return false;
+        }
+    }
+
+}

+ 208 - 0
common/helpers/http/ShopApi.php

@@ -0,0 +1,208 @@
+<?php
+/**
+ * 内部API助手类
+ * Created by PhpStorm.
+ * User: Ming
+ * Date: 2018/3/13
+ * Time: 10:21
+ */
+
+namespace common\helpers\http;
+use Yii;
+use yii\httpclient\Client;
+class ShopApi {
+    /**
+     * 通讯密钥
+     */
+    const AUTH_KEY = '';
+    /**
+     * 允许的时间差(秒)
+     */
+    const TIME_DIFF = '';
+    /**
+     * 请求方法
+     */
+    const HTTP_METHOD_POST = 'post';
+    const HTTP_METHOD_GET = 'get';
+    /**
+     * 返回成功的标记
+     */
+    const RETURN_SUCCESS = 'success';
+    /**
+     * http client
+     * @var null
+     */
+    private static $_client = null;
+    /**
+     * http response
+     * @var null
+     */
+    protected static $response = null;
+    /**
+     * 错误代码
+     * @var int
+     */
+    private static $_errorCode = 0;
+    /**
+     * 错误信息
+     * @var null
+     */
+    private static $_errorMessage = null;
+    /**
+     * 获取密钥
+     * @return string
+     */
+    public static function getAuthKey(){
+        if(self::AUTH_KEY === ''){
+            return Yii::$app->params['http']['shopApi']['authKey'];
+        }
+
+        return self::AUTH_KEY;
+    }
+
+    /**
+     * 获取允许的时间差
+     * @return string
+     */
+    public static function getTimeDiff(){
+        if(self::TIME_DIFF === ''){
+            return Yii::$app->params['http']['shopApi']['timeDiff'];
+        }
+        return self::TIME_DIFF;
+    }
+    /**
+     * 生成签名
+     * @param array $params
+     * @return array
+     */
+    public static function paramsFormat(array $params){
+        if(!isset($params['timestamp'])){
+            $params['timestamp'] = time();
+        }
+        if(isset($params['signature'])){
+            unset($params['signature']);
+        }
+        ksort($params);
+        $string = '';
+        foreach($params as $key=>$value){
+            $string .= $key.'='.$value . '&';
+        }
+        $params['signature'] = sha1(trim($string,'&') . self::getAuthKey());
+        return $params;
+    }
+
+    /**
+     * 验证签名
+     * @param $signature
+     * @param array $params
+     * @return bool
+     */
+    public static function checkSignature($signature, array $params){
+        $params = self::paramsFormat($params);
+        if($params['signature'] !== $signature){
+            return false;
+        }
+        $timeDiff = (int)self::getTimeDiff();
+        if($timeDiff > 0 && (time() -> $params['timestamp']) > $timeDiff){
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 获取http client 对象
+     * @return null|Client
+     */
+    public static function client(){
+        if(is_null(self::$_client)){
+            self::$_client = new Client();
+        }
+        return self::$_client;
+    }
+
+    /**
+     * request
+     * @return \yii\httpclient\Request
+     */
+    public static function request(){
+        return self::client()->createRequest();
+    }
+
+    /**
+     * POST请求
+     * @param $url
+     * @param $data
+     * @return mixed
+     */
+    public static function post($url, $data){
+        $data = self::paramsFormat($data);
+        static::$response = self::request()
+            ->setMethod(self::HTTP_METHOD_POST)
+            ->setUrl($url)
+            ->setData($data)
+            ->send();
+        unset($data);
+        return static::$response;
+    }
+
+    /**
+     * GET请求
+     * @param $url
+     * @param array $data
+     * @return \yii\httpclient\Response
+     */
+    public static function get($url, $data = []){
+        $data = self::paramsFormat($data);
+        $query = self::request()
+            ->setMethod(self::HTTP_METHOD_GET)
+            ->setUrl($url);
+        if($data){
+            $query->setData($data);
+        }
+        static::$response = $query->send();
+        unset($data, $query);
+        return static::$response;
+    }
+    /**
+     * 输出是否正确
+     * @return mixed
+     */
+    public static function responsed(){
+        return self::$response->isOk && (is_array(self::$response->data) && isset(self::$response->data[self::RETURN_SUCCESS]));
+    }
+
+    /**
+     * 获取报文
+     * @return null
+     */
+    public static function getResponse(){
+        return self::$response;
+    }
+    /**
+     * 获取报文数据
+     * @return mixed
+     */
+    public static function result(){
+        return self::$response->data;
+    }
+
+    /**
+     * 设置错误
+     * @param $message
+     * @param null $code
+     */
+    public static function setError($message, $code = null){
+        if(!is_null($code)){
+            self::$_errorCode = $code;
+        }
+        self::$_errorMessage = $message;
+    }
+
+    /**
+     * 获取错误
+     * @return array
+     */
+    public static function getError(){
+        return [self::$_errorMessage, self::$_errorCode];
+    }
+}

+ 20 - 0
common/helpers/parsedown/LICENSE.txt

@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013-2018 Emanuil Rusev, erusev.com
+
+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.

+ 1679 - 0
common/helpers/parsedown/Parsedown.php

@@ -0,0 +1,1679 @@
+<?php
+namespace common\helpers\parsedown;
+#
+#
+# Parsedown
+# http://parsedown.org
+#
+# (c) Emanuil Rusev
+# http://erusev.com
+#
+# For the full license information, view the LICENSE file that was distributed
+# with this source code.
+#
+#
+
+class Parsedown
+{
+    # ~
+
+    const version = '1.7.1';
+
+    # ~
+
+    function text($text)
+    {
+        # make sure no definitions are set
+        $this->DefinitionData = array();
+
+        # standardize line breaks
+        $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+        # remove surrounding line breaks
+        $text = trim($text, "\n");
+
+        # split text into lines
+        $lines = explode("\n", $text);
+
+        # iterate through lines to identify blocks
+        $markup = $this->lines($lines);
+
+        # trim line breaks
+        $markup = trim($markup, "\n");
+
+        return $markup;
+    }
+
+    #
+    # Setters
+    #
+
+    function setBreaksEnabled($breaksEnabled)
+    {
+        $this->breaksEnabled = $breaksEnabled;
+
+        return $this;
+    }
+
+    protected $breaksEnabled;
+
+    function setMarkupEscaped($markupEscaped)
+    {
+        $this->markupEscaped = $markupEscaped;
+
+        return $this;
+    }
+
+    protected $markupEscaped;
+
+    function setUrlsLinked($urlsLinked)
+    {
+        $this->urlsLinked = $urlsLinked;
+
+        return $this;
+    }
+
+    protected $urlsLinked = true;
+
+    function setSafeMode($safeMode)
+    {
+        $this->safeMode = (bool) $safeMode;
+
+        return $this;
+    }
+
+    protected $safeMode;
+
+    protected $safeLinksWhitelist = array(
+        'http://',
+        'https://',
+        'ftp://',
+        'ftps://',
+        'mailto:',
+        'data:image/png;base64,',
+        'data:image/gif;base64,',
+        'data:image/jpeg;base64,',
+        'irc:',
+        'ircs:',
+        'git:',
+        'ssh:',
+        'news:',
+        'steam:',
+    );
+
+    #
+    # Lines
+    #
+
+    protected $BlockTypes = array(
+        '#' => array('Header'),
+        '*' => array('Rule', 'List'),
+        '+' => array('List'),
+        '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
+        '0' => array('List'),
+        '1' => array('List'),
+        '2' => array('List'),
+        '3' => array('List'),
+        '4' => array('List'),
+        '5' => array('List'),
+        '6' => array('List'),
+        '7' => array('List'),
+        '8' => array('List'),
+        '9' => array('List'),
+        ':' => array('Table'),
+        '<' => array('Comment', 'Markup'),
+        '=' => array('SetextHeader'),
+        '>' => array('Quote'),
+        '[' => array('Reference'),
+        '_' => array('Rule'),
+        '`' => array('FencedCode'),
+        '|' => array('Table'),
+        '~' => array('FencedCode'),
+    );
+
+    # ~
+
+    protected $unmarkedBlockTypes = array(
+        'Code',
+    );
+
+    #
+    # Blocks
+    #
+
+    protected function lines(array $lines)
+    {
+        $CurrentBlock = null;
+
+        foreach ($lines as $line)
+        {
+            if (chop($line) === '')
+            {
+                if (isset($CurrentBlock))
+                {
+                    $CurrentBlock['interrupted'] = true;
+                }
+
+                continue;
+            }
+
+            if (strpos($line, "\t") !== false)
+            {
+                $parts = explode("\t", $line);
+
+                $line = $parts[0];
+
+                unset($parts[0]);
+
+                foreach ($parts as $part)
+                {
+                    $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
+
+                    $line .= str_repeat(' ', $shortage);
+                    $line .= $part;
+                }
+            }
+
+            $indent = 0;
+
+            while (isset($line[$indent]) and $line[$indent] === ' ')
+            {
+                $indent ++;
+            }
+
+            $text = $indent > 0 ? substr($line, $indent) : $line;
+
+            # ~
+
+            $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
+
+            # ~
+
+            if (isset($CurrentBlock['continuable']))
+            {
+                $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
+
+                if (isset($Block))
+                {
+                    $CurrentBlock = $Block;
+
+                    continue;
+                }
+                else
+                {
+                    if ($this->isBlockCompletable($CurrentBlock['type']))
+                    {
+                        $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
+                    }
+                }
+            }
+
+            # ~
+
+            $marker = $text[0];
+
+            # ~
+
+            $blockTypes = $this->unmarkedBlockTypes;
+
+            if (isset($this->BlockTypes[$marker]))
+            {
+                foreach ($this->BlockTypes[$marker] as $blockType)
+                {
+                    $blockTypes []= $blockType;
+                }
+            }
+
+            #
+            # ~
+
+            foreach ($blockTypes as $blockType)
+            {
+                $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
+
+                if (isset($Block))
+                {
+                    $Block['type'] = $blockType;
+
+                    if ( ! isset($Block['identified']))
+                    {
+                        $Blocks []= $CurrentBlock;
+
+                        $Block['identified'] = true;
+                    }
+
+                    if ($this->isBlockContinuable($blockType))
+                    {
+                        $Block['continuable'] = true;
+                    }
+
+                    $CurrentBlock = $Block;
+
+                    continue 2;
+                }
+            }
+
+            # ~
+
+            if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
+            {
+                $CurrentBlock['element']['text'] .= "\n".$text;
+            }
+            else
+            {
+                $Blocks []= $CurrentBlock;
+
+                $CurrentBlock = $this->paragraph($Line);
+
+                $CurrentBlock['identified'] = true;
+            }
+        }
+
+        # ~
+
+        if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
+        {
+            $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
+        }
+
+        # ~
+
+        $Blocks []= $CurrentBlock;
+
+        unset($Blocks[0]);
+
+        # ~
+
+        $markup = '';
+
+        foreach ($Blocks as $Block)
+        {
+            if (isset($Block['hidden']))
+            {
+                continue;
+            }
+
+            $markup .= "\n";
+            $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
+        }
+
+        $markup .= "\n";
+
+        # ~
+
+        return $markup;
+    }
+
+    protected function isBlockContinuable($Type)
+    {
+        return method_exists($this, 'block'.$Type.'Continue');
+    }
+
+    protected function isBlockCompletable($Type)
+    {
+        return method_exists($this, 'block'.$Type.'Complete');
+    }
+
+    #
+    # Code
+
+    protected function blockCode($Line, $Block = null)
+    {
+        if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
+        {
+            return;
+        }
+
+        if ($Line['indent'] >= 4)
+        {
+            $text = substr($Line['body'], 4);
+
+            $Block = array(
+                'element' => array(
+                    'name' => 'pre',
+                    'handler' => 'element',
+                    'text' => array(
+                        'name' => 'code',
+                        'text' => $text,
+                    ),
+                ),
+            );
+
+            return $Block;
+        }
+    }
+
+    protected function blockCodeContinue($Line, $Block)
+    {
+        if ($Line['indent'] >= 4)
+        {
+            if (isset($Block['interrupted']))
+            {
+                $Block['element']['text']['text'] .= "\n";
+
+                unset($Block['interrupted']);
+            }
+
+            $Block['element']['text']['text'] .= "\n";
+
+            $text = substr($Line['body'], 4);
+
+            $Block['element']['text']['text'] .= $text;
+
+            return $Block;
+        }
+    }
+
+    protected function blockCodeComplete($Block)
+    {
+        $text = $Block['element']['text']['text'];
+
+        $Block['element']['text']['text'] = $text;
+
+        return $Block;
+    }
+
+    #
+    # Comment
+
+    protected function blockComment($Line)
+    {
+        if ($this->markupEscaped or $this->safeMode)
+        {
+            return;
+        }
+
+        if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
+        {
+            $Block = array(
+                'markup' => $Line['body'],
+            );
+
+            if (preg_match('/-->$/', $Line['text']))
+            {
+                $Block['closed'] = true;
+            }
+
+            return $Block;
+        }
+    }
+
+    protected function blockCommentContinue($Line, array $Block)
+    {
+        if (isset($Block['closed']))
+        {
+            return;
+        }
+
+        $Block['markup'] .= "\n" . $Line['body'];
+
+        if (preg_match('/-->$/', $Line['text']))
+        {
+            $Block['closed'] = true;
+        }
+
+        return $Block;
+    }
+
+    #
+    # Fenced Code
+
+    protected function blockFencedCode($Line)
+    {
+        if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches))
+        {
+            $Element = array(
+                'name' => 'code',
+                'text' => '',
+            );
+
+            if (isset($matches[1]))
+            {
+                $class = 'language-'.$matches[1];
+
+                $Element['attributes'] = array(
+                    'class' => $class,
+                );
+            }
+
+            $Block = array(
+                'char' => $Line['text'][0],
+                'element' => array(
+                    'name' => 'pre',
+                    'handler' => 'element',
+                    'text' => $Element,
+                ),
+            );
+
+            return $Block;
+        }
+    }
+
+    protected function blockFencedCodeContinue($Line, $Block)
+    {
+        if (isset($Block['complete']))
+        {
+            return;
+        }
+
+        if (isset($Block['interrupted']))
+        {
+            $Block['element']['text']['text'] .= "\n";
+
+            unset($Block['interrupted']);
+        }
+
+        if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
+        {
+            $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
+
+            $Block['complete'] = true;
+
+            return $Block;
+        }
+
+        $Block['element']['text']['text'] .= "\n".$Line['body'];
+
+        return $Block;
+    }
+
+    protected function blockFencedCodeComplete($Block)
+    {
+        $text = $Block['element']['text']['text'];
+
+        $Block['element']['text']['text'] = $text;
+
+        return $Block;
+    }
+
+    #
+    # Header
+
+    protected function blockHeader($Line)
+    {
+        if (isset($Line['text'][1]))
+        {
+            $level = 1;
+
+            while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
+            {
+                $level ++;
+            }
+
+            if ($level > 6)
+            {
+                return;
+            }
+
+            $text = trim($Line['text'], '# ');
+
+            $Block = array(
+                'element' => array(
+                    'name' => 'h' . min(6, $level),
+                    'text' => $text,
+                    'handler' => 'line',
+                ),
+            );
+
+            return $Block;
+        }
+    }
+
+    #
+    # List
+
+    protected function blockList($Line)
+    {
+        list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
+
+        if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
+        {
+            $Block = array(
+                'indent' => $Line['indent'],
+                'pattern' => $pattern,
+                'element' => array(
+                    'name' => $name,
+                    'handler' => 'elements',
+                ),
+            );
+
+            if($name === 'ol')
+            {
+                $listStart = stristr($matches[0], '.', true);
+
+                if($listStart !== '1')
+                {
+                    $Block['element']['attributes'] = array('start' => $listStart);
+                }
+            }
+
+            $Block['li'] = array(
+                'name' => 'li',
+                'handler' => 'li',
+                'text' => array(
+                    $matches[2],
+                ),
+            );
+
+            $Block['element']['text'] []= & $Block['li'];
+
+            return $Block;
+        }
+    }
+
+    protected function blockListContinue($Line, array $Block)
+    {
+        if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
+        {
+            if (isset($Block['interrupted']))
+            {
+                $Block['li']['text'] []= '';
+
+                $Block['loose'] = true;
+
+                unset($Block['interrupted']);
+            }
+
+            unset($Block['li']);
+
+            $text = isset($matches[1]) ? $matches[1] : '';
+
+            $Block['li'] = array(
+                'name' => 'li',
+                'handler' => 'li',
+                'text' => array(
+                    $text,
+                ),
+            );
+
+            $Block['element']['text'] []= & $Block['li'];
+
+            return $Block;
+        }
+
+        if ($Line['text'][0] === '[' and $this->blockReference($Line))
+        {
+            return $Block;
+        }
+
+        if ( ! isset($Block['interrupted']))
+        {
+            $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
+
+            $Block['li']['text'] []= $text;
+
+            return $Block;
+        }
+
+        if ($Line['indent'] > 0)
+        {
+            $Block['li']['text'] []= '';
+
+            $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
+
+            $Block['li']['text'] []= $text;
+
+            unset($Block['interrupted']);
+
+            return $Block;
+        }
+    }
+
+    protected function blockListComplete(array $Block)
+    {
+        if (isset($Block['loose']))
+        {
+            foreach ($Block['element']['text'] as &$li)
+            {
+                if (end($li['text']) !== '')
+                {
+                    $li['text'] []= '';
+                }
+            }
+        }
+
+        return $Block;
+    }
+
+    #
+    # Quote
+
+    protected function blockQuote($Line)
+    {
+        if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
+        {
+            $Block = array(
+                'element' => array(
+                    'name' => 'blockquote',
+                    'handler' => 'lines',
+                    'text' => (array) $matches[1],
+                ),
+            );
+
+            return $Block;
+        }
+    }
+
+    protected function blockQuoteContinue($Line, array $Block)
+    {
+        if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
+        {
+            if (isset($Block['interrupted']))
+            {
+                $Block['element']['text'] []= '';
+
+                unset($Block['interrupted']);
+            }
+
+            $Block['element']['text'] []= $matches[1];
+
+            return $Block;
+        }
+
+        if ( ! isset($Block['interrupted']))
+        {
+            $Block['element']['text'] []= $Line['text'];
+
+            return $Block;
+        }
+    }
+
+    #
+    # Rule
+
+    protected function blockRule($Line)
+    {
+        if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
+        {
+            $Block = array(
+                'element' => array(
+                    'name' => 'hr'
+                ),
+            );
+
+            return $Block;
+        }
+    }
+
+    #
+    # Setext
+
+    protected function blockSetextHeader($Line, array $Block = null)
+    {
+        if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
+        {
+            return;
+        }
+
+        if (chop($Line['text'], $Line['text'][0]) === '')
+        {
+            $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
+
+            return $Block;
+        }
+    }
+
+    #
+    # Markup
+
+    protected function blockMarkup($Line)
+    {
+        if ($this->markupEscaped or $this->safeMode)
+        {
+            return;
+        }
+
+        if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
+        {
+            $element = strtolower($matches[1]);
+
+            if (in_array($element, $this->textLevelElements))
+            {
+                return;
+            }
+
+            $Block = array(
+                'name' => $matches[1],
+                'depth' => 0,
+                'markup' => $Line['text'],
+            );
+
+            $length = strlen($matches[0]);
+
+            $remainder = substr($Line['text'], $length);
+
+            if (trim($remainder) === '')
+            {
+                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+                {
+                    $Block['closed'] = true;
+
+                    $Block['void'] = true;
+                }
+            }
+            else
+            {
+                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+                {
+                    return;
+                }
+
+                if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
+                {
+                    $Block['closed'] = true;
+                }
+            }
+
+            return $Block;
+        }
+    }
+
+    protected function blockMarkupContinue($Line, array $Block)
+    {
+        if (isset($Block['closed']))
+        {
+            return;
+        }
+
+        if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
+        {
+            $Block['depth'] ++;
+        }
+
+        if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
+        {
+            if ($Block['depth'] > 0)
+            {
+                $Block['depth'] --;
+            }
+            else
+            {
+                $Block['closed'] = true;
+            }
+        }
+
+        if (isset($Block['interrupted']))
+        {
+            $Block['markup'] .= "\n";
+
+            unset($Block['interrupted']);
+        }
+
+        $Block['markup'] .= "\n".$Line['body'];
+
+        return $Block;
+    }
+
+    #
+    # Reference
+
+    protected function blockReference($Line)
+    {
+        if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
+        {
+            $id = strtolower($matches[1]);
+
+            $Data = array(
+                'url' => $matches[2],
+                'title' => null,
+            );
+
+            if (isset($matches[3]))
+            {
+                $Data['title'] = $matches[3];
+            }
+
+            $this->DefinitionData['Reference'][$id] = $Data;
+
+            $Block = array(
+                'hidden' => true,
+            );
+
+            return $Block;
+        }
+    }
+
+    #
+    # Table
+
+    protected function blockTable($Line, array $Block = null)
+    {
+        if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
+        {
+            return;
+        }
+
+        if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
+        {
+            $alignments = array();
+
+            $divider = $Line['text'];
+
+            $divider = trim($divider);
+            $divider = trim($divider, '|');
+
+            $dividerCells = explode('|', $divider);
+
+            foreach ($dividerCells as $dividerCell)
+            {
+                $dividerCell = trim($dividerCell);
+
+                if ($dividerCell === '')
+                {
+                    continue;
+                }
+
+                $alignment = null;
+
+                if ($dividerCell[0] === ':')
+                {
+                    $alignment = 'left';
+                }
+
+                if (substr($dividerCell, - 1) === ':')
+                {
+                    $alignment = $alignment === 'left' ? 'center' : 'right';
+                }
+
+                $alignments []= $alignment;
+            }
+
+            # ~
+
+            $HeaderElements = array();
+
+            $header = $Block['element']['text'];
+
+            $header = trim($header);
+            $header = trim($header, '|');
+
+            $headerCells = explode('|', $header);
+
+            foreach ($headerCells as $index => $headerCell)
+            {
+                $headerCell = trim($headerCell);
+
+                $HeaderElement = array(
+                    'name' => 'th',
+                    'text' => $headerCell,
+                    'handler' => 'line',
+                );
+
+                if (isset($alignments[$index]))
+                {
+                    $alignment = $alignments[$index];
+
+                    $HeaderElement['attributes'] = array(
+                        'style' => 'text-align: '.$alignment.';',
+                    );
+                }
+
+                $HeaderElements []= $HeaderElement;
+            }
+
+            # ~
+
+            $Block = array(
+                'alignments' => $alignments,
+                'identified' => true,
+                'element' => array(
+                    'name' => 'table',
+                    'handler' => 'elements',
+                ),
+            );
+
+            $Block['element']['text'] []= array(
+                'name' => 'thead',
+                'handler' => 'elements',
+            );
+
+            $Block['element']['text'] []= array(
+                'name' => 'tbody',
+                'handler' => 'elements',
+                'text' => array(),
+            );
+
+            $Block['element']['text'][0]['text'] []= array(
+                'name' => 'tr',
+                'handler' => 'elements',
+                'text' => $HeaderElements,
+            );
+
+            return $Block;
+        }
+    }
+
+    protected function blockTableContinue($Line, array $Block)
+    {
+        if (isset($Block['interrupted']))
+        {
+            return;
+        }
+
+        if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
+        {
+            $Elements = array();
+
+            $row = $Line['text'];
+
+            $row = trim($row);
+            $row = trim($row, '|');
+
+            preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
+
+            foreach ($matches[0] as $index => $cell)
+            {
+                $cell = trim($cell);
+
+                $Element = array(
+                    'name' => 'td',
+                    'handler' => 'line',
+                    'text' => $cell,
+                );
+
+                if (isset($Block['alignments'][$index]))
+                {
+                    $Element['attributes'] = array(
+                        'style' => 'text-align: '.$Block['alignments'][$index].';',
+                    );
+                }
+
+                $Elements []= $Element;
+            }
+
+            $Element = array(
+                'name' => 'tr',
+                'handler' => 'elements',
+                'text' => $Elements,
+            );
+
+            $Block['element']['text'][1]['text'] []= $Element;
+
+            return $Block;
+        }
+    }
+
+    #
+    # ~
+    #
+
+    protected function paragraph($Line)
+    {
+        $Block = array(
+            'element' => array(
+                'name' => 'p',
+                'text' => $Line['text'],
+                'handler' => 'line',
+            ),
+        );
+
+        return $Block;
+    }
+
+    #
+    # Inline Elements
+    #
+
+    protected $InlineTypes = array(
+        '"' => array('SpecialCharacter'),
+        '!' => array('Image'),
+        '&' => array('SpecialCharacter'),
+        '*' => array('Emphasis'),
+        ':' => array('Url'),
+        '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
+        '>' => array('SpecialCharacter'),
+        '[' => array('Link'),
+        '_' => array('Emphasis'),
+        '`' => array('Code'),
+        '~' => array('Strikethrough'),
+        '\\' => array('EscapeSequence'),
+    );
+
+    # ~
+
+    protected $inlineMarkerList = '!"*_&[:<>`~\\';
+
+    #
+    # ~
+    #
+
+    public function line($text, $nonNestables=array())
+    {
+        $markup = '';
+
+        # $excerpt is based on the first occurrence of a marker
+
+        while ($excerpt = strpbrk($text, $this->inlineMarkerList))
+        {
+            $marker = $excerpt[0];
+
+            $markerPosition = strpos($text, $marker);
+
+            $Excerpt = array('text' => $excerpt, 'context' => $text);
+
+            foreach ($this->InlineTypes[$marker] as $inlineType)
+            {
+                # check to see if the current inline type is nestable in the current context
+
+                if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables))
+                {
+                    continue;
+                }
+
+                $Inline = $this->{'inline'.$inlineType}($Excerpt);
+
+                if ( ! isset($Inline))
+                {
+                    continue;
+                }
+
+                # makes sure that the inline belongs to "our" marker
+
+                if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
+                {
+                    continue;
+                }
+
+                # sets a default inline position
+
+                if ( ! isset($Inline['position']))
+                {
+                    $Inline['position'] = $markerPosition;
+                }
+
+                # cause the new element to 'inherit' our non nestables
+
+                foreach ($nonNestables as $non_nestable)
+                {
+                    $Inline['element']['nonNestables'][] = $non_nestable;
+                }
+
+                # the text that comes before the inline
+                $unmarkedText = substr($text, 0, $Inline['position']);
+
+                # compile the unmarked text
+                $markup .= $this->unmarkedText($unmarkedText);
+
+                # compile the inline
+                $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
+
+                # remove the examined text
+                $text = substr($text, $Inline['position'] + $Inline['extent']);
+
+                continue 2;
+            }
+
+            # the marker does not belong to an inline
+
+            $unmarkedText = substr($text, 0, $markerPosition + 1);
+
+            $markup .= $this->unmarkedText($unmarkedText);
+
+            $text = substr($text, $markerPosition + 1);
+        }
+
+        $markup .= $this->unmarkedText($text);
+
+        return $markup;
+    }
+
+    #
+    # ~
+    #
+
+    protected function inlineCode($Excerpt)
+    {
+        $marker = $Excerpt['text'][0];
+
+        if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
+        {
+            $text = $matches[2];
+            $text = preg_replace("/[ ]*\n/", ' ', $text);
+
+            return array(
+                'extent' => strlen($matches[0]),
+                'element' => array(
+                    'name' => 'code',
+                    'text' => $text,
+                ),
+            );
+        }
+    }
+
+    protected function inlineEmailTag($Excerpt)
+    {
+        if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
+        {
+            $url = $matches[1];
+
+            if ( ! isset($matches[2]))
+            {
+                $url = 'mailto:' . $url;
+            }
+
+            return array(
+                'extent' => strlen($matches[0]),
+                'element' => array(
+                    'name' => 'a',
+                    'text' => $matches[1],
+                    'attributes' => array(
+                        'href' => $url,
+                    ),
+                ),
+            );
+        }
+    }
+
+    protected function inlineEmphasis($Excerpt)
+    {
+        if ( ! isset($Excerpt['text'][1]))
+        {
+            return;
+        }
+
+        $marker = $Excerpt['text'][0];
+
+        if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
+        {
+            $emphasis = 'strong';
+        }
+        elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
+        {
+            $emphasis = 'em';
+        }
+        else
+        {
+            return;
+        }
+
+        return array(
+            'extent' => strlen($matches[0]),
+            'element' => array(
+                'name' => $emphasis,
+                'handler' => 'line',
+                'text' => $matches[1],
+            ),
+        );
+    }
+
+    protected function inlineEscapeSequence($Excerpt)
+    {
+        if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
+        {
+            return array(
+                'markup' => $Excerpt['text'][1],
+                'extent' => 2,
+            );
+        }
+    }
+
+    protected function inlineImage($Excerpt)
+    {
+        if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
+        {
+            return;
+        }
+
+        $Excerpt['text']= substr($Excerpt['text'], 1);
+
+        $Link = $this->inlineLink($Excerpt);
+
+        if ($Link === null)
+        {
+            return;
+        }
+
+        $Inline = array(
+            'extent' => $Link['extent'] + 1,
+            'element' => array(
+                'name' => 'img',
+                'attributes' => array(
+                    'src' => $Link['element']['attributes']['href'],
+                    'alt' => $Link['element']['text'],
+                ),
+            ),
+        );
+
+        $Inline['element']['attributes'] += $Link['element']['attributes'];
+
+        unset($Inline['element']['attributes']['href']);
+
+        return $Inline;
+    }
+
+    protected function inlineLink($Excerpt)
+    {
+        $Element = array(
+            'name' => 'a',
+            'handler' => 'line',
+            'nonNestables' => array('Url', 'Link'),
+            'text' => null,
+            'attributes' => array(
+                'href' => null,
+                'title' => null,
+            ),
+        );
+
+        $extent = 0;
+
+        $remainder = $Excerpt['text'];
+
+        if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
+        {
+            $Element['text'] = $matches[1];
+
+            $extent += strlen($matches[0]);
+
+            $remainder = substr($remainder, $extent);
+        }
+        else
+        {
+            return;
+        }
+
+        if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
+        {
+            $Element['attributes']['href'] = $matches[1];
+
+            if (isset($matches[2]))
+            {
+                $Element['attributes']['title'] = substr($matches[2], 1, - 1);
+            }
+
+            $extent += strlen($matches[0]);
+        }
+        else
+        {
+            if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
+            {
+                $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
+                $definition = strtolower($definition);
+
+                $extent += strlen($matches[0]);
+            }
+            else
+            {
+                $definition = strtolower($Element['text']);
+            }
+
+            if ( ! isset($this->DefinitionData['Reference'][$definition]))
+            {
+                return;
+            }
+
+            $Definition = $this->DefinitionData['Reference'][$definition];
+
+            $Element['attributes']['href'] = $Definition['url'];
+            $Element['attributes']['title'] = $Definition['title'];
+        }
+
+        return array(
+            'extent' => $extent,
+            'element' => $Element,
+        );
+    }
+
+    protected function inlineMarkup($Excerpt)
+    {
+        if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
+        {
+            return;
+        }
+
+        if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches))
+        {
+            return array(
+                'markup' => $matches[0],
+                'extent' => strlen($matches[0]),
+            );
+        }
+
+        if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
+        {
+            return array(
+                'markup' => $matches[0],
+                'extent' => strlen($matches[0]),
+            );
+        }
+
+        if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
+        {
+            return array(
+                'markup' => $matches[0],
+                'extent' => strlen($matches[0]),
+            );
+        }
+    }
+
+    protected function inlineSpecialCharacter($Excerpt)
+    {
+        if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
+        {
+            return array(
+                'markup' => '&amp;',
+                'extent' => 1,
+            );
+        }
+
+        $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
+
+        if (isset($SpecialCharacter[$Excerpt['text'][0]]))
+        {
+            return array(
+                'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
+                'extent' => 1,
+            );
+        }
+    }
+
+    protected function inlineStrikethrough($Excerpt)
+    {
+        if ( ! isset($Excerpt['text'][1]))
+        {
+            return;
+        }
+
+        if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
+        {
+            return array(
+                'extent' => strlen($matches[0]),
+                'element' => array(
+                    'name' => 'del',
+                    'text' => $matches[1],
+                    'handler' => 'line',
+                ),
+            );
+        }
+    }
+
+    protected function inlineUrl($Excerpt)
+    {
+        if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
+        {
+            return;
+        }
+
+        if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
+        {
+            $url = $matches[0][0];
+
+            $Inline = array(
+                'extent' => strlen($matches[0][0]),
+                'position' => $matches[0][1],
+                'element' => array(
+                    'name' => 'a',
+                    'text' => $url,
+                    'attributes' => array(
+                        'href' => $url,
+                    ),
+                ),
+            );
+
+            return $Inline;
+        }
+    }
+
+    protected function inlineUrlTag($Excerpt)
+    {
+        if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
+        {
+            $url = $matches[1];
+
+            return array(
+                'extent' => strlen($matches[0]),
+                'element' => array(
+                    'name' => 'a',
+                    'text' => $url,
+                    'attributes' => array(
+                        'href' => $url,
+                    ),
+                ),
+            );
+        }
+    }
+
+    # ~
+
+    protected function unmarkedText($text)
+    {
+        if ($this->breaksEnabled)
+        {
+            $text = preg_replace('/[ ]*\n/', "<br />\n", $text);
+        }
+        else
+        {
+            $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
+            $text = str_replace(" \n", "\n", $text);
+        }
+
+        return $text;
+    }
+
+    #
+    # Handlers
+    #
+
+    protected function element(array $Element)
+    {
+        if ($this->safeMode)
+        {
+            $Element = $this->sanitiseElement($Element);
+        }
+
+        $markup = '<'.$Element['name'];
+
+        if (isset($Element['attributes']))
+        {
+            foreach ($Element['attributes'] as $name => $value)
+            {
+                if ($value === null)
+                {
+                    continue;
+                }
+
+                $markup .= ' '.$name.'="'.self::escape($value).'"';
+            }
+        }
+
+        if (isset($Element['text']))
+        {
+            $markup .= '>';
+
+            if (!isset($Element['nonNestables'])) 
+            {
+                $Element['nonNestables'] = array();
+            }
+
+            if (isset($Element['handler']))
+            {
+                $markup .= $this->{$Element['handler']}($Element['text'], $Element['nonNestables']);
+            }
+            else
+            {
+                $markup .= self::escape($Element['text'], true);
+            }
+
+            $markup .= '</'.$Element['name'].'>';
+        }
+        else
+        {
+            $markup .= ' />';
+        }
+
+        return $markup;
+    }
+
+    protected function elements(array $Elements)
+    {
+        $markup = '';
+
+        foreach ($Elements as $Element)
+        {
+            $markup .= "\n" . $this->element($Element);
+        }
+
+        $markup .= "\n";
+
+        return $markup;
+    }
+
+    # ~
+
+    protected function li($lines)
+    {
+        $markup = $this->lines($lines);
+
+        $trimmedMarkup = trim($markup);
+
+        if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
+        {
+            $markup = $trimmedMarkup;
+            $markup = substr($markup, 3);
+
+            $position = strpos($markup, "</p>");
+
+            $markup = substr_replace($markup, '', $position, 4);
+        }
+
+        return $markup;
+    }
+
+    #
+    # Deprecated Methods
+    #
+
+    function parse($text)
+    {
+        $markup = $this->text($text);
+
+        return $markup;
+    }
+
+    protected function sanitiseElement(array $Element)
+    {
+        static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
+        static $safeUrlNameToAtt  = array(
+            'a'   => 'href',
+            'img' => 'src',
+        );
+
+        if (isset($safeUrlNameToAtt[$Element['name']]))
+        {
+            $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
+        }
+
+        if ( ! empty($Element['attributes']))
+        {
+            foreach ($Element['attributes'] as $att => $val)
+            {
+                # filter out badly parsed attribute
+                if ( ! preg_match($goodAttribute, $att))
+                {
+                    unset($Element['attributes'][$att]);
+                }
+                # dump onevent attribute
+                elseif (self::striAtStart($att, 'on'))
+                {
+                    unset($Element['attributes'][$att]);
+                }
+            }
+        }
+
+        return $Element;
+    }
+
+    protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
+    {
+        foreach ($this->safeLinksWhitelist as $scheme)
+        {
+            if (self::striAtStart($Element['attributes'][$attribute], $scheme))
+            {
+                return $Element;
+            }
+        }
+
+        $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
+
+        return $Element;
+    }
+
+    #
+    # Static Methods
+    #
+
+    protected static function escape($text, $allowQuotes = false)
+    {
+        return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
+    }
+
+    protected static function striAtStart($string, $needle)
+    {
+        $len = strlen($needle);
+
+        if ($len > strlen($string))
+        {
+            return false;
+        }
+        else
+        {
+            return strtolower(substr($string, 0, $len)) === strtolower($needle);
+        }
+    }
+
+    static function instance($name = 'default')
+    {
+        if (isset(self::$instances[$name]))
+        {
+            return self::$instances[$name];
+        }
+
+        $instance = new static();
+
+        self::$instances[$name] = $instance;
+
+        return $instance;
+    }
+
+    private static $instances = array();
+
+    #
+    # Fields
+    #
+
+    protected $DefinitionData;
+
+    #
+    # Read-Only
+
+    protected $specialCharacters = array(
+        '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
+    );
+
+    protected $StrongRegex = array(
+        '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
+        '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
+    );
+
+    protected $EmRegex = array(
+        '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
+        '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
+    );
+
+    protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
+
+    protected $voidElements = array(
+        'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
+    );
+
+    protected $textLevelElements = array(
+        'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
+        'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
+        'i', 'rp', 'del', 'code',          'strike', 'marquee',
+        'q', 'rt', 'ins', 'font',          'strong',
+        's', 'tt', 'kbd', 'mark',
+        'u', 'xm', 'sub', 'nobr',
+                   'sup', 'ruby',
+                   'var', 'span',
+                   'wbr', 'time',
+    );
+}

+ 86 - 0
common/helpers/parsedown/README.md

@@ -0,0 +1,86 @@
+> I also make [Caret](https://caret.io?ref=parsedown) - a Markdown editor for Mac and PC.
+
+## Parsedown
+
+[![Build Status](https://img.shields.io/travis/erusev/parsedown/master.svg?style=flat-square)](https://travis-ci.org/erusev/parsedown)
+<!--[![Total Downloads](http://img.shields.io/packagist/dt/erusev/parsedown.svg?style=flat-square)](https://packagist.org/packages/erusev/parsedown)-->
+
+Better Markdown Parser in PHP
+
+[Demo](http://parsedown.org/demo) |
+[Benchmarks](http://parsedown.org/speed) |
+[Tests](http://parsedown.org/tests/) |
+[Documentation](https://github.com/erusev/parsedown/wiki/)
+
+### Features
+
+* One File
+* No Dependencies
+* Super Fast
+* Extensible
+* [GitHub flavored](https://help.github.com/articles/github-flavored-markdown)
+* Tested in 5.3 to 7.1 and in HHVM
+* [Markdown Extra extension](https://github.com/erusev/parsedown-extra)
+
+### Installation
+
+Include `Parsedown.php` or install [the composer package](https://packagist.org/packages/erusev/parsedown).
+
+### Example
+
+``` php
+$Parsedown = new Parsedown();
+
+echo $Parsedown->text('Hello _Parsedown_!'); # prints: <p>Hello <em>Parsedown</em>!</p>
+```
+
+More examples in [the wiki](https://github.com/erusev/parsedown/wiki/) and in [this video tutorial](http://youtu.be/wYZBY8DEikI).
+
+### Security
+
+Parsedown is capable of escaping user-input within the HTML that it generates. Additionally Parsedown will apply sanitisation to additional scripting vectors (such as scripting link destinations) that are introduced by the markdown syntax itself.
+
+To tell Parsedown that it is processing untrusted user-input, use the following:
+```php
+$parsedown = new Parsedown;
+$parsedown->setSafeMode(true);
+```
+
+If instead, you wish to allow HTML within untrusted user-input, but still want output to be free from XSS it is recommended that you make use of a HTML sanitiser that allows HTML tags to be whitelisted, like [HTML Purifier](http://htmlpurifier.org/).
+
+In both cases you should strongly consider employing defence-in-depth measures, like [deploying a Content-Security-Policy](https://scotthelme.co.uk/content-security-policy-an-introduction/) (a browser security feature) so that your page is likely to be safe even if an attacker finds a vulnerability in one of the first lines of defence above.
+
+#### Security of Parsedown Extensions
+
+Safe mode does not necessarily yield safe results when using extensions to Parsedown. Extensions should be evaluated on their own to determine their specific safety against XSS.
+
+### Escaping HTML
+> ⚠️  **WARNING:** This method isn't safe from XSS!
+
+If you wish to escape HTML **in trusted input**, you can use the following:
+```php
+$parsedown = new Parsedown;
+$parsedown->setMarkupEscaped(true);
+```
+
+Beware that this still allows users to insert unsafe scripting vectors, such as links like `[xss](javascript:alert%281%29)`.
+
+### Questions
+
+**How does Parsedown work?**
+
+It tries to read Markdown like a human. First, it looks at the lines. It’s interested in how the lines start. This helps it recognise blocks. It knows, for example, that if a line starts with a `-` then perhaps it belongs to a list. Once it recognises the blocks, it continues to the content. As it reads, it watches out for special characters. This helps it recognise inline elements (or inlines).
+
+We call this approach "line based". We believe that Parsedown is the first Markdown parser to use it. Since the release of Parsedown, other developers have used the same approach to develop other Markdown parsers in PHP and in other languages.
+
+**Is it compliant with CommonMark?**
+
+It passes most of the CommonMark tests. Most of the tests that don't pass deal with cases that are quite uncommon. Still, as CommonMark matures, compliance should improve.
+
+**Who uses it?**
+
+[Laravel Framework](https://laravel.com/), [Bolt CMS](http://bolt.cm/), [Grav CMS](http://getgrav.org/), [Herbie CMS](http://www.getherbie.org/), [Kirby CMS](http://getkirby.com/), [October CMS](http://octobercms.com/), [Pico CMS](http://picocms.org), [Statamic CMS](http://www.statamic.com/), [phpDocumentor](http://www.phpdoc.org/), [RaspberryPi.org](http://www.raspberrypi.org/), [Symfony demo](https://github.com/symfony/symfony-demo) and [more](https://packagist.org/packages/erusev/parsedown/dependents).
+
+**How can I help?**
+
+Use it, star it, share it and if you feel generous, [donate](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=528P3NZQMP8N2).

+ 33 - 0
common/helpers/parsedown/composer.json

@@ -0,0 +1,33 @@
+{
+    "name": "erusev/parsedown",
+    "description": "Parser for Markdown.",
+    "keywords": ["markdown", "parser"],
+    "homepage": "http://parsedown.org",
+    "type": "library",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Emanuil Rusev",
+            "email": "hello@erusev.com",
+            "homepage": "http://erusev.com"
+        }
+    ],
+    "require": {
+        "php": ">=5.3.0",
+        "ext-mbstring": "*"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "^4.8.35"
+    },
+    "autoload": {
+        "psr-0": {"Parsedown": ""}
+    },
+    "autoload-dev": {
+        "psr-0": {
+            "TestParsedown": "test/",
+            "ParsedownTest": "test/",
+            "CommonMarkTest": "test/",
+            "CommonMarkTestWeak": "test/"
+        }
+    }
+}

+ 40 - 0
common/helpers/snowflake/PageSnowFake.php

@@ -0,0 +1,40 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: congli
+ * Date: 2020/2/28
+ * Time: 1:42 PM
+ */
+namespace common\helpers\snowflake;
+
+use yii\base\BaseObject;
+use yii\base\StaticInstanceTrait;
+
+class PageSnowFake extends BaseObject
+{
+    use StaticInstanceTrait;
+
+    public $snowFlakeService;
+
+    public function init()
+    {
+        parent::init();
+
+        $this->snowFlakeService = new \Godruoyi\Snowflake\Snowflake($expiresIn = \Yii::$app->params['pageSnowFake']['dataCenterId'], $expiresIn = \Yii::$app->params['pageSnowFake']['workerId']);
+    }
+
+    /**
+     * 获取service
+     * @return mixed
+     */
+    public function getService() {
+        return $this->snowFlakeService;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function generateId() {
+        return $this->snowFlakeService->id();
+    }
+}

+ 40 - 0
common/helpers/snowflake/SnowFake.php

@@ -0,0 +1,40 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: congli
+ * Date: 2020/2/28
+ * Time: 1:42 PM
+ */
+namespace common\helpers\snowflake;
+
+use yii\base\BaseObject;
+use yii\base\StaticInstanceTrait;
+
+class SnowFake extends BaseObject
+{
+    use StaticInstanceTrait;
+
+    public $snowFlakeService;
+
+    public function init()
+    {
+        parent::init();
+
+        $this->snowFlakeService = new \Godruoyi\Snowflake\Snowflake($expiresIn = \Yii::$app->params['snowFake']['dataCenterId'], $expiresIn = \Yii::$app->params['snowFake']['workerId']);
+    }
+
+    /**
+     * 获取service
+     * @return mixed
+     */
+    public function getService() {
+        return $this->snowFlakeService;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function generateId() {
+        return $this->snowFlakeService->id();
+    }
+}

+ 90 - 0
common/libs/LoginIpChecker.php

@@ -0,0 +1,90 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon
+ * Date: 2018/8/25
+ * Time: 上午10:49
+ */
+
+namespace common\libs;
+
+class LoginIpChecker {
+    private $_ip;
+    private $_bindIp;
+    /**
+     * LoginIpChecker constructor.
+     * @param $ip
+     * @param $this->_bindIp
+     */
+    public function __construct($ip, $bindIp) {
+        $this->_ip = $ip;
+        $this->_bindIp = $bindIp;
+    }
+
+    /**
+     * 掩码模式验证
+     * @return bool
+     */
+    private function _mask(){
+        $ip = (double) (sprintf("%u", ip2long($this->_ip)));
+        $s = explode('/', $this->_bindIp);
+        $start = (double) (sprintf("%u", ip2long($s[0])));
+        $len = pow(2, 32 - $s[1]);
+        $end = $start + $len - 1;
+        if ($ip >= $start && $ip <= $end) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 验证ip段
+     * @return bool
+     */
+    private function _range(){
+        if(strpos($this->_bindIp, '-') !== false){
+            list($startIp, $endIp) = explode('-', $this->_bindIp);
+            if(strpos($endIp, '.') === false){  //127.0.0.2-254
+                $start = explode('.', $startIp);
+                $endIp = $start[0] . '.' . $start[1] . '.' . $start[2] . '.' . $endIp;
+            }
+
+            $startIp = $this->_getIplong($startIp);
+            $endIp = $this->_getIplong($endIp);
+            $ip = $this->_getIplong($this->_ip);
+
+            if($ip >= $startIp && $ip <=$endIp){
+                return true;
+            }
+            return false;
+        }else{ //单个ip 127.0.0.1
+            if($this->_bindIp == $this->_ip){
+                return true;
+            }
+            return false;
+        }
+    }
+    /**
+     * bindec(decbin(ip2long('这里填ip地址')));
+     * ip2long();的意思是将IP地址转换成整型 ,
+     * 之所以要decbin和bindec一下是为了防止IP数值过大int型存储不了出现负数。
+     * @param $ip
+     * @return float|int
+     */
+    private function _getIplong($ip){
+        return bindec(decbin(ip2long($ip)));
+    }
+
+    /**
+     * 验证
+     * @return bool
+     */
+    public function validate(){
+        if(strpos($this->_bindIp, '/') !== false){  //掩码模式  127.0.0.1/24
+            $result = $this->_mask();
+        }else{ // ip段10.0.0.1-254 OR 10.0.0.1-10.0.0.254
+            $result = $this->_range();
+        }
+        return $result;
+    }
+}

+ 115 - 0
common/libs/api/sms/SmsApi.php

@@ -0,0 +1,115 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: leo
+ * Date: 2018/5/22
+ * Time: 上午9:39
+ */
+
+namespace common\libs\api\sms;
+
+use common\helpers\Cache;
+use common\helpers\ocr\baidu\AipOcr;
+use common\helpers\Tool;
+use common\libs\api\sms\meilian\SmsSend;
+use yii\base\BaseObject;
+use yii\base\StaticInstanceTrait;
+
+class SmsApi extends BaseObject {
+    use StaticInstanceTrait;
+
+    const TYPE_MEILIAN = 'meilian';
+    const MEILIAN_CONFIGS = [
+        'username' => 'string',
+        'password' => 'string',
+        'apikey' => 'string',
+    ];
+
+    private $_type;
+    private $_typeClass;
+    private $_smsApiObj;
+    private $_errorCode;
+    private $_error = [];
+
+    /**
+     * 初始化
+     * @throws \yii\base\InvalidConfigException
+     */
+    public function init() {
+        parent::init();
+        // 获取当前使用的ocr识别接口
+        $smsApiConfig = Cache::getSmsApiConfig();
+        $this->_type = $smsApiConfig['API_NAME'];
+        $this->_typeClass = null;
+        $apiConfig = [];
+        if ($smsApiConfig['API_NAME'] == self::TYPE_MEILIAN) {
+            $this->_typeClass = SmsSend::class;
+            foreach ($smsApiConfig['CONFIG'] as $value) {
+                $apiConfig[] = $value;
+            }
+        }
+        $this->_errorCode = require \Yii::getAlias('@common/libs/api/sms/meilian/') . 'errorCode.php';
+        // 创建短信接口对象
+        $this->_smsApiObj = \Yii::createObject($this->_typeClass, $apiConfig);
+    }
+
+    /**
+     * 发送短信
+     * @param array $params
+     * [
+     *      'mobiles' => '13903351111,13903352222',
+     *      'content' => '测试短息,您好',
+     * ]
+     * @return bool
+     */
+    public function send(array $params = []) {
+        if(!$this->_smsApiObj->send($params)){
+            $this->_error[] = $this->_smsApiObj->getError();
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 异步协程发送
+     * @param array $params
+     */
+    public function goSend(array $params = []) {
+        go(function() use($params){
+            $this->send($params);
+        });
+    }
+
+    /**
+     * 获取错误
+     * @return mixed
+     */
+    public function getError(){
+        return $this->_error;
+    }
+
+    /**
+     * 清空错误
+     */
+    public function clearError(){
+        $this->_error = [];
+    }
+
+    /**
+     * 获取api设置的字段
+     * @param $apiName
+     * @return array
+     */
+    public static function apiConfigs($apiName) {
+        $allApiModelData = \common\models\SmsApi::getAllData();
+        $result = [];
+        if ($apiName == self::TYPE_MEILIAN) {
+            $result = [
+                'username' => ['TITLE' => 'username', 'CONFIG_NAME' => 'username', 'INPUT_TYPE' => 1, 'OPTIONS' => null, 'VALUE' => $allApiModelData[self::TYPE_MEILIAN]['CONFIG']['username'] ?? null],
+                'password' => ['TITLE' => 'password', 'CONFIG_NAME' => 'password', 'INPUT_TYPE' => 1, 'OPTIONS' => null, 'VALUE' => $allApiModelData[self::TYPE_MEILIAN]['CONFIG']['password'] ?? null],
+                'apikey' => ['TITLE' => 'apikey', 'CONFIG_NAME' => 'apikey', 'INPUT_TYPE' => 1, 'OPTIONS' => null, 'VALUE' => $allApiModelData[self::TYPE_MEILIAN]['CONFIG']['apikey'] ?? null],
+            ];
+        }
+        return $result;
+    }
+}

+ 79 - 0
common/libs/api/sms/meilian/SmsSend.php

@@ -0,0 +1,79 @@
+<?php
+
+
+namespace common\libs\api\sms\meilian;
+
+use yii\helpers\Json;
+use yii\httpclient\Client;
+
+class SmsSend
+{
+    private $_username;
+    private $_password;
+    private $_apikey;
+
+    private $_url='https://m.5c.com.cn/api/send/index.php';
+    private $_encode='UTF-8';
+
+    private $_client;
+    private $_response;
+    private $_error = [];
+
+    private $_errorCode;
+
+    public function __construct($username, $password, $apikey) {
+        $this->_username = $username;
+        $this->_password = $password;
+        $this->_apikey = $apikey;
+        $this->_errorCode = require \Yii::getAlias('@common/libs/api/sms/meilian/') . 'errorCode.php';
+    }
+
+    public function getError(){
+        return $this->_error;
+    }
+
+    public function send(array $params) {
+        $this->_error = null;
+        if($this->_client === null) {
+            $this->_client = new Client();
+        }
+        $data = [
+            'type' => 'send',
+            'apikey' => $this->_apikey,
+            'username' => $this->_username,
+            'password_md5' => md5($this->_password),
+            'encode' => $this->_encode,
+            'mobile' => $params['mobiles'],
+            'content' => urlencode($params['content']),
+        ];
+        $sendData = [
+            'format' => 'json',
+            'data' => Json::encode($data),
+        ];
+        $this->_response = $this->_client->createRequest()
+            ->setMethod('post')
+            ->setUrl($this->_url)
+            ->setData($sendData)
+            ->send();
+
+        if(!$this->_response->isOk){
+            $this->_error[] = '网络错误,短信发送失败';
+            return false;
+        }
+        if($this->_response->data['status'] == 'error'){
+            $errKey = $this->_response->data['msg'];
+            if(isset($this->_errorCode[$errKey])){
+                $this->_error[] = $this->_errorCode[$errKey];
+            } else {
+                $this->_error[] = $errKey;
+            }
+            return false;
+        }
+        unset($data);
+        return true;
+    }
+
+
+
+
+}

+ 19 - 0
common/libs/api/sms/meilian/errorCode.php

@@ -0,0 +1,19 @@
+<?php
+/**
+ * 短信返回错误码
+ */
+return [
+    'msgid' => '提交失败',
+    'Missing username' => '接口缺少用户名',
+    'Missing password' => '接口缺少密码',
+    'Missing apikey' => '接口缺少APIKEY',
+    'Missing recipient' => '手机号码为空',
+    'Missing message content' => '短信内容为空或编码不正确',
+    'Account is blocked' => '账户已被禁用',
+    'Unrecognized encoding' => '编码未能识别',
+    'APIKEY or password error' => 'APIKEY或密码错误',
+    'Unauthorized IP address' => 'IP地址未授权',
+    'Account balance is insufficient' => '短信接口余额不足',
+    'Throughput Rate Exceeded' => '发送频率受限',
+    'Invalid md5 password length' => '密码长度非法',
+];

+ 461 - 0
common/libs/dataList/DataList.php

@@ -0,0 +1,461 @@
+<?php
+namespace common\libs\dataList;
+
+use backendApi\modules\v1\models\Admin;
+use backendApi\modules\v1\models\AdminRole;
+use common\components\Model;
+use common\helpers\Tool;
+use yii\base\Exception;
+
+/**
+ * Class DataList
+ * @package common\libs\dataList
+ * @method getListName
+ * 列表名称
+ * @method dataHandle
+ * 需要子类继承的方法实现数据的获取
+ * @method getColumn
+ * 需要子类继承的方法实现column的获取
+ * 传null是指:这种传输方式主要是用于索引,因为过滤后的字段可能没有这种ID,但是一些功能的操作还需要用这种ID去关联,例如前台会员列表中的勾选批量状态管理,这里需要的就是USER_ID
+ * 例如:
+ * $this->columns = [
+ *      'USER_ID' => null,
+ *      'USER_NAME' => [
+ *          'header' => '会员编号',
+ *          'headerOther' => ['width' => '150'],
+ *      ],
+ *      'IS_DEC' => [
+ *          'header' => '是否报单中心',
+ *          'value' => function($row) {
+ *              return (new YesNo([
+ *                  'value' => $row['IS_DEC'],
+ *              ]))->result();
+ *          },
+ *          'show' => function($row) {
+ *              return '<div>列表展示的值结果</div>';
+ *          }
+ *          'headerOther' => function($row) {
+ *              return [
+ *                  'width' => '120',
+ *                  'tag'=>['type'=>$row['IS_DEC'] ? 'success' : 'info', 'size' => 'small']
+ *              ];
+ *          },
+ *      ],
+ * ]
+ * @method getFilterTypes
+ */
+class DataList extends Model
+{
+    /**
+     * 导出
+     * @var bool
+     */
+    public $isExport = false;
+    /**
+     * 列表当前页码
+     * @var null
+     */
+    protected $page = null;
+    /**
+     * 列表每页数量
+     * @var int
+     */
+    protected $pageSize = 0;
+    /**
+     * 列表查询条件
+     * @var string
+     */
+    protected $condition = '';
+    /**
+     * 列表查询条件参数
+     * @var array
+     */
+    protected $params = [];
+    /**
+     * 列表查询其他参数
+     * @var array
+     */
+    protected $others = [];
+    /**
+     * 列表数据
+     * @var
+     */
+    protected $listData;
+    /**
+     * 列表字段限制条件
+     * @var
+     */
+    protected $columns;
+    /**
+     * 用于筛选的字段
+     * @var
+     */
+    protected $filterTypes;
+    /**
+     * 主要是给导出时候的表头使用
+     * @var array
+     */
+    protected $headers = [];
+    /**
+     * 主要是给页面输出是展示用的就是columns去掉value的结果,让vue方便循环表头和表头样式
+     * @var array
+     */
+    protected $columnsShow = [];
+    /**
+     * 主要是给前端的筛选类型
+     * @var array
+     */
+    protected $filterTypesShow = [];
+
+    /**
+     * 获取列表
+     * @param null $params
+     * @return mixed
+     * @throws Exception
+     */
+    public function getList($params = null){
+        if($params !== null) {
+            $this->prepare($params);
+        }
+        // 获取数据
+        $this->dataHandle();
+        // 获取列信息
+        $this->getColumn();
+        // 获取筛选字段的信息,用于前端筛选
+        if(!$this->isExport){
+            $this->getFilterTypes();
+        }
+        // 利用权限处理掉不需要的字段(只需要从$this->columns中去掉就可以)
+        $userId = isset($params['userId']) && $params['userId'] ? $params['userId'] : null;
+        $this->permissionColumn($userId);
+        // 处理数据
+        $this->_formatDataByColumn();
+        return $this->listData;
+    }
+
+    /**
+     * 获取导出用到的列标题
+     * @param $userId
+     * @return array
+     * @throws Exception
+     */
+    public function getExportHeaders($userId){
+        if(!$this->headers && $this->isExport){
+            $this->getColumn();
+            // 利用权限处理掉不需要的字段(只需要从$this->columns中去掉就可以)
+            $this->permissionColumn($userId);
+            foreach($this->columns as $hk => $column){
+                if(is_string($column)){
+                    $this->headers[] = Tool::textConvert($column);
+                } elseif(is_array($column)) {
+                    if(!isset($column['header'])){
+                        throw new Exception('column数组中必须存在header');
+                    }
+                    if(isset($column['hidden'])&&$column['hidden']) continue;
+                    if(is_callable($column['header'])){
+                        $header = $column['header']([]);
+                    } else {
+                        $header = $column['header'];
+                    }
+                    $this->headers[] = Tool::textConvert($header);
+                } elseif (is_callable($column)) {
+                    $this->headers[] = Tool::textConvert($column([]));
+                }
+            }
+        }
+        return $this->headers;
+    }
+
+    /**
+     * 获取全部列及列名,主要用于后台权限的筛选
+     * @return array
+     * @throws Exception
+     */
+    public function getAllColumnHeaderName(){
+        $result = [];
+        foreach($this->getColumn() as $hk => $column){
+            if(is_string($column)){
+                $result[] = [
+                    'header' => $column,
+                    'index' => $hk,
+                ];
+            } elseif(is_array($column)) {
+                if(!isset($column['header'])){
+                    throw new Exception('column数组中必须存在header');
+                }
+                //if(isset($column['hidden'])&&$column['hidden']) continue;
+                if(is_callable($column['header'])){
+                    $header = $column['header']([]);
+                } else {
+                    $header = $column['header'];
+                }
+                $result[] = [
+                    'header' => $header,
+                    'index' => $hk,
+                ];
+            } elseif (is_callable($column)) {
+                $result[] = [
+                    'header' => $column([]),
+                    'index' => $hk,
+                ];
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * 整理参数
+     * @param $params
+     */
+    protected function prepare($params){
+        $default = [
+            'condition' => '',
+            'params' => [],
+            'others' => [],
+            'page' => null,
+            'pageSize' => 0,
+        ];
+        $params = Tool::deepParse($params ,$default);
+        $this->condition = $params['condition'];
+        $this->params = $params['params'];
+        $this->others = $params['others'];
+        $this->page = $params['page'];
+        $this->pageSize = $params['pageSize'];
+    }
+
+    /**
+     * 格式化输出的数据
+     * @throws Exception
+     */
+    private function _formatDataByColumn(){
+        $data = [];
+        foreach($this->listData['list'] as $key=>$row){
+            foreach($this->columns as $hk => $column){
+                if(is_string($column)){
+                    if($key == 0){
+                        $this->headers[] = $this->isExport ? Tool::textConvert($column) : $column;
+                        $this->columnsShow[] = [
+                            'header' => $column,
+                            'index' => $hk,
+                            'other' => [],
+                        ];
+                        if(isset($this->filterTypes[$hk])){
+                            $this->filterTypesShow[$hk] = $this->filterTypes[$hk];
+                        }
+                    }
+                    if($this->isExport){
+                        $data[$key][$hk] = $row[$hk];
+                    } else {
+                        $data[$key][$hk] = [
+                            'value' => $row[$hk],
+                            'other' => [],
+                        ];
+                    }
+                } elseif(is_array($column)) {
+                    if(!isset($column['header'])){
+                        throw new Exception('column数组中必须存在header');
+                    }
+                    if($key == 0){
+                        if(!isset($column['hidden']) || !$column['hidden']){
+                            if(is_callable($column['header'])){
+                                $header = $column['header']($row);
+                            } else {
+                                $header = $column['header'];
+                            }
+                            if(isset($column['headerOther'])){
+                                if(is_callable($column['headerOther'])){
+                                    $headerOther = $column['headerOther']($row);
+                                } else {
+                                    $headerOther = $column['headerOther'];
+                                }
+                            } else {
+                                $headerOther = [];
+                            }
+                            $this->headers[] = $this->isExport ? Tool::textConvert($header) : $header;
+                            $this->columnsShow[] = [
+                                'header' => $header,
+                                'index' => $hk,
+                                'other' => $headerOther,
+                            ];
+                        }
+                        if(isset($this->filterTypes[$hk])){
+                            $this->filterTypesShow[$hk] = $this->filterTypes[$hk];
+                        }
+                    }
+                    if(isset($column['hidden'])&&$column['hidden']) continue;
+                    // 如果是回调函数,则直接调用回调函数,否则直接赋值
+                    $tempValue = null;
+                    if(!isset($column['value'])){
+                        $tempValue = $row[$hk];
+                    } else {
+                        if(is_callable($column['value'])){
+                            $tempValue = $column['value']($row);
+                        } else {
+                            $tempValue = $column['value'];
+                        }
+                    }
+                    if(!$this->isExport && isset($column['showValue'])){
+                        if(is_callable($column['showValue'])){
+                            $tempValue = $column['showValue']($row);
+                        } else {
+                            $tempValue = $column['showValue'];
+                        }
+                    }
+                    if(!isset($column['valueOther'])){
+                        $tempListValueOther = [];
+                    } else {
+                        if(is_callable($column['valueOther'])){
+                            $tempListValueOther = $column['valueOther']($row);
+                        } else {
+                            $tempListValueOther = $column['valueOther'];
+                        }
+                    }
+                    if($this->isExport){
+                        $data[$key][$hk] = $tempValue;
+                    } else {
+                        $data[$key][$hk] = [
+                            'value' => $tempValue,
+                            'other' => $tempListValueOther,
+                        ];
+                    }
+                } elseif (is_callable($column)) {
+                    if($key == 0){
+                        $this->headers[] = $this->isExport ? Tool::textConvert($column($row)) : $column($row);
+                        $this->columnsShow[] = [
+                            'header' => $column($row),
+                            'index' => $hk,
+                            'other' => [],
+                        ];
+                        if(isset($this->filterTypes[$hk])){
+                            $this->filterTypesShow[$hk] = $this->filterTypes[$hk];
+                        }
+                    }
+                    if($this->isExport){
+                        $data[$key][$hk] = $row[$hk];
+                    } else {
+                        $data[$key][$hk] = [
+                            'value' => $row[$hk],
+                            'other' => [],
+                        ];
+                    }
+                } elseif ($column === null){
+                    // 这种直接加入到list中,但是header和columns里面没有
+                    if(!$this->isExport){
+                        $data[$key][$hk] = $row[$hk];
+                    }
+                }
+            }
+        }
+        $this->listData['listName'] = $this->getListName();
+        $this->listData['list'] = $data;
+        if($this->isExport){
+            $this->listData['headers'] = $this->headers;
+        } else {
+            if(empty($this->listData['list'])){
+                $this->getShowHeaders();
+            }
+            $this->listData['columnsShow'] = $this->columnsShow;
+            $this->listData['filterTypes'] = $this->filterTypesShow;
+        }
+        unset($data);
+    }
+
+    /**
+     * 列表为空时需要调用此方法获取一下显示给前端的表头
+     * @throws Exception
+     */
+    public function getShowHeaders(){
+        foreach($this->columns as $hk => $column){
+            if(is_string($column)){
+                $this->headers[] = Tool::textConvert($column);
+                if(isset($this->filterTypes[$hk])){
+                    $this->filterTypesShow[$hk] = $this->filterTypes[$hk];
+                }
+            } elseif(is_array($column)) {
+                if(!isset($column['header'])){
+                    throw new Exception('column数组中必须存在header');
+                }
+                if(isset($column['hidden'])&&$column['hidden']) continue;
+                if(is_callable($column['header'])){
+                    $header = $column['header']([]);
+                } else {
+                    $header = $column['header'];
+                }
+                if(isset($column['headerOther'])){
+                    if(is_callable($column['headerOther'])){
+                        $headerOther = $column['headerOther']([]);
+                    } else {
+                        $headerOther = $column['headerOther'];
+                    }
+                } else {
+                    $headerOther = [];
+                }
+                $this->columnsShow[] = [
+                    'header' => $header,
+                    'index' => $hk,
+                    'other' => $headerOther,
+                ];
+                if(isset($this->filterTypes[$hk])){
+                    $this->filterTypesShow[$hk] = $this->filterTypes[$hk];
+                }
+            } elseif (is_callable($column)) {
+                $this->columnsShow[] = [
+                    'header' => $column([]),
+                    'index' => $hk,
+                    'other' => [],
+                ];
+                if(isset($this->filterTypes[$hk])){
+                    $this->filterTypesShow[$hk] = $this->filterTypes[$hk];
+                }
+            }
+        }
+    }
+
+    /**
+     * 按照权限处理哪些字段显示哪些字段不显示
+     * @param null $userId
+     * @throws Exception
+     */
+    protected function permissionColumn($userId = null){
+        return ;
+        $allRole = AdminRole::getFromCache();
+        if($userId === null){
+            $roleId = \Yii::$app->user->userInfo['roleId'];
+
+        } else {
+            $admin = Admin::findOneAsArray('ID=:ID', [':ID'=>$userId]);
+            if(!$admin){
+                throw new Exception('管理员不存在');
+            }
+            $roleId = $admin['ROLE_ID'];
+            if(!$roleId){
+                throw new Exception('管理组不存在');
+            }
+        }
+        // 如果是超级管理员直接返回不做处理
+        if($roleId == \Yii::$app->params['superAdminRoleId']){
+            return ;
+        }
+        $role = isset($allRole[$roleId]) ? $allRole[$roleId] : null;
+        if($role){
+            $className = get_class($this);
+            $listModuleArr = explode('\\', $className);
+            $listModuleArrCount = count($listModuleArr);
+            $listModule = $listModuleArr[$listModuleArrCount-2].'/'.$listModuleArr[$listModuleArrCount-1];
+            // 把列里面没有的内容过滤掉
+            if($this->columns){
+                foreach($this->columns as $key=>$column){
+                    if($column !== null){
+                        if(!in_array($key, $role['COLUMN_PERMISSION'][$listModule])){
+                            unset($this->columns[$key]);
+                            // 把筛选里面没有的内容也过滤掉
+                            if(isset($this->filterTypes[$key])) unset($this->filterTypes[$key]);
+                        }
+                    }
+                }
+            }
+        } else {
+            $this->columns = [];
+        }
+    }
+}

+ 30 - 0
common/libs/dataList/DataListInterface.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace common\libs\dataList;
+
+
+interface DataListInterface
+{
+    /**
+     * 获取列表名称
+     * @return mixed
+     */
+    public function getListName();
+
+    /**
+     * 处理列表数据
+     */
+    public function dataHandle();
+
+    /**
+     * 获取列
+     * @return mixed
+     */
+    public function getColumn();
+
+    /**
+     * 获取筛选类型
+     * @return mixed
+     */
+    public function getFilterTypes();
+}

+ 24 - 0
common/libs/dataList/column/AbstractColumn.php

@@ -0,0 +1,24 @@
+<?php
+
+
+namespace common\libs\dataList\column;
+
+
+use yii\base\Component;
+
+abstract class AbstractColumn extends Component {
+
+    public $value;
+    public $defaultText = '';
+
+    public function __construct($config = []) {
+        parent::__construct($config);
+    }
+
+    public function setValue($value){
+        $this->value = $value;
+    }
+    public function getValue(){
+        return $this->value;
+    }
+}

+ 37 - 0
common/libs/dataList/column/Area.php

@@ -0,0 +1,37 @@
+<?php
+
+
+namespace common\libs\dataList\column;
+use common\helpers\Config as ConfigHelper;
+
+class Area extends AbstractColumn implements InterfaceColumn {
+
+    public function __construct($config = []) {
+        parent::__construct($config);
+    }
+    public function format(){
+        $value = $this->getValue();
+        if(!$value){
+            return null;
+        }
+        $count = count($value);
+        switch ($count){
+            case 1:
+                $data = ConfigHelper::province($value[0]);
+                break;
+            case 2:
+                $data = ConfigHelper::city($value[0], $value[1]);
+                break;
+            case 3:
+                $data = ConfigHelper::county($value[0], $value[1], $value[2]);
+                break;
+            default:
+                $data = null;
+                break;
+        }
+        return ($data && isset($data['REGION_NAME'])) ? $data['REGION_NAME'] : $this->defaultText;
+    }
+    public function result(){
+        return $this->format();
+    }
+}

+ 27 - 0
common/libs/dataList/column/DateTime.php

@@ -0,0 +1,27 @@
+<?php
+
+
+namespace common\libs\dataList\column;
+
+
+class DateTime extends AbstractColumn implements InterfaceColumn {
+
+    public $format = 'Y-m-d H:i:s';
+
+    public function __construct($config = []) {
+        parent::__construct($config);
+    }
+
+    public  function setFormat($format){
+        $this->format = $format;
+        return $this;
+    }
+
+    public function result(){
+        $value = $this->getValue();
+        if(!$value){
+            return $this->defaultText;
+        }
+        return date($this->format, $value);
+    }
+}

+ 12 - 0
common/libs/dataList/column/InterfaceColumn.php

@@ -0,0 +1,12 @@
+<?php
+
+
+namespace common\libs\dataList\column;
+
+
+interface InterfaceColumn {
+    /**
+     * @return mixed
+     */
+    public function result();
+}

+ 20 - 0
common/libs/dataList/column/LongNumber.php

@@ -0,0 +1,20 @@
+<?php
+
+
+namespace common\libs\dataList\column;
+
+
+class LongNumber extends AbstractColumn implements InterfaceColumn {
+
+    public function __construct($config = []) {
+        parent::__construct($config);
+    }
+
+    public function result(){
+        $value = $this->getValue();
+        if(!$value){
+            return $this->defaultText;
+        }
+        return "\t" . $value;
+    }
+}

+ 22 - 0
common/libs/dataList/column/Price.php

@@ -0,0 +1,22 @@
+<?php
+
+
+namespace common\libs\dataList\column;
+
+
+use common\helpers\Tool;
+
+class Price extends AbstractColumn implements InterfaceColumn {
+
+    public function __construct($config = []) {
+        parent::__construct($config);
+    }
+
+    public function result(){
+        $value = $this->getValue();
+        if(!$value){
+            return '0.00';
+        }
+        return Tool::formatPrice($value);
+    }
+}

+ 20 - 0
common/libs/dataList/column/Text.php

@@ -0,0 +1,20 @@
+<?php
+
+
+namespace common\libs\dataList\column;
+
+
+class Text extends AbstractColumn implements InterfaceColumn {
+
+    public function __construct($config = []) {
+        parent::__construct($config);
+    }
+
+    public function result(){
+        $value = $this->getValue();
+        if(!$value){
+            return $this->defaultText;
+        }
+        return str_replace(',',',',$value);
+    }
+}

+ 22 - 0
common/libs/dataList/column/YesNo.php

@@ -0,0 +1,22 @@
+<?php
+
+
+namespace common\libs\dataList\column;
+
+
+class YesNo extends AbstractColumn implements InterfaceColumn {
+
+    public $label = [
+        'yes' => 'Yes',//yes
+        'no' => 'No',//NO
+    ];
+
+    public function __construct($config = []) {
+        parent::__construct($config);
+    }
+
+    public function result(){
+        $value = $this->getValue();
+        return ($value == 1) ? $this->label['yes'] : $this->label['no'];
+    }
+}

+ 1088 - 0
common/libs/export/BaseExport.php

@@ -0,0 +1,1088 @@
+<?php
+
+namespace common\libs\export;
+
+use common\helpers\Cache as CacheHelper;
+use common\helpers\Date;
+use common\helpers\Form;
+use common\helpers\http\RemoteUploadApi;
+use common\helpers\Tool;
+use common\helpers\user\Info;
+use common\libs\dataList\column\DateTime;
+use common\libs\dataList\DataList;
+use common\models\ApproachOrder;
+use common\models\ApproachOrderGoods;
+use common\models\Export;
+use common\models\Order;
+use common\models\OrderGoods;
+use common\models\Region;
+use common\models\ShopGoods;
+use common\models\User;
+use mysql_xdevapi\Result;
+use Yii;
+use yii\base\Exception;
+use yii\base\StaticInstanceTrait;
+use yii\base\Component;
+use yii\data\Pagination;
+use yii\db\Query;
+
+class BaseExport extends Component {
+    use StaticInstanceTrait;
+    public $moduleId;
+    /**
+     * @var string the model class name. This property must be set.
+     */
+    public $modelClass;
+    /**
+     * @var
+     */
+    public $listModelClass;
+    /**
+     * @var int
+     */
+    public $pageSize = 100;
+    /**
+     * @var int
+     */
+    public $totalCount = 0;
+    /**
+     * @var int
+     */
+    public $offset = 0;
+    /**
+     * @var int
+     */
+    public $limit = 100;
+    /**
+     * csv文件名称
+     * @var null
+     */
+    public $fileName = null;
+    /**
+     * 保存的基本路径
+     * @var null
+     */
+    public $saveBasePath = null;
+    /**
+     * 保存路径
+     * @var null
+     */
+    public $savePath = null;
+    /**
+     * 正在导出的id
+     * 来源于exporting_file表
+     * @var null
+     */
+    public $exportId = null;
+    /**
+     * 操作人的id
+     * @var null
+     */
+    public $userId = null;
+    /**
+     * 任务id
+     * @var null
+     */
+    public $taskId = null;
+    /**
+     * 是否完成
+     * @var bool
+     */
+    public $completed = false;
+    /**
+     * @var array
+     */
+    public $params = [];
+    /**
+     * 文件句柄
+     * @var null
+     */
+    private $_fp = null;
+    /**
+     * 是否上传远程
+     * @var bool
+     */
+    public $isRemote = true;
+
+    /**
+     * @var DataList
+     */
+    private $_listModel;
+
+    public static function factory($taskId) {
+        return new self([
+                'taskId' => $taskId
+            ]
+        );
+    }
+
+    /**
+     *
+     */
+    public function init() {
+        parent::init();
+        $this->isRemote = \Yii::$app->params['isRemoteUpload'];
+    }
+
+    /**
+     * 生成复杂的路径
+     * @return string
+     */
+    public function pathCreator(...$params) {
+        $hash = md5(implode('_', $params));
+        $first = substr($hash, 0, 3);
+        $second = substr($hash, 3, 2);
+        $dir = $first . __DS__ . $second . __DS__;
+        return $dir;
+    }
+
+    /**
+     * 获取文件名
+     * @return string|null
+     * @throws \Exception
+     */
+    public function getFileName($ext = '.csv') {
+        $this->fileName = date('YmdHis', Date::nowTime()) . random_int(1000, 9999) . $ext;
+        return $this->fileName;
+    }
+
+    /**
+     * 获取保存的路径
+     * @return array|null
+     */
+    public function getSavePath() {
+        $this->savePath = $this->pathCreator(date('Ymd', Date::nowTime()));
+        return trim($this->savePath, __DS__);
+    }
+
+    /**
+     * @return bool|string|null
+     */
+    public function getSaveBasePath() {
+        $this->saveBasePath = Yii::getAlias('@backendApi') . __DS__ . 'web' . __DS__ . 'upload' . __DS__ . \Yii::$app->params['excelLocalDir'];
+        return trim($this->saveBasePath, __DS__);
+    }
+
+    /**
+     * 创建目录(递归创建)
+     *
+     * @param $dir
+     * @return string|false
+     */
+    public function mkdir($dir) {
+        if (!is_dir($dir)) {
+            $this->mkdir(dirname($dir));
+            if (!mkdir($dir, 0777))
+                return false;
+            @exec('chown -R www:www /'.$dir);
+            @exec('chmod -R 777 /'.$dir);
+        }
+        return $dir;
+    }
+
+    /**
+     * @return array|mixed
+     */
+    public function getParams() {
+        $this->params = CacheHelper::getAsyncParams($this->taskId);
+        return $this->params;
+    }
+
+    /**
+     * @return mixed|null
+     */
+    public function getUserId() {
+        $this->userId = isset($this->params['userId']) ? $this->params['userId'] : null;
+        return $this->userId;
+    }
+
+    /**
+     * @return |null
+     */
+    public function getExportId() {
+        $this->exportId = isset($this->params['exportId']) ? $this->params['exportId'] : null;
+        return $this->userId;
+    }
+
+    /**
+     * 生成
+     * @return bool
+     * @throws Exception
+     * @throws \yii\base\InvalidConfigException
+     * @throws \yii\httpclient\Exception
+     */
+    public function generate() {
+        //Logger::info(date('Y-m-d H:i:s'), 'export');
+        $this->getParams();
+        if (!$this->params) {
+            throw new Exception('无法获取需要的参数');
+        }
+        $path = $this->getSaveBasePath() . __DS__ . $this->getSavePath();
+        $path = __DS__ . $path;
+        $realFile = $this->mkdir($path) . __DS__ . $this->getFileName();
+        $this->completed = false;
+        $this->getExportId();
+        $this->getUserId();
+        $fileNameUpdated = false;
+        $this->_fp = fopen($realFile, 'w');
+        @exec('chown -R www:www /'.$realFile);
+        @exec('chmod -R 777 /'.$realFile);
+        // 获取列表数据及表头
+        $this->_listModel = new $this->listModelClass();
+        $this->_listModel->isExport = true;
+        if(method_exists($this->_listModel, 'getExportHeaders')){
+            if(method_exists($this->_listModel, 'exportPrepare')) {//导出数据提前设置参数
+                $this->_listModel->exportPrepare(['condition' => $this->params['condition'], 'params' => $this->params['params'], 'others' => isset($this->params['others']) ? $this->params['others'] : [], 'page' => 0, 'pageSize' => $this->pageSize, 'userId' => $this->userId]);
+            }
+            $headers = $this->_listModel->getExportHeaders($this->userId);
+            fputcsv($this->_fp, $headers);
+            unset($headers);
+            $this->_updateFirst($realFile, 1);
+        } else {
+            throw new Exception($this->listModelClass.'的getExportHeaders方法不存在');
+        }
+        $this->_loopWriteData($realFile, $fileNameUpdated);
+        $this->complete();
+        return true;
+    }
+
+    /**
+     * 循环写入数据
+     * @param $realFile
+     * @param $fileNameUpdated
+     * @param int $page
+     * @param int $counter
+     * @return string
+     * @throws Exception
+     */
+    private function _loopWriteData($realFile, &$fileNameUpdated, $page = 0, $counter = 0){
+        if(method_exists($this->_listModel, 'getList')){
+            $list = $this->_listModel->getList(['condition'=>$this->params['condition'], 'params'=> $this->params['params'] ?? [], 'others'=> $this->params['others'] ?? [], 'page'=>$page, 'pageSize'=>$this->pageSize, 'userId'=>$this->userId]);
+        } else {
+            throw new Exception($this->listModelClass.'的getList方法不存在');
+        }
+        if($page >= $list['totalPages']){
+            return 'finish';
+        }
+        if(!empty($list['list'])){
+            foreach($list['list'] as $columnData){
+                fputcsv($this->_fp, Tool::arrTextConvert($columnData));
+                $counter++;
+
+                if ($list['totalCount'] < $counter) {
+//                    $counter = $list['totalCount'];
+                    return 'finish';
+                }
+                $percent = intval(($counter / $list['totalCount']) * 100);
+                $this->_updatePercent($percent);
+//                if (!$fileNameUpdated) {
+//                    $this->_updateFirst($realFile, $percent);
+//                } else {
+//                    $this->_updatePercent($percent);
+//                }
+//                $fileNameUpdated = true;
+                unset($percent, $columnData);
+            }
+            $page++;
+            unset($list);
+            return $this->_loopWriteData($realFile, $fileNameUpdated, $page, $counter);
+        }
+        unset($list);
+        return 'finish';
+    }
+
+    /**
+     * 循环写入数据
+     */
+    private function _loopWriteDataOrder()
+    {
+        $orderQuery = Order::find()
+            ->alias('O')
+            ->where($this->params['condition'], $this->params['params'])
+            ->select('O.*,U.REAL_NAME,U.DEC_ID,SG.CATEGORY_TYPE,OG.REAL_PRICE,OG.TAX_RATE,OG.BUY_NUMS,OG.SKU_CODE,OG.GOODS_TITLE,OG.REAL_PV')
+            ->join('LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID')
+            ->join('LEFT JOIN', OrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN')
+            ->join('LEFT JOIN', ShopGoods::tableName() . ' AS SG', 'SG.ID=OG.GOODS_ID')
+            ->orderBy('O.CREATED_AT DESC');
+
+        // 订单中间表只查询待支付和支付失败的订单
+        $this->params['params'][':NOT_PAID'] = \Yii::$app->params['orderStatus']['notPaid']['value'];   // 待支付
+        $this->params['params'][':FAIL_PAID'] = \Yii::$app->params['orderStatus']['failPaid']['value'];   // 支付失败
+        $orderStandardQuery = ApproachOrder::find()
+            ->alias('O')
+            ->where($this->params['condition'] . ' AND (O.STATUS = :NOT_PAID OR O.STATUS = :FAIL_PAID)', $this->params['params'])
+            ->select('O.*,U.REAL_NAME,U.DEC_ID,SG.CATEGORY_TYPE,OG.REAL_PRICE,OG.TAX_RATE,OG.BUY_NUMS,OG.SKU_CODE,OG.GOODS_TITLE,OG.REAL_PV')
+            ->join('LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID')
+            ->join('LEFT JOIN', ApproachOrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN')
+            ->join('LEFT JOIN', ShopGoods::tableName() . ' AS SG', 'SG.ID=OG.GOODS_ID')
+            ->orderBy('O.CREATED_AT DESC');
+
+        $queryAll = $orderQuery->union($orderStandardQuery, true);
+        $query = (new Query())->from(['Q' => $queryAll])->select('Q.*')->distinct()->orderBy(['CREATED_AT' => SORT_DESC]);
+        $lists = $query->all();
+
+        if(!empty($lists)){
+            foreach($lists as $columnData) {
+                $CREATE_USER_ID = Info::getUserIdByUserName($columnData['CREATE_USER']);
+                $createUserName = Info::getUserRealNameByUserId($CREATE_USER_ID);
+                $decUserName = Info::getUserNameByUserId($columnData['DEC_ID']);
+
+                $columnAccept = [
+                    'USER_NAME' => $columnData['USER_NAME'],
+                    'DEC_USER_NAME' => $decUserName,
+                    'CREATE_USER' => $columnData['CREATE_USER'],
+                    'CREATE_USER_NAME' => $createUserName,
+                    'SN' => $columnData['SN'],
+                    'STATUS' => \Yii::$app->params['orderStatus'][$columnData['STATUS']]['label'] ?? '',
+                    'SKU_CODE' => $columnData['SKU_CODE'],
+                    'GOODS_TITLE' => $columnData['GOODS_TITLE'],
+                    'BUY_NUMS' => $columnData['BUY_NUMS'],
+                    'CONSIGNEE' => $columnData['CONSIGNEE'],
+                    'MOBILE' => $columnData['MOBILE'],
+                    'TEL' => $columnData['TEL'],
+                    'PROVINCE' => $regionConfig[$columnData['PROVINCE']]['REGION_NAME'] ?? '',
+                    'CITY' => $regionConfig[$columnData['CITY']]['REGION_NAME'] ?? '',
+                    'COUNTY' => $regionConfig[$columnData['COUNTY']]['REGION_NAME'] ?? '',
+                    'ADDRESS' => $columnData['ADDRESS'],
+                    'PERIOD_NUM' => $columnData['PERIOD_NUM'],
+                    'ORDER_TYPE' => ($columnData['ORDER_TYPE'] == 'ZC') ? 'Welcome pack' : (in_array($columnData['PAY_TYPE'], ['cash', 'pay_stack']) ? 'Reselling': 'Points'),
+                    // 'WAREHOUSE' => $columnData['WAREHOUSE'],
+                    'CREATED_AT' => Date('Y-m-d H:i:s', $columnData['CREATED_AT']),
+                    'PAY_TYPE' => ShopGoods::payTypes()[$columnData['PAY_TYPE']]['name'] ?? ShopGoods::payTypes()['cash']['name'],
+                    'PAY_AT' => Date('Y-m-d H:i:s', $columnData['PAY_AT']),
+                    'DELIVERY_AT' => Date('Y-m-d H:i:s', $columnData['DELIVERY_AT']),
+                    'REAL_PRICE' => $columnData['REAL_PRICE'],
+                    'REAL_PV' => $columnData['REAL_PV'],
+                    'PAY_FREIGHT' => $columnData['PAY_FREIGHT'],
+                    'TAX_RATE' => $columnData['TAX_RATE'],
+                    'TAX_AMOUNT' => Tool::calculateTax($columnData['REAL_PRICE'], $columnData['TAX_RATE'], $columnData['TAX_RATE']),
+                    'EXPRESS_COMPANY' => $columnData['EXPRESS_COMPANY'],
+                    'ORDER_TRACK_NO' => $columnData['ORDER_TRACK_NO'],
+                    'EXPRESS_TYPE' => $columnData['EXPRESS_TYPE'] == 0 ? 'mailing ':' auto pick',
+                    'FRONT_REMARK' => $columnData['FRONT_REMARK'],
+                    'DELIVERY_STATUS_NAME' => \Yii::$app->params['deliveryStatus'][$columnData['DELIVERY_STATUS']]['label'] ?? '',
+                ];
+
+                fputcsv($this->_fp, Tool::arrTextConvert($columnAccept));
+                unset($percent, $columnData, $columnAccept);
+            }
+            unset($list);
+        }
+
+        unset($list);
+        return 'finish';
+    }
+
+    /**
+     * 完成
+     * @return bool
+     * @throws Exception
+     * @throws \yii\base\InvalidConfigException
+     * @throws \yii\httpclient\Exception
+     */
+    public function complete() {
+        $this->_updatePercent(100);
+        if ($this->completed) {
+            return true;
+        }
+        if ($this->_fp) {
+            fclose($this->_fp);
+        }
+        // 把导出的文件上传至静态文件服务器
+        if ($this->isRemote) {
+            $realFile = $this->getSaveBasePath() . __DS__ . $this->getSavePath() . __DS__ . $this->getFileName();
+            $remoteUploadApi = RemoteUploadApi::instance();
+            if ($uploadResult = $remoteUploadApi->upload($realFile)) {
+                Export::updateAll(['REMOTE_URL' => $uploadResult['url'], 'IS_EXPORTING' => 0, 'EXPORT_PERCENT' => 100, 'ENDED_AT' => Date::nowTime()], 'ID=:ID', [':ID' => $this->exportId]);
+            } else {
+                $this->_errorHandle();
+                throw new Exception('文件远程上传失败');
+            }
+            // 删除本地临时文件
+            unlink($realFile);
+        } else {
+            Export::updateAll(['IS_EXPORTING' => 0, 'EXPORT_PERCENT' => 100, 'ENDED_AT' => Date::nowTime()], 'ID=:ID', [':ID' => $this->exportId]);
+        }
+
+        \Yii::$app->swooleAsyncTimer->pushAsyncPercentToAdmin(100, ['MODEL' => 'EXPORT', 'ID' => $this->exportId, 'FIELD' => 'EXPORT_PERCENT']);
+        CacheHelper::deleteAsyncParams($this->taskId);
+        $this->completed = true;
+    }
+
+    /**
+     * @param $message
+     */
+    private function _errorNotice($message) {
+        Yii::$app->swooleAsyncTimer->pushAsyncResultToAdmin($this->userId, $message, false);
+    }
+
+    /**
+     * 首次更新
+     * @param $exportId
+     * @param $realName
+     */
+    private function _updateFirst($realName, $percent) {
+        $fileName = str_replace($this->getSaveBasePath() . __DS__, '', $realName);
+        Export::updateAll([
+            'FILE_NAME' => str_replace(__DS__, '/', $fileName),
+            'UPDATED_AT' => Date::nowTime(),
+            'EXPORT_PERCENT' => $percent,
+        ], 'ID=:ID', [':ID' => $this->exportId]);
+    }
+
+    /**
+     * 更新百分比
+     * @param $exportId
+     * @param $percent
+     */
+    private function _updatePercent($percent) {
+        // 把数据写入数据库中
+        Export::updateAll(['EXPORT_PERCENT' => $percent], 'ID=:ID', [':ID' => $this->exportId]);
+        \Yii::$app->swooleAsyncTimer->pushAsyncPercentToAdmin($percent, ['MODEL' => 'EXPORT', 'ID' => $this->exportId, 'FIELD' => 'EXPORT_PERCENT']);
+    }
+
+    /**
+     * 发生错误处理
+     * @param $exportId
+     */
+    private function _errorHandle() {
+        Export::updateAll(['IS_EXPORTING' => 0], 'ID=:ID', [':ID' => $this->exportId]);
+    }
+
+    /**
+     * 导出逻辑
+     * @param $filter
+     * @param $listName
+     * @param null $consoleRouter
+     * @throws Exception
+     */
+    public function exportHandle($filter, $listName, $consoleRouter = null){
+        $params = [
+            'moduleId' => $this->moduleId,
+            'listName' => $listName,
+            'action' => $consoleRouter ? $consoleRouter : Yii::$app->controller->id.'/'.Yii::$app->controller->action->id, // 这里这么写,是因为调用的异步路由和同步的控制器和方法是一样的,所以,只要不传默认调和同步一样的异步方法
+            'userId' => Yii::$app->user->id,
+        ];
+        $this->webToAsync($params,$filter);
+    }
+
+    /**
+     * 页面到异步的请求
+     * @param $params
+     * @param array $extend
+     * @return bool
+     * @throws Exception
+     */
+    public function webToAsync($params, $extend = []) {
+        $default = [
+            'moduleId' => null,
+            'listName' => null,
+            'remark' => null,
+            'action' => null,
+            'userId' => null,
+        ];
+        $params = Tool::deepParse($params, $default);
+        if (!$params['moduleId'] || !$params['listName'] || !$params['action']) {
+            throw new Exception('请设置moduleId,listName和action');
+        }
+
+        // 把文件对应的相关资料存入数据库中
+        $export = new Export();
+        $export->EXPORT_NAME = $params['listName'] . '_' . date('Exp_y-m-d H:i:s', Date::nowTime());
+        $export->MODULE_NAME = $params['moduleId'];
+        $export->REMARK = $params['remark'];
+        $export->IS_EXPORTING = 1;
+        $export->ADMIN_ID = \Yii::$app->user->id;
+        $export->STARTED_AT = Date::nowTime();
+        $export->CREATED_AT = Date::nowTime();
+        $export->EXPORT_PERCENT = 0;
+        $export->ENDED_AT = 0;
+        $export->UPDATED_AT = 0;
+        if (!$export->save()) {
+            throw new Exception(Form::formatErrorsForApi($export->getErrors()));
+        }
+        $extend = array_merge($extend, [
+            'exportId' => $export->ID,
+            'userId' => $params['userId'],
+        ]);
+        // 异步处理添加任务
+        $taskKey = \Yii::$app->swooleAsyncTimer->asyncHandle($params['action'], $extend);
+        if($taskKey === false){
+            // 删除刚刚添加的导出数据
+            Export::deleteAll(['ID'=>$export->ID]);
+            throw new Exception('导出失败,请求异步处理服务器失败');
+        }
+        return $taskKey;
+    }
+
+    /**
+     * 生成
+     * @return bool
+     * @throws Exception
+     * @throws \yii\base\InvalidConfigException
+     * @throws \yii\httpclient\Exception
+     */
+    public function generateOrderExcel() {
+        $this->getParams();
+        if (!$this->params) {
+            throw new Exception('无法获取需要的参数');
+        }
+        $path = $this->getSaveBasePath() . __DS__ . $this->getSavePath();
+        $path = __DS__ . $path;
+        $realFile = $this->mkdir($path) . __DS__ . $this->getFileName();
+        $this->completed = false;
+        $this->getExportId();
+        $this->getUserId();
+        $fileNameUpdated = false;
+        $this->_fp = fopen($realFile, 'w');
+        @exec('chown -R www:www /'.$realFile);
+        @exec('chmod -R 777 /'.$realFile);
+        // 获取列表数据及表头
+        $this->_listModel = new $this->listModelClass();
+        $this->_listModel->isExport = true;
+        if(method_exists($this->_listModel, 'getExportHeaders')){
+            if(method_exists($this->_listModel, 'exportPrepare')) {//导出数据提前设置参数
+                $this->_listModel->exportPrepare(['condition' => $this->params['condition'], 'params' => $this->params['params'], 'others' => $this->params['others'] ?? [], 'page' => 0, 'pageSize' => 100000, 'userId' => $this->userId]);
+            }
+            $headers = $this->_listModel->getExportHeaders($this->userId);
+            fputcsv($this->_fp, $headers);
+            unset($headers);
+            $this->_updateFirst($realFile, 1);
+        } else {
+            throw new Exception($this->listModelClass.'的getExportHeaders方法不存在');
+        }
+        $this->_loopWriteDataOrder();
+        $this->complete();
+        return true;
+    }
+
+    /**
+     * 生成
+     * @return bool
+     * @throws Exception
+     * @throws \yii\base\InvalidConfigException
+     * @throws \yii\httpclient\Exception
+     */
+    public function generateOrderPDF() {
+        $this->getParams();
+        if (!$this->params) {
+            throw new Exception('无法获取需要的参数');
+        }
+        $path = __DS__ . $this->getSaveBasePath() . __DS__ . $this->getSavePath();
+        $realFile = $path . __DS__ . $this->getFileName('.pdf');
+
+        $this->completed = false;
+        $this->getExportId();
+        $this->getUserId();
+        $fileNameUpdated = false;
+
+        // 获取列表数据及表头
+        $this->_listModel = new $this->listModelClass();
+        $this->_listModel->isExport = true;
+
+        // 查询订单数据
+//        $oderList = $this->_loopWriteDataPDF($realFile, $fileNameUpdated);
+        $orderQuery = Order::find()
+            ->alias('O')
+            ->where($this->params['condition'], $this->params['params'])
+            ->select('O.*,U.REAL_NAME,U.DEC_ID,SG.CATEGORY_TYPE,OG.REAL_PRICE,OG.TAX_RATE,OG.BUY_NUMS,OG.SKU_CODE,OG.GOODS_TITLE,OG.REAL_PV')
+            ->join('LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID')
+            ->join('LEFT JOIN', OrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN')
+            ->join('LEFT JOIN', ShopGoods::tableName() . ' AS SG', 'SG.ID=OG.GOODS_ID')
+            ->orderBy('O.CREATED_AT DESC');
+
+        // 订单中间表只查询待支付和支付失败的订单
+        $this->params['params'][':NOT_PAID'] = \Yii::$app->params['orderStatus']['notPaid']['value'];   // 待支付
+        $this->params['params'][':FAIL_PAID'] = \Yii::$app->params['orderStatus']['failPaid']['value'];   // 支付失败
+        $orderStandardQuery = ApproachOrder::find()
+            ->alias('O')
+            ->where($this->params['condition'] . ' AND (O.STATUS = :NOT_PAID OR O.STATUS = :FAIL_PAID)', $this->params['params'])
+            ->select('O.*,U.REAL_NAME,U.DEC_ID,SG.CATEGORY_TYPE,OG.REAL_PRICE,OG.TAX_RATE,OG.BUY_NUMS,OG.SKU_CODE,OG.GOODS_TITLE,OG.REAL_PV')
+            ->join('LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID')
+            ->join('LEFT JOIN', ApproachOrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN')
+            ->join('LEFT JOIN', ShopGoods::tableName() . ' AS SG', 'SG.ID=OG.GOODS_ID')
+            ->orderBy('O.CREATED_AT DESC');
+
+        $queryAll = $orderQuery->union($orderStandardQuery, true);
+        $query = (new Query())->from(['Q' => $queryAll])->select('Q.*')->distinct()->orderBy(['CREATED_AT' => SORT_DESC]);
+        $oderList = $query->all();
+
+        if ($oderList) {
+            $userId = '';
+            $userName = '';
+            $address = '';
+            $mobile = '';
+            $orderAt = '';
+            $orderDetails = '';
+            $orderSn = '';
+            $orderAmount = 0;  // 合计总额
+            $orderNums = 0; // 合计总数
+            $totalTaxAmount = 0; // 合计税额
+            $totalAmount = 0;
+            foreach ($oderList as $key => $value) {
+                $provinceName = $value['PROVINCE'] ? Region::getCnName($value['PROVINCE']) : '';
+                $cityName = $value['CITY'] ? Region::getCnName($value['CITY']) : '';
+                $countyName = $value['COUNTY'] ? Region::getCnName($value['COUNTY']) : '';
+
+                $userId = $value['USER_NAME'];
+                $userName = $value['REAL_NAME'];
+                $address = $provinceName . $cityName . $countyName . $value['ADDRESS'];
+                $mobile = $value['MOBILE'];
+                $orderAt = Date::convert($value['CREATED_AT'],'Y-m-d H:i:s');
+                $orderSn = $value['SN'];
+                // 总价
+                $totalAmount = $value['BUY_NUMS'] * $value['REAL_PRICE'];
+                $orderAmount += $totalAmount;
+                $orderNums += $value['BUY_NUMS'];
+                // 税额
+                $taxAmount = Tool::calculateTax($value['REAL_PRICE'], $value['TAX_RATE'], $value['BUY_NUMS']);
+                $totalTaxAmount += $taxAmount;
+                $taxAmount = Tool::formatAmount($taxAmount);
+                $totalAmount = Tool::formatAmount($totalAmount);
+                // 订单详情
+                $orderDetails .= <<<EOT
+                <tr>
+                    <td>{$value['SKU_CODE']}</td>
+                    <td>{$value['GOODS_TITLE']}</td>
+                    <td style="text-align: right;">{$value['REAL_PRICE']}</td>
+                    <td>{$value['BUY_NUMS']}</td>
+                    <td style="text-align: right;">{$value['TAX_RATE']}</td>
+                    <td style="text-align: right;">{$taxAmount}</td>
+                    <td style="text-align: right;">{$totalAmount}</td> 
+                </tr>
+EOT;
+            }
+
+            // 订单基本信息
+            $orderBase = <<<ORDER
+            <table border="1" style="table-layout: fixed; padding: 10px 20px;" width="100%">
+                <tr>
+                    <td width="30%" style="font-weight: bold; text-align: center; font-size: 14px;">Member code</td>
+                    <td width="70%">{$userId}</td>
+                </tr>
+                <tr>
+                    <td width="30%" style="font-weight: bold; text-align: center; font-size: 14px;">Member name</td>
+                    <td width="70%">{$userName}</td>
+                </tr>
+                <tr>
+                    <td width="30%" style="font-weight: bold; text-align: center; font-size: 14px;">Member address</td>
+                    <td width="70%">{$address}</td>
+                </tr>
+                <tr>
+                    <td width="30%" style="font-weight: bold; text-align: center; font-size: 14px;">Member phone</td>
+                    <td width="70%">{$mobile}</td>
+                </tr>
+                <tr>
+                    <td width="30%" style="font-weight: bold; text-align: center; font-size: 14px;">Order code</td>
+                    <td width="70%">{$orderSn}</td>
+                </tr>
+                <tr>
+                    <td width="30%" style="font-weight: bold; text-align: center; font-size: 14px;">Creation time</td>
+                    <td width="70%">{$orderAt}</td>
+                </tr>
+                <tr>
+                    <td class="bg" style="font-weight: bold; font-size: 14px; text-align: center;">Order detail</td>
+                    <td class="bg"></td>
+                </tr>
+            </table>
+ORDER;
+
+            $l['a_meta_charset'] = 'UTF-8';
+            $l['a_meta_dir'] = 'ltr';
+            $l['a_meta_language'] = 'zh';
+            $l['w_page'] = '页面';
+
+            $orderAmount = Tool::formatAmount($orderAmount);
+            $totalTaxAmount = Tool::formatAmount($totalTaxAmount);
+
+            $context = <<<ORDER
+            <!doctype html>
+            <html lang="en">
+            <head>
+                <meta charset="UTF-8" />
+                <title>Order detail</title>
+                <style>
+                    table {
+                        border-collapse: collapse;
+                    }
+                    table td, table th {
+                        border: 1px solid #ccc;
+                        padding: 5px 5px;
+                        border-collapse: collapse;
+                    }
+                    /*td {*/
+                    /*    padding: 120px;*/
+                    /*}*/
+                    .bg {
+                        background-color: #ccc;
+                    }
+                </style>
+            </head>
+            <body>
+                <div class="content">
+                    <p style="text-align: center; font-weight: bold; font-size: 22px;"><b>Order detail</b><br></p>
+                    <div>
+                        <div style="display: block; width: 100%;">
+                            {$orderBase}
+                            
+                            <table border="1" width="100%" style="padding: 10px 5px; text-align: center;">
+                                <tr>
+                                    <th width="15%" style="font-size: 14px; font-weight: bold; text-align: center;">Product Code</th>
+                                    <th width="25%" style="font-size: 14px; font-weight: bold; text-align: center;">Product Name</th>
+                                    <th width="15%" style="font-size: 14px; font-weight: bold; text-align: center;">Product Price</th>
+                                    <th width="10%" style="font-size: 14px; font-weight: bold; text-align: center;">Qty</th>
+                                    <th width="10%" style="font-size: 14px; font-weight: bold; text-align: center;">Tax Rate</th>
+                                    <th width="10%" style="font-size: 14px; font-weight: bold; text-align: center;">Tax</th>
+                                    <th width="15%" style="font-size: 14px; font-weight: bold; text-align: center;">Total Amount</th>
+                                </tr>
+                                {$orderDetails}
+                                <tr>
+                                    <td colspan="3">Total</td>
+                                    <td>{$orderNums}</td>
+                                    <td></td>
+                                    <td style="text-align: right;">{$totalTaxAmount}</td>
+                                    <td style="text-align: right;">{$orderAmount}</td>
+                                </tr>
+                            </table>
+                        </div>
+                        
+                        <div style="width: 100%; margin-top: 50px; height: 30px;">
+                            <table width="100%" style="border: none; padding: 10px 20px; text-align: center;">
+                                <tr style="border: none;">
+                                    <td width="70%" style="border: none;"></td>
+                                    <td width="30%" style="font-weight: bold; text-align: left; font-size: 14px; border: none;">Signature:</td>
+                                </tr>
+                                <tr style="border: none;">
+                                    <td width="70%" style="border: none;"></td>
+                                    <td width="30%" style="font-weight: bold; text-align: left; font-size: 14px; border: none;">Date:</td>
+                                </tr>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </body>
+            </html>
+ORDER;
+
+            require_once (\Yii::$app->vendorPath . '/tecnickcom/tcpdf/tcpdf.php');
+
+            $pdf = new \TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
+            // 设置打印模式
+            $pdf->SetCreator(PDF_CREATOR);
+            $pdf->SetAuthor('DaZe');
+            $pdf->SetTitle($orderSn);
+            $pdf->SetSubject('TCPDF Tutorial');
+            $pdf->SetKeywords('TCPDF, PDF, example, test, guide');
+            // 是否显示页眉
+            $pdf->setPrintHeader(false);
+            // 设置页眉字体
+            $pdf->setHeaderFont(Array('dejavusans', '', '12'));
+            // 页眉距离顶部的距离
+            $pdf->SetHeaderMargin('5');
+            // 是否显示页脚
+            $pdf->setPrintFooter(false);
+            // 设置默认等宽字体
+            $pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED);
+            // 设置行高
+            $pdf->setCellHeightRatio(1);
+            // 设置左、上、右的间距
+            $pdf->SetMargins('10', '0', '10');
+            // 设置是否自动分页  距离底部多少距离时分页
+            $pdf->SetAutoPageBreak(TRUE, '15');
+            // 设置图像比例因子
+            $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
+            if (@file_exists(\Yii::$app->vendorPath . 'tecnickcom/tcpdf/examples/lang/eng.php')) {
+                require_once(\Yii::$app->vendorPath . '/tecnickcom/tcpdf/examples/lang/eng.php');
+                $pdf->setLanguageArray($l);
+            }
+            $pdf->setFontSubsetting(true);
+            $pdf->AddPage();
+            // 设置字体
+            $pdf->SetFont('stsongstdlight', '', 10, '', true);
+            $image = file_get_contents(\Yii::$app->basePath . '/../frontendEle/src/static/img/ngds-logo.jpg');
+            $pdf->Image('@' . $image, 15, 12, 20, 7, 'JPG');
+            $pdf->writeHTML($context);
+            $pdf->Output($realFile, 'F');
+
+            $this->_updateFirst($realFile, 1);
+        }
+
+        $this->complete();
+        return true;
+    }
+
+    /**
+     * 生成
+     * @return bool
+     * @throws Exception
+     * @throws \yii\base\InvalidConfigException
+     * @throws \yii\httpclient\Exception
+     */
+    public function generateDecOrderPDF() {
+        $this->getParams();
+        if (!$this->params) {
+            throw new Exception('无法获取需要的参数');
+        }
+        $path = __DS__ . $this->getSaveBasePath() . __DS__ . $this->getSavePath();
+        $realFile = $path . __DS__ . $this->getFileName('.pdf');
+
+        $this->completed = false;
+        $this->getExportId();
+        $this->getUserId();
+        $fileNameUpdated = false;
+
+        // 获取列表数据及表头
+        $this->_listModel = new $this->listModelClass();
+        $this->_listModel->isExport = true;
+
+        // 查询订单数据
+//        $oderList = $this->_loopWriteDataPDF($realFile, $fileNameUpdated);
+        $orderQuery = Order::find()
+            ->alias('O')
+            ->where($this->params['condition'], $this->params['params'])
+            ->select('O.*,U.REAL_NAME,U.DEC_ID,SG.CATEGORY_TYPE,OG.REAL_PRICE,OG.TAX_RATE,OG.BUY_NUMS,OG.SKU_CODE,OG.GOODS_TITLE,OG.REAL_PV')
+            ->join('LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID')
+            ->join('LEFT JOIN', OrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN')
+            ->join('LEFT JOIN', ShopGoods::tableName() . ' AS SG', 'SG.ID=OG.GOODS_ID')
+            ->orderBy('O.CREATED_AT DESC');
+
+        // 订单中间表只查询待支付和支付失败的订单
+        $this->params['params'][':NOT_PAID'] = \Yii::$app->params['orderStatus']['notPaid']['value'];   // 待支付
+        $this->params['params'][':FAIL_PAID'] = \Yii::$app->params['orderStatus']['failPaid']['value'];   // 支付失败
+        $orderStandardQuery = ApproachOrder::find()
+            ->alias('O')
+            ->where($this->params['condition'] . ' AND (O.STATUS = :NOT_PAID OR O.STATUS = :FAIL_PAID)', $this->params['params'])
+            ->select('O.*,U.REAL_NAME,U.DEC_ID,SG.CATEGORY_TYPE,OG.REAL_PRICE,OG.TAX_RATE,OG.BUY_NUMS,OG.SKU_CODE,OG.GOODS_TITLE,OG.REAL_PV')
+            ->join('LEFT JOIN', User::tableName() . ' AS U', 'U.ID=O.USER_ID')
+            ->join('LEFT JOIN', ApproachOrderGoods::tableName() . ' AS OG', 'OG.ORDER_SN=O.SN')
+            ->join('LEFT JOIN', ShopGoods::tableName() . ' AS SG', 'SG.ID=OG.GOODS_ID')
+            ->orderBy('O.CREATED_AT DESC');
+
+        $queryAll = $orderQuery->union($orderStandardQuery, true);
+        $query = (new Query())->from(['Q' => $queryAll])->select('Q.*')->distinct()->orderBy(['CREATED_AT' => SORT_DESC]);
+        $oderList = $query->all();
+
+        if ($oderList) {
+            $userId = '';
+            $userName = '';
+            $address = '';
+            $mobile = '';
+            $orderAt = '';
+            $orderDetails = '';
+            $orderSn = '';
+            $orderAmount = 0;  // 合计总额
+            $orderNums = 0; // 合计总数
+            $totalTaxAmount = 0; // 合计税额
+            $totalAmount = 0;
+            foreach ($oderList as $key => $value) {
+                $provinceName = $value['PROVINCE'] ? Region::getCnName($value['PROVINCE']) : '';
+                $cityName = $value['CITY'] ? Region::getCnName($value['CITY']) : '';
+                $countyName = $value['COUNTY'] ? Region::getCnName($value['COUNTY']) : '';
+
+                $userId = $value['USER_NAME'];
+                $userName = $value['REAL_NAME'];
+                $address = $provinceName . $cityName . $countyName . $value['ADDRESS'];
+                $mobile = $value['MOBILE'];
+                $orderAt = Date::convert($value['CREATED_AT'],'Y-m-d H:i:s');
+                $orderSn = $value['SN'];
+                // 总价
+                $totalAmount = $value['BUY_NUMS'] * $value['REAL_PRICE'];
+                $orderAmount += $totalAmount;
+                $orderNums += $value['BUY_NUMS'];
+                // 税额
+                $taxAmount = Tool::calculateTax($value['REAL_PRICE'], $value['TAX_RATE'], $value['BUY_NUMS']);
+                $totalTaxAmount += $taxAmount;
+                $taxAmount = Tool::formatAmount($taxAmount);
+                $totalAmount = Tool::formatAmount($totalAmount);
+                // 订单详情
+                $orderDetails .= <<<EOT
+                <tr>
+                    <td>{$value['SKU_CODE']}</td>
+                    <td>{$value['GOODS_TITLE']}</td>
+                    <td style="text-align: right;">{$value['REAL_PRICE']}</td>
+                    <td>{$value['BUY_NUMS']}</td>
+                    <td style="text-align: right;">{$value['TAX_RATE']}</td>
+                    <td style="text-align: right;">{$taxAmount}</td>
+                    <td style="text-align: right;">{$totalAmount}</td> 
+                </tr>
+EOT;
+            }
+
+            // 订单基本信息
+            $orderBase = <<<ORDER
+            <table border="1" style="table-layout: fixed; padding: 10px 20px;" width="100%">
+                <tr>
+                    <td width="30%" style="font-weight: bold; text-align: center; font-size: 14px;">Member code</td>
+                    <td width="70%">{$userId}</td>
+                </tr>
+                <tr>
+                    <td width="30%" style="font-weight: bold; text-align: center; font-size: 14px;">Member name</td>
+                    <td width="70%">{$userName}</td>
+                </tr>
+                <tr>
+                    <td width="30%" style="font-weight: bold; text-align: center; font-size: 14px;">Member address</td>
+                    <td width="70%">{$address}</td>
+                </tr>
+                <tr>
+                    <td width="30%" style="font-weight: bold; text-align: center; font-size: 14px;">Member phone</td>
+                    <td width="70%">{$mobile}</td>
+                </tr>
+                <tr>
+                    <td width="30%" style="font-weight: bold; text-align: center; font-size: 14px;">Order code</td>
+                    <td width="70%">{$orderSn}</td>
+                </tr>
+                <tr>
+                    <td width="30%" style="font-weight: bold; text-align: center; font-size: 14px;">Creation time</td>
+                    <td width="70%">{$orderAt}</td>
+                </tr>
+                <tr>
+                    <td class="bg" style="font-weight: bold; font-size: 14px; text-align: center;">Order detail</td>
+                    <td class="bg"></td>
+                </tr>
+            </table>
+ORDER;
+
+            $l['a_meta_charset'] = 'UTF-8';
+            $l['a_meta_dir'] = 'ltr';
+            $l['a_meta_language'] = 'zh';
+            $l['w_page'] = '页面';
+
+            $orderAmount = Tool::formatAmount($orderAmount);
+            $totalTaxAmount = Tool::formatAmount($totalTaxAmount);
+
+            $context = <<<ORDER
+            <!doctype html>
+            <html lang="en">
+            <head>
+                <meta charset="UTF-8" />
+                <title>Order detail</title>
+                <style>
+                    table {
+                        border-collapse: collapse;
+                    }
+                    table td, table th {
+                        border: 1px solid #ccc;
+                        padding: 5px 5px;
+                        border-collapse: collapse;
+                    }
+                    /*td {*/
+                    /*    padding: 120px;*/
+                    /*}*/
+                    .bg {
+                        background-color: #ccc;
+                    }
+                </style>
+            </head>
+            <body>
+                <div class="content">
+                    <p style="text-align: center; font-weight: bold; font-size: 22px;"><b>Order detail</b><br></p>
+                    <div>
+                        <div style="display: block; width: 100%;">
+                            {$orderBase}
+                            
+                            <table border="1" width="100%" style="padding: 10px 5px; text-align: center;">
+                                <tr>
+                                    <th width="15%" style="font-size: 14px; font-weight: bold; text-align: center;">Product Code</th>
+                                    <th width="25%" style="font-size: 14px; font-weight: bold; text-align: center;">Product Name</th>
+                                    <th width="15%" style="font-size: 14px; font-weight: bold; text-align: center;">Product Price</th>
+                                    <th width="10%" style="font-size: 14px; font-weight: bold; text-align: center;">Qty</th>
+                                    <th width="10%" style="font-size: 14px; font-weight: bold; text-align: center;">Tax Rate</th>
+                                    <th width="10%" style="font-size: 14px; font-weight: bold; text-align: center;">Tax</th>
+                                    <th width="15%" style="font-size: 14px; font-weight: bold; text-align: center;">Total Amount</th>
+                                </tr>
+                                {$orderDetails}
+                                <tr>
+                                    <td colspan="3">Total</td>
+                                    <td>{$orderNums}</td>
+                                    <td></td>
+                                    <td style="text-align: right;">{$totalTaxAmount}</td>
+                                    <td style="text-align: right;">{$orderAmount}</td>
+                                </tr>
+                            </table>
+                        </div>
+                        
+                        <div style="width: 100%; margin-top: 50px; height: 30px;">
+                            <table width="100%" style="border: none; padding: 10px 20px; text-align: center;">
+                                <tr style="border: none;">
+                                    <td width="70%" style="border: none;"></td>
+                                    <td width="30%" style="font-weight: bold; text-align: left; font-size: 14px; border: none;">Signature:</td>
+                                </tr>
+                                <tr style="border: none;">
+                                    <td width="70%" style="border: none;"></td>
+                                    <td width="30%" style="font-weight: bold; text-align: left; font-size: 14px; border: none;">Date:</td>
+                                </tr>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </body>
+            </html>
+ORDER;
+
+            require_once (\Yii::$app->vendorPath . '/tecnickcom/tcpdf/tcpdf.php');
+
+            $pdf = new \TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
+            // 设置打印模式
+            $pdf->SetCreator(PDF_CREATOR);
+            $pdf->SetAuthor('DaZe');
+            $pdf->SetTitle($orderSn);
+            $pdf->SetSubject('TCPDF Tutorial');
+            $pdf->SetKeywords('TCPDF, PDF, example, test, guide');
+            // 是否显示页眉
+            $pdf->setPrintHeader(false);
+            // 设置页眉字体
+            $pdf->setHeaderFont(Array('dejavusans', '', '12'));
+            // 页眉距离顶部的距离
+            $pdf->SetHeaderMargin('5');
+            // 是否显示页脚
+            $pdf->setPrintFooter(false);
+            // 设置默认等宽字体
+            $pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED);
+            // 设置行高
+            $pdf->setCellHeightRatio(1);
+            // 设置左、上、右的间距
+            $pdf->SetMargins('10', '10', '10');
+            // 设置是否自动分页  距离底部多少距离时分页
+            $pdf->SetAutoPageBreak(TRUE, '15');
+            // 设置图像比例因子
+            $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
+            if (@file_exists(\Yii::$app->vendorPath . 'tecnickcom/tcpdf/examples/lang/eng.php')) {
+                require_once(\Yii::$app->vendorPath . '/tecnickcom/tcpdf/examples/lang/eng.php');
+                $pdf->setLanguageArray($l);
+            }
+            $pdf->setFontSubsetting(true);
+            $pdf->AddPage();
+            // 设置字体
+            $pdf->SetFont('stsongstdlight', '', 10, '', true);
+            $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
+            $image = file_get_contents(\Yii::$app->basePath . '/../frontendEle/src/static/img/ngds-logo.jpg');
+            $pdf->Image('@' . $image, 165, 5, 20, 10, 'JPG');
+            $pdf->writeHTML($context);
+            $pdf->Output($realFile, 'F');
+
+            $this->_updateFirst($realFile, 1);
+        }
+
+        $this->complete();
+        return true;
+    }
+
+    /**
+     * 循环写入数据
+     * @param $realFile
+     * @param $fileNameUpdated
+     * @param int $page
+     * @param int $counter
+     * @return array
+     * @throws Exception
+     */
+    private function _loopWriteDataPDF($realFile, &$fileNameUpdated, $page = 0, $counter = 0){
+        if(method_exists($this->_listModel, 'getList')){
+            $list = $this->_listModel->getList(['condition'=>$this->params['condition'], 'params'=>isset($this->params['params']) ? $this->params['params'] : [], 'others'=>isset($this->params['others']) ? $this->params['others'] : [], 'page'=>$page, 'pageSize'=>$this->pageSize, 'userId'=>$this->userId]);
+        } else {
+            throw new Exception($this->listModelClass.'的getList方法不存在');
+        }
+
+        return $list['list'];
+    }
+}

+ 10 - 0
common/libs/export/module/AtlasExport.php

@@ -0,0 +1,10 @@
+<?php
+namespace common\libs\export\module;
+
+use common\libs\export\BaseExport;
+
+class AtlasExport extends BaseExport
+{
+    public $moduleId = 'atlas';
+
+}

+ 10 - 0
common/libs/export/module/BonusExport.php

@@ -0,0 +1,10 @@
+<?php
+namespace common\libs\export\module;
+
+use common\libs\export\BaseExport;
+
+class BonusExport extends BaseExport
+{
+    public $moduleId = 'bonus';
+
+}

+ 10 - 0
common/libs/export/module/FinanceExport.php

@@ -0,0 +1,10 @@
+<?php
+namespace common\libs\export\module;
+
+use common\libs\export\BaseExport;
+
+class FinanceExport extends BaseExport
+{
+    public $moduleId = 'finance';
+
+}

+ 10 - 0
common/libs/export/module/LogExport.php

@@ -0,0 +1,10 @@
+<?php
+namespace common\libs\export\module;
+
+use common\libs\export\BaseExport;
+
+class LogExport extends BaseExport
+{
+    public $moduleId = 'log';
+
+}

+ 10 - 0
common/libs/export/module/OrderExport.php

@@ -0,0 +1,10 @@
+<?php
+namespace common\libs\export\module;
+
+use common\libs\export\BaseExport;
+
+class OrderExport extends BaseExport
+{
+    public $moduleId = 'order';
+
+}

+ 10 - 0
common/libs/export/module/ReconsumeExport.php

@@ -0,0 +1,10 @@
+<?php
+namespace common\libs\export\module;
+
+use common\libs\export\BaseExport;
+
+class ReconsumeExport extends BaseExport
+{
+    public $moduleId = 'reconsume';
+
+}

+ 10 - 0
common/libs/export/module/ShopExport.php

@@ -0,0 +1,10 @@
+<?php
+namespace common\libs\export\module;
+
+use common\libs\export\BaseExport;
+
+class ShopExport extends BaseExport
+{
+    public $moduleId = 'shop';
+
+}

+ 10 - 0
common/libs/export/module/UserExport.php

@@ -0,0 +1,10 @@
+<?php
+namespace common\libs\export\module;
+
+use common\libs\export\BaseExport;
+
+class UserExport extends BaseExport
+{
+    public $moduleId = 'user';
+
+}

+ 120 - 0
common/libs/lock/RedisLock.php

@@ -0,0 +1,120 @@
+<?php
+namespace common\libs\lock;
+
+use Yii;
+use yii\base\BaseObject;
+use yii\base\StaticInstanceTrait;
+
+/**
+ * Redis分布锁
+ * 使用锁:
+ * $key = 'lock';
+ * if (!$lock->lock($key)) {
+ * 	throw new Exception('error');
+ * }
+ */
+class RedisLock extends BaseObject {
+    use StaticInstanceTrait;
+    /**
+     * 前缀
+     */
+    const KEY_PREFIX = 'BonusRedisLock:';
+    /**
+     * 重试次数,0:无限重试
+     * @var int
+     */
+    public $retryCount = 0;
+    /**
+     * 获取锁失败重试等待的时间,微妙 ,1ms=1000us
+     * @var int
+     */
+    public $retryInterval = 100000;
+    /**
+     * 获取锁失败,是否重试
+     * @var bool
+     */
+    public $retry = true;
+    /**
+     * 锁的超时时间,防止死锁发生,应该是业务的最大处理时间
+     * @var int
+     */
+    public $expire = 5;
+    /**
+     * Redis 对象
+     * @var null
+     */
+    public $redis = null;
+    /**
+     * @inheritdoc
+     */
+    public function init() {
+        parent::init();
+    }
+
+    /**
+     * 获取redis对象
+     * @return mixed|null
+     */
+    public function getRedis(){
+        if(!is_null($this->redis)){
+            return $this->redis;
+        }
+        $this->redis = Yii::$app->redis;
+        return $this->redis;
+    }
+
+    /**
+     * @return float
+     */
+    public static function microTime() {
+        // 获取当前毫秒时间戳
+        list ($s1, $s2) = explode(' ', microtime());
+        $currentTime = (float) sprintf('%.0f', (floatval($s1) + floatval($s2)) * 1000);
+        return $currentTime;
+    }
+
+    /**
+     * @param $key
+     * @return string
+     */
+    public static function getKey($key){
+        return self::KEY_PREFIX . $key;
+    }
+
+    /**
+     * 加锁
+     * @param $key
+     * @return bool
+     */
+    public function lock($key) {
+        $count = 0;
+        $key = self::getKey($key);
+        while (true) {
+            $nowTime = self::microtime();
+            $lockValue = self::microtime() + $this->expire;
+            $lock = $this->getRedis()->setnx($key, $lockValue);
+            if (!empty($lock) || ($this->getRedis()->get($key) < $nowTime && $this->getRedis()->getset($key, $lockValue) < $nowTime )) {
+                $this->getRedis()->expire($key, $this->expire);
+                return true;
+            } elseif ($this->retry && (($this->retryCount > 0 && ++$count < $this->retryCount) || ($this->retryCount == 0))) {
+                usleep($this->retryInterval);
+            } else {
+                break;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 解锁
+     * @param $key
+     * @return bool
+     */
+    public function unlock($key) {
+        $key = self::getKey($key);
+        if ($this->getRedis()->ttl($key)) {
+            return $this->getRedis()->del($key);
+        }
+        return true;
+    }
+}

+ 76 - 0
common/libs/logging/login/AdminLogin.php

@@ -0,0 +1,76 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019-07-30
+ * Time: 16:47
+ */
+
+namespace common\libs\logging\login;
+
+use common\helpers\Form;
+use common\models\forms\LogAdminLoginForm;
+use common\helpers\Date;
+use common\models\Period;
+use Yii;
+
+class AdminLogin {
+
+    /**
+     * 登录成功
+     * @param $adminInfo
+     * @return LogAdminLoginForm
+     * @throws \Exception
+     */
+    public static function success($adminInfo){
+        $returnResult = '';
+        $successTimes = intval($adminInfo['LOGIN_NUMS']) + 1;
+        $result = self::recorder($adminInfo['ADMIN_NAME'], '1', $returnResult, $adminInfo['FAIL_NUMS'], $successTimes);
+        return $result;
+    }
+
+    /**
+     * 登录失败
+     * @param $adminInfo
+     * @param $returnResult
+     * @return LogAdminLoginForm
+     * @throws \Exception
+     */
+    public static function fail($adminInfo, $returnResult){
+        $failTimes = intval($adminInfo['FAIL_NUMS']) + 1;
+        $result = self::recorder($adminInfo['ADMIN_NAME'], '0', $returnResult, $failTimes, $adminInfo['LOGIN_NUMS']);
+        return $result;
+    }
+
+    /**
+     * 记录器
+     * @param $account
+     * @param $optType
+     * @param $returnResult
+     * @param $failTimes
+     * @param $successTimes
+     * @return LogAdminLoginForm
+     * @throws \Exception
+     */
+    public static function recorder($account, $optType, $returnResult, $failTimes, $successTimes){
+        $period = Period::instance();
+        $periodNum = $period->getNowPeriodNum();
+        $form = new LogAdminLoginForm([
+            'adm_name' => trim($account),
+            'ip' => Yii::$app->request->getUserIP(),
+            'created_at' => intval(Date::nowTime()),
+            'user_agent' => Yii::$app->request->getUserAgent(),
+            'period_num' => intval($periodNum),
+            'opt_type' => $optType,
+            'success_times' => intval($successTimes),
+            'fail_times' => intval($failTimes),
+            'device' => Yii::$app->request->getDevice(),
+            'request_route' => Yii::$app->requestedRoute,
+            'return_result' => $returnResult,
+        ]);
+        if(!$form->add()){
+            throw new \Exception(Form::formatErrorsForApi($form->getErrors()));
+        }
+        return $form;
+    }
+}

+ 76 - 0
common/libs/logging/login/UserLogin.php

@@ -0,0 +1,76 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019-07-30
+ * Time: 16:47
+ */
+
+namespace common\libs\logging\login;
+
+use common\helpers\Date;
+use common\helpers\Form;
+use common\models\forms\LogUserLoginForm;
+use common\models\Period;
+use Yii;
+
+class UserLogin {
+
+    /**
+     * 登录成功
+     * @param $userInfo
+     * @return LogUserLoginForm
+     * @throws \Exception
+     */
+    public static function success($userInfo){
+        $returnResult = '';
+        $successTimes = intval($userInfo['LOGIN_NUMS']) + 1;
+        $result = self::recorder($userInfo['USER_NAME'], '1', $returnResult, $userInfo['FAIL_NUMS'], $successTimes);
+        return $result;
+    }
+
+    /**
+     * 登录失败
+     * @param $userInfo
+     * @param $returnResult
+     * @return LogUserLoginForm
+     * @throws \Exception
+     */
+    public static function fail($userInfo, $returnResult){
+        $failTimes = intval($userInfo['FAIL_NUMS']) + 1;
+        $result = self::recorder($userInfo['USER_NAME'], '0', $returnResult, $failTimes, $userInfo['LOGIN_NUMS']);
+        return $result;
+    }
+
+    /**
+     * 记录器
+     * @param $account
+     * @param $optType
+     * @param $returnResult
+     * @param $failTimes
+     * @param $successTimes
+     * @return LogUserLoginForm
+     * @throws \Exception
+     */
+    public static function recorder($account, $optType, $returnResult, $failTimes, $successTimes){
+        $period = Period::instance();
+        $periodNum = $period->getNowPeriodNum();
+        $form = new LogUserLoginForm([
+            'user_name' => trim($account),
+            'ip' => Yii::$app->request->getUserIP(),
+            'created_at' => intval(Date::nowTime()),
+            'user_agent' => Yii::$app->request->getUserAgent(),
+            'period_num' => intval($periodNum),
+            'opt_type' => $optType,
+            'success_times' => intval($successTimes),
+            'fail_times' => intval($failTimes),
+            'device' => Yii::$app->request->getDevice(),
+            'request_route' => Yii::$app->requestedRoute,
+            'return_result' => $returnResult,
+        ]);
+        if(!$form->add()){
+            throw new \Exception(Form::formatErrorsForApi($form->getErrors()));
+        }
+        return $form;
+    }
+}

+ 589 - 0
common/libs/logging/operate/AbstractOperate.php

@@ -0,0 +1,589 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019-07-31
+ * Time: 09:22
+ */
+
+namespace common\libs\logging\operate;
+
+
+use common\helpers\Form;
+use common\helpers\Tool;
+use common\libs\logging\operate\provider\BatchProvider;
+use common\libs\logging\operate\provider\SingleProvider;
+use common\models\User;
+use SebastianBergmann\CodeCoverage\Report\PHP;
+use yii\base\Component;
+use yii\base\ErrorException;
+use yii\base\Exception;
+use yii\helpers\ArrayHelper;
+
+abstract class AbstractOperate extends Component {
+    /**
+     * 保存日志的model
+     * @var null
+     */
+    public $logModel = null;
+    /**
+     * 保存日志的form
+     * @var null
+     */
+    public $logForm = null;
+    /**
+     * 用于查询保存存数据的model
+     * @var
+     */
+    public $fetchClass = null;
+    /**
+     * @var array
+     */
+    public $attrLabels = [];
+    /**
+     * @var null
+     */
+    public $saveBeforeContent = null;
+    /**
+     * @var null
+     */
+    public $saveAfterContent = null;
+    /**
+     * @var array
+     */
+    public $columns = [];
+    /**
+     * @var null
+     */
+    public $data = null;
+    /**
+     * @var null
+     */
+    protected $_dataType = null;
+    /**
+     * @var array
+     */
+    public $params = [];
+    /**
+     * @var bool
+     */
+    protected $_isBatch = false;
+    /**
+     * @var string
+     */
+    protected $_batchField = 'ID';
+    /**
+     * @var string
+     */
+    protected $_optObjField = 'USER_ID';
+    /**
+     * @var null
+     */
+    protected $_errors = null;
+    /**
+     * Factory constructor.
+     * @param array $config
+     */
+    public function __construct($config = []) {
+        parent::__construct($config);
+    }
+
+    /**
+     * @param array $format
+     * @param array $params
+     * @return array
+     */
+    public function paramsFormat(array $format, array $params = []){
+        if(!$params){
+            return $format;
+        }
+        return ArrayHelper::merge($format, $params);
+    }
+
+    /**
+     * @return |null
+     */
+    public function getDataType(){
+        return $this->_dataType;
+    }
+
+    /**
+     * @param $value
+     * @return $this
+     */
+    public function setDataType($value){
+        $this->_dataType = $value;
+        return $this;
+    }
+    /**
+     * @return mixed
+     */
+    public function getIsBatch(){
+        return $this->_isBatch;
+    }
+
+    /**
+     * @param $value
+     * @return $this
+     */
+    public function setIsBatch($value){
+        $this->_isBatch = $value;
+        return $this;
+    }
+
+    /**
+     * @param $value
+     * @return $this
+     */
+    public function setBatchField($value){
+        $this->_batchField = $value;
+        return $this;
+    }
+
+    /**
+     * @param $value
+     * @return $this
+     */
+    public function setOptObjField($value){
+        $this->_optObjField = $value;
+        return $this;
+    }
+
+
+    /**
+     * @param $value
+     * @return $this
+     */
+    public function setSaveBeforeContent($value){
+        $this->saveBeforeContent = $value;
+        return $this;
+    }
+
+    /**
+     * @param $value
+     * @return $this
+     */
+    public function setSaveAfterContent($value){
+        $this->saveAfterContent = $value;
+        return $this;
+    }
+
+    /**
+     * 分发器
+     * @param $method
+     * @return array|null
+     */
+    public function dispatcher($method){
+        $initParams = $this->paramsFormat([
+            'data' => $this->data,
+            'fetchClass' => $this->fetchClass,
+            'dataType' => $this->_dataType,
+            'attrLabels' => $this->attrLabels,
+        ], $this->params);
+        if($this->_isBatch){
+            $entryClass = new BatchProvider($initParams);
+        }else{
+            $entryClass = new SingleProvider($initParams);
+        }
+        if(method_exists($entryClass, $method)){
+            $entryClass->{$method}();
+            return $entryClass->result;
+        }
+        return null;
+    }
+
+    /**
+     * 更新之前
+     * @param null $data
+     * @param null $dataType
+     * @param array $params
+     * @return $this|bool
+     */
+    public function beforeUpdate($data = null, $dataType = null, array $params = []){
+        if(!$data){
+            return false;
+        }
+        $this->data = $data;
+        $this->_dataType = $dataType;
+        $this->params = $params;
+        $this->saveBeforeContent = $this->dispatcher('beforeUpdate');
+        return $this;
+    }
+
+    /**
+     * @param $data
+     * @param null $dataType
+     * @param array $params
+     * @return $this|bool
+     */
+    public function afterUpdate($data, $dataType = null, array $params = []){
+        if(!$data){
+            return false;
+        }
+        $this->data = $data;
+        $this->_dataType = $dataType;
+        $this->params = $params;
+        $this->saveAfterContent = $this->dispatcher('afterUpdate');
+        return $this;
+    }
+
+    /**
+     * @param $data
+     * @param null $dataType
+     * @param array $params
+     * @return $this|bool
+     */
+    public function beforeDelete($data, $dataType = null,  array $params = []){
+        if(!$data){
+            return false;
+        }
+        $this->data = $data;
+        $this->params = $params;
+        $this->_dataType = $dataType;
+        $this->saveBeforeContent = $this->dispatcher('beforeDelete');
+        return $this;
+    }
+
+    /**
+     * @param $data
+     * @param string $dataType
+     * @param array $params
+     * @return $this|bool
+     */
+    public function afterInsert($data, $dataType = 'ID', array $params = []){
+        if(!$data || !$dataType){
+            return false;
+        }
+        $this->data = $data;
+        $this->params = $params;
+        $this->_dataType = $dataType;
+
+        $this->saveAfterContent = $this->dispatcher('afterInsert');
+        return $this;
+    }
+
+    /**
+     * @return $this
+     */
+    public function clean(){
+        $this->columns = [];
+        return $this;
+    }
+
+    /**
+     * @param array $params
+     * @return bool
+     */
+    public function save(array $params = []){
+        $this->setColumn($params);
+        if(!$this->columns){
+            return false;
+        }
+//        $taskKey = \Yii::$app->swooleAsyncTimer->asyncHandle('log/save',  [
+//            'logModel' => $this->logModel,
+//            'logForm' => $this->logForm,
+//            'isBatch' => $this->_isBatch,
+//            'batchField' => $this->_batchField,
+//            'optObjField' => $this->_optObjField,
+//            'columns' => $this->columns,
+//        ]);
+//        if ($taskKey === false) {
+//            $this->setErrors('异步请求日志服务失败');
+//            return false;
+//        }
+
+        if($this->_isBatch){
+            $modelClass = $this->logModel;
+            // mongodb 的批量添加要特殊处理
+            $db = $modelClass::getDb();
+            $collectionName = $modelClass::collectionName();
+            if(is_array($collectionName)){
+                $collection = count($collectionName) > 1 ? $collectionName[1] : $collectionName[0];
+            }else{
+                $collection = $collectionName;
+            }
+
+            $batchList = [];
+            $saveAfterContent = $this->columns[0]['save_after_content'] ?? [];
+            $saveBeforeContent = $this->columns[0]['save_before_content'] ?? [];
+
+            if( $saveBeforeContent && $saveAfterContent ) {
+                $saveBeforeContentList = [];
+                foreach ($saveBeforeContent as $key => $beforeItem) {
+                    if (isset($beforeItem[$this->_batchField])) {
+                        $saveBeforeContentList[$beforeItem[$this->_batchField]['value']] = $beforeItem;
+                    } else {
+                        $saveBeforeContentList[$key] = $beforeItem;
+                    }
+                }
+                foreach ($saveAfterContent as $key => $afterItem) {
+                    if (isset($afterItem[$this->_batchField])) {
+                        if (!isset($saveBeforeContentList[$afterItem[$this->_batchField]['value']])) continue;
+
+                        $beforeItem = $saveBeforeContentList[$afterItem[$this->_batchField]['value']];
+                    } else {
+                        if (!isset($saveBeforeContentList[$key])) continue;
+
+                        $beforeItem = $saveBeforeContentList[$key];
+                    }
+
+                    $mergeData = [
+                        'save_before_content' => $beforeItem,
+                        'save_after_content' => $afterItem,
+                    ];
+
+                    //记录opt_obj_id 和 opt_obj_name @todo待优化
+                    $this->_fixOptObjField($collection, $beforeItem);
+
+                    $batchList[] = array_merge($this->columns[0], $mergeData);
+                }
+                unset($saveBeforeContentList);
+            }else if( $saveBeforeContent && !$saveAfterContent ) {
+                foreach ($saveBeforeContent as $key => $beforeItem) {
+                    $mergeData = [
+                        'save_before_content' => $beforeItem,
+                        'save_after_content' => null,
+                    ];
+                    //记录opt_obj_id 和 opt_obj_name @todo待优化
+                    $this->_fixOptObjField($collection, $beforeItem);
+                    $batchList[] = array_merge($this->columns[0], $mergeData);
+                }
+            }else if( !$saveBeforeContent && $saveAfterContent ) {
+                foreach ($saveAfterContent as $key => $afterItem) {
+                    $mergeData = [
+                        'save_before_content' => null,
+                        'save_after_content' => $afterItem,
+                    ];
+
+                    //记录opt_obj_id 和 opt_obj_name @todo待优化
+                    $this->_fixOptObjField($collection, $afterItem);
+
+                    $batchList[] = array_merge($this->columns[0], $mergeData);
+                }
+            }else {
+                $this->setErrors('日志批量写入错误');
+                return false;
+            }
+            unset($saveBeforeContent, $saveAfterContent);
+
+            if (!$batchList) {
+                $this->setErrors('数据无效');
+                return false;
+            }
+            if(!$db->createCommand()->batchInsert($collection, $batchList)){
+                $this->setErrors('日志批量写入失败');
+                return false;
+            }
+
+//            if(!$db->createCommand()->batchInsert($collection, $this->columns)){
+//                $this->setErrors('日志批量写入失败');
+//                return false;
+//            }
+            return true;
+        }else {
+            $modelFormClass = $this->logForm;
+            $logForm = new $modelFormClass($this->columns[0]);
+            if(!$logForm->add()){
+                $this->setErrors($logForm->getErrors());
+                return false;
+            }
+        }
+
+
+        return true;
+    }
+
+    /**
+     * 异步保存(异步控制器调用此方法)
+     * @return bool
+     */
+    public function asyncSave() {
+        try {
+            if( !$this->columns || !isset($this->columns[0]) ) {
+                throw new Exception('储存的数据错误');
+            }
+            if ($this->_isBatch) {
+                $this->_batchInsert();
+            } else {
+                $modelFormClass = $this->logForm;
+                $logForm = new $modelFormClass($this->columns[0]);
+                if (!$logForm->add()) {
+                    throw new Exception(Form::formatErrorsForApi($logForm->getErrors()));
+                }
+            }
+            return true;
+        }catch (\Exception $e) {
+            $this->setErrors($e->getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * console里面记日志,直接保存,无需调用异步,因为本身就是异步
+     * @param array $params
+     * @return bool
+     */
+    public function saveByConsole(array $params = []){
+        $this->setColumn($params);
+        if(!$this->columns){
+            return false;
+        }
+
+        try {
+            if( !$this->columns || !isset($this->columns[0]) ) {
+                throw new Exception('储存的数据错误');
+            }
+
+            if ($this->_isBatch) {
+                $this->_batchInsert();
+            } else {
+                $modelFormClass = $this->logForm;
+                $logForm = new $modelFormClass($this->columns[0]);
+                if (!$logForm->add()) {
+                    throw new Exception(Form::formatErrorsForApi($logForm->getErrors()));
+                }
+            }
+        }catch (\Exception $e) {
+            $this->setErrors($e->getMessage());
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 批量写入
+     * @return bool
+     * @throws ErrorException
+     * @throws \yii\base\InvalidConfigException
+     */
+    protected function _batchInsert() {
+        $modelClass = $this->logModel;
+        // mongodb 的批量添加要特殊处理
+        $db = $modelClass::getDb();
+        $collectionName = $modelClass::collectionName();
+        if(is_array($collectionName)){
+            $collection = count($collectionName) > 1 ? $collectionName[1] : $collectionName[0];
+        }else{
+            $collection = $collectionName;
+        }
+
+        $batchList = [];
+        $saveAfterContent = $this->columns[0]['save_after_content'] ?? [];
+        $saveBeforeContent = $this->columns[0]['save_before_content'] ?? [];
+
+        if( $saveBeforeContent && $saveAfterContent ) {
+            $saveBeforeContentList = [];
+            foreach ($saveBeforeContent as $key => $beforeItem) {
+                if (isset($beforeItem[$this->_batchField])) {
+                    $saveBeforeContentList[$beforeItem[$this->_batchField]['value']] = $beforeItem;
+                } else {
+                    $saveBeforeContentList[$key] = $beforeItem;
+                }
+            }
+            foreach ($saveAfterContent as $key => $afterItem) {
+                if (isset($afterItem[$this->_batchField])) {
+                    if (!isset($saveBeforeContentList[$afterItem[$this->_batchField]['value']])) continue;
+
+                    $beforeItem = $saveBeforeContentList[$afterItem[$this->_batchField]['value']];
+                } else {
+                    if (!isset($saveBeforeContentList[$key])) continue;
+
+                    $beforeItem = $saveBeforeContentList[$key];
+                }
+
+                $mergeData = [
+                    'save_before_content' => $beforeItem,
+                    'save_after_content' => $afterItem,
+                ];
+
+                //记录opt_obj_id 和 opt_obj_name @todo待优化
+                $this->_fixOptObjField($collection, $beforeItem);
+
+                $batchList[] = array_merge($this->columns[0], $mergeData);
+            }
+            unset($saveBeforeContentList);
+        }else if( $saveBeforeContent && !$saveAfterContent ) {
+            foreach ($saveBeforeContent as $key => $beforeItem) {
+                $mergeData = [
+                    'save_before_content' => $beforeItem,
+                    'save_after_content' => null,
+                ];
+                //记录opt_obj_id 和 opt_obj_name @todo待优化
+                $this->_fixOptObjField($collection, $beforeItem);
+                $batchList[] = array_merge($this->columns[0], $mergeData);
+            }
+        }else if( !$saveBeforeContent && $saveAfterContent ) {
+            foreach ($saveAfterContent as $key => $afterItem) {
+                $mergeData = [
+                    'save_before_content' => null,
+                    'save_after_content' => $afterItem,
+                ];
+
+                //记录opt_obj_id 和 opt_obj_name @todo待优化
+                $this->_fixOptObjField($collection, $afterItem);
+
+                $batchList[] = array_merge($this->columns[0], $mergeData);
+            }
+        }else {
+            throw new ErrorException('日志批量写入错误');
+        }
+        unset($saveBeforeContent, $saveAfterContent);
+
+        if (!$batchList) {
+            throw new ErrorException('数据无效' . var_export($this->columns, true));
+        }
+        if(!$db->createCommand()->batchInsert($collection, $batchList)){
+            throw new ErrorException('日志批量写入失败');
+        }
+        return true;
+    }
+
+    /**
+     * 修正批量时的补操作对象信息
+     * @param $collection
+     * @param $item
+     * @return bool
+     * @throws \yii\base\InvalidConfigException
+     */
+    private function _fixOptObjField($collection, $item) {
+        if( $collection !== 'ar_bonus_admin_log') {
+            return false;
+        }
+        if( !isset( $item[$this->_optObjField] ) ) {
+            return false;
+        }
+
+        $this->columns[0]['opt_obj_id'] = $item[$this->_optObjField]['value'];
+
+        if( isset( $item['USER_NAME'] ) ) {
+            $this->columns[0]['opt_obj_name'] = $item['USER_NAME']['value'];
+        }else {
+            //@todo
+            $userOne = User::find()->select(['USER_NAME'])->where('ID=:ID', ['ID'=>$item[$this->_optObjField]['value']])->asArray()->one();
+
+            if( $userOne && isset($userOne['USER_NAME']) ) {
+                $this->columns[0]['opt_obj_name'] = $userOne['USER_NAME'];
+            }
+
+        }
+
+        return true;
+    }
+
+    /**
+     * @param $error
+     * @return $this
+     */
+    public function setErrors($error){
+        $this->_errors = $error;
+        return $this;
+    }
+
+    /**
+     * @return array|null
+     */
+    public function getErrors(){
+        return $this->_errors;
+    }
+}

+ 65 - 0
common/libs/logging/operate/AdminOperate.php

@@ -0,0 +1,65 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019/07/29
+ * Time: 上午9:39
+ */
+
+
+namespace common\libs\logging\operate;
+
+use common\models\forms\LogAdminHandleForm;
+use common\models\LogAdminHandle;
+use common\models\Period;
+use Yii;
+use common\helpers\Date;
+
+class AdminOperate extends AbstractOperate {
+    /**
+     * Factory constructor.
+     * @param array $config
+     */
+    public function __construct($config = []) {
+        parent::__construct($config);
+        if(!$this->logModel){
+            $this->logModel = LogAdminHandle::class;
+        }
+        if(!$this->logForm){
+            $this->logForm = LogAdminHandleForm::class;
+        }
+    }
+
+    /**
+     * set column
+     * @param array $params
+     * @return $this|bool
+     * @throws \Exception
+     */
+    public function setColumn(array $params = []){
+        if(!isset($params['optType'])){
+            throw new \Exception('请设置操作类型');
+        }
+
+        $period = Period::instance();
+        $periodNum = $period->getNowPeriodNum();
+        $this->columns[] = [
+            'user_agent' => Yii::$app->request->getUserAgent(),
+            'created_at' => intval(Date::nowTime()),
+            'period_num' => intval($periodNum),
+            'save_before_content' => $this->saveBeforeContent,
+            'save_after_content' => $this->saveAfterContent,
+            'admin_id' => Yii::$app->user->id,
+            'admin_name' => isset(\Yii::$app->user->getUserInfo()['adminName']) ?\Yii::$app->user->getUserInfo()['adminName'] :$params['adminName'],
+            'ip' => Yii::$app->request->getUserIP(),
+            'request_route' => Yii::$app->requestedRoute,
+            'opt_type' => $params['optType'],
+            'opt_obj_id' => isset($params['userId']) ? $params['userId'] : null,
+            'opt_obj_name' => isset($params['userName']) ? $params['userName'] : 'System',
+            'remark' => isset($params['remark']) ? $params['remark'] : null,
+            'is_batch' => $this->_isBatch ? 1 : 0,
+            'device' => Yii::$app->request->getDevice(),
+        ];
+        return $this;
+    }
+}

+ 65 - 0
common/libs/logging/operate/UserOperate.php

@@ -0,0 +1,65 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019/07/29
+ * Time: 上午9:39
+ */
+
+
+namespace common\libs\logging\operate;
+
+use common\models\forms\LogUserHandleForm;
+use common\models\LogUserHandle;
+use common\models\Period;
+use Yii;
+use common\helpers\Date;
+
+
+class UserOperate extends AbstractOperate {
+    /**
+     * Factory constructor.
+     * @param array $config
+     */
+    public function __construct($config = []) {
+        parent::__construct($config);
+        if(!$this->logModel){
+            $this->logModel = LogUserHandle::class;
+        }
+        if(!$this->logForm){
+            $this->logForm = LogUserHandleForm::class;
+        }
+    }
+
+    /**
+     * @param array $params
+     * @return $this
+     * @throws \Exception
+     */
+    public function setColumn(array $params = []){
+        if(!isset($params['optType'])){
+            throw new \Exception('请设置操作类型');
+        }
+        $period = Period::instance();
+        $periodNum = $period->getNowPeriodNum();
+        $this->columns[] = [
+            'user_agent' => Yii::$app->request->getUserAgent(),
+            'created_at' => intval(Date::nowTime()),
+            'period_num' => intval($periodNum),
+            'save_before_content' => $this->saveBeforeContent,
+            'save_after_content' => $this->saveAfterContent,
+            'user_id' => Yii::$app->user->id,
+            'user_name' => Yii::$app->user->getUserInfo()['userName'],
+            'ip' => Yii::$app->request->getUserIP(),
+            'request_route' => Yii::$app->requestedRoute,
+            'opt_type' => $params['optType'],
+            'opt_obj_id' => isset($params['userId']) ? $params['userId'] : null,
+            'opt_obj_name' => isset($params['userName']) ? $params['userName'] : 'System',
+            'remark' => isset($params['remark']) ? $params['remark'] : null,
+            'key_log' => 1,
+            'is_batch' => $this->_isBatch ? 1 : 0,
+            'device' => Yii::$app->request->getDevice(),
+        ];
+        return $this;
+    }
+}

+ 147 - 0
common/libs/logging/operate/provider/AbstractProvider.php

@@ -0,0 +1,147 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019/07/29
+ * Time: 上午9:39
+ */
+
+namespace common\libs\logging\operate\provider;
+
+use common\helpers\Tool;
+use yii\base\Component;
+use yii\helpers\Json;
+use common\libs\logging\operate\valueType\Config as ValueTypeConfig;
+
+abstract class AbstractProvider extends Component {
+    /**
+     * 基本数据
+     * @var null
+     */
+    public $data = null;
+    /**
+     * 默认data为一个数组,就是form提交的数据
+     * 如果data不是数组,说明传入的data是用来筛选修改、删除前的数据,那么dataType必须传和和data对应的字段名
+     * 如果是批量修改、删除、插入,看BatchProvider
+     * @var string
+     */
+    public $dataType = null;
+    /**
+     * @var null
+     */
+    public $fetchClass = null;
+    /**
+     * @var array
+     */
+    public $attrLabels = [];
+    /**
+     * @var array
+     */
+    public $result = [];
+
+    /**
+     * @var string
+     */
+    public $select = '';
+
+    /**
+     * AbstractParser constructor.
+     * @param array $config
+     */
+    public function __construct($config = []) {
+        parent::__construct($config);
+        if(!$this->dataType){
+            $this->dataType = 'array';
+        }
+    }
+
+    /**
+     * 处理数据
+     * @param $attrLabel
+     * @param $value
+     * @return array
+     */
+    public function handleData($attrLabel, $value){
+        if(!is_array($attrLabel)){
+            $name = $attrLabel;
+            if(is_array($value) || is_object($value)){
+                $value = Json::encode($value);
+            }
+        }else{
+            [$name, $value] = $this->handleComplexValue($attrLabel, $value);
+        }
+        return [
+            'label' => $name,
+            'value' => $value,
+        ];
+    }
+
+    /**
+     * 处理复杂的数据
+     * @param $attrLabel
+     * @param $value
+     * @return array
+     */
+    public function handleComplexValue($attrLabel, $value){
+        $valueTypes = ValueTypeConfig::getValues();
+        $data = isset($attrLabel['data']) ? $attrLabel['data'] : [];
+        $result = '';
+        if(in_array($attrLabel['type'], $valueTypes)){
+            $name = Tool::toCamelize($attrLabel['type']);
+            $class = '\common\libs\logging\operate\valueType\\' . ucfirst($name);
+            if(class_exists($class)){
+                $result = (new $class([
+                    'data' => isset($attrLabel['data']) ? $attrLabel['data'] : [],
+                    'value' => $value,
+                ]))->result();
+            }
+        }else if($attrLabel['type'] instanceof \Closure){
+            $result = call_user_func($attrLabel['type'], [
+                'data' => $data,
+                'value' => $value,
+            ]);
+        }else{
+            if(is_array($value) || is_object($value)){
+                $result = Json::encode($value);
+            }else{
+                $result = $value;
+            }
+        }
+        return [$attrLabel['label'], $result];
+    }
+    /**
+     * 获取标签
+     * @return bool
+     */
+    public function getLabels(){
+        if(!$this->fetchClass){
+            return false;
+        }
+        $fetchClass = $this->fetchClass;
+        $class = new $fetchClass();
+        $this->attrLabels = $class->attrLabelsWithLogType();
+        return true;
+    }
+
+    /**
+     * 判断是否需要组合数据
+     * 如:会员在前台修改开户行时,地区的字段是分开的,分为BANK_PROVINCE, BANK_CITY, BANK_COUNTY
+     * 我需要把他们三个组合在一起现实为"所在地区"
+     * 'BANK_PROVINCE' => [
+    'label' => '银行所在地区',
+    'type' => ValueTypeConfig::AREA_TYPE,
+    'compose' => [
+    'BANK_CITY', 'BANK_COUNTY',
+    ],
+    ],
+     * @param $attrLabel
+     * @return bool
+     */
+    public function needCompose($attrLabel){
+        if(isset($attrLabel['compose']) && !empty($attrLabel['compose'])){
+            return true;
+        }
+        return false;
+    }
+
+}

+ 89 - 0
common/libs/logging/operate/provider/BatchProvider.php

@@ -0,0 +1,89 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019/07/29
+ * Time: 上午9:39
+ */
+
+namespace common\libs\logging\operate\provider;
+
+
+class BatchProvider extends AbstractProvider {
+
+    /**
+     * 格式化 sql 中的 in
+     * @param array $array
+     * @return string
+     */
+    public static function staticFormatIn(array $array){
+        $array = array_unique($array);
+        $in = "'" . implode("','", $array) . "'";
+        return $in;
+    }
+
+    /**
+     * 标准化
+     * @param $data
+     * @return array|bool
+     */
+    public function normalize($data){
+        if(!$data){
+            return false;
+        }
+        if(!$this->getLabels()){
+            return false;
+        }
+        $result = [];
+        foreach($data as $key => $value){
+            if(!isset($this->attrLabels[$key])) continue;
+            if($this->needCompose($this->attrLabels[$key])){
+                $value = [$value];
+                foreach($this->attrLabels[$key]['compose'] as $field){
+                    if(isset($data[$field])){
+                        $value[] = $data[$field];
+                    }
+                }
+            }
+            $result[$key] = $this->handleData($this->attrLabels[$key], $value);
+        }
+        return $result;
+    }
+
+    /**
+     * 批量获取数据
+     */
+    public function batchGetData(){
+        if(!$this->data){
+            return ;
+        }
+        $this->data = array_unique($this->data);
+        $ids = self::staticFormatIn($this->data);
+        $fetchClass = $this->fetchClass;
+        if($this->select){
+            $list = $fetchClass::find()->select($this->select)->where("{$this->dataType} IN (".$ids.")")->indexBy($this->dataType)->asArray()->all();
+        }else{
+            $list = $fetchClass::find()->selectNoText()->where("{$this->dataType} IN (".$ids.")")->indexBy($this->dataType)->asArray()->all();
+        }
+        $this->result = [];
+        if($list){
+            foreach($this->data as $id){
+                if(isset($list[$id])){
+                    $this->result[$id] = $this->normalize($list[$id]);
+                }
+            }
+        }
+    }
+
+    public function beforeDelete(){
+        $this->batchGetData();
+    }
+
+    public function beforeUpdate(){
+        $this->batchGetData();
+    }
+
+    public function afterUpdate(){
+        $this->batchGetData();
+    }
+}

+ 89 - 0
common/libs/logging/operate/provider/SingleProvider.php

@@ -0,0 +1,89 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019/07/29
+ * Time: 上午9:39
+ */
+
+
+namespace common\libs\logging\operate\provider;
+
+
+use yii\base\Exception;
+
+class SingleProvider extends AbstractProvider {
+
+    /**
+     * 标准化
+     * @param null $data
+     * @return bool
+     */
+    public function normalize($data = null){
+        if(!$data){
+            return false;
+        }
+        if(!$this->getLabels()){
+            return false;
+        }
+        $this->result = [];
+        foreach($data as $key => $value){
+            if(!isset($this->attrLabels[$key])) continue;
+            if($this->needCompose($this->attrLabels[$key])){
+                $value = [$value];
+                foreach($this->attrLabels[$key]['compose'] as $field){
+                    if(isset($data[$field])){
+                        $value[] = $data[$field];
+                    }
+                }
+            }
+            $this->result[$key] = $this->handleData($this->attrLabels[$key], $value);
+        }
+        return true;
+    }
+
+    /**
+     * @return bool
+     */
+    public function singleGetData(){
+        if(!$this->data){
+            return false;
+        }
+        // 说明传入的是delete id
+        // data必须和dateType对应
+        if(is_string($this->data)){
+            if(!$this->dataType){
+                return false;
+            }
+            $fetchClass = $this->fetchClass;
+            if($this->select){
+                $row = $fetchClass::find()->select($this->select)->is($this->data, $this->dataType)->asArray()->one();
+            }else{
+                $row = $fetchClass::find()->selectNoText()->is($this->data, $this->dataType)->asArray()->one();
+            }
+            $this->normalize($row);
+            unset($fetchClass, $row);
+        }else{
+            $this->normalize($this->data);
+        }
+        return true;
+    }
+
+    public function beforeUpdate(){
+        $this->singleGetData();
+    }
+
+    public function afterUpdate(){
+        $this->singleGetData();
+    }
+
+
+    public function beforeDelete(){
+        $this->singleGetData();
+    }
+
+
+    public function afterInsert(){
+        $this->singleGetData();
+    }
+}

+ 23 - 0
common/libs/logging/operate/valueType/ADMIN.php

@@ -0,0 +1,23 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019/07/29
+ * Time: 上午9:39
+ */
+
+namespace common\libs\logging\operate\valueType;
+
+class ADMIN extends AbstractValueType {
+
+    /**
+     * @return mixed|string
+     */
+    public function result(){
+        $value = $this->getValue();
+        $result = \backendApi\modules\v1\models\Admin::findOneAsArray('ID=:ID', [':ID'=>$value], 'ADMIN_NAME');
+        return $result['ADMIN_NAME'];
+    }
+
+
+}

+ 59 - 0
common/libs/logging/operate/valueType/AbstractValueType.php

@@ -0,0 +1,59 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019/07/29
+ * Time: 上午9:39
+ */
+
+
+namespace common\libs\logging\operate\valueType;
+
+use yii\base\Component;
+
+abstract class AbstractValueType extends Component {
+    /**
+     * @var
+     */
+    public $data = [];
+    /**
+     * @var null
+     */
+    public $mapLabel = null;
+    /**
+     * @var
+     */
+    public $value;
+    /**
+     * @var string
+     */
+    public $defaultText = '';
+    /**
+     * AbstractValueType constructor.
+     * @param array $config
+     */
+    public function __construct($config = []) {
+        parent::__construct($config);
+    }
+    public function setValue($value){
+        $this->value = $value;
+    }
+    public function getValue(){
+        return $this->value;
+    }
+
+    /**
+     * 处理传入的值
+     */
+    public function handleValue(){
+        $value = $this->getValue();
+        if(!is_array($value)){
+            if(strpos($value, ',') !== false){
+                $value = explode(',', $value);
+            }else{
+                $value = (array)$value;
+            }
+        }
+        $this->value = $value;
+    }
+}

+ 40 - 0
common/libs/logging/operate/valueType/Area.php

@@ -0,0 +1,40 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019/07/29
+ * Time: 上午9:39
+ */
+
+
+namespace common\libs\logging\operate\valueType;
+use common\helpers\Config as ConfigHelper;
+
+class Area extends AbstractValueType {
+
+    public function format(){
+        $value = $this->getValue();
+        if(!$value){
+            return null;
+        }
+        $count = count($value);
+        switch ($count){
+            case 1:
+                $data = ($value[0] > 0) ? ConfigHelper::province($value[0]) : $this->defaultText;
+                break;
+            case 2:
+                $data = ($value[0] > 0 && $value[1] > 0) ? ConfigHelper::city($value[0], $value[1]) : $this->defaultText;
+                break;
+            case 3:
+                $data = ($value[0] > 0 && $value[1] > 0 && $value[2] > 0) ? ConfigHelper::county($value[0], $value[1], $value[2]) : $this->defaultText;
+                break;
+            default:
+                $data = null;
+                break;
+        }
+        return ($data && isset($data['REGION_NAME'])) ? $data['REGION_NAME'] : $this->defaultText;
+    }
+    public function result(){
+        return $this->format();
+    }
+}

+ 18 - 0
common/libs/logging/operate/valueType/AuditStatus.php

@@ -0,0 +1,18 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019/07/29
+ * Time: 上午9:39
+ */
+
+
+namespace common\libs\logging\operate\valueType;
+
+class AuditStatus extends AbstractValueType {
+    public function result(){
+        $value = $this->getValue();
+        $auditStatus = array_column(\Yii::$app->params['auditStatus'], null, 'value');
+        return $auditStatus[$value]['label']??'';
+    }
+}

+ 40 - 0
common/libs/logging/operate/valueType/Config.php

@@ -0,0 +1,40 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019/07/29
+ * Time: 上午9:39
+ */
+
+namespace common\libs\logging\operate\valueType;
+
+class Config{
+
+    const YES_NO_TYPE = 'yes_no';
+    const DATE_TIME_TYPE = 'date_time';
+    const DATE_TYPE = 'date';
+    const AREA_TYPE = 'area';
+    const EMP_LV_TYPE = 'emp_lv';
+    const DEC_LV_TYPE = 'dec_lv';
+    const CROWN_LV_TYPE = 'crown_lv';
+    const DEC_ROLE_ID_TYPE = 'dec_role_id';
+    const USER_SYSTEM_TYPE = 'user_system';
+    const STATUS_TYPE = 'status';
+    const MAP_DATA_TYPE = 'map_data';
+    const USER_STATUS_TYPE = 'user_status';
+    const AUDIT_STATUS_TYPE = 'audit_status';
+    const REGION_TYPE = 'region';
+    const ADMIN_TYPE = 'admin';
+
+    private static $_data = [];
+
+
+    public static function getValues() {
+        if(!self::$_data){
+            $objClass = new \ReflectionClass(__CLASS__);
+            self::$_data = array_values($objClass->getConstants());
+            unset($objClass);
+        }
+        return self::$_data;
+    }
+}

+ 22 - 0
common/libs/logging/operate/valueType/Date.php

@@ -0,0 +1,22 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019/07/29
+ * Time: 上午9:39
+ */
+
+namespace common\libs\logging\operate\valueType;
+
+class Date extends AbstractValueType {
+
+    public $format = 'Y-m-d';
+
+    public function result(){
+        $value = $this->getValue();
+        if(!$value){
+            return $this->defaultText;
+        }
+        return date($this->format, $value);
+    }
+}

+ 22 - 0
common/libs/logging/operate/valueType/DateTime.php

@@ -0,0 +1,22 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019/07/29
+ * Time: 上午9:39
+ */
+
+namespace common\libs\logging\operate\valueType;
+
+class DateTime extends AbstractValueType {
+
+    public $format = 'Y-m-d H:i:s';
+
+    public function result(){
+        $value = $this->getValue();
+        if(!$value){
+            return $this->defaultText;
+        }
+        return date($this->format, $value);
+    }
+}

+ 29 - 0
common/libs/logging/operate/valueType/DecLv.php

@@ -0,0 +1,29 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019/07/29
+ * Time: 上午9:39
+ */
+
+
+namespace common\libs\logging\operate\valueType;
+use common\models\DeclarationLevel;
+
+class DecLv extends AbstractValueType {
+    public function result(){
+        $value = $this->getValue();
+        if(!is_array($value)){
+            $value = (array)$value;
+        }
+        $allData = DeclarationLevel::getAllData();
+        $result = '';
+        foreach($value as $id){
+            if(isset($allData[$id])){
+                $result .= $allData[$id]['LEVEL_NAME'] . ' ';
+            }
+        }
+        return $result;
+    }
+
+}

+ 27 - 0
common/libs/logging/operate/valueType/DecRoleId.php

@@ -0,0 +1,27 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: sunmoon<i@liming.me>
+ * Date: 2019/07/29
+ * Time: 上午9:39
+ */
+
+
+namespace common\libs\logging\operate\valueType;
+
+class DecRoleId extends AbstractValueType {
+    public function result(){
+        $value = $this->getValue();
+        if(!is_array($value)){
+            $value = (array)$value;
+        }
+        $allData = \common\models\DecRole::getAllData();
+        $result = '';
+        foreach($value as $id){
+            if(isset($allData[$id])){
+                $result .= $allData[$id]['ROLE_NAME'] . ' ';
+            }
+        }
+        return $result;
+    }
+}

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov