luck před 4 roky
revize
19403774b9
100 změnil soubory, kde provedl 20892 přidání a 0 odebrání
  1. 5 0
      .buildpath
  2. 10 0
      .idea/cable.iml
  3. 3 0
      .idea/dictionaries/Administrator.xml
  4. 6 0
      .idea/encodings.xml
  5. 10 0
      .idea/fipres.iml
  6. 6 0
      .idea/misc.xml
  7. 8 0
      .idea/modules.xml
  8. 6 0
      .idea/vcs.xml
  9. 305 0
      .idea/workspace.xml
  10. 22 0
      .project
  11. 3 0
      .settings/org.eclipse.php.core.prefs
  12. 42 0
      .travis.yml
  13. 927 0
      CHANGELOG.md
  14. 32 0
      LICENSE.txt
  15. 180 0
      README.md
  16. 1 0
      application/.htaccess
  17. 12 0
      application/command.php
  18. 12 0
      application/common.php
  19. 352 0
      application/common/functions.php
  20. 171 0
      application/common/plugin/Jssdk.php
  21. 81 0
      application/common/plugin/Jwt.php
  22. 94 0
      application/common/plugin/Pkcs7Encoder.php
  23. 34 0
      application/common/plugin/Varbinary.php
  24. 4562 0
      application/common/plugin/Wechat.php
  25. 681 0
      application/common/plugin/WechatAuth.php
  26. 66 0
      application/common/plugin/WxBizDataCrypt.php
  27. 14 0
      application/provider.php
  28. 28 0
      application/tags.php
  29. 164 0
      application/web/controller/CommonController.php
  30. 698 0
      application/web/controller/IndexController.php
  31. 530 0
      application/web/controller/MainController.php
  32. 168 0
      application/web/controller/NotifyController.php
  33. 723 0
      application/web/controller/UserController.php
  34. 411 0
      application/web/controller/WxController.php
  35. 261 0
      application/web/controller/WxpayController.php
  36. 168 0
      application/web/view/map/index.html
  37. 74 0
      application/web/view/map/test.html
  38. 69 0
      application/web/view/test/v.html
  39. 26 0
      build.php
  40. 35 0
      composer.json
  41. 869 0
      composer.lock
  42. 147 0
      config/app.php
  43. 25 0
      config/cache.php
  44. 20 0
      config/console.php
  45. 30 0
      config/cookie.php
  46. 38 0
      config/database.php
  47. 30 0
      config/log.php
  48. 18 0
      config/middleware.php
  49. 26 0
      config/session.php
  50. 35 0
      config/template.php
  51. 18 0
      config/trace.php
  52. 2 0
      extend/.gitignore
  53. 8 0
      public/.htaccess
  54. 6 0
      public/buttonLoading/ladda-themeless.min.css
  55. 0 0
      public/buttonLoading/ladda.min.js
  56. 0 0
      public/buttonLoading/spin.min.js
  57. binární
      public/cable.apk
  58. 1 0
      public/collect.json
  59. binární
      public/favicon.ico
  60. 23 0
      public/index.php
  61. 3 0
      public/phpinfo.php
  62. 2 0
      public/robots.txt
  63. 17 0
      public/router.php
  64. 2 0
      public/static/.gitignore
  65. binární
      public/upload/map.jpg
  66. 1 0
      public/user.json
  67. 1 0
      public/userConfig.json
  68. 225 0
      public/wx_share.php
  69. 20 0
      route/route.php
  70. 2 0
      runtime/.gitignore
  71. 22 0
      think
  72. 8 0
      thinkphp/.gitignore
  73. 1 0
      thinkphp/.htaccess
  74. 119 0
      thinkphp/CONTRIBUTING.md
  75. 32 0
      thinkphp/LICENSE.txt
  76. 93 0
      thinkphp/README.md
  77. 52 0
      thinkphp/base.php
  78. 35 0
      thinkphp/composer.json
  79. 327 0
      thinkphp/convention.php
  80. 726 0
      thinkphp/helper.php
  81. 144 0
      thinkphp/lang/zh-cn.php
  82. 981 0
      thinkphp/library/think/App.php
  83. 415 0
      thinkphp/library/think/Build.php
  84. 133 0
      thinkphp/library/think/Cache.php
  85. 552 0
      thinkphp/library/think/Collection.php
  86. 398 0
      thinkphp/library/think/Config.php
  87. 829 0
      thinkphp/library/think/Console.php
  88. 568 0
      thinkphp/library/think/Container.php
  89. 287 0
      thinkphp/library/think/Controller.php
  90. 268 0
      thinkphp/library/think/Cookie.php
  91. 197 0
      thinkphp/library/think/Db.php
  92. 278 0
      thinkphp/library/think/Debug.php
  93. 113 0
      thinkphp/library/think/Env.php
  94. 147 0
      thinkphp/library/think/Error.php
  95. 56 0
      thinkphp/library/think/Exception.php
  96. 125 0
      thinkphp/library/think/Facade.php
  97. 496 0
      thinkphp/library/think/File.php
  98. 220 0
      thinkphp/library/think/Hook.php
  99. 284 0
      thinkphp/library/think/Lang.php
  100. 417 0
      thinkphp/library/think/Loader.php

+ 5 - 0
.buildpath

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<buildpath>
+	<buildpathentry kind="src" path=""/>
+	<buildpathentry kind="con" path="org.eclipse.php.core.LANGUAGE"/>
+</buildpath>

+ 10 - 0
.idea/cable.iml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/application" isTestSource="false" packagePrefix="app\" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 3 - 0
.idea/dictionaries/Administrator.xml

@@ -0,0 +1,3 @@
+<component name="ProjectDictionaryState">
+  <dictionary name="Administrator" />
+</component>

+ 6 - 0
.idea/encodings.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
+    <file url="PROJECT" charset="UTF-8" />
+  </component>
+</project>

+ 10 - 0
.idea/fipres.iml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/application" isTestSource="false" packagePrefix="app\" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
.idea/misc.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JavaScriptSettings">
+    <option name="languageLevel" value="ES6" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/fipres.iml" filepath="$PROJECT_DIR$/.idea/fipres.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 305 - 0
.idea/workspace.xml

@@ -0,0 +1,305 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ChangeListManager">
+    <list default="true" id="ef7451eb-64f5-40b3-81e2-6b92833acafd" name="Default" comment="" />
+    <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
+    <option name="TRACKING_ENABLED" value="true" />
+    <option name="SHOW_DIALOG" value="false" />
+    <option name="HIGHLIGHT_CONFLICTS" value="true" />
+    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+    <option name="LAST_RESOLUTION" value="IGNORE" />
+  </component>
+  <component name="ComposerSettings" doNotAsk="true" synchronizationState="SYNCHRONIZE">
+    <pharConfigPath>$PROJECT_DIR$/composer.json</pharConfigPath>
+  </component>
+  <component name="FileEditorManager">
+    <leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
+      <file leaf-file-name="database.php" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/config/database.php">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="378">
+              <caret line="26" column="23" selection-start-line="26" selection-start-column="23" selection-end-line="26" selection-end-column="23" />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="IndexController.php" pinned="false" current-in-tab="true">
+        <entry file="file://$PROJECT_DIR$/application/web/controller/IndexController.php">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="399">
+              <caret line="39" lean-forward="true" selection-start-line="39" selection-end-line="39" />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="MainController.php" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/application/web/controller/MainController.php">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="504">
+              <caret line="47" column="28" lean-forward="true" selection-start-line="47" selection-start-column="28" selection-end-line="47" selection-end-column="28" />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="index.php" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/public/index.php">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="294">
+              <caret line="22" column="53" selection-start-line="22" selection-start-column="53" selection-end-line="22" selection-end-column="53" />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="app.php" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/config/app.php">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="147">
+              <caret line="57" column="36" selection-start-line="57" selection-start-column="33" selection-end-line="57" selection-end-column="36" />
+            </state>
+          </provider>
+        </entry>
+      </file>
+    </leaf>
+  </component>
+  <component name="Git.Settings">
+    <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
+  </component>
+  <component name="IdeDocumentHistory">
+    <option name="CHANGED_PATHS">
+      <list>
+        <option value="$PROJECT_DIR$/application/web/controller/CommonController.php" />
+        <option value="$PROJECT_DIR$/config/app.php" />
+        <option value="$PROJECT_DIR$/application/web/controller/MainController.php" />
+        <option value="$PROJECT_DIR$/config/database.php" />
+        <option value="$PROJECT_DIR$/application/web/controller/IndexController.php" />
+      </list>
+    </option>
+  </component>
+  <component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
+  <component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
+  <component name="JsFlowSettings">
+    <service-enabled>true</service-enabled>
+    <exe-path />
+    <other-services-enabled>true</other-services-enabled>
+    <auto-save>true</auto-save>
+  </component>
+  <component name="JsGulpfileManager">
+    <detection-done>true</detection-done>
+    <sorting>DEFINITION_ORDER</sorting>
+  </component>
+  <component name="ProjectFrameBounds" extendedState="6">
+    <option name="x" value="-8" />
+    <option name="y" value="-8" />
+    <option name="width" value="1936" />
+    <option name="height" value="1056" />
+  </component>
+  <component name="ProjectView">
+    <navigator proportions="" version="1">
+      <foldersAlwaysOnTop value="true" />
+    </navigator>
+    <panes>
+      <pane id="Scope" />
+      <pane id="ProjectPane">
+        <subPane>
+          <expand>
+            <path>
+              <item name="fipres" type="b2602c69:ProjectViewProjectNode" />
+              <item name="fipres" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+            <path>
+              <item name="fipres" type="b2602c69:ProjectViewProjectNode" />
+              <item name="fipres" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="application" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+            <path>
+              <item name="fipres" type="b2602c69:ProjectViewProjectNode" />
+              <item name="fipres" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="application" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="web" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+            <path>
+              <item name="fipres" type="b2602c69:ProjectViewProjectNode" />
+              <item name="fipres" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="application" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="web" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="controller" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+            <path>
+              <item name="fipres" type="b2602c69:ProjectViewProjectNode" />
+              <item name="fipres" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="config" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+            <path>
+              <item name="fipres" type="b2602c69:ProjectViewProjectNode" />
+              <item name="fipres" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="extend" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+            <path>
+              <item name="fipres" type="b2602c69:ProjectViewProjectNode" />
+              <item name="fipres" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="public" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+          </expand>
+          <select />
+        </subPane>
+      </pane>
+    </panes>
+  </component>
+  <component name="PropertiesComponent">
+    <property name="WebServerToolWindowFactoryState" value="false" />
+    <property name="last_opened_file_path" value="$PROJECT_DIR$" />
+    <property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
+    <property name="nodejs_npm_path_reset_for_default_project" value="true" />
+    <property name="settings.editor.selected.configurable" value="editor.preferences.gutterIcons" />
+  </component>
+  <component name="RunDashboard">
+    <option name="ruleStates">
+      <list>
+        <RuleState>
+          <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
+        </RuleState>
+        <RuleState>
+          <option name="name" value="StatusDashboardGroupingRule" />
+        </RuleState>
+      </list>
+    </option>
+  </component>
+  <component name="SvnConfiguration">
+    <configuration />
+  </component>
+  <component name="TaskManager">
+    <task active="true" id="Default" summary="Default task">
+      <changelist id="ef7451eb-64f5-40b3-81e2-6b92833acafd" name="Default" comment="" />
+      <created>1604131607358</created>
+      <option name="number" value="Default" />
+      <option name="presentableId" value="Default" />
+      <updated>1604131607358</updated>
+      <workItem from="1604131609158" duration="2010000" />
+      <workItem from="1604311357072" duration="1672000" />
+      <workItem from="1604479748634" duration="1226000" />
+      <workItem from="1604485963290" duration="11722000" />
+    </task>
+    <servers />
+  </component>
+  <component name="TimeTrackingManager">
+    <option name="totallyTimeSpent" value="16630000" />
+  </component>
+  <component name="ToolWindowManager">
+    <frame x="-8" y="-8" width="1936" height="1056" extended-state="6" />
+    <editor active="true" />
+    <layout>
+      <window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.25799572" />
+      <window_info anchor="bottom" id="TODO" order="6" />
+      <window_info anchor="bottom" id="Docker" order="7" show_stripe_button="false" />
+      <window_info anchor="bottom" id="Event Log" order="7" side_tool="true" />
+      <window_info anchor="right" id="Database" order="3" />
+      <window_info anchor="bottom" id="Database Changes" order="7" show_stripe_button="false" />
+      <window_info anchor="bottom" id="Version Control" order="7" />
+      <window_info anchor="bottom" id="Run" order="2" />
+      <window_info id="Structure" order="1" side_tool="true" weight="0.25" />
+      <window_info anchor="bottom" id="Terminal" order="7" />
+      <window_info id="Favorites" order="2" side_tool="true" />
+      <window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
+      <window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
+      <window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
+      <window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
+      <window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
+      <window_info anchor="bottom" id="Message" order="0" />
+      <window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
+      <window_info anchor="bottom" id="Find" order="1" />
+    </layout>
+  </component>
+  <component name="TypeScriptGeneratedFilesManager">
+    <option name="version" value="1" />
+  </component>
+  <component name="VcsContentAnnotationSettings">
+    <option name="myLimit" value="2678400000" />
+  </component>
+  <component name="editorHistoryManager">
+    <entry file="file://$PROJECT_DIR$/application/web/controller/CommonController.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="3108">
+          <caret line="148" column="11" lean-forward="true" selection-start-line="148" selection-start-column="11" selection-end-line="148" selection-end-column="11" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/application/web/controller/CommonController.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="3066">
+          <caret line="146" lean-forward="true" selection-start-line="146" selection-end-line="146" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/application/web/controller/CommonController.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="3192">
+          <caret line="152" column="9" lean-forward="true" selection-start-line="152" selection-start-column="9" selection-end-line="152" selection-end-column="9" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/application/web/controller/CommonController.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="220">
+          <caret line="132" column="32" selection-start-line="132" selection-start-column="32" selection-end-line="132" selection-end-column="32" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/config/console.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="252">
+          <caret line="20" lean-forward="true" selection-start-line="20" selection-end-line="20" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/public/index.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="294">
+          <caret line="22" column="53" selection-start-line="22" selection-start-column="53" selection-end-line="22" selection-end-column="53" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/config/app.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="147">
+          <caret line="57" column="36" selection-start-line="57" selection-start-column="33" selection-end-line="57" selection-end-column="36" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/application/web/controller/MainController.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="504">
+          <caret line="47" column="28" lean-forward="true" selection-start-line="47" selection-start-column="28" selection-end-line="47" selection-end-column="28" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/config/database.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="378">
+          <caret line="26" column="23" selection-start-line="26" selection-start-column="23" selection-end-line="26" selection-end-column="23" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/application/web/controller/IndexController.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="399">
+          <caret line="39" lean-forward="true" selection-start-line="39" selection-end-line="39" />
+        </state>
+      </provider>
+    </entry>
+  </component>
+  <component name="masterDetails">
+    <states>
+      <state key="ScopeChooserConfigurable.UI">
+        <settings>
+          <splitter-proportions>
+            <option name="proportions">
+              <list>
+                <option value="0.2" />
+              </list>
+            </option>
+          </splitter-proportions>
+        </settings>
+      </state>
+    </states>
+  </component>
+</project>

+ 22 - 0
.project

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>cable</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.wst.validation.validationbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.dltk.core.scriptbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.php.core.PHPNature</nature>
+	</natures>
+</projectDescription>

+ 3 - 0
.settings/org.eclipse.php.core.prefs

@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+include_path=0;/cable
+use_asp_tags_as_php=false

+ 42 - 0
.travis.yml

@@ -0,0 +1,42 @@
+sudo: false
+
+language: php
+
+branches:
+  only:
+    - stable
+
+cache:
+  directories:
+    - $HOME/.composer/cache
+
+before_install:
+  - composer self-update
+
+install:
+  - composer install --no-dev --no-interaction --ignore-platform-reqs
+  - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip .
+  - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0"
+  - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0"
+  - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip .
+
+script:
+  - php think unit
+
+deploy:
+  provider: releases
+  api_key:
+    secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw=
+  file:
+    - ThinkPHP_Core.zip
+    - ThinkPHP_Full.zip
+  skip_cleanup: true
+  on:
+    tags: true

+ 927 - 0
CHANGELOG.md

@@ -0,0 +1,927 @@
+## V5.1.39 LTS(2019-11-18)
+
+本次更新为常规更新,主要包括:
+
+* 修正`memcached`驱动
+* 改进`HasManyThrough`关联查询
+* 改进`Request`类`isJson`方法
+* 改进关联查询
+* 改进`redis`驱动
+* 增加 Model类`getWhere`方法对复合主键的支持
+* 改进`newQuery`方法
+* 改进闭包查询的参数绑定
+* 修正`Validate`
+* 修复某些情况下URL会多一个冒号
+* 调整composer.json
+* 修复使用`Cache::clear()`时,报错缓存文件不存在问题
+* 使用File类的unlink方法进行文件删除
+* 改进`paraseData`方法
+* 修正image验证方法
+* 改进Url生成
+* 改进空操作对数字的支持
+* 改进一处PHP7.4兼容性问题
+
+## V5.1.38 LTS(2019-8-8)
+
+本次更新为常规更新,主要包括:
+
+* `Request`类增加`isJson`方法
+* 改进浮点型查询
+* 修正关联查询关联外键为空的查询错误
+* 远程一对多支持关联统计和预载入查询
+* 远程一对多关联支持`has`/`hasWhere`查询
+* 优化`parseIn`解析
+* 改进`parseLike`查询
+* 改进Url生成
+* 改进模型的`toArray`方法
+* 修正`notIn`查询
+* 改进`JSON`字段查询
+* 改进Controller类`display`/`fetch`方法返回`ViewResponse`对象
+* 改进`param`方法
+* 改进`mysql`驱动`getExplain`方法
+* 改进时间查询
+* 改进模型关联的`has`/`hasWhere`方法对软删除的支持
+* 修正社区反馈的BUG
+
+## V5.1.37 LTS(2019-5-26)
+
+本次更新为常规更新,主要更新如下:
+
+* 改进关联数据更新
+* 修正关联动态获取器
+* 改进`redis`驱动 
+* 修复验证规则里面出现二维数组时的错误
+* 改进跨域请求支持
+* 完善模型`hidden`方法对关联属性的支持
+* 改进`where`查询方法传入`Query`对象的支持`bind`数据
+* 改进数据集对象的`load`方法
+* 修正缓存类`clear`方法对`tag`的支持
+
+## V5.1.36 LTS(2019-4-28)
+
+本次更新为常规更新,主要更新如下:
+
+* 修正`chunk`方法一处异常抛出的错误
+* 修正模型输出的`visible`
+* 改进环境变量加载
+* 改进命令行日志的`level`配置支持
+* 修复设置有缓存前缀时,无法清空缓存标签的问题
+* HasMony对象`saveAll`方法兼容`Collection`格式参数格式
+* 修正`whereOr`查询使用字符串的问题
+* 改进`dateFormat`设置对写入数据的影响
+* 修正查询缓存
+* 记住指定的跳转地址
+* 改进软删除
+* 改进聚合查询SQL去除limit 1
+* 改进缓存驱动
+
+## V5.1.35 LTS(2019-3-2)
+
+本次主要为常规更新,修正了一些反馈的问题。
+
+* 修正验证类自定义验证方法执行两次的问题
+* 模型增加`isEmpty`方法用于判断是否空模型
+* 改进获取器对`append`的支持
+* 修正一对多关联的`withCount`自关联问题
+* facade类注释调整
+* 改进关联属性的`visible`和`hidden`判断
+* 修正路由分组的`MISS`路由
+* 改进pgsql.sql
+
+## V5.1.34 LTS(2019-1-30)
+
+本次更新为常规更新,修正了一些反馈的问题。
+
+* 改进Request类的`has`方法,支持`patch`
+* 改进`unique`验证的多条件支持
+* 修复自定义上传验证,检测文件大小
+* 改进`in`查询支持表达式
+* 改进路由的`getBind`方法
+* 改进验证类的错误信息获取
+* 改进`response`助手函数默认值
+* 修正mysql的`regexp`查询
+* 改进模型类型强制转换写入对`Expression`对象的支持
+
+## V5.1.33 LTS(2019-1-16)
+
+* 修复路由中存在多个相同替换的正则BUG
+* 修正whereLike查询
+* join方法支持参数绑定
+* 改进union方法
+* 修正多对多关联的attach方法
+* 改进验证类的正则规则自定义
+* 改进Request类method方法
+* 改进File日志类型的CLI日志写入
+* 改进文件日志time_format配置对JSON格式的支持
+
+## V5.1.32 LTS(2018-12-24)
+
+本次主要为常规更新,修正了一些反馈的问题。
+
+
+* 改进多对多关联的`attach`方法 
+* 改进聚合查询的`field`处理
+* 改进关联的`save`方法
+* 修正模型`exists`方法返回值
+* 改进时间字段写入和输出
+* 改进控制器中间件的调用
+* 改进路由变量替换的性能
+* 改进缓存标签的处理机制
+
+## V5.1.31 LTS (2018-12-9)
+
+本次版本包含一个安全更新,建议升级。
+
+* 改进`field`方法
+* 改进`count`方法返回类型
+* `download`函数增加在浏览器中显示文件功能
+* 修正多对多模型的中间表数据写入
+* 改进`sqlsrv`驱动支持多个Schemas模式查询
+* 统一助手函数与\think\response\Download函数文件过期时间
+* 完善关联模型的`save`方法 增加`make`方法仅创建对象不保存
+* 修改条件表达式对静态变量的支持
+* 修正控制器名获取
+* 改进view方法的`field`解析
+
+## V5.1.30 LTS(2018-11-30)
+
+该版本为常规更新,修正了一些社区反馈的问题。
+
+主要更新如下:
+
+* 改进查询类的`execute`方法
+* 判断路由规则定义添加对请求类型的判断
+* 修复`orderRaw`异常
+* 修正 `optimize:autoload`指令
+* 改进软删除的`destroy`方法造成重复执行事件的问题
+* 改进验证类对扩展验证规则 始终验证 不管是否`require`
+* 修复自定义验证`remove`所有规则的异常
+* 改进时间字段的自动写入支持微秒数据
+* 改进`Connection`类的`getrealsql`方法
+* 修正`https`地址的URL生成
+* 修复 `array_walk_recursive` 在低于PHP7.1消耗内部指针问题
+* 改进手动参数绑定使用
+* 改进聚合查询方法的`field`参数支持`Expression`
+
+## V5.1.29 LTS(2018-11-11)
+
+该版本主要改进了参数绑定的解析问题和提升性能,并修正了一些反馈的问题。
+
+* 改进手动参数绑定
+* 修正MISS路由的分组参数无效问题
+* 行为支持对象的方法
+* 修正全局查询范围
+* 改进`belongsto`关联的`has`方法
+* 改进`hasMany`关联
+* 改进模型观察者多次注册的问题
+* 改进`query`类的默认查询参数处理
+* 修正`parseBetween`解析方法
+* 改进路由地址生成的本地域名支持
+* 改进参数绑定的实际URL解析性能
+* 改进`Env`类的`getEnv`和`get`方法
+* 改进模板缓存的生成优化
+* 修复验证类的多语言支持
+* 修复自定义场景验证`remove`规则异常
+* File类添加是否自动补全扩展名的选项
+* 改进`strpos`对子串是否存在的判断
+* 修复`choice`无法用值选择第一个选项问题
+* 验证器支持多维数组取值验证
+* 改进解析`extend`和`block`标签的正则
+
+## V5.1.28 LTS(2018-10-29)
+
+该版本主要修正了上一个版本存在的一些问题,并改进了关联查询
+
+* 改进聚合查询方法的字段支持DISTINCT
+* 改进定义路由后url函数的端口生成
+* 改进控制器中间件对`swoole`等的支持
+* 改进Log类`save`方法
+* 改进验证类的闭包验证参数
+* 多对多关联支持指定中间表数据的名称
+* 关联聚合查询支持闭包方式指定聚合字段
+* 改进Lang类`get`方法
+* 多对多关联增加判断关联数据是否存在的方法
+* 改进关联查询使用`fetchsql`的情况
+* 改进修改器的是否已经执行判断
+* 增加`afterWith`和`beforeWith`验证规则 用于比较日期字段
+
+## V5.1.27 LTS(2018-10-22)
+
+该版本主要修正了路由绑定的参数,改进了修改器的执行多次问题,并正式宣布为LTS版本!
+
+
+* 修正路由绑定的参数丢失问题
+* 修正路由别名的参数获取
+* 改进修改器会执行多次的问题
+
+## V5.1.26(2018-10-12)
+
+该版本主要修正了上一个版本的一些问题,并改进了全局查询范围的支持,同时包含了一个安全更新。
+
+
+* 修正单一模块下注解路由无效的问题
+* 改进数据库的聚合查询的字段处理
+* 模型类增加`globalScope`属性定义 用于指定全局的查询范围
+* 模型的`useGlobalScope`方法支持传入数组 用于指定当前查询需要使用的全局查询范围
+* 改进数据集的`order`方法对数字类型的支持
+* 修正上一个版本`order`方法解析的一处BUG
+* 排序字段不合法或者错误的时候抛出异常
+* 改进`Request`类的`file`方法对上传文件的错误判断
+
+##  V5.1.25(2018-9-21)
+
+该版本主要改进了查询参数绑定的性能和对浮点型的支持,以及一些细节的完善。
+
+* 修正一处命令行问题
+* 改进`Socketlog`日志驱动,支持自定义默认展开日志类别
+* 修正`MorphMany`一处bug
+* 跳转到上次记住的url,并支持默认值
+* 改进模型的异常提示
+* 改进参数绑定对浮点型的支持
+* 改进`order`方法解析
+* 改进`json`字段数据的自动编码
+* 改进日志`log_write`可能造成的日志写入死循环
+* Log类增加`log_level`行为标签位置,用于对某个类型的日志进行处理
+* Route类增加`clear`方法清空路由规则
+* 分布式数据库配置支持使用数组
+* 单日志文件也支持`max_files`参数
+* 改进查询参数绑定的性能
+* 改进别名路由的URL后缀参数检测
+* 控制器前置方法和控制器中间件的`only`和`except`定义不区分大小写
+
+## V5.1.24(2018-9-5)
+
+该版本主要增加了命令行的表格输出功能,并增加了查看路由定义的指令,以及修正了社区的一些反馈问题。
+
+* 修正`Request`类的`file`方法
+* 修正路由的`cache`方法
+* 修正路由缓存的一处问题
+* 改进上传文件获取的异常处理
+* 改进`fetchCollection`方法支持传入数据集类名
+* 修正多级控制器的注解路由生成
+* 改进`Middleware`类`clear`方法
+* 增加`route:list`指令用于[查看定义的路由](752690) 并支持排序
+* 命令行增加`Table`输出类
+* `Command`类增加`table`方法用于输出表格
+* 改进搜索器查询方法支持别名定义
+* 命令行配置增加`auto_path`参数用于定义自动载入的命令类路径
+* 增加`make:command`指令用于[快速生成指令](354146)
+* 改进`make:controller`指令对操作方法后缀的支持
+* 改进命令行的定义文件支持索引数组 用于指令对象的惰性加载
+* 改进`value`和`column`方法对后续查询结果的影响
+* 改进`RuleName`类的`setRule`方法
+
+## V5.1.23(2018-8-23)
+
+该版本主要改进了数据集对象的处理,增加了`findOrEmpty`方法,并且修正了一些社区反馈的BUG。
+
+* 数据集类增加`diff`/`intersect`方法用于获取差集和交集(默认根据主键值比较)
+* 数据集类增加`order`方法支持指定字段排序
+* 数据集类增加`map`方法使用回调函数处理数据并返回新的数据集对象
+* Db增加`allowEmpty`方法允许`find`方法在没有数据的时候返回空数组或者空模型对象而不是null
+* Db增加`findOrEmpty`方法
+* Db增加`fetchCollection`方法用于指定查询返回数据集对象
+* 改进`order`方法的数组方式解析,增强安全性
+* 改进`withSearch`方法,支持第三个参数传入字段前缀标识,用于多表查询字段搜索
+* 修正`optimize:route`指令开启类库后缀后的注解路由生成
+* 修正redis缓存及session驱动
+* 支持指定`Yaconf`的独立配置文件
+* 增加`yaconf`助手函数用于配置文件
+
+
+## V5.1.22(2018-8-9)
+
+该版本主要增加了模型搜索器和`withJoin`方法,完善了模型输出和对`Yaconf`的支持,修正了一些社区反馈的BUG。
+
+* 改进一对一关联的`table`识别问题
+* 改进内置`Facade`类
+* 增加`withJoin`方法支持`join`方式的[一对一关联](一对一关联.md)查询
+* 改进`join`预载入查询的空数据问题
+* 改进`Config`类的`load`方法支持快速加载配置文件
+* 改进`execute`方法和事务的断线重连
+* 改进`memcache`驱动的`has`方法
+* 模型类支持定义[搜索器](搜索器.md)方法
+* 完善`Config`类对`Yaconf`的支持
+* 改进模型的`hidden/visible/append/withAttr`方法,支持在[查询前后调用](数组访问.md),以及支持数据集对象
+* 数据集对象增加`where`方法根据字段或者关联数据[过滤数据](模型数据集.md)
+* 改进AJAX请求的`204`判断
+
+
+## V5.1.21(2018-8-2)
+
+该版本主要增加了下载响应对象和数组查询对象的支持,并修正了一些社区反馈的问题。
+
+* 改进核心对象的无用信息调试输出
+* 改进模型的`isRelationAttr`方法判断
+* 模型类的`get`和`all`方法并入Db类
+* 增加[下载响应对象](文件下载.md)和`download`助手函数
+* 修正别名路由配置定义读取
+* 改进`resultToModel`方法
+* 修正开启类库后缀后的注解路由生成
+* `Response`类增加`noCache`快捷方法
+* 改进路由对象在`Swoole`/`Workerman`下面参数多次合并问题
+* 修正路由`ajax`/`pjax`参数后路由变量无法正确获取的问题
+* 增加清除中间件的方法
+* 改进依赖注入的参数规范自动识别(便于对接前端小写+下划线规范)
+* 改进`hasWhere`的数组条件的字段判断
+* 增加[数组查询对象](高级查询.md)`Where`支持(喜欢数组查询的福音)
+* 改进多对多关联的闭包支持
+
+## V5.1.20(2018-7-25)
+
+该版本主要增加了Db和模型的动态获取器的支持,并修正了一些已知问题。
+
+* Db类添加[获取器支持](703981)
+* 支持模型及关联模型字段[动态定义获取器](354046)
+* 动态获取器支持`JSON`字段
+* 改进路由的`before`行为执行(匹配后执行)
+*  `Config`类支持`Yaconf`
+* 改进Url生成的端口问题
+* Request类增加`setUrl`和`setBaseUrl`方法
+* 改进页面trace的信息显示
+* 修正`MorphOne`关联
+* 命令行添加[查看版本指令](703994)
+
+## V5.1.19 (2018-7-13)
+
+该版本是一个小幅改进版本,针对`Swoole`和`Workerman`的`Cookie`支持做了一些改进,并修正了一些已知的问题。
+
+
+* 改进query类`delete`方法对软删除条件判断
+* 修正分表查询的软删除问题
+* 模型查询的时候同时传入`table`和`name`属性
+* 容器类增加`IteratorAggregate`和`Countable`接口支持
+* 路由分组支持对下面的资源路由统一设置`only/except/vars`参数
+* 改进Cookie类更好支持扩展
+* 改进Request类`post`方法
+* 改进模型自关联的自动识别
+* 改进Request类对`php://input`数据的处理
+
+
+## V5.1.18 (2018-6-30)
+
+该版本主要完善了对`Swoole`和`Workerman`的`HttpServer`运行支持,改进`Request`类,并修正了一些已知的问题。
+
+* 改进关联`append`方法的处理
+* 路由初始化和检测方法分离
+* 修正`destroy`方法强制删除
+* `app_init`钩子位置移入`run`方法
+* `think-swoole`扩展更新到2.0版本
+* `think-worker`扩展更新到2.0版本
+* 改进Url生成的域名自动识别
+* `Request`类增加`setPathinfo`方法和`setHost`方法
+* `Request`类增加`withGet`/`withPost`/`withHeader`/`withServer`/`withCookie`/`withEnv`方法进行赋值操作
+* Route类改进`host`属性的获取
+* 解决注解路由配置不生效的问题
+* 取消Test日志驱动,改为使用`close`设置关闭全局日志写入
+* 修正路由的`response`参数
+* 修正204响应输出的判断
+
+## V5.1.17 (2018-6-18)
+
+该版本主要增加了控制器中间件的支持,改进了路由功能,并且修正了社区反馈的一些问题。
+
+* 修正软删除的`delete`方法
+* 修正Query类`Count`方法
+* 改进多对多`detach`方法
+* 改进Request类`Session`方法
+* 增加控制器中间件支持
+* 模型类增加`jsonAssoc`属性用于定义json数据是否返回数组
+* 修正Request类`method`方法的请求伪装
+* 改进静态路由的匹配
+* 分组首页路由自动完整匹配
+* 改进sqlsrv的`column`方法
+* 日志类的`apart_level`配置支持true自动生成对应类型的日志文件
+* 改进`204`输出判断
+* 修正cli下页面输出的BUG
+* 验证类使用更高效的`ctype`验证机制
+* 改进Request类`cookie`方法
+* 修正软删除的`withTrashed`方法
+* 改进多态一对多的预载入查询
+* 改进Query类`column`方法的缓存读取
+* Query类增加`whereBetweenTimeField`方法
+* 改进分组下多个相同路由规则的合并匹配问题
+* 路由类增加`getRule`/`getRuleList`方法获取定义的路由
+
+## V5.1.16 (2018-6-7)
+
+该版本主要修正了社区反馈的一些问题,并对Request类做了进一步规范和优化。
+
+* 改进Session类的`boot`方法
+* App类的初始化方法可以单独执行
+* 改进Request类的`param`方法
+* 改进资源路由的变量替换
+* Request类增加`__isset`方法
+* 改进`useGlobalScope`方法对软删除的影响
+* 修正命令行调用
+* 改进Cookie类`init`方法
+* 改进多对多关联删除的返回值
+* 一对多关联写入支持`replace`
+* 路由增加`filter`检测方法,用于通过请求参数检测路由是否匹配
+* 取消Request类`session/env/server`方法的`filter`参数
+* 改进关联的指定属性输出
+* 模型删除操作删除后不清空对象数据仅作标记
+* 调整模型的`save`方法返回值为布尔值
+* 修正Request类`isAjax`方法
+* 修正中间件的模块配置读取
+* 取消Request类的请求变量的设置功能
+* 取消请求变量获取的默认修饰符
+* Request类增加`setAction/setModule/setController`方法
+* 关联模型的`delete`方法调用Query类
+* 改进URL生成的域名识别
+* 改进URL检测对已定义路由的域名判断
+* 模型类增加`isExists`和`isForce`方法
+* 软删除的`destroy`和`restore`方法返回值调整为布尔值
+
+## V5.1.15 (2018-6-1)
+
+该版本主要改进了路由缓存的性能和缓存方式设置,增加了JSON格式文件日志的支持,并修正了社区反馈的一些问题。
+
+* 容器类增加`exists`方法 仅判断是否存在对象实例
+* 取消配置类的`autoload`方法
+* 改进路由缓存大小提高性能
+* 改进Dispatch类`init`方法
+* 增加`make:validate`指令生成验证器类
+* Config类`get`方法支持默认值参数
+* 修正字段缓存指令
+* 改进App类对`null`数据的返回
+* 改进模型类的`__isset`方法判断
+* 修正`Query`类的`withAggregate`方法
+* 改进`RuleItem`类的`setRuleName`方法
+* 修正依赖注入和参数的冲突问题
+* 修正Db类对第三方驱动的支持
+* 修正模型类查询对象问题
+* 修正File缓存驱动的`has`方法
+* 修正资源路由嵌套
+* 改进Request类对`$_SERVER`变量的读取
+* 改进请求缓存处理
+* 路由缓存支持指定单独的缓存方式和参数
+* 修正资源路由的中间件多次执行问题
+* 修正`optimize:config`指令
+* 文件日志支持`JSON`格式日志保存
+* 修正Db类`connect`方法
+* 改进Log类`write`方法不会自动写入之前日志
+* 模型的关联操作默认启用事务
+* 改进软删除的事件响应
+
+## V5.1.14 (2018-5-18)
+
+该版本主要对底层容器进行了一些优化改进,并增加了路由缓存功能,可以进一步提升路由性能。
+
+* 依赖注入的对象参数传入改进
+* 改进核心类的容器实例化
+* 改进日期字段的读取
+* 改进验证类的`getScene`方法
+* 模型的`create`方法和`save`方法支持`replace`操作
+* 改进`Db`类的调用机制
+* App类调整为容器类
+* 改进容器默认绑定
+* `Loader`类增加工厂类的实例化方法
+* 增加路由变量默认规则配置参数
+* 增加路由缓存设计
+* 错误处理机制改进
+* 增加清空路由缓存指令
+
+
+## V5.1.13 (2018-5-11)
+
+该版本主要增加了MySQL的XA事务支持,模型事件支持观察者,以及对Facade类的改进。
+
+* 改进自动缓存
+* 改进Url生成
+* 修正数据缓存
+* 修正`value`方法的缓存
+* `join`方法和`view`方法的条件支持使用`Expression`对象
+* 改进驱动的`parseKey`方法
+* 改进Request类`host`方法和`domain`方法对端口的处理
+* 模型增加`withEvent`方法用于控制当前操作是否需要执行模型事件
+* 模型`setInc/setDec`方法支持更新事件
+* 模型添加`before_restore/after_restore`事件
+* 增加模型事件观察者
+* 路由增加`mobile`方法设置是否允许手机访问
+* 数据库XA事务支持
+* 改进索引数组查询对`IN`查询的支持
+* 修正`invokeMethod`方法
+* 修正空数据写入返回值的BUG
+* redis驱动支持`predis`
+* 改进`parseData`方法
+* 改进模块加载
+* App类初始化方法调整
+* 改进数组查询对表达式`Expression`对象支持
+* 改进闭包的依赖注入调用
+* 改进多对多关联的中间表模型更新
+* 增加容器中对象的自定义实例化
+
+## V5.1.12 (2018-4-25)
+
+该版本主要改进了主从查询的及时性,并支持动态设置请求数据。
+
+* 支持动态设置请求数据
+* 改进`comment`方法解析
+* 修正App类`__unset`方法
+* 改进url生成的域名绑定
+* 改进主从查询的及时性
+* 修正`value`的数据缓存功能
+* 改进分页类的集合对象方法调用
+* 改进Db类的代码提示
+* SQL日志增加主从标记
+
+## V5.1.11 (2018-4-19)
+
+该版本为安全和修正版本,改进了JSON查询的参数绑定问题和容器类对象实例获取,并包含一处可能的安全隐患,建议更新。
+
+* 支持指定JSON数据查询的字段类型
+* 修正`selectInsert`方法
+* `whereColumn`方法支持数组方式
+* 改进容器类`make`方法
+* 容器类`delete`方法支持数组
+* 改进`composer`自动加载
+* 改进模板引擎
+* 修正`like`查询的一处安全隐患
+
+## V5.1.10 (2018-4-16)
+
+该版本为修正版本,修正上一个版本的一些BUG,并增强了`think clear`指令。
+
+* 改进`orderField`方法
+* 改进`exists`查询
+* 修改cli模式入口文件位置计算
+* 修正`null`查询
+* 改进`parseTime`方法
+* 修正关联预载入查询
+* 改进`mysql`驱动
+* 改进`think clear`指令 支持 `-c -l -r `选项
+* 改进路由规则对`/`结尾的支持
+
+## V5.1.9 (2018-4-12)
+
+该版本主要是一些改进和修正,并包含一个安全更新,是一个推荐更新版本。
+
+* 默认模板渲染规则支持配置保持操作方法名
+* 改进`Request`类的`ip`方法
+* 支持模型软删除字段的默认值定义
+* 改进路由变量规则对中文的支持
+* 使用闭包查询的时候使用`cache(true)` 抛出异常提示
+* 改进`Loader`类`loadComposerAutoloadFiles`方法
+* 改进查询方法安全性
+* 修正路由地址中控制器名驼峰问题
+* 调整上一个版本的`module_init`和`app_begin`的钩子顺序问题
+* 改进CLI命令行执行的问题
+* 修正社区反馈的其它问题
+
+## V5.1.8 (2018-4-5)
+
+该版本主要改进了中间件的域名和模块支持,并同时修正了几个已知问题。
+
+* 增加`template.auto_rule` 参数设置默认模板渲染的操作名自动转换规则
+* 默认模板渲染规则改由视图驱动实现
+* 修正路由标识定义
+* 修正控制器路由方法
+* 改进Request类`ip`方法支持自定义代理IP参数
+* 路由注册中间件支持数组方式别名
+* 改进命令行执行下的`composer`自动加载
+* 添加域名中间件注册支持
+* 全局中间件支持模块定义文件
+* Log日志配置支持`close`参数可以全局关闭日志写入
+* 中间件方法中捕获`HttpResponseException`异常
+* 改进中间件的闭包参数传入
+* 改进分组路由的延迟解析
+* 改进URL生成对域名绑定的支持
+* 改进文件缓存和文件日志驱动的并发支持
+
+## V5.1.7 (2018-3-28)
+
+该版本主要修正了路由的一些问题,并改进了查询的安全性。
+
+* 支持`middleware`配置文件预先定义中间件别名方便路由调用
+* 修正资源路由
+* 改进`field`方法 自动识别`fieldRaw`
+* 增加`Expression`类
+* Query类增加`raw`方法
+* Query类的`field`/ `order` 和` where`方法都支持使用`raw`表达式查询
+* 改进`inc/dec`查询 支持批量更新
+* 改进路由分组
+* 改进Response类`create`方法
+* 改进composer自动加载
+* 修正域名路由的`append`方法
+* 修正操作方法的初始化方法获取不到问题
+
+## V5.1.6 (2018-3-26)
+
+该版本主要改进了路由规则的匹配算法,大幅提升了路由性能。并正式引入了中间件的支持,可以在路由中定义或者全局定义。另外包含了一个安全更新,是一个建议更新版本。
+
+* 改进URL生成对路由`ext`方法的支持
+* 改进查询缓存对不同数据库相同表名的支持
+* 改进composer自动加载的性能
+* 改进空路由变量对默认参数的影响
+* mysql的`json`字段查询支持多级
+* Query类增加`option`方法
+* 优化路由匹配
+* 修复验证规则数字键名丢失问题
+* 改进路由Url生成
+* 改进一对一关联预载入查询
+* Request类增加`rootDomain`方法
+* 支持API资源控制器生成 `make:controller --api`
+* 优化Template类的标签解析
+* 容器类增加删除和清除对象实例的方法
+* 修正MorphMany关联的`eagerlyMorphToMany`方法一处错误
+* Container类的异常捕获改进
+* Domain对象支持`bind`方法
+* 修正分页参数
+* 默认模板的输出规则不受URL影响
+* 注解路由支持多级控制器
+* Query类增加`getNumRows`方法获取前次操作影响的记录数
+* 改进查询条件的性能
+* 改进模型类`readTransform`方法对序列化类型的处理
+* Log类增加`close`方法可以临时关闭当前请求的日志写入
+* 文件日志方式增加自动清理功能(设置`max_files`参数)
+* 修正Query类的`getPk`方法
+* 修正模板缓存的布局开关问题
+* 修正Query类`select`方法的缓存
+* 改进input助手函数
+* 改进断线重连的信息判断
+* 改进正则验证方法
+* 调整语言包的加载顺序 放到`app_init`之前
+* controller类`fetch`方法改为`final`
+* 路由地址中的变量支持使用`<var>`方式
+* 改进XMLResponse 支持传入编码过的xml内容
+* 修正Query类`view`方法的数组表名支持
+* 改进路由的模型闭包绑定
+* 改进分组变量规则的继承
+* 改进`cli-server`模式下的`composer`自动加载
+* 路由变量规则异常捕获
+* 引入中间件支持
+* 路由定义增加`middleware`方法
+* 增加生成中间件指令`make:middleware` 
+* 增加全局中间件定义支持
+* 改进`optimize:config`指令对全局中间件的支持
+* 改进config类`has`方法
+* 改进时间查询的参数绑定
+* 改进`inc/dec/exp`查询的安全性
+
+
+## V5.1.5 (2018-1-31)
+
+该版本主要增强了数据库的JSON查询,并支持JSON字段的聚合查询,改进了一些性能问题,修正了路由的一些BUG,主要更新如下:
+
+* 改进数据集查询对`JSON`数据的支持
+* 改进聚合查询对`JSON`字段的支持
+* 模型类增加`getOrFail`方法
+* 改进数据库驱动的`parseKey`方法
+* 改进Query类`join`方法的自关联查询
+* 改进数据查询不存在不生成查询缓存
+* 增加`run`命令行指令启动内置服务器
+* `Request`类`pathinfo`方法改进对`cli-server`支持
+* `Session`类增加`use_lock`配置参数设置是否启用锁机制
+* 优化`File`缓存自动生成空目录的问题
+* 域名及分组路由支持`append`方法传递隐式参数
+* 改进日志的并发写入问题
+* 改进`Query`类的`where`方法支持传入`Query`对象
+* 支持设置单个日志文件的文件名
+* 修正路由规则的域名条件约束 
+* `Request`类增加`subDomain`方法用于获取当前子域名
+* `Response`类增加`allowCache`方法控制是否允许请求缓存
+* `Request`类增加`sendData`方法便于扩展
+* 改进`Env`类不依赖`putenv`方法
+* 改进控制台`trace`显示错误
+* 改进`MorphTo`关联
+* 改进完整路由匹配后带斜线访问出错的情况
+* 改进路由的多级分组问题
+* 路由url地址生成支持多级分组
+* 改进路由Url生成的`url_convert`参数的影响
+* 改进`miss`和`auto`路由内部解析
+* 取消预载入关联查询缓存功能
+
+## V5.1.4 (2018-1-19)
+
+该版本主要增强了数据库和模型操作,主要更新如下:
+
+* 支持设置 `deleteTime`属性为`false` 关闭软删除
+* 模型增加`getError`方法
+* 改进Query类的`getTableFields`/`getFieldsType`方法 支持表名自动获取
+* 模型类`toCollection`方法增加参数指定数据集类
+* 改进`union`查询
+* 关联预载入`with`方法增加缓存参数
+* 改进模型类的`get`和`all`方法的缓存 支持关联缓存
+* 支持`order by field`操作
+* 改进`insertAll`分批写入
+* 改进`json`字段数据支持
+* 增加JSON数据的模型对象化操作
+* 改进路由`ext`参数检测 
+* 修正`rule`方法的`method`参数使用 `get|post` 方式注册路由的问题
+
+## V5.1.3 (2018-1-12)
+
+该版本主要改进了路由及调整函数加载顺序,主要更新如下:
+
+* 增加`env`助手函数;
+* 增加`route`助手函数;
+* 增加视图路由方法;
+* 增加路由重定向方法;
+* 路由默认区分最后的目录斜杆(支持设置不区分);
+* 调整公共文件和配置文件的加载顺序(可以在配置文件中直接使用助手函数);
+* 视图类增加`filter`方法设置输出过滤;
+* `view`助手函数增加`filter`参数;
+* 改进缓存生成指令;
+* Session类的`get`方法支持获取多级;
+* Request类`only`方法支持指定默认值;
+* 改进路由分组;
+* 修正使用闭包查询的时候自动数据缓存出错的情况;
+* 废除`view_filter`钩子位置;
+* 修正分组下面的资源路由;
+* 改进session驱动;
+
+## V5.1.2 (2018-1-8)
+
+该版本改进了配置类及数据库类,主要更新如下:
+
+* 修正嵌套路由分组;
+* 修正自定义模板标签界定符后表达式语法出错的情况;
+* 修正自关联的多次调用问题;
+* 修正数组查询的`null`条件查询;
+* 修正Query类的`order`及`field`的一处可能的BUG;
+* 配置参数设置支持三级;
+* 配置对象支持`ArrayAccess`;
+* App类增加`path`方法用于设置应用目录;
+* 关联定义增加`selfRelation`方法用于设置是否为自关联;
+
+## V5.1.1 (2018-1-3)
+
+修正一些反馈的BUG,包括:
+
+* 修正Cookie类存取数组的问题
+* 修正Controller的`fetch`方法
+* 改进跨域请求
+* 修正`insertAll`方法
+* 修正`chunk`方法
+
+## V5.1.0 (2018-1-1)
+
+主要更新如下:
+
+* 增加注解路由支持
+* 路由支持跨域请求设置
+* 增加`app_dispatch`钩子位置
+* 修正多对多关联的`detach`方法
+* 修正软删除的`destroy`方法
+* Cookie类`httponly`参数默认为false
+* 日志File驱动增加`single`参数配置记录同一个文件(不按日期生成)
+* 路由的`ext`和`denyExt`方法支持不传任何参数
+* 改进模型的`save`方法对`oracle`的支持
+* Query类的`insertall`方法支持配合`data`和`limit`方法
+* 增加`whereOr`动态查询支持
+* 日志的ip地址记录改进
+* 模型`saveAll`方法支持`isUpdate`方法
+* 改进`Pivot`模型的实例化操作
+* 改进Model类的`data`方法
+* 改进多对多中间表模型类
+* 模型增加`force`方法强制更新所有数据
+* Hook类支持设置入口方法名称
+* 改进验证类
+* 改进`hasWhere`查询的数据重复问题
+* 模型的`saveall`方法返回数据集对象
+* 改进File缓存的`clear`方法
+* 缓存添加统一的序列化机制
+* 改进泛三级域名的绑定
+* 改进泛域名的传值和取值
+* Request类增加`panDomain`方法
+* 改进废弃字段判断
+* App类增加`create`方法用于实例化应用类库
+* 容器类增加`has`方法
+* 改进多数据库切换连接
+* 改进断线重连的异常捕获
+* 改进模型类`buildQuery`方法
+* Query类增加`unionAll`方法
+* 关联统计功能增强(支持Sum/Max/Min/Avg)
+* 修正延迟写入
+* chunk方法支持复合主键
+* 改进JSON类型的写入
+* 改进Mysql的insertAll方法
+* Model类`save`方法改进复合主键包含自增的情况
+* 改进Query类`inc`和`dec`方法的关键字处理
+* File缓存inc和dec方法保持原来的有效期
+* 改进redis缓存的有效期判断
+* 增加checkRule方法用于单独数据的多个验证规则
+* 修正setDec方法的延迟写入
+* max和min方法增加force参数
+* 二级配置参数区分大小写
+* 改进join方法自关联的问题
+* 修正关联模型自定义表名的情况
+* Query类增加getFieldsType和getTableFields方法
+* 取消视图替换功能及view_replace_str配置参数
+* 改进域名绑定模块后的额外路由规则问题
+* 改进mysql的insertAll方法
+* 改进insertAll方法写入json字段数据的支持
+* 改进redis长连接多编号库的情况
+
+## RC3版本(2017-11-6)
+
+主要更新如下:
+
+* 改进redis驱动的`get`方法
+* 修正Query类的`alias`方法
+* `File`类错误信息支持多语言
+* 修正路由的额外参数解析
+* 改进`whereTime`方法
+* 改进Model类`getAttr`方法
+* 改进App类的`controller`和`validate`方法支持多层
+* 改进`HasManyThrough`类
+* 修正软删除的`restore`方法
+* 改进`MorpthTo`关联
+* 改进数据库驱动类的`parseKey`方法
+* 增加`whereField`动态查询方法
+* 模型增加废弃字段功能
+* 改进路由的`after`行为检查和`before`行为机制
+* 改进路由分组的检查
+* 修正mysql的`json`字段查询
+* 取消Connection类的`quote`方法
+* 改进命令行的支持
+* 验证信息支持多语言
+* 修正路由模型绑定
+* 改进参数绑定类型对枚举类型的支持
+* 修正模板的`{$Think.version} `输出
+* 改进模板`date`函数解析
+* 改进`insertAll`方法支持分批执行
+* Request类`host`方法支持反向代理
+* 改进`JumpResponse`支持区分成功和错误模板
+* 改进开启类库后缀后的关联外键自动识别问题
+* 修正一对一关联的JOIN方式预载入查询问题
+* Query类增加`hidden`方法
+
+## RC2版本(2017-10-17)
+
+主要更新如下:
+
+* 修正视图查询
+* 修正资源路由
+* 修正`HasMany`关联 修正`where`方法的闭包查询
+* 一对一关联绑定属性到父模型后 关联属性不再保留
+* 修正应用的命令行配置文件读取
+* 改进`Connection`类的`getCacheKey`方法
+* 改进文件上传的非法图像异常
+* 改进验证类的`unique`规则
+* Config类`get`方法支持获取一级配置
+* 修正count方法对`fetchSql`的支持
+* 修正mysql驱动对`socket`支持
+* 改进Connection类的`getRealSql`方法
+* 修正`view`助手函数
+* Query类增加`leftJoin` `rightJoin` 和 `fullJoin`方法
+* 改进app_namespace的获取
+* 改进`append`方法对一对一`bind`属性的支持
+* 改进关联的`saveall`方法的返回值
+* 路由标识设置异常修复
+* 改进Route类`rule`方法
+* 改进模型的`table`属性设置
+* 改进composer autofile的加载顺序
+* 改进`exception_handle`配置对闭包的支持
+* 改进app助手函数增加参数
+* 改进composer的加载路径判断
+* 修正路由组合变量的URL生成
+* 修正路由URL生成
+* 改进`whereTime`查询并支持扩展规则
+* File类的`move`方法第二个参数支持`false`
+* 改进Config类
+* 改进缓存类`remember`方法
+* 惯例配置文件调整 Url类当普通模式参数的时候不做`urlencode`处理
+* 取消`ROOT_PATH`和`APP_PATH`常量定义 如需更改应用目录 自己重新定义入口文件
+* 增加`app_debug`的`Env`获取
+* 修正泛域名绑定
+* 改进查询表达式的解析机制
+* mysql增加`regexp`查询表达式 支持正则查询
+* 改进查询表达式的异常判断
+* 改进model类的`destroy`方法
+* 改进Builder类 取消`parseValue`方法
+* 修正like查询的参数绑定问题
+* console和start文件移出核心纳入应用库
+* 改进Db类主键删除方法
+* 改进泛域名绑定模块
+* 取消`BIND_MODULE`常量 改为在入口文件使用`bind`方法设置
+* 改进数组查询
+* 改进模板渲染的异常处理
+* 改进控制器基类的架构方法参数
+* 改进Controller类的`success`和`error`方法
+* 改进对浏览器`JSON-Handle`插件的支持
+* 优化跳转模板的移动端显示
+* 修正模型查询的`chunk`方法对时间字段的支持
+* 改进trace驱动
+* Collection类增加`push`方法
+* 改进Redis Session驱动
+* 增加JumpResponse驱动
+
+
+## RC1(2017-9-8)
+
+主要新特性为:
+
+* 引入容器和Facade支持
+* 依赖注入完善和支持更多场景
+* 重构的(对象化)路由
+* 配置和路由目录独立
+* 取消系统常量
+* 助手函数增强
+* 类库别名机制
+* 模型和数据库增强
+* 验证类增强
+* 模板引擎改进
+* 支持PSR-3日志规范
+* RC1版本取消了5.0多个字段批量数组查询的方式

+ 32 - 0
LICENSE.txt

@@ -0,0 +1,32 @@
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn)
+All rights reserved。
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+Apache Licence是著名的非盈利开源组织Apache采用的协议。
+该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,
+允许代码修改,再作为开源或商业软件发布。需要满足
+的条件: 
+1. 需要给代码的用户一份Apache Licence ;
+2. 如果你修改了代码,需要在被修改的文件中说明;
+3. 在延伸的代码中(修改和有源代码衍生的代码中)需要
+带有原来代码中的协议,商标,专利声明和其他原来作者规
+定需要包含的说明;
+4. 如果再发布的产品中包含一个Notice文件,则在Notice文
+件中需要带有本协议内容。你可以在Notice中增加自己的
+许可,但不可以表现为对Apache Licence构成更改。 
+具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0
+
+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.

+ 180 - 0
README.md

@@ -0,0 +1,180 @@
+![](https://box.kancloud.cn/5a0aaa69a5ff42657b5c4715f3d49221) 
+
+ThinkPHP 5.1(LTS版本) —— 12载初心,你值得信赖的PHP框架
+===============
+
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/top-think/framework/badges/quality-score.png?b=5.1)](https://scrutinizer-ci.com/g/top-think/framework/?branch=5.1)
+[![Build Status](https://travis-ci.org/top-think/framework.svg?branch=master)](https://travis-ci.org/top-think/framework)
+[![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework)
+[![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework)
+[![PHP Version](https://img.shields.io/badge/php-%3E%3D5.6-8892BF.svg)](http://www.php.net/)
+[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework)
+
+ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特性包括:
+
+ + 采用容器统一管理对象
+ + 支持Facade
+ + 注解路由支持
+ + 路由跨域请求支持
+ + 配置和路由目录独立
+ + 取消系统常量
+ + 助手函数增强
+ + 类库别名机制
+ + 增加条件查询
+ + 改进查询机制
+ + 配置采用二级
+ + 依赖注入完善
+ + 支持`PSR-3`日志规范
+ + 中间件支持(V5.1.6+)
+ + Swoole/Workerman支持(V5.1.18+)
+
+
+> ThinkPHP5的运行环境要求PHP5.6以上。
+
+## 安装
+
+使用composer安装
+
+~~~
+composer create-project topthink/think tp
+~~~
+
+启动服务
+
+~~~
+cd tp
+php think run
+~~~
+
+然后就可以在浏览器中访问
+
+~~~
+http://localhost:8000
+~~~
+
+更新框架
+~~~
+composer update topthink/framework
+~~~
+
+
+## 在线手册
+
++ [完全开发手册](https://www.kancloud.cn/manual/thinkphp5_1/content)
++ [升级指导](https://www.kancloud.cn/manual/thinkphp5_1/354155) 
+
+## 目录结构
+
+初始的目录结构如下:
+
+~~~
+www  WEB部署目录(或者子目录)
+├─application           应用目录
+│  ├─common             公共模块目录(可以更改)
+│  ├─module_name        模块目录
+│  │  ├─common.php      模块函数文件
+│  │  ├─controller      控制器目录
+│  │  ├─model           模型目录
+│  │  ├─view            视图目录
+│  │  └─ ...            更多类库目录
+│  │
+│  ├─command.php        命令行定义文件
+│  ├─common.php         公共函数文件
+│  └─tags.php           应用行为扩展定义文件
+│
+├─config                应用配置目录
+│  ├─module_name        模块配置目录
+│  │  ├─database.php    数据库配置
+│  │  ├─cache           缓存配置
+│  │  └─ ...            
+│  │
+│  ├─app.php            应用配置
+│  ├─cache.php          缓存配置
+│  ├─cookie.php         Cookie配置
+│  ├─database.php       数据库配置
+│  ├─log.php            日志配置
+│  ├─session.php        Session配置
+│  ├─template.php       模板引擎配置
+│  └─trace.php          Trace配置
+│
+├─route                 路由定义目录
+│  ├─route.php          路由定义
+│  └─...                更多
+│
+├─public                WEB目录(对外访问目录)
+│  ├─index.php          入口文件
+│  ├─router.php         快速测试文件
+│  └─.htaccess          用于apache的重写
+│
+├─thinkphp              框架系统目录
+│  ├─lang               语言文件目录
+│  ├─library            框架类库目录
+│  │  ├─think           Think类库包目录
+│  │  └─traits          系统Trait目录
+│  │
+│  ├─tpl                系统模板目录
+│  ├─base.php           基础定义文件
+│  ├─console.php        控制台入口文件
+│  ├─convention.php     框架惯例配置文件
+│  ├─helper.php         助手函数文件
+│  ├─phpunit.xml        phpunit配置文件
+│  └─start.php          框架入口文件
+│
+├─extend                扩展类库目录
+├─runtime               应用的运行时目录(可写,可定制)
+├─vendor                第三方类库目录(Composer依赖库)
+├─build.php             自动生成定义文件(参考)
+├─composer.json         composer 定义文件
+├─LICENSE.txt           授权说明文件
+├─README.md             README 文件
+├─think                 命令行入口文件
+~~~
+
+> 可以使用php自带webserver快速测试
+> 切换到根目录后,启动命令:php think run
+
+## 命名规范
+
+`ThinkPHP5`遵循PSR-2命名规范和PSR-4自动加载规范,并且注意如下规范:
+
+### 目录和文件
+
+*   目录不强制规范,驼峰和小写+下划线模式均支持;
+*   类库、函数文件统一以`.php`为后缀;
+*   类的文件名均以命名空间定义,并且命名空间的路径和类库文件所在路径一致;
+*   类名和类文件名保持一致,统一采用驼峰法命名(首字母大写);
+
+### 函数和类、属性命名
+
+*   类的命名采用驼峰法,并且首字母大写,例如 `User`、`UserType`,默认不需要添加后缀,例如`UserController`应该直接命名为`User`;
+*   函数的命名使用小写字母和下划线(小写字母开头)的方式,例如 `get_client_ip`;
+*   方法的命名使用驼峰法,并且首字母小写,例如 `getUserName`;
+*   属性的命名使用驼峰法,并且首字母小写,例如 `tableName`、`instance`;
+*   以双下划线“__”打头的函数或方法作为魔法方法,例如 `__call` 和 `__autoload`;
+
+### 常量和配置
+
+*   常量以大写字母和下划线命名,例如 `APP_PATH`和 `THINK_PATH`;
+*   配置参数以小写字母和下划线命名,例如 `url_route_on` 和`url_convert`;
+
+### 数据表和字段
+
+*   数据表和字段采用小写加下划线方式命名,并注意字段名不要以下划线开头,例如 `think_user` 表和 `user_name`字段,不建议使用驼峰和中文作为数据表字段命名。
+
+## 参与开发
+
+请参阅 [ThinkPHP5 核心框架包](https://github.com/top-think/framework)。
+
+## 版权信息
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+
+本项目包含的第三方源码和二进制文件之版权信息另行标注。
+
+版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn)
+
+All rights reserved。
+
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+更多细节参阅 [LICENSE.txt](LICENSE.txt)

+ 1 - 0
application/.htaccess

@@ -0,0 +1 @@
+deny from all

+ 12 - 0
application/command.php

@@ -0,0 +1,12 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+return [];

+ 12 - 0
application/common.php

@@ -0,0 +1,12 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 流年 <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// 应用公共文件

+ 352 - 0
application/common/functions.php

@@ -0,0 +1,352 @@
+<?php
+
+use think\Db;
+use think\helper\Time;
+
+/**
+ * @name post请求
+ * @param $url
+ * @param $data
+ * @return bool|string
+ */
+function curl_post($url, $data)
+{
+    $ch = curl_init();
+    curl_setopt($ch, CURLOPT_URL, $url);
+    curl_setopt($ch, CURLOPT_POST, 1);
+    curl_setopt($ch, CURLOPT_HEADER, 0);
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+    $return = curl_exec($ch);
+    curl_close($ch);
+    return $return;
+}
+
+/**
+ * @name get请求
+ * @param $url
+ * @return mixed
+ */
+function curl_get($url)
+{
+    $ch = curl_init();
+    // 设置选项,包括URL
+    curl_setopt($ch, CURLOPT_URL, $url);
+    curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+    curl_setopt($ch, CURLOPT_HEADER, 0);
+
+    if (stripos($url, "https://") !== FALSE) {
+        curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
+    } else {
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, TRUE);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);//严格校验
+    }
+    // 执行并获取HTML文档内容
+    $output = curl_exec($ch);
+    // 释放curl句柄
+    $err_code = curl_errno($ch);
+    curl_close($ch);
+
+    return json_decode($output);
+}
+
+if(!function_exists('public_path')){
+    function public_path($path){
+        return CMF_ROOT."public/upload/".$path;
+    }
+}
+
+/*************** 校验身份证 start *******************/
+if(!function_exists('checkIdCard')){
+    function checkIdCard($idc){
+        if(empty($idc)){
+            return false;
+        }
+        $idcard = $idc;
+        $City = array(11=>"北京",12=>"天津",13=>"河北",14=>"山西",15=>"内蒙古",21=>"辽宁",22=>"吉林",23=>"黑龙江",31=>"上海",32=>"江苏",33=>"浙江",34=>"安徽",35=>"福建",36=>"江西",37=>"山东",41=>"河南",42=>"湖北",43=>"湖南",44=>"广东",45=>"广西",46=>"海南",50=>"重庆",51=>"四川",52=>"贵州",53=>"云南",54=>"西藏",61=>"陕西",62=>"甘肃",63=>"青海",64=>"宁夏",65=>"新疆",71=>"台湾",81=>"香港",82=>"澳门",91=>"国外");
+        $iSum = 0;
+        $idCardLength = strlen($idcard);
+        //长度验证
+        if(!preg_match('/^\d{17}(\d|x)$/i',$idcard) and !preg_match('/^\d{15}$/i',$idcard))
+        {
+            return false;
+        }
+        //地区验证
+        if(!array_key_exists(intval(substr($idcard,0,2)),$City))
+        {
+            return false;
+        }
+        // 15位身份证验证生日,转换为18位
+        if ($idCardLength == 15)
+        {
+            $sBirthday = '19'.substr($idcard,6,2).'-'.substr($idcard,8,2).'-'.substr($idcard,10,2);
+            //    $d = new DateTime($sBirthday);
+            //    $dd = $d->format('Y-m-d');
+            //    if($sBirthday != $dd)
+            if($sBirthday != $sBirthday)
+            {
+                return false;
+            }
+            $idcard = substr($idcard,0,6)."19".substr($idcard,6,9);//15to18
+            $Bit18 = getVerifyBit($idcard);//算出第18位校验码
+            $idcard = $idcard.$Bit18;
+        }
+        // 判断是否大于2078年,小于1900年
+        $year = substr($idcard,6,4);
+        if ($year<1900 || $year>2078 )
+        {
+            return false;
+        }
+
+        //18位身份证处理
+        $sBirthday = substr($idcard,6,4).'-'.substr($idcard,10,2).'-'.substr($idcard,12,2);
+        //    var_dump($sBirthday);
+        //    $d = new DateTime($sBirthday);
+
+        //    $dd = $d->format('Y-m-d');
+        //    echo $dd;
+        //    die();
+        //    if($sBirthday != $dd)
+        if($sBirthday != $sBirthday)
+        {
+            return false;
+        }
+        //身份证编码规范验证
+        $idcard_base = substr($idcard,0,17);
+        if(strtoupper(substr($idcard,17,1)) != getVerifyBit($idcard_base))
+        {
+            return false;
+        }
+        return true;
+    }
+}
+
+// 计算身份证校验码,根据国家标准GB 11643-1999
+if(!function_exists('getVerifyBit')){
+    function getVerifyBit($idcard_base)
+    {
+        if(strlen($idcard_base) != 17)
+        {
+            return false;
+        }
+        //加权因子
+        $factor = array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2);
+        //校验码对应值
+        $verify_number_list = array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2');
+        $checksum = 0;
+        for ($i = 0; $i < strlen($idcard_base); $i++)
+        {
+            $checksum += substr($idcard_base, $i, 1) * $factor[$i];
+        }
+        $mod = $checksum % 11;
+        $verify_number = $verify_number_list[$mod];
+        return $verify_number;
+    }
+}
+
+function getTime($nian=0,$yue=0){
+    if(empty($nian) || empty($yue)){
+        $now = time();
+        $nian = date("Y",$now);
+        $yue =  date("m",$now);
+    }
+    $time['begin'] = mktime(0,0,0,$yue,1,$nian);
+    $time['end'] = mktime(23,59,59,($yue+1),0,$nian);
+    return $time;
+}
+
+/**
+ * 发送站内信息
+ */
+function siteMessage($content='',$siteurl='',$user_id=0,$type=1,$distributor_id=0,$membership_id=0){
+    if(empty($content))
+        return;
+    if(!isset($user_id)||empty($user_id)){
+        return;
+    }
+    $data['user_id']=$user_id;
+    $data['add_time']=time();
+    $data['content']=$content;
+    $data['siteurl']=$siteurl;
+    $data['type']=$type;
+    $data['distributor_id']=$distributor_id;
+    $data['membership_id']=$membership_id;
+    Db::name('user_message')->insert($data);
+    return true;
+    //}
+}
+
+/**
+ * 获取七天记录
+ */
+function week7(){
+    $week=array();
+    for($i=0;$i<7;$i++){
+        array_push($week,Time::daysAfter(-$i));
+    }
+
+    return $week;
+}
+
+/**
+ * 获取七天记录
+ */
+function week7_day(){
+    $week=array();
+    for($i=1;$i<7;$i++){
+        array_push($week,date('Y-m-d',Time::daysAfter(-$i)));
+    }
+
+    return $week;
+}
+
+function get_date1($start,$end){
+    $start_time=strtotime($start);
+    $end_time=strtotime($end);
+    $date=array();
+    $days=($end_time - $start_time) / 86400;
+//    $weekArr=array('星期日','星期一','星期二','星期三','星期四','星期五','星期六');
+    for($i=0;$i<=$days;$i++){
+//        $num_week=date("w",$start+($i*86400));
+      //  echo date('Y-m-d',$start+($i*86400)).'-----'.$weekArr[$num_week].'</br>';
+        array_push($date,date('Y-m-d',$start_time+($i*86400)));
+    }
+
+    return $date;
+}
+
+/***
+ * @param $fields 驼峰对象
+ * @return array
+ */
+function CamelToUnderLineArr($fields)
+{
+    $newArr = [];
+    if (!is_array($fields) || !array_values($fields))  return $newArr;
+
+    foreach ($fields as $key => $v) {
+        $keyTmp = strtolower(preg_replace('/((?<=[a-z])(?=[A-Z]))/', '_', $key));
+        $newArr[$keyTmp] = $v;
+        unset($fields->$key);
+    }
+
+    return $newArr;
+}
+
+
+/***
+ * @param $fields 下划线数组
+ * @return \stdClass
+ */
+function underLineArrTOCamel($fields)
+{
+
+    $newObj = new \stdClass();
+    if(!is_array($fields) || !$fields) return null;
+    foreach ($fields as $key => $v) {
+        $keyTmp = array_reduce(explode('_',$key), function($v1, $v2) {
+            return ucfirst($v1).ucfirst($v2);
+        });
+        $keyTmp = lcfirst($keyTmp);
+        $newObj->$keyTmp = $v;
+        unset($fields[$key]);
+    }
+    return $newObj;
+}
+
+
+/*
+ * 下划线转驼峰
+ */
+function convertUnderline($str)
+{
+    $str = preg_replace_callback('/([-_]+([a-z]{1}))/i',function($matches){
+        return strtoupper($matches[2]);
+    },$str);
+    return $str;
+}
+
+/*
+ * 驼峰转下划线
+ */
+function humpToLine($str){
+    $str = preg_replace_callback('/([A-Z]{1})/',function($matches){
+        return '_'.strtolower($matches[0]);
+    },$str);
+    return $str;
+}
+
+function xoc($xoc,&$res=0,$y=1){
+
+    $a = str_split($xoc, 2);
+    $num=count($a);
+    //echo $y;
+    $b = $a[$y];
+    if($res==0){
+        $i =hexdec($a[0]) ;
+    }else{
+        $i=$res;
+    }
+
+    //var_dump($i .'^'.hexdec($b));
+    $c = $i ^ hexdec($b);
+    if($y<$num-1){
+        return xoc($xoc,$c,$y+1);
+    }else{
+        return dechex($c);
+    }
+}
+
+/**
+ **字符串转16进制
+ **/
+function String2Hex($string){
+    $hex='';
+    for ($i=0; $i < strlen($string); $i++){
+        $hex .= dechex(ord($string[$i]));
+    }
+    return $hex;
+}
+
+/**
+ **16进制转字符串
+ **/
+
+function Hex2String($hex){
+    $string='';
+    for ($i=0; $i < strlen($hex)-1; $i+=2){
+        $string .= chr(hexdec($hex[$i].$hex[$i+1]));
+    }
+    return $string;
+}
+
+
+#@param check_code|校验码 为空是计算校验码,不为空为验证校验码
+function bcc($msg, $check_code = '') {
+    //按两位字符切割字符串
+    $check_str_array = str_split($msg, 2);
+    $str_len = count($check_str_array);
+
+    $xor = hexdec($check_str_array[0]);
+
+    for ($i = 1; $i < $str_len; $i++) {
+        $xor ^= hexdec($check_str_array[$i]);
+    }
+    $xor = dechex($xor);
+    $xor = str_pad($xor, 2, 0, STR_PAD_LEFT); #不足两位前面填充0
+    if (!$check_code) {
+        return $xor;
+    }
+    return $xor == $check_code;
+}
+
+/**
+ * @name 企业微信
+ * @param $config
+ */
+function work(array $config){
+    return \EasyWeChat\Factory::work($config);
+}

+ 171 - 0
application/common/plugin/Jssdk.php

@@ -0,0 +1,171 @@
+<?php
+namespace app\common\plugin;
+/**
+ * Created by PhpStorm.
+ * User: shaoguo
+ * Date: 2019-02-26
+ * Time: 12:57
+ */
+
+class Jssdk
+{
+    private $app_Id;
+    private $app_Secret;
+
+    public function __construct($str='mp_wxconfig') {
+        $wxconfig=config($str);
+        $this->app_Id =$wxconfig['appid'];
+        $this->app_Secret =$wxconfig['secret'];
+//        dump($wxconfig);
+    }
+
+    public function getSignPackage($url="") {
+        $jsapiTicket = $this->getJsApiTicket();
+
+        // 注意 URL 一定要动态获取,不能 hardcode.
+        $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
+
+        $timestamp = time();
+        $nonceStr = $this->createNonceStr();
+
+        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
+        $string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";
+
+        $signature = sha1($string);
+
+        $signPackage = array(
+            "appId"     => $this->app_Id,
+            "nonceStr"  => $nonceStr,
+            "timestamp" => $timestamp,
+            "url"       => $url,
+            "signature" => $signature,
+            "rawString" => $string,
+        	"ticket"	=>$jsapiTicket
+        );
+        return $signPackage;
+    }
+
+    private function createNonceStr($length = 16) {
+        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+        $str = "";
+        for ($i = 0; $i < $length; $i++) {
+            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
+        }
+        return $str;
+    }
+
+    private function getJsApiTicket() {
+        // jsapi_ticket 应该全局存储与更新,以下代码以写入到文件中做示例
+    	$path=$this->app_Id."jsapi_ticket.json";
+    	if(!file_exists($path)){
+    		file_put_contents($path, '');
+    		chmod($path, 0777);
+    	}
+        $data = json_decode(file_get_contents($path));
+        $accessToken = $this->getAccessToken();
+        // 如果是企业号用以下 URL 获取 ticket
+        // $url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=$accessToken";
+        $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=$accessToken";
+        if(!empty($data)){
+            if ($data->expire_time < time()) {
+
+                $res = json_decode($this->httpGet($url));
+                $ticket = $res->ticket;
+                if ($ticket) {
+                    $data->expire_time = time() + 7000;
+                    $data->jsapi_ticket = $ticket;
+                    $fp = fopen($path, "w");
+                    fwrite($fp, json_encode($data));
+                    fclose($fp);
+                }
+            } else {
+                $ticket = $data->jsapi_ticket;
+            }
+
+        }else{
+            $res = json_decode($this->httpGet($url));
+            $ticket = $res->ticket;
+            if ($ticket) {
+                $res->expire_time = time() + 7000;
+                $res->jsapi_ticket = $ticket;
+                $fp = fopen($path, "w");
+                fwrite($fp, json_encode($res));
+                fclose($fp);
+            }
+        }
+
+        return $ticket;
+    }
+
+	public function getAccessToken($refresh=false) {
+        // access_token 应该全局存储与更新,以下代码以写入到文件中做示例
+    	$path=$this->app_Id."access_token.json";
+    	if(!file_exists($path)){
+    		file_put_contents($path, '');
+    		chmod($path, 0777);
+    	}
+        $data = json_decode(file_get_contents($path));
+        $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$this->app_Id&secret=$this->app_Secret";
+        if(!is_null($data)){
+            if ($data->expire_time < time()||$refresh) {
+                // 如果是企业号用以下URL获取access_token
+                // $url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=$this->app_Id&corpsecret=$this->app_Secret";
+                $res = json_decode($this->httpGet($url));
+                $access_token = $res->access_token;
+
+                $data->expire_time = time() + 7000;
+                $data->access_token = $access_token;
+
+                $fp = fopen($path, "w");
+                fwrite($fp, json_encode($data));
+                fclose($fp);
+            } else {
+            	//检测是否失效
+            	$test_url='https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token='.$data->access_token;
+            	$res = json_decode($this->httpGet($test_url),true);
+            	
+            	if(isset($res['errcode'])){
+            		$res = json_decode($this->httpGet($url));
+            		$access_token = $res->access_token;
+            		
+            		$data->expire_time = time() + 7000;
+            		$data->access_token = $access_token;
+            		
+            		$fp = fopen($path, "w");
+            		fwrite($fp, json_encode($data));
+            		fclose($fp);
+            	}else{
+                	$access_token = $data->access_token;
+            	}
+            }
+        }else{
+            $res = json_decode($this->httpGet($url));
+            $access_token = $res->access_token;
+            if ($access_token) {
+                $res->expire_time = time() + 7000;
+                $res->access_token = $access_token;
+                $fp = fopen($path, "w");
+                fwrite($fp, json_encode($res));
+                fclose($fp);
+            }
+
+        }
+
+        return $access_token;
+    }
+
+    private function httpGet($url) {
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($curl, CURLOPT_TIMEOUT, 500);
+        // 为保证第三方服务器与微信服务器之间数据传输的安全性,所有微信接口采用https方式调用,必须使用下面2行代码打开ssl安全校验。
+        // 如果在部署过程中代码在此处验证失败,请到 http://curl.haxx.se/ca/cacert.pem 下载新的证书判别文件。
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
+//        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, true);
+        curl_setopt($curl, CURLOPT_URL, $url);
+
+        $res = curl_exec($curl);
+        curl_close($curl);
+        return $res;
+    }
+}

+ 81 - 0
application/common/plugin/Jwt.php

@@ -0,0 +1,81 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: shaoguo
+ * Date: 2019-03-07
+ * Time: 17:25
+ */
+
+namespace app\common\plugin;
+
+use Firebase\JWT\BeforeValidException;
+use Firebase\JWT\ExpiredException;
+use \Firebase\JWT\JWT as JWTP;
+use Firebase\JWT\SignatureInvalidException;
+class Jwt extends JWTP
+{
+    static private $key='VKcZGQQ4zjAzjc0yZsa0';
+
+    static private $type=['HS256'];
+
+    /**
+     * @name 获取token
+     * @param $openid
+     * @param $major
+     * @return array|false|\PDOStatement|string|Model
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    static public function getToken($user_id,$major='aaa'){
+        $time=time();
+        //$user=db('wx_user')->where(['wx_openid'=>$openid])->find();
+        $token=[
+            'iss'=>url("/"),
+            'iat'=>$time,
+            'exp'=>$time+7200*24*3600,
+            'data'=>[
+                'userid'=>$user_id,
+                'major'=>$major
+            ]
+        ];
+        return self::encode($token,static::$key);
+    }
+
+   static public function verify($token){
+        $message=[];
+        try{
+            self::$leeway=60;
+            $decoded=self::decode($token,static::$key,static::$type);
+            $arr=(array)$decoded;
+            $message=[
+                'code'=>1,
+                'message'=>$arr
+            ];
+
+        }catch (SignatureInvalidException $e){
+            $message=[
+                'code'=>0,
+                'message'=>"签名错误"
+            ];
+        } catch (BeforeValidException $e){
+            $message=[
+                'code'=>0,
+                'message'=>"签名在某个时段才能运行"
+            ];
+        }catch(ExpiredException $e){
+            $message=[
+                'code'=>0,
+                'message'=>"登录过期"
+            ];
+        }catch (\Exception $e){
+            $message=[
+                'code'=>0,
+                'message'=>'签名异常错误'
+            ];
+        }
+
+        return $message;
+
+    }
+}

+ 94 - 0
application/common/plugin/Pkcs7Encoder.php

@@ -0,0 +1,94 @@
+<?php
+/**
+ * PKCS7Encoder class
+ *
+ * 提供基于PKCS7算法的加解密接口.
+ */
+class PKCS7Encoder
+{
+	public static $block_size = 16;
+
+	/**
+	 * 对需要加密的明文进行填充补位
+	 * @param $text 需要进行填充补位操作的明文
+	 * @return 补齐明文字符串
+	 */
+	function encode( $text )
+	{
+		$block_size = PKCS7Encoder::$block_size;
+		$text_length = strlen( $text );
+		//计算需要填充的位数
+		$amount_to_pad = PKCS7Encoder::$block_size - ( $text_length % PKCS7Encoder::$block_size );
+		if ( $amount_to_pad == 0 ) {
+			$amount_to_pad = PKCS7Encoder::block_size;
+		}
+		//获得补位所用的字符
+		$pad_chr = chr( $amount_to_pad );
+		$tmp = "";
+		for ( $index = 0; $index < $amount_to_pad; $index++ ) {
+			$tmp .= $pad_chr;
+		}
+		return $text . $tmp;
+	}
+
+	/**
+	 * 对解密后的明文进行补位删除
+	 * @param decrypted 解密后的明文
+	 * @return 删除填充补位后的明文
+	 */
+	function decode($text)
+	{
+
+		$pad = ord(substr($text, -1));
+		if ($pad < 1 || $pad > 32) {
+			$pad = 0;
+		}
+		return substr($text, 0, (strlen($text) - $pad));
+	}
+
+}
+
+/**
+ * Prpcrypt class
+ *
+ * 
+ */
+class Prpcrypt
+{
+	public $key;
+
+	function __construct( $k )
+	{
+		$this->key = $k;
+	}
+
+	/**
+	 * 对密文进行解密
+	 * @param string $aesCipher 需要解密的密文
+     * @param string $aesIV 解密的初始向量
+	 * @return string 解密得到的明文
+	 */
+ 	public function decrypt( $aesCipher, $aesIV )
+    {
+        try {
+            //解密
+            $decrypted = openssl_decrypt($aesCipher, 'aes-128-cbc', $this->key, OPENSSL_RAW_DATA, $aesIV);
+            // dump(($aesCipher));
+            // dump(($this->key));
+            // dump(($aesIV));
+        } catch (\Exception $e) {
+            return false;
+        }
+        try {
+            //去除补位字符
+            $pkc_encoder = new PKCS7Encoder;
+            $result = $pkc_encoder->decode($decrypted);
+        } catch (\Exception $e) {
+            //print $e;
+            return false;
+        }
+        return array(0, $result);
+    }
+}
+
+?>

+ 34 - 0
application/common/plugin/Varbinary.php

@@ -0,0 +1,34 @@
+<?php
+namespace app\common\plugin;
+/**
+ * byte数组与字符串转化类
+* @author ZT
+*/
+
+class Varbinary {
+	
+	public static function byteArrayTofloatArray($bb)
+	{
+		$f=array();
+		for ($i = 0; $i < strlen($bb); $i = $i + 4)
+		{
+			array_push($f,sprintf("%.5f",array_values(unpack('f',$bb[$i].$bb[$i + 1].$bb[$i + 2].$bb[$i + 3] ))[0]));
+		}
+		return $f;
+	}
+	
+	 public static function byteArrayTodatetimeArray($bb)
+	{
+		$t=array();
+		for ($i = 0; $i < strlen($bb); $i = $i + 8)
+		{
+			array_push($t,self::dealValue(array_values(unpack('Q',$bb[$i].$bb[$i + 1].$bb[$i + 2].$bb[$i + 3].$bb[$i + 4].$bb[$i + 5].$bb[$i + 6].$bb[$i + 7]))[0]));
+		}
+		return $t;
+	} 
+	
+	public static function dealValue($date){
+		return date('Y-m-d H:i:s',($date - 621355968000000000) / 10000000-8*3600);
+	}
+	 
+}

+ 4562 - 0
application/common/plugin/Wechat.php

@@ -0,0 +1,4562 @@
+<?php
+/**
+ *	微信公众平台PHP-SDK, 官方API部分
+ *  @author  dodge <dodgepudding@gmail.com>
+ *  @link https://github.com/dodgepudding/wechat-php-sdk
+ *  @version 1.2
+ *  usage:
+ *   $options = array(
+ *			'token'=>'tokenaccesskey', //填写你设定的key
+ *			'encodingaeskey'=>'encodingaeskey', //填写加密用的EncodingAESKey
+ *			'appid'=>'wxdk1234567890', //填写高级调用功能的app id
+ *			'appsecret'=>'xxxxxxxxxxxxxxxxxxx' //填写高级调用功能的密钥
+ *		);
+ *	 $weObj = new Wechat($options);
+ *   $weObj->valid();
+ *   $type = $weObj->getRev()->getRevType();
+ *   switch($type) {
+ *   		case Wechat::MSGTYPE_TEXT:
+ *   			$weObj->text("hello, I'm wechat")->reply();
+ *   			exit;
+ *   			break;
+ *   		case Wechat::MSGTYPE_EVENT:
+ *   			....
+ *   			break;
+ *   		case Wechat::MSGTYPE_IMAGE:
+ *   			...
+ *   			break;
+ *   		default:
+ *   			$weObj->text("help info")->reply();
+ *   }
+ *
+ *   //获取菜单操作:
+ *   $menu = $weObj->getMenu();
+ *   //设置菜单
+ *   $newmenu =  array(
+ *   		"button"=>
+ *   			array(
+ *   				array('type'=>'click','name'=>'最新消息','key'=>'MENU_KEY_NEWS'),
+ *   				array('type'=>'view','name'=>'我要搜索','url'=>'http://www.baidu.com'),
+ *   				)
+ *  		);
+ *   $result = $weObj->createMenu($newmenu);
+ */
+namespace app\common\plugin;
+class Wechat
+{
+	const MSGTYPE_TEXT = 'text';
+	const MSGTYPE_IMAGE = 'image';
+	const MSGTYPE_LOCATION = 'location';
+	const MSGTYPE_LINK = 'link';
+	const MSGTYPE_EVENT = 'event';
+	const MSGTYPE_MUSIC = 'music';
+	const MSGTYPE_NEWS = 'news';
+	const MSGTYPE_VOICE = 'voice';
+	const MSGTYPE_VIDEO = 'video';
+	const MSGTYPE_SHORTVIDEO = 'shortvideo';
+	const EVENT_SUBSCRIBE = 'subscribe';       //订阅
+	const EVENT_UNSUBSCRIBE = 'unsubscribe';   //取消订阅
+	const EVENT_SCAN = 'SCAN';                 //扫描带参数二维码
+	const EVENT_LOCATION = 'LOCATION';         //上报地理位置
+	const EVENT_MENU_VIEW = 'VIEW';                     //菜单 - 点击菜单跳转链接
+	const EVENT_MENU_CLICK = 'CLICK';                   //菜单 - 点击菜单拉取消息
+	const EVENT_MENU_SCAN_PUSH = 'scancode_push';       //菜单 - 扫码推事件(客户端跳URL)
+	const EVENT_MENU_SCAN_WAITMSG = 'scancode_waitmsg'; //菜单 - 扫码推事件(客户端不跳URL)
+	const EVENT_MENU_PIC_SYS = 'pic_sysphoto';          //菜单 - 弹出系统拍照发图
+	const EVENT_MENU_PIC_PHOTO = 'pic_photo_or_album';  //菜单 - 弹出拍照或者相册发图
+	const EVENT_MENU_PIC_WEIXIN = 'pic_weixin';         //菜单 - 弹出微信相册发图器
+	const EVENT_MENU_LOCATION = 'location_select';      //菜单 - 弹出地理位置选择器
+	const EVENT_SEND_MASS = 'MASSSENDJOBFINISH';        //发送结果 - 高级群发完成
+	const EVENT_SEND_TEMPLATE = 'TEMPLATESENDJOBFINISH';//发送结果 - 模板消息发送结果
+	const EVENT_KF_SEESION_CREATE = 'kfcreatesession';  //多客服 - 接入会话
+	const EVENT_KF_SEESION_CLOSE = 'kfclosesession';    //多客服 - 关闭会话
+	const EVENT_KF_SEESION_SWITCH = 'kfswitchsession';  //多客服 - 转接会话
+	const EVENT_CARD_PASS = 'card_pass_check';          //卡券 - 审核通过
+	const EVENT_CARD_NOTPASS = 'card_not_pass_check';   //卡券 - 审核未通过
+	const EVENT_CARD_USER_GET = 'user_get_card';        //卡券 - 用户领取卡券
+	const EVENT_CARD_USER_DEL = 'user_del_card';        //卡券 - 用户删除卡券
+	const EVENT_MERCHANT_ORDER = 'merchant_order';        //微信小店 - 订单付款通知
+	const API_URL_PREFIX = 'https://api.weixin.qq.com/cgi-bin';
+	const AUTH_URL = '/token?grant_type=client_credential&';
+	const MENU_CREATE_URL = '/menu/create?';
+	const MENU_GET_URL = '/menu/get?';
+	const MENU_DELETE_URL = '/menu/delete?';
+	const MENU_ADDCONDITIONAL_URL = '/menu/addconditional?';
+	const MENU_DELCONDITIONAL_URL = '/menu/delconditional?';
+	const MENU_TRYMATCH_URL = '/menu/trymatch?';
+	const GET_TICKET_URL = '/ticket/getticket?';
+	const CALLBACKSERVER_GET_URL = '/getcallbackip?';
+	const QRCODE_CREATE_URL='/qrcode/create?';
+	const QR_SCENE = 0;
+	const QR_LIMIT_SCENE = 1;
+	const QRCODE_IMG_URL='https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=';
+	const SHORT_URL='/shorturl?';
+	const USER_GET_URL='/user/get?';
+	const USER_INFO_URL='/user/info?';
+	const USERS_INFO_URL='/user/info/batchget?';
+	const USER_UPDATEREMARK_URL='/user/info/updateremark?';
+	const GROUP_GET_URL='/groups/get?';
+	const USER_GROUP_URL='/groups/getid?';
+	const GROUP_CREATE_URL='/groups/create?';
+	const GROUP_UPDATE_URL='/groups/update?';
+	const GROUP_MEMBER_UPDATE_URL='/groups/members/update?';
+	const GROUP_MEMBER_BATCHUPDATE_URL='/groups/members/batchupdate?';
+	const CUSTOM_SEND_URL='/message/custom/send?';
+	const MEDIA_UPLOADNEWS_URL = '/media/uploadnews?';
+	const MASS_SEND_URL = '/message/mass/send?';
+	const TEMPLATE_SET_INDUSTRY_URL = '/template/api_set_industry?';
+	const TEMPLATE_ADD_TPL_URL = '/template/api_add_template?';
+	const TEMPLATE_SEND_URL = '/message/template/send?';
+	const MASS_SEND_GROUP_URL = '/message/mass/sendall?';
+	const MASS_DELETE_URL = '/message/mass/delete?';
+	const MASS_PREVIEW_URL = '/message/mass/preview?';
+	const MASS_QUERY_URL = '/message/mass/get?';
+	const UPLOAD_MEDIA_URL = 'http://file.api.weixin.qq.com/cgi-bin';
+	const MEDIA_UPLOAD_URL = '/media/upload?';
+	const MEDIA_UPLOADIMG_URL = '/media/uploadimg?';//图片上传接口
+	const MEDIA_GET_URL = '/media/get?';
+	const MEDIA_VIDEO_UPLOAD = '/media/uploadvideo?';
+    const MEDIA_FOREVER_UPLOAD_URL = '/material/add_material?';
+    const MEDIA_FOREVER_NEWS_UPLOAD_URL = '/material/add_news?';
+    const MEDIA_FOREVER_NEWS_UPDATE_URL = '/material/update_news?';
+    const MEDIA_FOREVER_GET_URL = '/material/get_material?';
+    const MEDIA_FOREVER_DEL_URL = '/material/del_material?';
+    const MEDIA_FOREVER_COUNT_URL = '/material/get_materialcount?';
+    const MEDIA_FOREVER_BATCHGET_URL = '/material/batchget_material?';
+	const OAUTH_PREFIX = 'https://open.weixin.qq.com/connect/oauth2';
+	const OAUTH_AUTHORIZE_URL = '/authorize?';
+	///多客服相关地址
+	const CUSTOM_SERVICE_GET_RECORD = '/customservice/getrecord?';
+	const CUSTOM_SERVICE_GET_KFLIST = '/customservice/getkflist?';
+	const CUSTOM_SERVICE_GET_ONLINEKFLIST = '/customservice/getonlinekflist?';
+	const API_BASE_URL_PREFIX = 'https://api.weixin.qq.com'; //以下API接口URL需要使用此前缀
+	const OAUTH_TOKEN_URL = '/sns/oauth2/access_token?';
+	const OAUTH_REFRESH_URL = '/sns/oauth2/refresh_token?';
+	const OAUTH_USERINFO_URL = '/sns/userinfo?';
+	const OAUTH_AUTH_URL = '/sns/auth?';
+	///多客服相关地址
+	const CUSTOM_SESSION_CREATE = '/customservice/kfsession/create?';
+	const CUSTOM_SESSION_CLOSE = '/customservice/kfsession/close?';
+	const CUSTOM_SESSION_SWITCH = '/customservice/kfsession/switch?';
+	const CUSTOM_SESSION_GET = '/customservice/kfsession/getsession?';
+	const CUSTOM_SESSION_GET_LIST = '/customservice/kfsession/getsessionlist?';
+	const CUSTOM_SESSION_GET_WAIT = '/customservice/kfsession/getwaitcase?';
+	const CS_KF_ACCOUNT_ADD_URL = '/customservice/kfaccount/add?';
+	const CS_KF_ACCOUNT_UPDATE_URL = '/customservice/kfaccount/update?';
+	const CS_KF_ACCOUNT_DEL_URL = '/customservice/kfaccount/del?';
+	const CS_KF_ACCOUNT_UPLOAD_HEADIMG_URL = '/customservice/kfaccount/uploadheadimg?';
+	///卡券相关地址
+	const CARD_CREATE                     = '/card/create?';
+	const CARD_DELETE                     = '/card/delete?';
+	const CARD_UPDATE                     = '/card/update?';
+	const CARD_GET                        = '/card/get?';
+        const CARD_USER_GETCARDLIST         = '/card/user/getcardlist?';
+        const CARD_BATCHGET                   = '/card/batchget?';
+	const CARD_MODIFY_STOCK               = '/card/modifystock?';
+	const CARD_LOCATION_BATCHADD          = '/card/location/batchadd?';
+	const CARD_LOCATION_BATCHGET          = '/card/location/batchget?';
+	const CARD_GETCOLORS                  = '/card/getcolors?';
+	const CARD_QRCODE_CREATE              = '/card/qrcode/create?';
+	const CARD_CODE_CONSUME               = '/card/code/consume?';
+	const CARD_CODE_DECRYPT               = '/card/code/decrypt?';
+	const CARD_CODE_GET                   = '/card/code/get?';
+	const CARD_CODE_UPDATE                = '/card/code/update?';
+	const CARD_CODE_UNAVAILABLE           = '/card/code/unavailable?';
+	const CARD_TESTWHILELIST_SET          = '/card/testwhitelist/set?';
+	const CARD_MEETINGCARD_UPDATEUSER      = '/card/meetingticket/updateuser?';    //更新会议门票
+	const CARD_MEMBERCARD_ACTIVATE        = '/card/membercard/activate?';      //激活会员卡
+	const CARD_MEMBERCARD_UPDATEUSER      = '/card/membercard/updateuser?';    //更新会员卡
+	const CARD_MOVIETICKET_UPDATEUSER     = '/card/movieticket/updateuser?';   //更新电影票(未加方法)
+	const CARD_BOARDINGPASS_CHECKIN       = '/card/boardingpass/checkin?';     //飞机票-在线选座(未加方法)
+	const CARD_LUCKYMONEY_UPDATE          = '/card/luckymoney/updateuserbalance?';     //更新红包金额
+	const SEMANTIC_API_URL = '/semantic/semproxy/search?'; //语义理解
+	///数据分析接口
+	static $DATACUBE_URL_ARR = array(        //用户分析
+	        'user' => array(
+	                'summary' => '/datacube/getusersummary?',		//获取用户增减数据(getusersummary)
+	                'cumulate' => '/datacube/getusercumulate?',		//获取累计用户数据(getusercumulate)
+	        ),
+	        'article' => array(            //图文分析
+	                'summary' => '/datacube/getarticlesummary?',		//获取图文群发每日数据(getarticlesummary)
+	                'total' => '/datacube/getarticletotal?',		//获取图文群发总数据(getarticletotal)
+	                'read' => '/datacube/getuserread?',			//获取图文统计数据(getuserread)
+	                'readhour' => '/datacube/getuserreadhour?',		//获取图文统计分时数据(getuserreadhour)
+	                'share' => '/datacube/getusershare?',			//获取图文分享转发数据(getusershare)
+	                'sharehour' => '/datacube/getusersharehour?',		//获取图文分享转发分时数据(getusersharehour)
+	        ),
+	        'upstreammsg' => array(        //消息分析
+	                'summary' => '/datacube/getupstreammsg?',		//获取消息发送概况数据(getupstreammsg)
+					'hour' => '/datacube/getupstreammsghour?',	//获取消息分送分时数据(getupstreammsghour)
+	                'week' => '/datacube/getupstreammsgweek?',	//获取消息发送周数据(getupstreammsgweek)
+	                'month' => '/datacube/getupstreammsgmonth?',	//获取消息发送月数据(getupstreammsgmonth)
+	                'dist' => '/datacube/getupstreammsgdist?',	//获取消息发送分布数据(getupstreammsgdist)
+	                'distweek' => '/datacube/getupstreammsgdistweek?',	//获取消息发送分布周数据(getupstreammsgdistweek)
+	               	'distmonth' => '/datacube/getupstreammsgdistmonth?',	//获取消息发送分布月数据(getupstreammsgdistmonth)
+	        ),
+	        'interface' => array(        //接口分析
+	                'summary' => '/datacube/getinterfacesummary?',	//获取接口分析数据(getinterfacesummary)
+	                'summaryhour' => '/datacube/getinterfacesummaryhour?',	//获取接口分析分时数据(getinterfacesummaryhour)
+	        )
+	);
+	///微信摇一摇周边
+	const SHAKEAROUND_DEVICE_APPLYID = '/shakearound/device/applyid?';//申请设备ID
+    const SHAKEAROUND_DEVICE_UPDATE = '/shakearound/device/update?';//编辑设备信息
+	const SHAKEAROUND_DEVICE_SEARCH = '/shakearound/device/search?';//查询设备列表
+	const SHAKEAROUND_DEVICE_BINDLOCATION = '/shakearound/device/bindlocation?';//配置设备与门店ID的关系
+	const SHAKEAROUND_DEVICE_BINDPAGE = '/shakearound/device/bindpage?';//配置设备与页面的绑定关系
+    const SHAKEAROUND_MATERIAL_ADD = '/shakearound/material/add?';//上传摇一摇图片素材
+	const SHAKEAROUND_PAGE_ADD = '/shakearound/page/add?';//增加页面
+	const SHAKEAROUND_PAGE_UPDATE = '/shakearound/page/update?';//编辑页面
+	const SHAKEAROUND_PAGE_SEARCH = '/shakearound/page/search?';//查询页面列表
+	const SHAKEAROUND_PAGE_DELETE = '/shakearound/page/delete?';//删除页面
+	const SHAKEAROUND_USER_GETSHAKEINFO = '/shakearound/user/getshakeinfo?';//获取摇周边的设备及用户信息
+	const SHAKEAROUND_STATISTICS_DEVICE = '/shakearound/statistics/device?';//以设备为维度的数据统计接口
+    const SHAKEAROUND_STATISTICS_PAGE = '/shakearound/statistics/page?';//以页面为维度的数据统计接口
+	///微信小店相关接口
+	const MERCHANT_ORDER_GETBYID = '/merchant/order/getbyid?';//根据订单ID获取订单详情
+	const MERCHANT_ORDER_GETBYFILTER = '/merchant/order/getbyfilter?';//根据订单状态/创建时间获取订单详情
+	const MERCHANT_ORDER_SETDELIVERY = '/merchant/order/setdelivery?';//设置订单发货信息
+	const MERCHANT_ORDER_CLOSE = '/merchant/order/close?';//关闭订单
+	private $token;
+	private $encodingAesKey;
+	private $encrypt_type;
+	private $appid;
+	private $appsecret;
+	private $access_token;
+	private $jsapi_ticket;
+	private $api_ticket;
+	private $user_token;
+	private $partnerid;
+	private $partnerkey;
+	private $paysignkey;
+	private $postxml;
+	private $_msg;
+	private $_funcflag = false;
+	private $_receive;
+	private $_text_filter = true;
+	public $debug =  false;
+	public $errCode = 40001;
+	public $errMsg = "no access";
+	public $logcallback;
+	public function __construct($options)
+	{
+		$this->token = isset($options['token'])?$options['token']:'';
+		$this->encodingAesKey = isset($options['encodingaeskey'])?$options['encodingaeskey']:'';
+		$this->appid = isset($options['appid'])?$options['appid']:'';
+		$this->appsecret = isset($options['appsecret'])?$options['appsecret']:'';
+		$this->debug = isset($options['debug'])?$options['debug']:false;
+		$this->logcallback = isset($options['logcallback'])?$options['logcallback']:false;
+	}
+	/**
+	 * For weixin server validation
+	 */
+	private function checkSignature($str='')
+	{
+        $signature = isset($_GET["signature"])?$_GET["signature"]:'';
+	    $signature = isset($_GET["msg_signature"])?$_GET["msg_signature"]:$signature; //如果存在加密验证则用加密验证段
+        $timestamp = isset($_GET["timestamp"])?$_GET["timestamp"]:'';
+        $nonce = isset($_GET["nonce"])?$_GET["nonce"]:'';
+		$token = $this->token;
+		$tmpArr = array($token, $timestamp, $nonce,$str);
+		sort($tmpArr, SORT_STRING);
+		$tmpStr = implode( $tmpArr );
+		$tmpStr = sha1( $tmpStr );
+		if( $tmpStr == $signature ){
+			return true;
+		}else{
+			return false;
+		}
+	}
+	/**
+	 * For weixin server validation
+	 * @param bool $return 是否返回
+	 */
+	public function valid($return=false)
+    {
+        $encryptStr="";
+        if ($_SERVER['REQUEST_METHOD'] == "POST") {
+            $postStr = file_get_contents("php://input");
+            $array = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
+            $this->encrypt_type = isset($_GET["encrypt_type"]) ? $_GET["encrypt_type"]: '';
+            if ($this->encrypt_type == 'aes') { //aes加密
+                $this->log($postStr);
+            	$encryptStr = $array['Encrypt'];
+            	$pc = new Prpcrypt($this->encodingAesKey);
+            	$array = $pc->decrypt($encryptStr,$this->appid);
+            	if (!isset($array[0]) || ($array[0] != 0)) {
+            	    if (!$return) {
+            	        die('decrypt error!');
+            	    } else {
+            	        return false;
+            	    }
+            	}
+            	$this->postxml = $array[1];
+            	if (!$this->appid)
+            	    $this->appid = $array[2];//为了没有appid的订阅号。
+            } else {
+                $this->postxml = $postStr;
+            }
+        } elseif (isset($_GET["echostr"])) {
+        	$echoStr = $_GET["echostr"];
+        	if ($return) {
+        		if ($this->checkSignature())
+        			return $echoStr;
+        		else
+        			return false;
+        	} else {
+        		if ($this->checkSignature())
+        			die($echoStr);
+        		else
+        			die('no access');
+        	}
+        }
+        if (!$this->checkSignature($encryptStr)) {
+        	if ($return)
+        		return false;
+        	else
+        		die('no access');
+        }
+        return true;
+    }
+	/**
+	 * 设置发送消息
+	 * @param array $msg 消息数组
+	 * @param bool $append 是否在原消息数组追加
+	 */
+    public function Message($msg = '',$append = false){
+    		if (is_null($msg)) {
+    			$this->_msg =array();
+    		}elseif (is_array($msg)) {
+    			if ($append)
+    				$this->_msg = array_merge($this->_msg,$msg);
+    			else
+    				$this->_msg = $msg;
+    			return $this->_msg;
+    		} else {
+    			return $this->_msg;
+    		}
+    }
+    /**
+     * 设置消息的星标标志,官方已取消对此功能的支持
+     */
+    public function setFuncFlag($flag) {
+    		$this->_funcflag = $flag;
+    		return $this;
+    }
+    /**
+     * 日志记录,可被重载。
+     * @param mixed $log 输入日志
+     * @return mixed
+     */
+    protected function log($log){
+    		if ($this->debug && function_exists($this->logcallback)) {
+    			if (is_array($log)) $log = print_r($log,true);
+    			return call_user_func($this->logcallback,$log);
+    		}
+    }
+    /**
+     * 获取微信服务器发来的信息
+     */
+	public function getRev()
+	{
+		if ($this->_receive) return $this;
+		$postStr = !empty($this->postxml)?$this->postxml:file_get_contents("php://input");
+		//兼顾使用明文又不想调用valid()方法的情况
+		$this->log($postStr);
+		if (!empty($postStr)) {
+			$this->_receive = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
+		}
+		return $this;
+	}
+	/**
+	 * 获取微信服务器发来的信息
+	 */
+	public function getRevData()
+	{
+		return $this->_receive;
+	}
+	/**
+	 * 获取消息发送者
+	 */
+	public function getRevFrom() {
+		if (isset($this->_receive['FromUserName']))
+			return $this->_receive['FromUserName'];
+		else
+			return false;
+	}
+	/**
+	 * 获取消息接受者
+	 */
+	public function getRevTo() {
+		if (isset($this->_receive['ToUserName']))
+			return $this->_receive['ToUserName'];
+		else
+			return false;
+	}
+	/**
+	 * 获取接收消息的类型
+	 */
+	public function getRevType() {
+		if (isset($this->_receive['MsgType']))
+			return $this->_receive['MsgType'];
+		else
+			return false;
+	}
+	/**
+	 * 获取消息ID
+	 */
+	public function getRevID() {
+		if (isset($this->_receive['MsgId']))
+			return $this->_receive['MsgId'];
+		else
+			return false;
+	}
+	/**
+	 * 获取消息发送时间
+	 */
+	public function getRevCtime() {
+		if (isset($this->_receive['CreateTime']))
+			return $this->_receive['CreateTime'];
+		else
+			return false;
+	}
+	/**
+	 * 获取接收消息内容正文
+	 */
+	public function getRevContent(){
+		if (isset($this->_receive['Content']))
+			return $this->_receive['Content'];
+		else if (isset($this->_receive['Recognition'])) //获取语音识别文字内容,需申请开通
+			return $this->_receive['Recognition'];
+		else
+			return false;
+	}
+	/**
+	 * 获取接收消息图片
+	 */
+	public function getRevPic(){
+		if (isset($this->_receive['PicUrl']))
+			return array(
+				'mediaid'=>$this->_receive['MediaId'],
+				'picurl'=>(string)$this->_receive['PicUrl'],    //防止picurl为空导致解析出错
+			);
+		else
+			return false;
+	}
+	/**
+	 * 获取接收消息链接
+	 */
+	public function getRevLink(){
+		if (isset($this->_receive['Url'])){
+			return array(
+				'url'=>$this->_receive['Url'],
+				'title'=>$this->_receive['Title'],
+				'description'=>$this->_receive['Description']
+			);
+		} else
+			return false;
+	}
+	/**
+	 * 获取接收地理位置
+	 */
+	public function getRevGeo(){
+		if (isset($this->_receive['Location_X'])){
+			return array(
+				'x'=>$this->_receive['Location_X'],
+				'y'=>$this->_receive['Location_Y'],
+				'scale'=>$this->_receive['Scale'],
+				'label'=>$this->_receive['Label']
+			);
+		} else
+			return false;
+	}
+	/**
+	 * 获取上报地理位置事件
+	 */
+	public function getRevEventGeo(){
+        	if (isset($this->_receive['Latitude'])){
+        		 return array(
+				'x'=>$this->_receive['Latitude'],
+				'y'=>$this->_receive['Longitude'],
+				'precision'=>$this->_receive['Precision'],
+			);
+		} else
+			return false;
+	}
+	/**
+	 * 获取接收事件推送
+	 */
+	public function getRevEvent(){
+		if (isset($this->_receive['Event'])){
+			$array['event'] = $this->_receive['Event'];
+		}
+		if (isset($this->_receive['EventKey'])){
+			$array['key'] = $this->_receive['EventKey'];
+		}
+		if (isset($array) && count($array) > 0) {
+			return $array;
+		} else {
+			return false;
+		}
+	}
+	/**
+	 * 获取自定义菜单的扫码推事件信息
+	 *
+	 * 事件类型为以下两种时则调用此方法有效
+	 * Event	 事件类型,scancode_push
+	 * Event	 事件类型,scancode_waitmsg
+	 *
+	 * @return: array | false
+	 * array (
+	 *     'ScanType'=>'qrcode',
+	 *     'ScanResult'=>'123123'
+	 * )
+	 */
+	public function getRevScanInfo(){
+		if (isset($this->_receive['ScanCodeInfo'])){
+		    if (!is_array($this->_receive['ScanCodeInfo'])) {
+		        $array=(array)$this->_receive['ScanCodeInfo'];
+		        $this->_receive['ScanCodeInfo']=$array;
+		    }else {
+		        $array=$this->_receive['ScanCodeInfo'];
+		    }
+		}
+		if (isset($array) && count($array) > 0) {
+			return $array;
+		} else {
+			return false;
+		}
+	}
+	/**
+	 * 获取自定义菜单的图片发送事件信息
+	 *
+	 * 事件类型为以下三种时则调用此方法有效
+	 * Event	 事件类型,pic_sysphoto        弹出系统拍照发图的事件推送
+	 * Event	 事件类型,pic_photo_or_album  弹出拍照或者相册发图的事件推送
+	 * Event	 事件类型,pic_weixin          弹出微信相册发图器的事件推送
+	 *
+	 * @return: array | false
+	 * array (
+	 *   'Count' => '2',
+	 *   'PicList' =>array (
+	 *         'item' =>array (
+	 *             0 =>array ('PicMd5Sum' => 'aaae42617cf2a14342d96005af53624c'),
+	 *             1 =>array ('PicMd5Sum' => '149bd39e296860a2adc2f1bb81616ff8'),
+	 *         ),
+	 *   ),
+	 * )
+	 *
+	 */
+	public function getRevSendPicsInfo(){
+		if (isset($this->_receive['SendPicsInfo'])){
+		    if (!is_array($this->_receive['SendPicsInfo'])) {
+		        $array=(array)$this->_receive['SendPicsInfo'];
+		        if (isset($array['PicList'])){
+		            $array['PicList']=(array)$array['PicList'];
+		            $item=$array['PicList']['item'];
+		            $array['PicList']['item']=array();
+		            foreach ( $item as $key => $value ){
+		                $array['PicList']['item'][$key]=(array)$value;
+		            }
+		        }
+		        $this->_receive['SendPicsInfo']=$array;
+		    } else {
+		        $array=$this->_receive['SendPicsInfo'];
+		    }
+		}
+		if (isset($array) && count($array) > 0) {
+			return $array;
+		} else {
+			return false;
+		}
+	}
+	/**
+	 * 获取自定义菜单的地理位置选择器事件推送
+	 *
+	 * 事件类型为以下时则可以调用此方法有效
+	 * Event	 事件类型,location_select        弹出地理位置选择器的事件推送
+	 *
+	 * @return: array | false
+	 * array (
+	 *   'Location_X' => '33.731655000061',
+	 *   'Location_Y' => '113.29955200008047',
+	 *   'Scale' => '16',
+	 *   'Label' => '某某市某某区某某路',
+	 *   'Poiname' => '',
+	 * )
+	 *
+	 */
+	public function getRevSendGeoInfo(){
+	    if (isset($this->_receive['SendLocationInfo'])){
+	        if (!is_array($this->_receive['SendLocationInfo'])) {
+	            $array=(array)$this->_receive['SendLocationInfo'];
+	            if (empty($array['Poiname'])) {
+	                $array['Poiname']="";
+	            }
+	            if (empty($array['Label'])) {
+	                $array['Label']="";
+	            }
+	            $this->_receive['SendLocationInfo']=$array;
+	        } else {
+	            $array=$this->_receive['SendLocationInfo'];
+	        }
+	    }
+	    if (isset($array) && count($array) > 0) {
+	        return $array;
+	    } else {
+	        return false;
+	    }
+	}
+	/**
+	 * 获取接收语音推送
+	 */
+	public function getRevVoice(){
+		if (isset($this->_receive['MediaId'])){
+			return array(
+				'mediaid'=>$this->_receive['MediaId'],
+				'format'=>$this->_receive['Format'],
+			);
+		} else
+			return false;
+	}
+	/**
+	 * 获取接收视频推送
+	 */
+	public function getRevVideo(){
+		if (isset($this->_receive['MediaId'])){
+			return array(
+					'mediaid'=>$this->_receive['MediaId'],
+					'thumbmediaid'=>$this->_receive['ThumbMediaId']
+			);
+		} else
+			return false;
+	}
+	/**
+	 * 获取接收TICKET
+	 */
+	public function getRevTicket(){
+		if (isset($this->_receive['Ticket'])){
+			return $this->_receive['Ticket'];
+		} else
+			return false;
+	}
+	/**
+	* 获取二维码的场景值
+	*/
+	public function getRevSceneId (){
+		if (isset($this->_receive['EventKey'])){
+			return str_replace('qrscene_','',$this->_receive['EventKey']);
+		} else{
+			return false;
+		}
+	}
+	/**
+	* 获取主动推送的消息ID
+	* 经过验证,这个和普通的消息MsgId不一样
+	* 当Event为 MASSSENDJOBFINISH 或 TEMPLATESENDJOBFINISH
+	*/
+	public function getRevTplMsgID(){
+		if (isset($this->_receive['MsgID'])){
+			return $this->_receive['MsgID'];
+		} else
+			return false;
+	}
+	/**
+	* 获取模板消息发送状态
+	*/
+	public function getRevStatus(){
+		if (isset($this->_receive['Status'])){
+			return $this->_receive['Status'];
+		} else
+			return false;
+	}
+	/**
+	* 获取群发或模板消息发送结果
+	* 当Event为 MASSSENDJOBFINISH 或 TEMPLATESENDJOBFINISH,即高级群发/模板消息
+	*/
+	public function getRevResult(){
+		if (isset($this->_receive['Status'])) //发送是否成功,具体的返回值请参考 高级群发/模板消息 的事件推送说明
+			$array['Status'] = $this->_receive['Status'];
+		if (isset($this->_receive['MsgID'])) //发送的消息id
+			$array['MsgID'] = $this->_receive['MsgID'];
+		//以下仅当群发消息时才会有的事件内容
+		if (isset($this->_receive['TotalCount']))     //分组或openid列表内粉丝数量
+			$array['TotalCount'] = $this->_receive['TotalCount'];
+		if (isset($this->_receive['FilterCount']))    //过滤(过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数
+			$array['FilterCount'] = $this->_receive['FilterCount'];
+		if (isset($this->_receive['SentCount']))     //发送成功的粉丝数
+			$array['SentCount'] = $this->_receive['SentCount'];
+		if (isset($this->_receive['ErrorCount']))    //发送失败的粉丝数
+			$array['ErrorCount'] = $this->_receive['ErrorCount'];
+		if (isset($array) && count($array) > 0) {
+		    return $array;
+		} else {
+		    return false;
+		}
+	}
+	/**
+	 * 获取多客服会话状态推送事件 - 接入会话
+	 * 当Event为 kfcreatesession 即接入会话
+	 * @return string | boolean  返回分配到的客服
+	 */
+	public function getRevKFCreate(){
+		if (isset($this->_receive['KfAccount'])){
+			return $this->_receive['KfAccount'];
+		} else
+			return false;
+	}
+	/**
+	 * 获取多客服会话状态推送事件 - 关闭会话
+	 * 当Event为 kfclosesession 即关闭会话
+	 * @return string | boolean  返回分配到的客服
+	 */
+	public function getRevKFClose(){
+	    if (isset($this->_receive['KfAccount'])){
+	        return $this->_receive['KfAccount'];
+	    } else
+	        return false;
+	}
+	/**
+	 * 获取多客服会话状态推送事件 - 转接会话
+	 * 当Event为 kfswitchsession 即转接会话
+	 * @return array | boolean  返回分配到的客服
+	 * {
+	 *     'FromKfAccount' => '',      //原接入客服
+	 *     'ToKfAccount' => ''            //转接到客服
+	 * }
+	 */
+	public function getRevKFSwitch(){
+	    if (isset($this->_receive['FromKfAccount']))     //原接入客服
+	        $array['FromKfAccount'] = $this->_receive['FromKfAccount'];
+	    if (isset($this->_receive['ToKfAccount']))    //转接到客服
+	        $array['ToKfAccount'] = $this->_receive['ToKfAccount'];
+	    if (isset($array) && count($array) > 0) {
+	        return $array;
+	    } else {
+	        return false;
+	    }
+	}
+	/**
+	 * 获取卡券事件推送 - 卡卷审核是否通过
+	 * 当Event为 card_pass_check(审核通过) 或 card_not_pass_check(未通过)
+	 * @return string|boolean  返回卡券ID
+	 */
+	public function getRevCardPass(){
+	    if (isset($this->_receive['CardId']))
+	        return $this->_receive['CardId'];
+	    else
+	        return false;
+	}
+	/**
+	 * 获取卡券事件推送 - 领取卡券
+	 * 当Event为 user_get_card(用户领取卡券)
+	 * @return array|boolean
+	 */
+	public function getRevCardGet(){
+	    if (isset($this->_receive['CardId']))     //卡券 ID
+	        $array['CardId'] = $this->_receive['CardId'];
+	    if (isset($this->_receive['IsGiveByFriend']))    //是否为转赠,1 代表是,0 代表否。
+	        $array['IsGiveByFriend'] = $this->_receive['IsGiveByFriend'];
+	        $array['OldUserCardCode'] = $this->_receive['OldUserCardCode'];
+	    if (isset($this->_receive['UserCardCode']) && !empty($this->_receive['UserCardCode'])) //code 序列号。自定义 code 及非自定义 code的卡券被领取后都支持事件推送。
+	        $array['UserCardCode'] = $this->_receive['UserCardCode'];
+	    if (isset($array) && count($array) > 0) {
+	        return $array;
+	    } else {
+	        return false;
+	    }
+	}
+	/**
+	 * 获取卡券事件推送 - 删除卡券
+	 * 当Event为 user_del_card(用户删除卡券)
+	 * @return array|boolean
+	 */
+	public function getRevCardDel(){
+	    if (isset($this->_receive['CardId']))     //卡券 ID
+	        $array['CardId'] = $this->_receive['CardId'];
+	    if (isset($this->_receive['UserCardCode']) && !empty($this->_receive['UserCardCode'])) //code 序列号。自定义 code 及非自定义 code的卡券被领取后都支持事件推送。
+	        $array['UserCardCode'] = $this->_receive['UserCardCode'];
+	    if (isset($array) && count($array) > 0) {
+	        return $array;
+	    } else {
+	        return false;
+	    }
+	}
+	/**
+	 * 获取订单ID - 订单付款通知
+	 * 当Event为 merchant_order(订单付款通知)
+	 * @return orderId|boolean
+	 */
+	public function getRevOrderId(){
+		if (isset($this->_receive['OrderId']))     //订单 ID
+			return $this->_receive['OrderId'];
+		else
+			return false;
+	}
+	public static function xmlSafeStr($str)
+	{
+		return '<![CDATA['.preg_replace("/[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]/",'',$str).']]>';
+	}
+	/**
+	 * 数据XML编码
+	 * @param mixed $data 数据
+	 * @return string
+	 */
+	public static function data_to_xml($data) {
+	    $xml = '';
+	    foreach ($data as $key => $val) {
+	        is_numeric($key) && $key = "item id=\"$key\"";
+	        $xml    .=  "<$key>";
+	        $xml    .=  ( is_array($val) || is_object($val)) ? self::data_to_xml($val)  : self::xmlSafeStr($val);
+	        list($key, ) = explode(' ', $key);
+	        $xml    .=  "</$key>";
+	    }
+	    return $xml;
+	}
+	/**
+	 * XML编码
+	 * @param mixed $data 数据
+	 * @param string $root 根节点名
+	 * @param string $item 数字索引的子节点名
+	 * @param string $attr 根节点属性
+	 * @param string $id   数字索引子节点key转换的属性名
+	 * @param string $encoding 数据编码
+	 * @return string
+	*/
+	public function xml_encode($data, $root='xml', $item='item', $attr='', $id='id', $encoding='utf-8') {
+	    if(is_array($attr)){
+	        $_attr = array();
+	        foreach ($attr as $key => $value) {
+	            $_attr[] = "{$key}=\"{$value}\"";
+	        }
+	        $attr = implode(' ', $_attr);
+	    }
+	    $attr   = trim($attr);
+	    $attr   = empty($attr) ? '' : " {$attr}";
+	    $xml   = "<{$root}{$attr}>";
+	    $xml   .= self::data_to_xml($data, $item, $id);
+	    $xml   .= "</{$root}>";
+	    return $xml;
+	}
+	/**
+	 * 过滤文字回复\r\n换行符
+	 * @param string $text
+	 * @return string|mixed
+	 */
+	private function _auto_text_filter($text) {
+		if (!$this->_text_filter) return $text;
+		return str_replace("\r\n", "\n", $text);
+	}
+	/**
+	 * 设置回复消息
+	 * Example: $obj->text('hello')->reply();
+	 * @param string $text
+	 */
+	public function text($text='')
+	{
+		$FuncFlag = $this->_funcflag ? 1 : 0;
+		$msg = array(
+			'ToUserName' => $this->getRevFrom(),
+			'FromUserName'=>$this->getRevTo(),
+			'MsgType'=>self::MSGTYPE_TEXT,
+			'Content'=>$this->_auto_text_filter($text),
+			'CreateTime'=>time(),
+			'FuncFlag'=>$FuncFlag
+		);
+		$this->Message($msg);
+		return $this;
+	}
+	/**
+	 * 设置回复消息
+	 * Example: $obj->image('media_id')->reply();
+	 * @param string $mediaid
+	 */
+	public function image($mediaid='')
+	{
+		$FuncFlag = $this->_funcflag ? 1 : 0;
+		$msg = array(
+			'ToUserName' => $this->getRevFrom(),
+			'FromUserName'=>$this->getRevTo(),
+			'MsgType'=>self::MSGTYPE_IMAGE,
+			'Image'=>array('MediaId'=>$mediaid),
+			'CreateTime'=>time(),
+			'FuncFlag'=>$FuncFlag
+		);
+		$this->Message($msg);
+		return $this;
+	}
+	/**
+	 * 设置回复消息
+	 * Example: $obj->voice('media_id')->reply();
+	 * @param string $mediaid
+	 */
+	public function voice($mediaid='')
+	{
+		$FuncFlag = $this->_funcflag ? 1 : 0;
+		$msg = array(
+			'ToUserName' => $this->getRevFrom(),
+			'FromUserName'=>$this->getRevTo(),
+			'MsgType'=>self::MSGTYPE_VOICE,
+			'Voice'=>array('MediaId'=>$mediaid),
+			'CreateTime'=>time(),
+			'FuncFlag'=>$FuncFlag
+		);
+		$this->Message($msg);
+		return $this;
+	}
+	/**
+	 * 设置回复消息
+	 * Example: $obj->video('media_id','title','description')->reply();
+	 * @param string $mediaid
+	 */
+	public function video($mediaid='',$title='',$description='')
+	{
+		$FuncFlag = $this->_funcflag ? 1 : 0;
+		$msg = array(
+			'ToUserName' => $this->getRevFrom(),
+			'FromUserName'=>$this->getRevTo(),
+			'MsgType'=>self::MSGTYPE_VIDEO,
+			'Video'=>array(
+			        'MediaId'=>$mediaid,
+			        'Title'=>$title,
+			        'Description'=>$description
+			),
+			'CreateTime'=>time(),
+			'FuncFlag'=>$FuncFlag
+		);
+		$this->Message($msg);
+		return $this;
+	}
+	/**
+	 * 设置回复音乐
+	 * @param string $title
+	 * @param string $desc
+	 * @param string $musicurl
+	 * @param string $hgmusicurl
+	 * @param string $thumbmediaid 音乐图片缩略图的媒体id,非必须
+	 */
+	public function music($title,$desc,$musicurl,$hgmusicurl='',$thumbmediaid='') {
+		$FuncFlag = $this->_funcflag ? 1 : 0;
+		$msg = array(
+			'ToUserName' => $this->getRevFrom(),
+			'FromUserName'=>$this->getRevTo(),
+			'CreateTime'=>time(),
+			'MsgType'=>self::MSGTYPE_MUSIC,
+			'Music'=>array(
+				'Title'=>$title,
+				'Description'=>$desc,
+				'MusicUrl'=>$musicurl,
+				'HQMusicUrl'=>$hgmusicurl
+			),
+			'FuncFlag'=>$FuncFlag
+		);
+		if ($thumbmediaid) {
+			$msg['Music']['ThumbMediaId'] = $thumbmediaid;
+		}
+		$this->Message($msg);
+		return $this;
+	}
+	/**
+	 * 设置回复图文
+	 * @param array $newsData
+	 * 数组结构:
+	 *  array(
+	 *  	"0"=>array(
+	 *  		'Title'=>'msg title',
+	 *  		'Description'=>'summary text',
+	 *  		'PicUrl'=>'http://www.domain.com/1.jpg',
+	 *  		'Url'=>'http://www.domain.com/1.html'
+	 *  	),
+	 *  	"1"=>....
+	 *  )
+	 */
+	public function news($newsData=array())
+	{
+		$FuncFlag = $this->_funcflag ? 1 : 0;
+		$count = count($newsData);
+		$msg = array(
+			'ToUserName' => $this->getRevFrom(),
+			'FromUserName'=>$this->getRevTo(),
+			'MsgType'=>self::MSGTYPE_NEWS,
+			'CreateTime'=>time(),
+			'ArticleCount'=>$count,
+			'Articles'=>$newsData,
+			'FuncFlag'=>$FuncFlag
+		);
+		$this->Message($msg);
+		return $this;
+	}
+	/**
+	 *
+	 * 回复微信服务器, 此函数支持链式操作
+	 * Example: $this->text('msg tips')->reply();
+	 * @param string $msg 要发送的信息, 默认取$this->_msg
+	 * @param bool $return 是否返回信息而不抛出到浏览器 默认:否
+	 */
+	public function reply($msg=array(),$return = false)
+	{
+		if (empty($msg)) {
+		    if (empty($this->_msg))   //防止不先设置回复内容,直接调用reply方法导致异常
+		        return false;
+			$msg = $this->_msg;
+		}
+		$xmldata=  $this->xml_encode($msg);
+		$this->log($xmldata);
+		if ($this->encrypt_type == 'aes') { //如果来源消息为加密方式
+		    $pc = new Prpcrypt($this->encodingAesKey);
+		    $array = $pc->encrypt($xmldata, $this->appid);
+		    $ret = $array[0];
+		    if ($ret != 0) {
+		        $this->log('encrypt err!');
+		        return false;
+		    }
+		    $timestamp = time();
+		    $nonce = rand(77,999)*rand(605,888)*rand(11,99);
+		    $encrypt = $array[1];
+		    $tmpArr = array($this->token, $timestamp, $nonce,$encrypt);//比普通公众平台多了一个加密的密文
+		    sort($tmpArr, SORT_STRING);
+		    $signature = implode($tmpArr);
+		    $signature = sha1($signature);
+		    $xmldata = $this->generate($encrypt, $signature, $timestamp, $nonce);
+		    $this->log($xmldata);
+		}
+		if ($return)
+			return $xmldata;
+		else
+			echo $xmldata;
+	}
+    /**
+     * xml格式加密,仅请求为加密方式时再用
+     */
+	private function generate($encrypt, $signature, $timestamp, $nonce)
+	{
+	    //格式化加密信息
+	    $format = "<xml>
+<Encrypt><![CDATA[%s]]></Encrypt>
+<MsgSignature><![CDATA[%s]]></MsgSignature>
+<TimeStamp>%s</TimeStamp>
+<Nonce><![CDATA[%s]]></Nonce>
+</xml>";
+	    return sprintf($format, $encrypt, $signature, $timestamp, $nonce);
+	}
+	/**
+	 * GET 请求
+	 * @param string $url
+	 */
+	private function http_get($url){
+		$oCurl = curl_init();
+		if(stripos($url,"https://")!==FALSE){
+			curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
+			curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, FALSE);
+			curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1
+		}
+		curl_setopt($oCurl, CURLOPT_URL, $url);
+		curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 );
+		$sContent = curl_exec($oCurl);
+		$aStatus = curl_getinfo($oCurl);
+		curl_close($oCurl);
+		if(intval($aStatus["http_code"])==200){
+			return $sContent;
+		}else{
+			return false;
+		}
+	}
+	/**
+	 * POST 请求
+	 * @param string $url
+	 * @param array $param
+	 * @param boolean $post_file 是否文件上传
+	 * @return string content
+	 */
+	private function http_post($url,$param,$post_file=false){
+		$oCurl = curl_init();
+		if(stripos($url,"https://")!==FALSE){
+			curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
+			curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false);
+			curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1
+		}
+	        if (PHP_VERSION_ID >= 50500 && class_exists('\CURLFile')) {
+	            	$is_curlFile = true;
+	        } else {
+	        	$is_curlFile = false;
+	            	if (defined('CURLOPT_SAFE_UPLOAD')) {
+	                	curl_setopt($oCurl, CURLOPT_SAFE_UPLOAD, false);
+	            	}
+	        }
+		if (is_string($param)) {
+	            	$strPOST = $param;
+	        }elseif($post_file) {
+	            	if($is_curlFile) {
+		                foreach ($param as $key => $val) {
+		                    	if (substr($val, 0, 1) == '@') {
+		                        	$param[$key] = new \CURLFile(realpath(substr($val,1)));
+		                    	}
+		                }
+	            	}
+			$strPOST = $param;
+		} else {
+			$aPOST = array();
+			foreach($param as $key=>$val){
+				$aPOST[] = $key."=".urlencode($val);
+			}
+			$strPOST =  join("&", $aPOST);
+		}
+		curl_setopt($oCurl, CURLOPT_URL, $url);
+		curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 );
+		curl_setopt($oCurl, CURLOPT_POST,true);
+		curl_setopt($oCurl, CURLOPT_POSTFIELDS,$strPOST);
+		$sContent = curl_exec($oCurl);
+		$aStatus = curl_getinfo($oCurl);
+		curl_close($oCurl);
+		if(intval($aStatus["http_code"])==200){
+			return $sContent;
+		}else{
+			return false;
+		}
+	}
+	/**
+	 * 设置缓存,按需重载
+	 * @param string $cachename
+	 * @param mixed $value
+	 * @param int $expired
+	 * @return boolean
+	 */
+	protected function setCache($cachename,$value,$expired){
+		//TODO: set cache implementation
+		return false;
+	}
+	/**
+	 * 获取缓存,按需重载
+	 * @param string $cachename
+	 * @return mixed
+	 */
+	protected function getCache($cachename){
+		//TODO: get cache implementation
+		return false;
+	}
+	/**
+	 * 清除缓存,按需重载
+	 * @param string $cachename
+	 * @return boolean
+	 */
+	protected function removeCache($cachename){
+		//TODO: remove cache implementation
+		return false;
+	}
+	/**
+	 * 获取access_token
+	 * @param string $appid 如在类初始化时已提供,则可为空
+	 * @param string $appsecret 如在类初始化时已提供,则可为空
+	 * @param string $token 手动指定access_token,非必要情况不建议用
+	 */
+	public function checkAuth($appid='',$appsecret='',$token=''){
+		if (!$appid || !$appsecret) {
+			$appid = $this->appid;
+			$appsecret = $this->appsecret;
+		}
+		if ($token) { //手动指定token,优先使用
+		    $this->access_token=$token;
+		    return $this->access_token;
+		}
+		$authname = 'wechat_access_token'.$appid;
+		if ($rs = $this->getCache($authname))  {
+			$this->access_token = $rs;
+			return $rs;
+		}
+		$result = $this->http_get(self::API_URL_PREFIX.self::AUTH_URL.'appid='.$appid.'&secret='.$appsecret);
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || isset($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			$this->access_token = $json['access_token'];
+			$expire = $json['expires_in'] ? intval($json['expires_in'])-100 : 3600;
+			$this->setCache($authname,$this->access_token,$expire);
+			return $this->access_token;
+		}
+		return false;
+	}
+	/**
+	 * 删除验证数据
+	 * @param string $appid
+	 */
+	public function resetAuth($appid=''){
+		if (!$appid) $appid = $this->appid;
+		$this->access_token = '';
+		$authname = 'wechat_access_token'.$appid;
+		$this->removeCache($authname);
+		return true;
+	}
+	/**
+	 * 删除JSAPI授权TICKET
+	 * @param string $appid 用于多个appid时使用
+	 */
+	public function resetJsTicket($appid=''){
+		if (!$appid) $appid = $this->appid;
+		$this->jsapi_ticket = '';
+		$authname = 'wechat_jsapi_ticket'.$appid;
+		$this->removeCache($authname);
+		return true;
+	}
+	/**
+	 * 获取JSAPI授权TICKET
+	 * @param string $appid 用于多个appid时使用,可空
+	 * @param string $jsapi_ticket 手动指定jsapi_ticket,非必要情况不建议用
+	 */
+	public function getJsTicket($appid='',$jsapi_ticket=''){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		if (!$appid) $appid = $this->appid;
+		if ($jsapi_ticket) { //手动指定token,优先使用
+		    $this->jsapi_ticket = $jsapi_ticket;
+		    return $this->jsapi_ticket;
+		}
+		$authname = 'wechat_jsapi_ticket'.$appid;
+		if ($rs = $this->getCache($authname))  {
+			$this->jsapi_ticket = $rs;
+			return $rs;
+		}
+		$result = $this->http_get(self::API_URL_PREFIX.self::GET_TICKET_URL.'access_token='.$this->access_token.'&type=jsapi');
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			$this->jsapi_ticket = $json['ticket'];
+			$expire = $json['expires_in'] ? intval($json['expires_in'])-100 : 3600;
+			$this->setCache($authname,$this->jsapi_ticket,$expire);
+			return $this->jsapi_ticket;
+		}
+		return false;
+	}
+	/**
+	 * 获取JsApi使用签名
+	 * @param string $url 网页的URL,自动处理#及其后面部分
+	 * @param string $timestamp 当前时间戳 (为空则自动生成)
+	 * @param string $noncestr 随机串 (为空则自动生成)
+	 * @param string $appid 用于多个appid时使用,可空
+	 * @return array|bool 返回签名字串
+	 */
+	public function getJsSign($url, $timestamp=0, $noncestr='', $appid=''){
+	    if (!$this->jsapi_ticket && !$this->getJsTicket($appid) || !$url) return false;
+	    if (!$timestamp)
+	        $timestamp = time();
+	    if (!$noncestr)
+	        $noncestr = $this->generateNonceStr();
+	    $ret = strpos($url,'#');
+	    if ($ret)
+	        $url = substr($url,0,$ret);
+	    $url = trim($url);
+	    if (empty($url))
+	        return false;
+	    $arrdata = array("timestamp" => $timestamp, "noncestr" => $noncestr, "url" => $url, "jsapi_ticket" => $this->jsapi_ticket);
+	    $sign = $this->getSignature($arrdata);
+	    if (!$sign)
+	        return false;
+	    $signPackage = array(
+	            "appId"     => $this->appid,
+	            "nonceStr"  => $noncestr,
+	            "timestamp" => $timestamp,
+	            "url"       => $url,
+	            "signature" => $sign
+	    );
+	    return $signPackage;
+	}
+    /**
+     * 获取卡券签名cardSign
+     * @param string $card_type 卡券的类型,不可为空,官方jssdk文档说这个值可空,但签名验证工具又必填这个值,官方文档到处是坑,
+     * @param string $card_id 卡券的ID,可空
+     * @param string $location_id 卡券的适用门店ID,可空
+     * @param string $timestamp 当前时间戳 (为空则自动生成)
+     * @param string $noncestr 随机串 (为空则自动生成)
+     * @param string $appid 用于多个appid时使用,可空
+     * @return array|bool 返回签名字串
+     */
+    public function getCardSign($card_type='',$card_id='',$code='',$location_id='',$timestamp=0, $noncestr='', $appid=''){
+        if (!$this->api_ticket && !$this->getJsCardTicket($appid)) return false;
+        if (!$timestamp)
+            $timestamp = time();
+        if (!$noncestr)
+            $noncestr = $this->generateNonceStr();
+        $arrdata = array("api_ticket" => $this->api_ticket,"app_id" => $this->appid,"card_id" => $card_id,"code" => $code,"card_type" => $card_type,"location_id" => $location_id,"timestamp" => $timestamp, "noncestr" => $noncestr );
+        $sign = $this->getTicketSignature($arrdata);
+        if (!$sign)
+            return false;
+        $signPackage = array(
+            "cardType"     => $card_type,
+            "cardId"       => $card_id,
+            "shopId"       => $location_id,         //location_id就是shopId
+            "nonceStr"  => $noncestr,
+            "timestamp" => $timestamp,
+            "cardSign" => $sign
+        );
+        return $signPackage;
+    }
+	/**
+	 * 微信api不支持中文转义的json结构
+	 * @param array $arr
+	 */
+	static function json_encode($arr) {
+		if (count($arr) == 0) return "[]";
+		$parts = array ();
+		$is_list = false;
+		//Find out if the given array is a numerical array
+		$keys = array_keys ( $arr );
+		$max_length = count ( $arr ) - 1;
+		if (($keys [0] === 0) && ($keys [$max_length] === $max_length )) { //See if the first key is 0 and last key is length - 1
+			$is_list = true;
+			for($i = 0; $i < count ( $keys ); $i ++) { //See if each key correspondes to its position
+				if ($i != $keys [$i]) { //A key fails at position check.
+					$is_list = false; //It is an associative array.
+					break;
+				}
+			}
+		}
+		foreach ( $arr as $key => $value ) {
+			if (is_array ( $value )) { //Custom handling for arrays
+				if ($is_list)
+					$parts [] = self::json_encode ( $value ); /* :RECURSION: */
+				else
+					$parts [] = '"' . $key . '":' . self::json_encode ( $value ); /* :RECURSION: */
+			} else {
+				$str = '';
+				if (! $is_list)
+					$str = '"' . $key . '":';
+				//Custom handling for multiple data types
+				if (!is_string ( $value ) && is_numeric ( $value ) && $value<2000000000)
+					$str .= $value; //Numbers
+				elseif ($value === false)
+				$str .= 'false'; //The booleans
+				elseif ($value === true)
+				$str .= 'true';
+				else
+					$str .= '"' . addslashes ( $value ) . '"'; //All other things
+				// :TODO: Is there any more datatype we should be in the lookout for? (Object?)
+				$parts [] = $str;
+			}
+		}
+		$json = implode ( ',', $parts );
+		if ($is_list)
+			return '[' . $json . ']'; //Return numerical JSON
+		return '{' . $json . '}'; //Return associative JSON
+	}
+	/**
+	 * 获取签名
+	 * @param array $arrdata 签名数组
+	 * @param string $method 签名方法
+	 * @return boolean|string 签名值
+	 */
+	public function getSignature($arrdata,$method="sha1") {
+		if (!function_exists($method)) return false;
+		ksort($arrdata);
+		$paramstring = "";
+		foreach($arrdata as $key => $value)
+		{
+			if(strlen($paramstring) == 0)
+				$paramstring .= $key . "=" . $value;
+			else
+				$paramstring .= "&" . $key . "=" . $value;
+		}
+		$Sign = $method($paramstring);
+		return $Sign;
+	}
+	/**
+	 * 获取微信卡券api_ticket
+	 * @param string $appid 用于多个appid时使用,可空
+	 * @param string $api_ticket 手动指定api_ticket,非必要情况不建议用
+	 */
+	public function getJsCardTicket($appid='',$api_ticket=''){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		if (!$appid) $appid = $this->appid;
+		if ($api_ticket) { //手动指定token,优先使用
+		    $this->api_ticket = $api_ticket;
+		    return $this->api_ticket;
+		}
+		$authname = 'wechat_api_ticket_wxcard'.$appid;
+		if ($rs = $this->getCache($authname))  {
+			$this->api_ticket = $rs;
+			return $rs;
+		}
+		$result = $this->http_get(self::API_URL_PREFIX.self::GET_TICKET_URL.'access_token='.$this->access_token.'&type=wx_card');
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			$this->api_ticket = $json['ticket'];
+			$expire = $json['expires_in'] ? intval($json['expires_in'])-100 : 3600;
+			$this->setCache($authname,$this->api_ticket,$expire);
+			return $this->api_ticket;
+		}
+		return false;
+	}
+	/**
+	 * 获取微信卡券签名
+	 * @param array $arrdata 签名数组
+	 * @param string $method 签名方法
+	 * @return boolean|string 签名值
+	 */
+	public function getTicketSignature($arrdata,$method="sha1") {
+		if (!function_exists($method)) return false;
+		$newArray = array();
+		foreach($arrdata as $key => $value)
+		{
+			array_push($newArray,(string)$value);
+		}
+		sort($newArray,SORT_STRING);
+		return $method(implode($newArray));
+	}
+	/**
+	 * 生成随机字串
+	 * @param number $length 长度,默认为16,最长为32字节
+	 * @return string
+	 */
+	public function generateNonceStr($length=16){
+		// 密码字符集,可任意添加你需要的字符
+		$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+		$str = "";
+		for($i = 0; $i < $length; $i++)
+		{
+			$str .= $chars[mt_rand(0, strlen($chars) - 1)];
+		}
+		return $str;
+	}
+	/**
+	 * 获取微信服务器IP地址列表
+	 * @return array('127.0.0.1','127.0.0.1')
+	 */
+	public function getServerIp(){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_get(self::API_URL_PREFIX.self::CALLBACKSERVER_GET_URL.'access_token='.$this->access_token);
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || isset($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json['ip_list'];
+		}
+		return false;
+	}
+	/**
+	 * 创建菜单(认证后的订阅号可用)
+	 * @param array $data 菜单数组数据
+	 * example:
+     * 	array (
+     * 	    'button' => array (
+     * 	      0 => array (
+     * 	        'name' => '扫码',
+     * 	        'sub_button' => array (
+     * 	            0 => array (
+     * 	              'type' => 'scancode_waitmsg',
+     * 	              'name' => '扫码带提示',
+     * 	              'key' => 'rselfmenu_0_0',
+     * 	            ),
+     * 	            1 => array (
+     * 	              'type' => 'scancode_push',
+     * 	              'name' => '扫码推事件',
+     * 	              'key' => 'rselfmenu_0_1',
+     * 	            ),
+     * 	        ),
+     * 	      ),
+     * 	      1 => array (
+     * 	        'name' => '发图',
+     * 	        'sub_button' => array (
+     * 	            0 => array (
+     * 	              'type' => 'pic_sysphoto',
+     * 	              'name' => '系统拍照发图',
+     * 	              'key' => 'rselfmenu_1_0',
+     * 	            ),
+     * 	            1 => array (
+     * 	              'type' => 'pic_photo_or_album',
+     * 	              'name' => '拍照或者相册发图',
+     * 	              'key' => 'rselfmenu_1_1',
+     * 	            )
+     * 	        ),
+     * 	      ),
+     * 	      2 => array (
+     * 	        'type' => 'location_select',
+     * 	        'name' => '发送位置',
+     * 	        'key' => 'rselfmenu_2_0'
+     * 	      ),
+     * 	    ),
+     * 	)
+     * type可以选择为以下几种,其中5-8除了收到菜单事件以外,还会单独收到对应类型的信息。
+     * 1、click:点击推事件
+     * 2、view:跳转URL
+     * 3、scancode_push:扫码推事件
+     * 4、scancode_waitmsg:扫码推事件且弹出“消息接收中”提示框
+     * 5、pic_sysphoto:弹出系统拍照发图
+     * 6、pic_photo_or_album:弹出拍照或者相册发图
+     * 7、pic_weixin:弹出微信相册发图器
+     * 8、location_select:弹出地理位置选择器
+	 */
+	public function createMenu($data){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::MENU_CREATE_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return true;
+		}
+		return false;
+	}
+	/**
+	 * 获取菜单(认证后的订阅号可用)
+	 * @return array('menu'=>array(....s))
+	 */
+	public function getMenu(){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_get(self::API_URL_PREFIX.self::MENU_GET_URL.'access_token='.$this->access_token);
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || isset($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 删除菜单(认证后的订阅号可用)
+	 * @return boolean
+	 */
+	public function deleteMenu(){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_get(self::API_URL_PREFIX.self::MENU_DELETE_URL.'access_token='.$this->access_token);
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return true;
+		}
+		return false;
+	}
+	/**
+	 * 创建个性化菜单(认证后的订阅号可用)
+	 * @param array $data
+	 * @return bool
+	 *
+	 */
+	public function addconditionalMenu($data){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::MENU_ADDCONDITIONAL_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return true;
+		}
+		return false;
+	}
+	/**
+	 * 删除个性化菜单(认证后的订阅号可用)
+	 * @param $data {"menuid":"208379533"}
+	 *
+	 * @return bool
+	 */
+	public function delconditionalMenu($data){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::MENU_DELCONDITIONAL_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return true;
+		}
+		return false;
+	}
+	/**
+	 * 测试个性化菜单匹配结果(认证后的订阅号可用)
+	 * @param $data {"user_id":"weixin"} user_id可以是粉丝的OpenID,也可以是粉丝的微信号
+	 *
+	 * @return bool|array('button'=>array(....s))
+	 */
+	public function trymatchMenu($data){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::MENU_TRYMATCH_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 上传临时素材,有效期为3天(认证后的订阅号可用)
+	 * 注意:上传大文件时可能需要先调用 set_time_limit(0) 避免超时
+	 * 注意:数组的键值任意,但文件名前必须加@,使用单引号以避免本地路径斜杠被转义
+     * 注意:临时素材的media_id是可复用的!
+	 * @param array $data {"media":'@Path\filename.jpg'}
+	 * @param type 类型:图片:image 语音:voice 视频:video 缩略图:thumb
+	 * @return boolean|array
+	 */
+	public function uploadMedia($data, $type){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		//原先的上传多媒体文件接口使用 self::UPLOAD_MEDIA_URL 前缀
+		$result = $this->http_post(self::API_URL_PREFIX.self::MEDIA_UPLOAD_URL.'access_token='.$this->access_token.'&type='.$type,$data,true);
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 获取临时素材(认证后的订阅号可用)
+	 * @param string $media_id 媒体文件id
+	 * @param boolean $is_video 是否为视频文件,默认为否
+	 * @return raw data
+	 */
+	public function getMedia($media_id,$is_video=false){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		//原先的上传多媒体文件接口使用 self::UPLOAD_MEDIA_URL 前缀
+		//如果要获取的素材是视频文件时,不能使用https协议,必须更换成http协议
+		$url_prefix = $is_video?str_replace('https','http',self::API_URL_PREFIX):self::API_URL_PREFIX;
+		$result = $this->http_get($url_prefix.self::MEDIA_GET_URL.'access_token='.$this->access_token.'&media_id='.$media_id);
+		if ($result)
+		{
+            if (is_string($result)) {
+                $json = json_decode($result,true);
+                if (isset($json['errcode'])) {
+                    $this->errCode = $json['errcode'];
+                    $this->errMsg = $json['errmsg'];
+                    return false;
+                }
+            }
+			return $result;
+		}
+		return false;
+	}
+	/**
+	 * 上传图片,本接口所上传的图片不占用公众号的素材库中图片数量的5000个的限制。图片仅支持jpg/png格式,大小必须在1MB以下。 (认证后的订阅号可用)
+	 * 注意:上传大文件时可能需要先调用 set_time_limit(0) 避免超时
+	 * 注意:数组的键值任意,但文件名前必须加@,使用单引号以避免本地路径斜杠被转义      
+	 * @param array $data {"media":'@Path\filename.jpg'}
+	 * 
+	 * @return boolean|array
+	 */
+	public function uploadImg($data){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		//原先的上传多媒体文件接口使用 self::UPLOAD_MEDIA_URL 前缀
+		$result = $this->http_post(self::API_URL_PREFIX.self::MEDIA_UPLOADIMG_URL.'access_token='.$this->access_token,$data,true);
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+    /**
+     * 上传永久素材(认证后的订阅号可用)
+     * 新增的永久素材也可以在公众平台官网素材管理模块中看到
+     * 注意:上传大文件时可能需要先调用 set_time_limit(0) 避免超时
+     * 注意:数组的键值任意,但文件名前必须加@,使用单引号以避免本地路径斜杠被转义
+     * @param array $data {"media":'@Path\filename.jpg'}
+     * @param type 类型:图片:image 语音:voice 视频:video 缩略图:thumb
+     * @param boolean $is_video 是否为视频文件,默认为否
+     * @param array $video_info 视频信息数组,非视频素材不需要提供 array('title'=>'视频标题','introduction'=>'描述')
+     * @return boolean|array
+     */
+    public function uploadForeverMedia($data, $type,$is_video=false,$video_info=array()){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        //#TODO 暂不确定此接口是否需要让视频文件走http协议
+        //如果要获取的素材是视频文件时,不能使用https协议,必须更换成http协议
+        //$url_prefix = $is_video?str_replace('https','http',self::API_URL_PREFIX):self::API_URL_PREFIX;
+        //当上传视频文件时,附加视频文件信息
+        if ($is_video) $data['description'] = self::json_encode($video_info);
+        $result = $this->http_post(self::API_URL_PREFIX.self::MEDIA_FOREVER_UPLOAD_URL.'access_token='.$this->access_token.'&type='.$type,$data,true);
+        if ($result)
+        {
+            $json = json_decode($result,true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 上传永久图文素材(认证后的订阅号可用)
+     * 新增的永久素材也可以在公众平台官网素材管理模块中看到
+     * @param array $data 消息结构{"articles":[{...}]}
+     * @return boolean|array
+     */
+    public function uploadForeverArticles($data){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX.self::MEDIA_FOREVER_NEWS_UPLOAD_URL.'access_token='.$this->access_token,self::json_encode($data));
+        if ($result)
+        {
+            $json = json_decode($result,true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 修改永久图文素材(认证后的订阅号可用)
+     * 永久素材也可以在公众平台官网素材管理模块中看到
+     * @param string $media_id 图文素材id
+     * @param array $data 消息结构{"articles":[{...}]}
+     * @param int $index 更新的文章在图文素材的位置,第一篇为0,仅多图文使用
+     * @return boolean|array
+     */
+    public function updateForeverArticles($media_id,$data,$index=0){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if (!isset($data['media_id'])) $data['media_id'] = $media_id;
+        if (!isset($data['index'])) $data['index'] = $index;
+        $result = $this->http_post(self::API_URL_PREFIX.self::MEDIA_FOREVER_NEWS_UPDATE_URL.'access_token='.$this->access_token,self::json_encode($data));
+        if ($result)
+        {
+            $json = json_decode($result,true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 获取永久素材(认证后的订阅号可用)
+     * 返回图文消息数组或二进制数据,失败返回false
+     * @param string $media_id 媒体文件id
+     * @param boolean $is_video 是否为视频文件,默认为否
+     * @return boolean|array|raw data
+     */
+    public function getForeverMedia($media_id,$is_video=false){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data = array('media_id' => $media_id);
+        //#TODO 暂不确定此接口是否需要让视频文件走http协议
+        //如果要获取的素材是视频文件时,不能使用https协议,必须更换成http协议
+        //$url_prefix = $is_video?str_replace('https','http',self::API_URL_PREFIX):self::API_URL_PREFIX;
+        $result = $this->http_post(self::API_URL_PREFIX.self::MEDIA_FOREVER_GET_URL.'access_token='.$this->access_token,self::json_encode($data));
+        if ($result)
+        {
+            if (is_string($result)) {
+                $json = json_decode($result,true);
+                if ($json) {
+                    if (isset($json['errcode'])) {
+                        $this->errCode = $json['errcode'];
+                        $this->errMsg = $json['errmsg'];
+                        return false;
+                    }
+                    return $json;
+                } else {
+                    return $result;
+                }
+            }
+            return $result;
+        }
+        return false;
+    }
+    /**
+     * 删除永久素材(认证后的订阅号可用)
+     * @param string $media_id 媒体文件id
+     * @return boolean
+     */
+    public function delForeverMedia($media_id){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data = array('media_id' => $media_id);
+        $result = $this->http_post(self::API_URL_PREFIX.self::MEDIA_FOREVER_DEL_URL.'access_token='.$this->access_token,self::json_encode($data));
+        if ($result)
+        {
+            $json = json_decode($result,true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+    /**
+     * 获取永久素材列表(认证后的订阅号可用)
+     * @param string $type 素材的类型,图片(image)、视频(video)、语音 (voice)、图文(news)
+     * @param int $offset 全部素材的偏移位置,0表示从第一个素材
+     * @param int $count 返回素材的数量,取值在1到20之间
+     * @return boolean|array
+     * 返回数组格式:
+     * array(
+     *  'total_count'=>0, //该类型的素材的总数
+     *  'item_count'=>0,  //本次调用获取的素材的数量
+     *  'item'=>array()   //素材列表数组,内容定义请参考官方文档
+     * )
+     */
+    public function getForeverList($type,$offset,$count){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data = array(
+            'type' => $type,
+            'offset' => $offset,
+            'count' => $count,
+        );
+        $result = $this->http_post(self::API_URL_PREFIX.self::MEDIA_FOREVER_BATCHGET_URL.'access_token='.$this->access_token,self::json_encode($data));
+        if ($result)
+        {
+            $json = json_decode($result,true);
+            if (isset($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 获取永久素材总数(认证后的订阅号可用)
+     * @return boolean|array
+     * 返回数组格式:
+     * array(
+     *  'voice_count'=>0, //语音总数量
+     *  'video_count'=>0, //视频总数量
+     *  'image_count'=>0, //图片总数量
+     *  'news_count'=>0   //图文总数量
+     * )
+     */
+    public function getForeverCount(){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_URL_PREFIX.self::MEDIA_FOREVER_COUNT_URL.'access_token='.$this->access_token);
+        if ($result)
+        {
+            $json = json_decode($result,true);
+            if (isset($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+	/**
+	 * 上传图文消息素材,用于群发(认证后的订阅号可用)
+	 * @param array $data 消息结构{"articles":[{...}]}
+	 * @return boolean|array
+	 */
+	public function uploadArticles($data){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::MEDIA_UPLOADNEWS_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 上传视频素材(认证后的订阅号可用)
+	 * @param array $data 消息结构
+	 * {
+	 *     "media_id"=>"",     //通过上传媒体接口得到的MediaId
+	 *     "title"=>"TITLE",    //视频标题
+	 *     "description"=>"Description"        //视频描述
+	 * }
+	 * @return boolean|array
+	 * {
+	 *     "type":"video",
+	 *     "media_id":"mediaid",
+	 *     "created_at":1398848981
+	 *  }
+	 */
+	public function uploadMpVideo($data){
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $result = $this->http_post(self::UPLOAD_MEDIA_URL.self::MEDIA_VIDEO_UPLOAD.'access_token='.$this->access_token,self::json_encode($data));
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return $json;
+	    }
+	    return false;
+	}
+	/**
+	 * 高级群发消息, 根据OpenID列表群发图文消息(订阅号不可用)
+	 * 	注意:视频需要在调用uploadMedia()方法后,再使用 uploadMpVideo() 方法生成,
+	 *             然后获得的 mediaid 才能用于群发,且消息类型为 mpvideo 类型。
+	 * @param array $data 消息结构
+	 * {
+	 *     "touser"=>array(
+	 *         "OPENID1",
+	 *         "OPENID2"
+	 *     ),
+	 *      "msgtype"=>"mpvideo",
+	 *      // 在下面5种类型中选择对应的参数内容
+	 *      // mpnews | voice | image | mpvideo => array( "media_id"=>"MediaId")
+	 *      // text => array ( "content" => "hello")
+	 * }
+	 * @return boolean|array
+	 */
+	public function sendMassMessage($data){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::MASS_SEND_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 高级群发消息, 根据群组id群发图文消息(认证后的订阅号可用)
+	 * 	注意:视频需要在调用uploadMedia()方法后,再使用 uploadMpVideo() 方法生成,
+	 *             然后获得的 mediaid 才能用于群发,且消息类型为 mpvideo 类型。
+	 * @param array $data 消息结构
+	 * {
+	 *     "filter"=>array(
+	 *         "is_to_all"=>False,     //是否群发给所有用户.True不用分组id,False需填写分组id
+	 *         "group_id"=>"2"     //群发的分组id
+	 *     ),
+	 *      "msgtype"=>"mpvideo",
+	 *      // 在下面5种类型中选择对应的参数内容
+	 *      // mpnews | voice | image | mpvideo => array( "media_id"=>"MediaId")
+	 *      // text => array ( "content" => "hello")
+	 * }
+	 * @return boolean|array
+	 */
+	public function sendGroupMassMessage($data){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::MASS_SEND_GROUP_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 高级群发消息, 删除群发图文消息(认证后的订阅号可用)
+	 * @param int $msg_id 消息id
+	 * @return boolean|array
+	 */
+	public function deleteMassMessage($msg_id){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::MASS_DELETE_URL.'access_token='.$this->access_token,self::json_encode(array('msg_id'=>$msg_id)));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return true;
+		}
+		return false;
+	}
+	/**
+	 * 高级群发消息, 预览群发消息(认证后的订阅号可用)
+	 * 	注意:视频需要在调用uploadMedia()方法后,再使用 uploadMpVideo() 方法生成,
+	 *             然后获得的 mediaid 才能用于群发,且消息类型为 mpvideo 类型。
+	 * @param array $data 消息结构
+	 * {
+	 *     "touser"=>"OPENID",
+	 *      "msgtype"=>"mpvideo",
+	 *      // 在下面5种类型中选择对应的参数内容
+	 *      // mpnews | voice | image | mpvideo => array( "media_id"=>"MediaId")
+	 *      // text => array ( "content" => "hello")
+	 * }
+	 * @return boolean|array
+	 */
+	public function previewMassMessage($data){
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $result = $this->http_post(self::API_URL_PREFIX.self::MASS_PREVIEW_URL.'access_token='.$this->access_token,self::json_encode($data));
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return $json;
+	    }
+	    return false;
+	}
+	/**
+	 * 高级群发消息, 查询群发消息发送状态(认证后的订阅号可用)
+	 * @param int $msg_id 消息id
+	 * @return boolean|array
+	 * {
+	 *     "msg_id":201053012,     //群发消息后返回的消息id
+	 *     "msg_status":"SEND_SUCCESS" //消息发送后的状态,SENDING表示正在发送 SEND_SUCCESS表示发送成功
+	 * }
+	 */
+	public function queryMassMessage($msg_id){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::MASS_QUERY_URL.'access_token='.$this->access_token,self::json_encode(array('msg_id'=>$msg_id)));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 创建二维码ticket
+	 * @param int|string $scene_id 自定义追踪id,临时二维码只能用数值型
+	 * @param int $type 0:临时二维码;1:数值型永久二维码(此时expire参数无效);2:字符串型永久二维码(此时expire参数无效)
+	 * @param int $expire 临时二维码有效期,最大为604800秒
+	 * @return array('ticket'=>'qrcode字串','expire_seconds'=>604800,'url'=>'二维码图片解析后的地址')
+	 */
+	public function getQRCode($scene_id,$type=0,$expire=604800){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		if (!isset($scene_id)) return false;
+		switch ($type) {
+			case '0':
+				if (!is_numeric($scene_id))
+					return false;
+				$action_name = 'QR_SCENE';
+				$action_info = array('scene'=>(array('scene_id'=>$scene_id)));
+				break;
+			case '1':
+				if (!is_numeric($scene_id))
+					return false;
+				$action_name = 'QR_LIMIT_SCENE';
+				$action_info = array('scene'=>(array('scene_id'=>$scene_id)));
+				break;
+			case '2':
+				if (!is_string($scene_id))
+					return false;
+				$action_name = 'QR_LIMIT_STR_SCENE';
+				$action_info = array('scene'=>(array('scene_str'=>$scene_id)));
+				break;
+			default:
+				return false;
+		}
+		$data = array(
+			'action_name'    => $action_name,
+			'expire_seconds' => $expire,
+			'action_info'    => $action_info
+		);
+		if ($type) {
+			unset($data['expire_seconds']);
+		}
+		$result = $this->http_post(self::API_URL_PREFIX.self::QRCODE_CREATE_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result) {
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 获取二维码图片
+	 * @param string $ticket 传入由getQRCode方法生成的ticket参数
+	 * @return string url 返回http地址
+	 */
+	public function getQRUrl($ticket) {
+		return self::QRCODE_IMG_URL.urlencode($ticket);
+	}
+	/**
+	 * 长链接转短链接接口
+	 * @param string $long_url 传入要转换的长url
+	 * @return boolean|string url 成功则返回转换后的短url
+	 */
+	public function getShortUrl($long_url){
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $data = array(
+            'action'=>'long2short',
+            'long_url'=>$long_url
+	    );
+	    $result = $this->http_post(self::API_URL_PREFIX.self::SHORT_URL.'access_token='.$this->access_token,self::json_encode($data));
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return $json['short_url'];
+	    }
+	    return false;
+	}
+	/**
+	 * 获取统计数据
+	 * @param string $type  数据分类(user|article|upstreammsg|interface)分别为(用户分析|图文分析|消息分析|接口分析)
+	 * @param string $subtype   数据子分类,参考 DATACUBE_URL_ARR 常量定义部分 或者README.md说明文档
+	 * @param string $begin_date 开始时间
+	 * @param string $end_date   结束时间
+	 * @return boolean|array 成功返回查询结果数组,其定义请看官方文档
+	 */
+	public function getDatacube($type,$subtype,$begin_date,$end_date=''){
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+		if (!isset(self::$DATACUBE_URL_ARR[$type]) || !isset(self::$DATACUBE_URL_ARR[$type][$subtype]))
+			return false;
+	    $data = array(
+            'begin_date'=>$begin_date,
+            'end_date'=>$end_date?$end_date:$begin_date
+	    );
+	    $result = $this->http_post(self::API_BASE_URL_PREFIX.self::$DATACUBE_URL_ARR[$type][$subtype].'access_token='.$this->access_token,self::json_encode($data));
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return isset($json['list'])?$json['list']:$json;
+	    }
+	    return false;
+	}
+	/**
+	 * 批量获取关注用户列表
+	 * @param unknown $next_openid
+	 */
+	public function getUserList($next_openid=''){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_get(self::API_URL_PREFIX.self::USER_GET_URL.'access_token='.$this->access_token.'&next_openid='.$next_openid);
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (isset($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 获取关注者详细信息
+	 * @param string $openid
+	 * @param string $lang 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语
+	 * @return array {subscribe,openid,nickname,sex,city,province,country,language,headimgurl,subscribe_time,[unionid]}
+	 * 注意:unionid字段 只有在用户将公众号绑定到微信开放平台账号后,才会出现。建议调用前用isset()检测一下
+	 */
+	public function getUserInfo($openid, $lang = 'zh_CN'){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_get(self::API_URL_PREFIX.self::USER_INFO_URL.'access_token='.$this->access_token.'&openid='.$openid.'&lang='.$lang);
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (isset($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 批量获取关注者详细信息
+	 * @param array $openids user_list{{'openid:xxxxxx'},{},{}}
+	 * @return array user_info_list{subscribe,openid,nickname,sex,city,province,country,language,headimgurl,subscribe_time,[unionid]}{}{}...
+	 * 注意:unionid字段 只有在用户将公众号绑定到微信开放平台账号后,才会出现。建议调用前用isset()检测一下
+	 */
+	public function getUsersInfo($openids){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::USERS_INFO_URL.'access_token='.$this->access_token,json_encode($openids));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (isset($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 设置用户备注名
+	 * @param string $openid
+	 * @param string $remark 备注名
+	 * @return boolean|array
+	 */
+	public function updateUserRemark($openid,$remark){
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $data = array(
+			'openid'=>$openid,
+			'remark'=>$remark
+	    );
+	    $result = $this->http_post(self::API_URL_PREFIX.self::USER_UPDATEREMARK_URL.'access_token='.$this->access_token,self::json_encode($data));
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+	    }
+	    return false;
+	}
+	/**
+	 * 获取用户分组列表
+	 * @return boolean|array
+	 */
+	public function getGroup(){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_get(self::API_URL_PREFIX.self::GROUP_GET_URL.'access_token='.$this->access_token);
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (isset($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 获取用户所在分组
+	 * @param string $openid
+	 * @return boolean|int 成功则返回用户分组id
+	 */
+	public function getUserGroup($openid){
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $data = array(
+	            'openid'=>$openid
+	    );
+	    $result = $this->http_post(self::API_URL_PREFIX.self::USER_GROUP_URL.'access_token='.$this->access_token,self::json_encode($data));
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        } else
+                if (isset($json['groupid'])) return $json['groupid'];
+	    }
+	    return false;
+	}
+	/**
+	 * 新增自定分组
+	 * @param string $name 分组名称
+	 * @return boolean|array
+	 */
+	public function createGroup($name){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$data = array(
+				'group'=>array('name'=>$name)
+		);
+		$result = $this->http_post(self::API_URL_PREFIX.self::GROUP_CREATE_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 更改分组名称
+	 * @param int $groupid 分组id
+	 * @param string $name 分组名称
+	 * @return boolean|array
+	 */
+	public function updateGroup($groupid,$name){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$data = array(
+				'group'=>array('id'=>$groupid,'name'=>$name)
+		);
+		$result = $this->http_post(self::API_URL_PREFIX.self::GROUP_UPDATE_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 移动用户分组
+	 * @param int $groupid 分组id
+	 * @param string $openid 用户openid
+	 * @return boolean|array
+	 */
+	public function updateGroupMembers($groupid,$openid){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$data = array(
+				'openid'=>$openid,
+				'to_groupid'=>$groupid
+		);
+		$result = $this->http_post(self::API_URL_PREFIX.self::GROUP_MEMBER_UPDATE_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 批量移动用户分组
+	 * @param int $groupid 分组id
+	 * @param string $openid_list 用户openid数组,一次不能超过50个
+	 * @return boolean|array
+	 */
+	public function batchUpdateGroupMembers($groupid,$openid_list){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$data = array(
+				'openid_list'=>$openid_list,
+				'to_groupid'=>$groupid
+		);
+		$result = $this->http_post(self::API_URL_PREFIX.self::GROUP_MEMBER_BATCHUPDATE_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 发送客服消息
+	 * @param array $data 消息结构{"touser":"OPENID","msgtype":"news","news":{...}}
+	 * @return boolean|array
+	 */
+	public function sendCustomMessage($data){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::CUSTOM_SEND_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * oauth 授权跳转接口
+	 * @param string $callback 回调URI
+	 * @return string
+	 */
+	public function getOauthRedirect($callback,$state='',$scope='snsapi_userinfo'){
+		return self::OAUTH_PREFIX.self::OAUTH_AUTHORIZE_URL.'appid='.$this->appid.'&redirect_uri='.urlencode($callback).'&response_type=code&scope='.$scope.'&state='.$state.'#wechat_redirect';
+	}
+	/**
+	 * 通过code获取Access Token
+	 * @return array {access_token,expires_in,refresh_token,openid,scope}
+	 */
+	public function getOauthAccessToken(){
+		$code = isset($_GET['code'])?$_GET['code']:'';
+		if (!$code) return false;
+		$result = $this->http_get(self::API_BASE_URL_PREFIX.self::OAUTH_TOKEN_URL.'appid='.$this->appid.'&secret='.$this->appsecret.'&code='.$code.'&grant_type=authorization_code');
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			$this->user_token = $json['access_token'];
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 刷新access token并续期
+	 * @param string $refresh_token
+	 * @return boolean|mixed
+	 */
+	public function getOauthRefreshToken($refresh_token){
+		$result = $this->http_get(self::API_BASE_URL_PREFIX.self::OAUTH_REFRESH_URL.'appid='.$this->appid.'&grant_type=refresh_token&refresh_token='.$refresh_token);
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			$this->user_token = $json['access_token'];
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 获取授权后的用户资料
+	 * @param string $access_token
+	 * @param string $openid
+	 * @return array {openid,nickname,sex,province,city,country,headimgurl,privilege,[unionid]}
+	 * 注意:unionid字段 只有在用户将公众号绑定到微信开放平台账号后,才会出现。建议调用前用isset()检测一下
+	 */
+	public function getOauthUserinfo($access_token,$openid){
+		$result = $this->http_get(self::API_BASE_URL_PREFIX.self::OAUTH_USERINFO_URL.'access_token='.$access_token.'&openid='.$openid);
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 检验授权凭证是否有效
+	 * @param string $access_token
+	 * @param string $openid
+	 * @return boolean 是否有效
+	 */
+	public function getOauthAuth($access_token,$openid){
+	    $result = $this->http_get(self::API_BASE_URL_PREFIX.self::OAUTH_AUTH_URL.'access_token='.$access_token.'&openid='.$openid);
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        } else
+	          if ($json['errcode']==0) return true;
+	    }
+	    return false;
+	}
+	/**
+	 * 模板消息 设置所属行业
+	 * @param int $id1  公众号模板消息所属行业编号,参看官方开发文档 行业代码
+	 * @param int $id2  同$id1。但如果只有一个行业,此参数可省略
+	 * @return boolean|array
+	 */
+	public function setTMIndustry($id1,$id2=''){
+	    if ($id1) $data['industry_id1'] = $id1;
+	    if ($id2) $data['industry_id2'] = $id2;
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $result = $this->http_post(self::API_URL_PREFIX.self::TEMPLATE_SET_INDUSTRY_URL.'access_token='.$this->access_token,self::json_encode($data));
+	    if($result){
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return $json;
+	    }
+	    return false;
+	}
+	/**
+	 * 模板消息 添加消息模板
+	 * 成功返回消息模板的调用id
+	 * @param string $tpl_id 模板库中模板的编号,有“TM**”和“OPENTMTM**”等形式
+	 * @return boolean|string
+	 */
+	public function addTemplateMessage($tpl_id){
+	    $data = array ('template_id_short' =>$tpl_id);
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $result = $this->http_post(self::API_URL_PREFIX.self::TEMPLATE_ADD_TPL_URL.'access_token='.$this->access_token,self::json_encode($data));
+	    if($result){
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return $json['template_id'];
+	    }
+	    return false;
+	}
+	/**
+	 * 发送模板消息
+	 * @param array $data 消息结构
+	 * {
+			"touser":"OPENID",
+			"template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY",
+			"url":"http://weixin.qq.com/download",
+			"topcolor":"#FF0000",
+			"data":{
+				"参数名1": {
+					"value":"参数",
+					"color":"#173177"	 //参数颜色
+					},
+				"Date":{
+					"value":"06月07日 19时24分",
+					"color":"#173177"
+					},
+				"CardNumber":{
+					"value":"0426",
+					"color":"#173177"
+					},
+				"Type":{
+					"value":"消费",
+					"color":"#173177"
+					}
+			}
+		}
+	 * @return boolean|array
+	 */
+	public function sendTemplateMessage($data){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::TEMPLATE_SEND_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if($result){
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 获取多客服会话记录
+	 * @param array $data 数据结构{"starttime":123456789,"endtime":987654321,"openid":"OPENID","pagesize":10,"pageindex":1,}
+	 * @return boolean|array
+	 */
+	public function getCustomServiceMessage($data){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::CUSTOM_SERVICE_GET_RECORD.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 转发多客服消息
+	 * Example: $obj->transfer_customer_service($customer_account)->reply();
+	 * @param string $customer_account 转发到指定客服帐号:test1@test
+	 */
+	public function transfer_customer_service($customer_account = '')
+	{
+		$msg = array(
+			'ToUserName' => $this->getRevFrom(),
+			'FromUserName'=>$this->getRevTo(),
+			'CreateTime'=>time(),
+			'MsgType'=>'transfer_customer_service',
+		);
+		if ($customer_account) {
+			$msg['TransInfo'] = array('KfAccount'=>$customer_account);
+		}
+		$this->Message($msg);
+		return $this;
+	}
+	/**
+	 * 获取多客服客服基本信息
+	 *
+	 * @return boolean|array
+	 */
+	public function getCustomServiceKFlist(){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_get(self::API_URL_PREFIX.self::CUSTOM_SERVICE_GET_KFLIST.'access_token='.$this->access_token);
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 获取多客服在线客服接待信息
+	 *
+	 * @return boolean|array {
+	 "kf_online_list": [
+	 {
+	 "kf_account": "test1@test",	//客服账号@微信别名
+	 "status": 1,			//客服在线状态 1:pc在线,2:手机在线,若pc和手机同时在线则为 1+2=3
+	 "kf_id": "1001",		//客服工号
+	 "auto_accept": 0,		//客服设置的最大自动接入数
+	 "accepted_case": 1		//客服当前正在接待的会话数
+	 }
+	 ]
+	 }
+	 */
+	public function getCustomServiceOnlineKFlist(){
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $result = $this->http_get(self::API_URL_PREFIX.self::CUSTOM_SERVICE_GET_ONLINEKFLIST.'access_token='.$this->access_token);
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return $json;
+	    }
+	    return false;
+	}
+	/**
+	 * 创建指定多客服会话
+	 * @tutorial 当用户已被其他客服接待或指定客服不在线则会失败
+	 * @param string $openid           //用户openid
+	 * @param string $kf_account     //客服账号
+	 * @param string $text                 //附加信息,文本会展示在客服人员的多客服客户端,可为空
+	 * @return boolean | array            //成功返回json数组
+	 * {
+	 *   "errcode": 0,
+	 *   "errmsg": "ok",
+	 * }
+	 */
+	public function createKFSession($openid,$kf_account,$text=''){
+	    $data=array(
+	    	"openid" =>$openid,
+	        "kf_account" => $kf_account
+	    );
+	    if ($text) $data["text"] = $text;
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $result = $this->http_post(self::API_BASE_URL_PREFIX.self::CUSTOM_SESSION_CREATE.'access_token='.$this->access_token,self::json_encode($data));
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return $json;
+	    }
+	    return false;
+	}
+	/**
+	 * 关闭指定多客服会话
+	 * @tutorial 当用户被其他客服接待时则会失败
+	 * @param string $openid           //用户openid
+	 * @param string $kf_account     //客服账号
+	 * @param string $text                 //附加信息,文本会展示在客服人员的多客服客户端,可为空
+	 * @return boolean | array            //成功返回json数组
+	 * {
+	 *   "errcode": 0,
+	 *   "errmsg": "ok",
+	 * }
+	 */
+	public function closeKFSession($openid,$kf_account,$text=''){
+	    $data=array(
+	    	"openid" =>$openid,
+	        "kf_account" => $kf_account
+	    );
+	    if ($text) $data["text"] = $text;
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $result = $this->http_post(self::API_BASE_URL_PREFIX.self::CUSTOM_SESSION_CLOSE .'access_token='.$this->access_token,self::json_encode($data));
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return $json;
+	    }
+	    return false;
+	}
+	/**
+	 * 获取用户会话状态
+	 * @param string $openid           //用户openid
+	 * @return boolean | array            //成功返回json数组
+	 * {
+	 *     "errcode" : 0,
+	 *     "errmsg" : "ok",
+	 *     "kf_account" : "test1@test",    //正在接待的客服
+	 *     "createtime": 123456789,        //会话接入时间
+	 *  }
+	 */
+	public function getKFSession($openid){
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $result = $this->http_get(self::API_BASE_URL_PREFIX.self::CUSTOM_SESSION_GET .'access_token='.$this->access_token.'&openid='.$openid);
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return $json;
+	    }
+	    return false;
+	}
+	/**
+	 * 获取指定客服的会话列表
+	 * @param string $openid           //用户openid
+	 * @return boolean | array            //成功返回json数组
+	 *  array(
+	 *     'sessionlist' => array (
+	 *         array (
+	 *             'openid'=>'OPENID',             //客户 openid
+	 *             'createtime'=>123456789,  //会话创建时间,UNIX 时间戳
+	 *         ),
+	 *         array (
+	 *             'openid'=>'OPENID',             //客户 openid
+	 *             'createtime'=>123456789,  //会话创建时间,UNIX 时间戳
+	 *         ),
+	 *     )
+	 *  )
+	 */
+	public function getKFSessionlist($kf_account){
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $result = $this->http_get(self::API_BASE_URL_PREFIX.self::CUSTOM_SESSION_GET_LIST .'access_token='.$this->access_token.'&kf_account='.$kf_account);
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return $json;
+	    }
+	    return false;
+	}
+	/**
+	 * 获取未接入会话列表
+	 * @param string $openid           //用户openid
+	 * @return boolean | array            //成功返回json数组
+	 *  array (
+	 *     'count' => 150 ,                            //未接入会话数量
+	 *     'waitcaselist' => array (
+	 *         array (
+	 *             'openid'=>'OPENID',             //客户 openid
+	 *             'kf_account ' =>'',                   //指定接待的客服,为空则未指定
+	 *             'createtime'=>123456789,  //会话创建时间,UNIX 时间戳
+	 *         ),
+	 *         array (
+	 *             'openid'=>'OPENID',             //客户 openid
+	 *             'kf_account ' =>'',                   //指定接待的客服,为空则未指定
+	 *             'createtime'=>123456789,  //会话创建时间,UNIX 时间戳
+	 *         )
+	 *     )
+	 *  )
+	 */
+	public function getKFSessionWait(){
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $result = $this->http_get(self::API_BASE_URL_PREFIX.self::CUSTOM_SESSION_GET_WAIT .'access_token='.$this->access_token);
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return $json;
+	    }
+	    return false;
+	}
+	/**
+	 * 添加客服账号
+	 *
+	 * @param string $account      //完整客服账号,格式为:账号前缀@公众号微信号,账号前缀最多10个字符,必须是英文或者数字字符
+	 * @param string $nickname     //客服昵称,最长6个汉字或12个英文字符
+	 * @param string $password     //客服账号明文登录密码,会自动加密
+	 * @return boolean|array
+	 * 成功返回结果
+	 * {
+	 *   "errcode": 0,
+	 *   "errmsg": "ok",
+	 * }
+	 */
+	public function addKFAccount($account,$nickname,$password){
+	    $data=array(
+	    	"kf_account" =>$account,
+	        "nickname" => $nickname,
+	        "password" => md5($password)
+	    );
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_BASE_URL_PREFIX.self::CS_KF_ACCOUNT_ADD_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+	/**
+	 * 修改客服账号信息
+	 *
+	 * @param string $account      //完整客服账号,格式为:账号前缀@公众号微信号,账号前缀最多10个字符,必须是英文或者数字字符
+	 * @param string $nickname     //客服昵称,最长6个汉字或12个英文字符
+	 * @param string $password     //客服账号明文登录密码,会自动加密
+	 * @return boolean|array
+	 * 成功返回结果
+	 * {
+	 *   "errcode": 0,
+	 *   "errmsg": "ok",
+	 * }
+	 */
+	public function updateKFAccount($account,$nickname,$password){
+	    $data=array(
+	            "kf_account" =>$account,
+	            "nickname" => $nickname,
+	            "password" => md5($password)
+	    );
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $result = $this->http_post(self::API_BASE_URL_PREFIX.self::CS_KF_ACCOUNT_UPDATE_URL.'access_token='.$this->access_token,self::json_encode($data));
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return $json;
+	    }
+	    return false;
+	}
+	/**
+	 * 删除客服账号
+	 *
+	 * @param string $account      //完整客服账号,格式为:账号前缀@公众号微信号,账号前缀最多10个字符,必须是英文或者数字字符
+	 * @return boolean|array
+	 * 成功返回结果
+	 * {
+	 *   "errcode": 0,
+	 *   "errmsg": "ok",
+	 * }
+	 */
+	public function deleteKFAccount($account){
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $result = $this->http_get(self::API_BASE_URL_PREFIX.self::CS_KF_ACCOUNT_DEL_URL.'access_token='.$this->access_token.'&kf_account='.$account);
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return $json;
+	    }
+	    return false;
+	}
+	/**
+	 * 上传客服头像
+	 *
+	 * @param string $account //完整客服账号,格式为:账号前缀@公众号微信号,账号前缀最多10个字符,必须是英文或者数字字符
+	 * @param string $imgfile //头像文件完整路径,如:'D:\user.jpg'。头像文件必须JPG格式,像素建议640*640
+	 * @return boolean|array
+	 * 成功返回结果
+	 * {
+	 *   "errcode": 0,
+	 *   "errmsg": "ok",
+	 * }
+	 */
+	public function setKFHeadImg($account,$imgfile){
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $result = $this->http_post(self::API_BASE_URL_PREFIX.self::CS_KF_ACCOUNT_UPLOAD_HEADIMG_URL.'access_token='.$this->access_token.'&kf_account='.$account,array('media'=>'@'.$imgfile),true);
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return $json;
+	    }
+	    return false;
+	}
+	/**
+	 * 语义理解接口
+	 * @param String $uid      用户唯一id(非开发者id),用户区分公众号下的不同用户(建议填入用户openid)
+	 * @param String $query    输入文本串
+	 * @param String $category 需要使用的服务类型,多个用“,”隔开,不能为空
+	 * @param Float $latitude  纬度坐标,与经度同时传入;与城市二选一传入
+	 * @param Float $longitude 经度坐标,与纬度同时传入;与城市二选一传入
+	 * @param String $city     城市名称,与经纬度二选一传入
+	 * @param String $region   区域名称,在城市存在的情况下可省略;与经纬度二选一传入
+	 * @return boolean|array
+	 */
+	public function querySemantic($uid,$query,$category,$latitude=0,$longitude=0,$city="",$region=""){
+	    if (!$this->access_token && !$this->checkAuth()) return false;
+	    $data=array(
+	            'query' => $query,
+	            'category' => $category,
+	            'appid' => $this->appid,
+	            'uid' => ''
+	    );
+	    //地理坐标或城市名称二选一
+	    if ($latitude) {
+	        $data['latitude'] = $latitude;
+	        $data['longitude'] = $longitude;
+	    } elseif ($city) {
+	        $data['city'] = $city;
+	    } elseif ($region) {
+	        $data['region'] = $region;
+	    }
+	    $result = $this->http_post(self::API_BASE_URL_PREFIX.self::SEMANTIC_API_URL.'access_token='.$this->access_token,self::json_encode($data));
+	    if ($result)
+	    {
+	        $json = json_decode($result,true);
+	        if (!$json || !empty($json['errcode'])) {
+	            $this->errCode = $json['errcode'];
+	            $this->errMsg = $json['errmsg'];
+	            return false;
+	        }
+	        return $json;
+	    }
+	    return false;
+	}
+    /**
+     * 创建卡券
+     * @param Array $data      卡券数据
+     * @return array|boolean 返回数组中card_id为卡券ID
+     */
+    public function createCard($data) {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CREATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 更改卡券信息
+     * 调用该接口更新信息后会重新送审,卡券状态变更为待审核。已被用户领取的卡券会实时更新票面信息。
+     * @param string $data
+     * @return boolean
+     */
+    public function updateCard($data) {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_UPDATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+    /**
+     * 删除卡券
+     * 允许商户删除任意一类卡券。删除卡券后,该卡券对应已生成的领取用二维码、添加到卡包 JS API 均会失效。
+     * 注意:删除卡券不能删除已被用户领取,保存在微信客户端中的卡券,已领取的卡券依旧有效。
+     * @param string $card_id 卡券ID
+     * @return boolean
+     */
+    public function delCard($card_id) {
+        $data = array(
+            'card_id' => $card_id,
+        );
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_DELETE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+    /**
+     * 查询卡券详情
+     * @param string $card_id
+     * @return boolean|array    返回数组信息比较复杂,请参看卡券接口文档
+     */
+    public function getCardInfo($card_id) {
+        $data = array(
+            'card_id' => $card_id,
+        );
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_GET . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 获取用户已领取卡券接口
+     * @param string $openid
+     * @param string $card_id
+     * @return boolean|array    返回数组信息比较复杂,请参看卡券接口文档
+     * 成功返回结果
+     *  {
+     * "errcode":0,
+     * "errmsg":"ok",
+     * "card_list": [
+     * {"code": "xxx1434079154", "card_id": "xxxxxxxxxx"},
+     * {"code": "xxx1434079155", "card_id": "xxxxxxxxxx"}
+     * ]
+     * }
+     */
+    public function getUserCardList($openid,$card_id) {
+        $data = array(
+            'openid' => $openid,
+            'card_id' => $card_id
+        );
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_USER_GETCARDLIST . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 获取颜色列表
+	 * 获得卡券的最新颜色列表,用于创建卡券
+	 * @return boolean|array   返回数组请参看 微信卡券接口文档 的json格式
+     */
+    public function getCardColors() {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_BASE_URL_PREFIX . self::CARD_GETCOLORS . 'access_token=' . $this->access_token);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 拉取门店列表
+	 * 获取在公众平台上申请创建的门店列表
+	 * @param int $offset  开始拉取的偏移,默认为0从头开始
+	 * @param int $count   拉取的数量,默认为0拉取全部
+	 * @return boolean|array   返回数组请参看 微信卡券接口文档 的json格式
+     */
+    public function getCardLocations($offset=0,$count=0) {
+	    $data=array(
+	    	'offset'=>$offset,
+	        'count'=>$count
+	    );
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_LOCATION_BATCHGET . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 批量导入门店信息
+	 * @tutorial 返回插入的门店id列表,以逗号分隔。如果有插入失败的,则为-1,请自行核查是哪个插入失败
+	 * @param array $data    数组形式的json数据,由于内容较多,具体内容格式请查看 微信卡券接口文档
+	 * @return boolean|string 成功返回插入的门店id列表
+     */
+    public function addCardLocations($data) {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_LOCATION_BATCHADD . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 生成卡券二维码
+	 * 成功则直接返回ticket值,可以用 getQRUrl($ticket) 换取二维码url
+	 *
+	 * @param string $cardid 卡券ID 必须
+	 * @param string $code 指定卡券 code 码,只能被领一次。use_custom_code 字段为 true 的卡券必须填写,非自定义 code 不必填写。
+	 * @param string $openid 指定领取者的 openid,只有该用户能领取。bind_openid 字段为 true 的卡券必须填写,非自定义 openid 不必填写。
+	 * @param int $expire_seconds 指定二维码的有效时间,范围是 60 ~ 1800 秒。不填默认为永久有效。
+	 * @param boolean $is_unique_code 指定下发二维码,生成的二维码随机分配一个 code,领取后不可再次扫描。填写 true 或 false。默认 false。
+	 * @param string $balance 红包余额,以分为单位。红包类型必填(LUCKY_MONEY),其他卡券类型不填。
+     * @return boolean|string
+     */
+    public function createCardQrcode($card_id,$code='',$openid='',$expire_seconds=0,$is_unique_code=false,$balance='') {
+        $card = array(
+            'card_id' => $card_id
+        );
+        $data = array(
+            'action_name' => "QR_CARD"
+        );
+        if ($code)
+            $card['code'] = $code;
+        if ($openid)
+            $card['openid'] = $openid;
+        if ($is_unique_code)
+            $card['is_unique_code'] = $is_unique_code;
+        if ($balance)
+            $card['balance'] = $balance;
+        if ($expire_seconds)
+            $data['expire_seconds'] = $expire_seconds;
+        $data['action_info'] = array('card' => $card);
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_QRCODE_CREATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 消耗 code
+     * 自定义 code(use_custom_code 为 true)的优惠券,在 code 被核销时,必须调用此接口。
+     *
+     * @param string $code 要消耗的序列号
+     * @param string $card_id 要消耗序列号所述的 card_id,创建卡券时use_custom_code 填写 true 时必填。
+     * @return boolean|array
+     * {
+     *  "errcode":0,
+     *  "errmsg":"ok",
+     *  "card":{"card_id":"pFS7Fjg8kV1IdDz01r4SQwMkuCKc"},
+     *  "openid":"oFS7Fjl0WsZ9AMZqrI80nbIq8xrA"
+     * }
+     */
+    public function consumeCardCode($code,$card_id='') {
+        $data = array('code' => $code);
+        if ($card_id)
+            $data['card_id'] = $card_id;
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CODE_CONSUME . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * code 解码
+     * @param string $encrypt_code 通过 choose_card_info 获取的加密字符串
+     * @return boolean|array
+     * {
+     *  "errcode":0,
+     *  "errmsg":"ok",
+     *  "code":"751234212312"
+     *  }
+     */
+    public function decryptCardCode($encrypt_code) {
+        $data = array(
+            'encrypt_code' => $encrypt_code,
+        );
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CODE_DECRYPT . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 查询 code 的有效性(非自定义 code)
+     * @param string $code
+     * @return boolean|array
+     * {
+     *  "errcode":0,
+     *  "errmsg":"ok",
+     *  "openid":"oFS7Fjl0WsZ9AMZqrI80nbIq8xrA",    //用户 openid
+     *  "card":{
+     *      "card_id":"pFS7Fjg8kV1IdDz01r4SQwMkuCKc",
+     *      "begin_time": 1404205036,               //起始使用时间
+     *      "end_time": 1404205036,                 //结束时间
+     *  }
+     * }
+     */
+    public function checkCardCode($code) {
+        $data = array(
+            'code' => $code,
+        );
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CODE_GET . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 批量查询卡列表
+	 * @param $offset  开始拉取的偏移,默认为0从头开始
+	 * @param $count   需要查询的卡片的数量(数量最大50,默认50)
+     * @return boolean|array
+     * {
+     *  "errcode":0,
+     *  "errmsg":"ok",
+     *  "card_id_list":["ph_gmt7cUVrlRk8swPwx7aDyF-pg"],    //卡 id 列表
+     *  "total_num":1                                       //该商户名下 card_id 总数
+     * }
+     */
+    public function getCardIdList($offset=0,$count=50) {
+        if ($count>50)
+            $count = 50;
+        $data = array(
+            'offset' => $offset,
+            'count'  => $count,
+        );
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_BATCHGET . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 更改 code
+     * 为确保转赠后的安全性,微信允许自定义code的商户对已下发的code进行更改。
+     * 注:为避免用户疑惑,建议仅在发生转赠行为后(发生转赠后,微信会通过事件推送的方式告知商户被转赠的卡券code)对用户的code进行更改。
+     * @param string $code      卡券的 code 编码
+     * @param string $card_id   卡券 ID
+     * @param string $new_code  新的卡券 code 编码
+     * @return boolean
+     */
+    public function updateCardCode($code,$card_id,$new_code) {
+        $data = array(
+            'code' => $code,
+            'card_id' => $card_id,
+            'new_code' => $new_code,
+        );
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CODE_UPDATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+    /**
+     * 设置卡券失效
+     * 设置卡券失效的操作不可逆
+     * @param string $code 需要设置为失效的 code
+     * @param string $card_id 自定义 code 的卡券必填。非自定义 code 的卡券不填。
+     * @return boolean
+     */
+    public function unavailableCardCode($code,$card_id='') {
+        $data = array(
+            'code' => $code,
+        );
+        if ($card_id)
+            $data['card_id'] = $card_id;
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CODE_UNAVAILABLE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+    /**
+     * 库存修改
+     * @param string $data
+     * @return boolean
+     */
+    public function modifyCardStock($data) {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_MODIFY_STOCK . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+    /**
+     * 更新门票
+     * @param string $data
+     * @return boolean
+     */
+    public function updateMeetingCard($data) {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_MEETINGCARD_UPDATEUSER . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+    /**
+     * 激活/绑定会员卡
+     * @param string $data 具体结构请参看卡券开发文档(6.1.1 激活/绑定会员卡)章节
+     * @return boolean
+     */
+    public function activateMemberCard($data) {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_MEMBERCARD_ACTIVATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+    /**
+     * 会员卡交易
+     * 会员卡交易后每次积分及余额变更需通过接口通知微信,便于后续消息通知及其他扩展功能。
+     * @param string $data 具体结构请参看卡券开发文档(6.1.2 会员卡交易)章节
+     * @return boolean|array
+     */
+    public function updateMemberCard($data) {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_MEMBERCARD_UPDATEUSER . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 更新红包金额
+     * @param string $code      红包的序列号
+     * @param $balance          红包余额
+     * @param string $card_id   自定义 code 的卡券必填。非自定义 code 可不填。
+     * @return boolean|array
+     */
+    public function updateLuckyMoney($code,$balance,$card_id='') {
+        $data = array(
+                'code' => $code,
+                'balance' => $balance
+        );
+        if ($card_id)
+            $data['card_id'] = $card_id;
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_LUCKYMONEY_UPDATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+    /**
+     * 设置卡券测试白名单
+     * @param string $openid    测试的 openid 列表
+     * @param string $user      测试的微信号列表
+     * @return boolean
+     */
+    public function setCardTestWhiteList($openid=array(),$user=array()) {
+        $data = array();
+        if (count($openid) > 0)
+            $data['openid'] = $openid;
+        if (count($user) > 0)
+            $data['username'] = $user;
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_TESTWHILELIST_SET . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+    /**
+     * 申请设备ID
+     * [applyShakeAroundDevice 申请配置设备所需的UUID、Major、Minor。
+     * 若激活率小于50%,不能新增设备。单次新增设备超过500 个,需走人工审核流程。
+     * 审核通过后,可用迒回的批次ID 用“查询设备列表”接口拉取本次申请的设备ID]
+     * @param array $data
+     * array(
+     *      "quantity" => 3,         //申请的设备ID 的数量,单次新增设备超过500 个,需走人工审核流程(必填)
+     *      "apply_reason" => "测试",//申请理由(必填)
+     *      "comment" => "测试专用", //备注(非必填)
+     *      "poi_id" => 1234         //设备关联的门店ID(非必填)
+     * )
+     * @return boolean|mixed
+     * {
+        "data": {
+            "apply_id": 123,
+            "device_identifiers":[
+            {
+            "device_id":10100,
+            "uuid":"FDA50693-A4E2-4FB1-AFCF-C6EB07647825",
+            "major":10001,
+            "minor":10002
+            }
+            ]
+        },
+        "errcode": 0,
+        "errmsg": "success."
+        }
+        apply_id:申请的批次ID,可用在“查询设备列表”接口按批次查询本次申请成功的设备ID
+        device_identifiers:指定的设备ID 列表
+        device_id:设备编号
+        uuid、major、minor
+        audit_status:审核状态。0:审核未通过、1:审核中、2:审核已通过;审核会在三个工作日内完成
+        audit_comment:审核备注,包括审核不通过的原因
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-3-25 下午1:24:06
+     * @copyright Show More
+     */
+    public function applyShakeAroundDevice($data){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_APPLYID . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 编辑设备信息
+     * [updateShakeAroundDevice 编辑设备的备注信息。可用设备ID或完整的UUID、Major、Minor指定设备,二者选其一。]
+     * @param array $data
+     * array(
+     *      "device_identifier" => array(
+     *          		"device_id" => 10011,   //当提供了device_id则不需要使用uuid、major、minor,反之亦然
+     *          		"uuid" => "FDA50693-A4E2-4FB1-AFCF-C6EB07647825",
+     *          		"major" => 1002,
+     *          		"minor" => 1223
+     *      ),
+     *      "comment" => "测试专用", //备注(非必填)
+     * )
+     * {
+        "data": {
+        },
+        "errcode": 0,
+        "errmsg": "success."
+       }
+     * @return boolean
+     * @author binsee<binsee@163.com>
+     * @version 2015-4-20 23:45:00
+     */
+    public function updateShakeAroundDevice($data){
+    	if (!$this->access_token && !$this->checkAuth()) return false;
+    	$result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_UPDATE . 'access_token=' . $this->access_token, self::json_encode($data));
+    	$this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+    /**
+     * 查询设备列表
+     * [searchShakeAroundDevice 查询已有的设备ID、UUID、Major、Minor、激活状态、备注信息、关联门店、关联页面等信息。
+     * 可指定设备ID 或完整的UUID、Major、Minor 查询,也可批量拉取设备信息列表。]
+     * @param array $data
+     * $data 三种格式:
+     * ①查询指定设备时:$data = array(
+     *                              "device_identifiers" => array(
+     *                                                          array(
+     *                                                              "device_id" => 10100,
+     *                                                              "uuid" => "FDA50693-A4E2-4FB1-AFCF-C6EB07647825",
+     *                                                              "major" => 10001,
+     *                                                              "minor" => 10002
+     *                                                          )
+     *                                                      )
+     *                              );
+     * device_identifiers:指定的设备
+     * device_id:设备编号,若填了UUID、major、minor,则可不填设备编号,若二者都填,则以设备编号为优先
+     * uuid、major、minor:三个信息需填写完整,若填了设备编号,则可不填此信息
+     * +-------------------------------------------------------------------------------------------------------------
+     * ②需要分页查询或者指定范围内的设备时: $data = array(
+     *                                                  "begin" => 0,
+     *                                                  "count" => 3
+     *                                               );
+     * begin:设备列表的起始索引值
+     * count:待查询的设备个数
+     * +-------------------------------------------------------------------------------------------------------------
+     * ③当需要根据批次ID 查询时: $data = array(
+     *                                      "apply_id" => 1231,
+     *                                      "begin" => 0,
+     *                                      "count" => 3
+     *                                    );
+     * apply_id:批次ID
+     * +-------------------------------------------------------------------------------------------------------------
+     * @return boolean|mixed
+     *正确迒回JSON 数据示例:
+     *字段说明
+        {
+            "data": {
+                "devices": [          //指定的设备信息列表
+                    {
+                        "comment": "", //设备的备注信息
+                        "device_id": 10097, //设备编号
+                        "major": 10001,
+                        "minor": 12102,
+                        "page_ids": "15369", //与此设备关联的页面ID 列表,用逗号隔开
+                        "status": 1, //激活状态,0:未激活,1:已激活(但不活跃),2:活跃
+                        "poi_id": 0, //门店ID
+                        "uuid": "FDA50693-A4E2-4FB1-AFCF-C6EB07647825"
+                    },
+                    {
+                        "comment": "", //设备的备注信息
+                        "device_id": 10098, //设备编号
+                        "major": 10001,
+                        "minor": 12103,
+                        "page_ids": "15368", //与此设备关联的页面ID 列表,用逗号隔开
+                        "status": 1, //激活状态,0:未激活,1:已激活(但不活跃),2:活跃
+                        "poi_id": 0, //门店ID
+                        "uuid": "FDA50693-A4E2-4FB1-AFCF-C6EB07647825"
+                    }
+                ],
+                "total_count": 151 //商户名下的设备总量
+            },
+            "errcode": 0,
+            "errmsg": "success."
+        }
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-3-25 下午1:45:42
+     * @copyright Show More
+     */
+    public function searchShakeAroundDevice($data){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_SEARCH . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * [bindLocationShakeAroundDevice 配置设备与门店的关联关系]
+     * @param string $device_id 设备编号,若填了UUID、major、minor,则可不填设备编号,若二者都填,则以设备编号为优先
+     * @param int $poi_id 待关联的门店ID
+     * @param string $uuid UUID、major、minor,三个信息需填写完整,若填了设备编号,则可不填此信息
+     * @param int $major
+     * @param int $minor
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+        "data": {
+        },
+        "errcode": 0,
+        "errmsg": "success."
+       }
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-4-21 00:14:00
+     * @copyright Show More
+     */
+    public function bindLocationShakeAroundDevice($device_id,$poi_id,$uuid='',$major=0,$minor=0){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if(!$device_id){
+            if(!$uuid || !$major || !$minor){
+                return false;
+            }
+            $device_identifier = array(
+                'uuid' => $uuid,
+                'major' => $major,
+                'minor' => $minor
+            );
+        }else{
+            $device_identifier = array(
+                'device_id' => $device_id
+            );
+        }
+        $data = array(
+            'device_identifier' => $device_identifier,
+            'poi_id' => $poi_id
+        );
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_BINDLOCATION . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json; //这个可以更改为返回true
+        }
+        return false;
+    }
+    /**
+     * [bindPageShakeAroundDevice 配置设备与页面的关联关系。
+     * 支持建立或解除关联关系,也支持新增页面或覆盖页面等操作。
+     * 配置完成后,在此设备的信号范围内,即可摇出关联的页面信息。
+     * 若设备配置多个页面,则随机出现页面信息]
+     * @param string $device_id 设备编号,若填了UUID、major、minor,则可不填设备编号,若二者都填,则以设备编号为优先
+     * @param array $page_ids 待关联的页面列表
+     * @param number $bind 关联操作标志位, 0 为解除关联关系,1 为建立关联关系
+     * @param number $append 新增操作标志位, 0 为覆盖,1 为新增
+     * @param string $uuid UUID、major、minor,三个信息需填写完整,若填了设备编号,则可不填此信息
+     * @param int $major
+     * @param int $minor
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+        "data": {
+        },
+        "errcode": 0,
+        "errmsg": "success."
+       }
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-4-21 00:31:00
+     * @copyright Show More
+     */
+    public function bindPageShakeAroundDevice($device_id,$page_ids=array(),$bind=1,$append=1,$uuid='',$major=0,$minor=0){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if(!$device_id){
+            if(!$uuid || !$major || !$minor){
+                return false;
+            }
+            $device_identifier = array(
+                'uuid' => $uuid,
+                'major' => $major,
+                'minor' => $minor
+            );
+        }else{
+            $device_identifier = array(
+                'device_id' => $device_id
+            );
+        }
+        $data = array(
+            'device_identifier' => $device_identifier,
+            'page_ids' => $page_ids,
+            'bind' => $bind,
+            'append' => $append
+        );
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_BINDPAGE . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * 上传在摇一摇页面展示的图片素材
+     * 注意:数组的键值任意,但文件名前必须加@,使用单引号以避免本地路径斜杠被转义
+     * @param array $data {"media":'@Path\filename.jpg'} 格式限定为:jpg,jpeg,png,gif,图片大小建议120px*120 px,限制不超过200 px *200 px,图片需为正方形。
+     * @return boolean|array
+     * {
+        "data": {
+            "pic_url":"http://shp.qpic.cn/wechat_shakearound_pic/0/1428377032e9dd2797018cad79186e03e8c5aec8dc/120"
+        },
+            "errcode": 0,
+            "errmsg": "success."
+        }
+       }
+     * @author binsee<binsee@163.com>
+     * @version 2015-4-21 00:51:00
+     */
+    public function uploadShakeAroundMedia($data){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX.self::SHAKEAROUND_MATERIAL_ADD.'access_token='.$this->access_token,$data,true);
+        if ($result)
+        {
+            $json = json_decode($result,true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * [addShakeAroundPage 增加摇一摇出来的页面信息,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。]
+     * @param string $title 在摇一摇页面展示的主标题,不超过6 个字
+     * @param string $description 在摇一摇页面展示的副标题,不超过7 个字
+     * @param sting $icon_url 在摇一摇页面展示的图片, 格式限定为:jpg,jpeg,png,gif; 建议120*120 , 限制不超过200*200
+     * @param string $page_url 跳转链接
+     * @param string $comment 页面的备注信息,不超过15 个字,可不填
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+        "data": {
+            "page_id": 28840 //新增页面的页面id
+        }
+        "errcode": 0,
+        "errmsg": "success."
+       }
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-3-25 下午2:57:09
+     * @copyright Show More
+     */
+    public function addShakeAroundPage($title,$description,$icon_url,$page_url,$comment=''){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data = array(
+            "title" => $title,
+            "description" => $description,
+            "icon_url" => $icon_url,
+            "page_url" => $page_url,
+            "comment" => $comment
+        );
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_PAGE_ADD . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * [updateShakeAroundPage 编辑摇一摇出来的页面信息,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。]
+     * @param int $page_id
+     * @param string $title 在摇一摇页面展示的主标题,不超过6 个字
+     * @param string $description 在摇一摇页面展示的副标题,不超过7 个字
+     * @param sting $icon_url 在摇一摇页面展示的图片, 格式限定为:jpg,jpeg,png,gif; 建议120*120 , 限制不超过200*200
+     * @param string $page_url 跳转链接
+     * @param string $comment 页面的备注信息,不超过15 个字,可不填
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+        "data": {
+            "page_id": 28840 //编辑页面的页面ID
+        }
+        "errcode": 0,
+        "errmsg": "success."
+       }
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-3-25 下午3:02:51
+     * @copyright Show More
+     */
+    public function updateShakeAroundPage($page_id,$title,$description,$icon_url,$page_url,$comment=''){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data = array(
+            "page_id" => $page_id,
+            "title" => $title,
+            "description" => $description,
+            "icon_url" => $icon_url,
+            "page_url" => $page_url,
+            "comment" => $comment
+        );
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_PAGE_UPDATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * [searchShakeAroundPage 查询已有的页面,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。
+     * 提供两种查询方式,①可指定页面ID 查询,②也可批量拉取页面列表。]
+     * @param array $page_ids
+     * @param int $begin
+     * @param int $count
+     * ①需要查询指定页面时:
+     * {
+        "page_ids":[12345, 23456, 34567]
+       }
+     * +-------------------------------------------------------------------------------------------------------------
+     * ②需要分页查询或者指定范围内的页面时:
+     * {
+        "begin": 0,
+        "count": 3
+       }
+     * +-------------------------------------------------------------------------------------------------------------
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+        {
+            "data": {
+                "pages": [
+                    {
+                        "comment": "just for test",
+                        "description": "test",
+                        "icon_url": "https://www.baidu.com/img/bd_logo1.png",
+                        "page_id": 28840,
+                        "page_url": "http://xw.qq.com/testapi1",
+                        "title": "测试1"
+                    },
+                    {
+                        "comment": "just for test",
+                        "description": "test",
+                        "icon_url": "https://www.baidu.com/img/bd_logo1.png",
+                        "page_id": 28842,
+                        "page_url": "http://xw.qq.com/testapi2",
+                        "title": "测试2"
+                    }
+                    ],
+                "total_count": 2
+            },
+            "errcode": 0,
+            "errmsg": "success."
+        }
+     *字段说明:
+     *total_count 商户名下的页面总数
+     *page_id 摇周边页面唯一ID
+     *title 在摇一摇页面展示的主标题
+     *description 在摇一摇页面展示的副标题
+     *icon_url 在摇一摇页面展示的图片
+     *page_url 跳转链接
+     *comment 页面的备注信息
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-3-25 下午3:12:17
+     * @copyright Show More
+     */
+    public function searchShakeAroundPage($page_ids=array(),$begin=0,$count=1){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if(!empty($page_ids)){
+            $data = array(
+                'page_ids' => $page_ids
+            );
+        }else{
+            $data = array(
+                'begin' => $begin,
+                'count' => $count
+            );
+        }
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_PAGE_SEARCH . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * [deleteShakeAroundPage 删除已有的页面,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。
+     * 只有页面与设备没有关联关系时,才可被删除。]
+     * @param array $page_ids
+     * {
+        "page_ids":[12345,23456,34567]
+       }
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+        "data": {
+        },
+        "errcode": 0,
+        "errmsg": "success."
+       }
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-3-25 下午3:23:00
+     * @copyright Show More
+     */
+    public function deleteShakeAroundPage($page_ids=array()){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data = array(
+            'page_ids' => $page_ids
+        );
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_PAGE_DELETE . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * [getShakeInfoShakeAroundUser 获取设备信息,包括UUID、major、minor,以及距离、openID 等信息。]
+     * @param string $ticket 摇周边业务的ticket,可在摇到的URL 中得到,ticket生效时间为30 分钟
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+        "data": {
+            "page_id ": 14211,
+            "beacon_info": {
+                "distance": 55.00620700469034,
+                "major": 10001,
+                "minor": 19007,
+                "uuid": "FDA50693-A4E2-4FB1-AFCF-C6EB07647825"
+            },
+            "openid": "oVDmXjp7y8aG2AlBuRpMZTb1-cmA"
+        },
+        "errcode": 0,
+        "errmsg": "success."
+       }
+     * 字段说明:
+     * beacon_info 设备信息,包括UUID、major、minor,以及距离
+     * UUID、major、minor UUID、major、minor
+     * distance Beacon 信号与手机的距离
+     * page_id 摇周边页面唯一ID
+     * openid 商户AppID 下用户的唯一标识
+     * poi_id 门店ID,有的话则返回,没有的话不会在JSON 格式内
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-3-25 下午3:28:20
+     * @copyright Show More
+     */
+    public function getShakeInfoShakeAroundUser($ticket){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data = array('ticket' => $ticket);
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_USER_GETSHAKEINFO . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * [deviceShakeAroundStatistics 以设备为维度的数据统计接口。
+     * 查询单个设备进行摇周边操作的人数、次数,点击摇周边消息的人数、次数;查询的最长时间跨度为30天。]
+     * @param int $device_id 设备编号,若填了UUID、major、minor,即可不填设备编号,二者选其一
+     * @param int $begin_date 起始日期时间戳,最长时间跨度为30 天
+     * @param int $end_date 结束日期时间戳,最长时间跨度为30 天
+     * @param string $uuid UUID、major、minor,三个信息需填写完成,若填了设备编辑,即可不填此信息,二者选其一
+     * @param int $major
+     * @param int $minor
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+        "data": [
+            {
+                "click_pv": 0,
+                "click_uv": 0,
+                "ftime": 1425052800,
+                "shake_pv": 0,
+                "shake_uv": 0
+            },
+            {
+                "click_pv": 0,
+                "click_uv": 0,
+                "ftime": 1425139200,
+                "shake_pv": 0,
+                "shake_uv": 0
+            }
+        ],
+        "errcode": 0,
+        "errmsg": "success."
+       }
+     * 字段说明:
+     * ftime 当天0 点对应的时间戳
+     * click_pv 点击摇周边消息的次数
+     * click_uv 点击摇周边消息的人数
+     * shake_pv 摇周边的次数
+     * shake_uv 摇周边的人数
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-4-21 00:39:00
+     * @copyright Show More
+     */
+    public function deviceShakeAroundStatistics($device_id,$begin_date,$end_date,$uuid='',$major=0,$minor=0){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if(!$device_id){
+            if(!$uuid || !$major || !$minor){
+                return false;
+            }
+            $device_identifier = array(
+                'uuid' => $uuid,
+                'major' => $major,
+                'minor' => $minor
+            );
+        }else{
+            $device_identifier = array(
+                'device_id' => $device_id
+            );
+        }
+        $data = array(
+            'device_identifier' => $device_identifier,
+            'begin_date' => $begin_date,
+            'end_date' => $end_date
+        );
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_STATISTICS_DEVICE . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+    /**
+     * [pageShakeAroundStatistics 以页面为维度的数据统计接口。
+     * 查询单个页面通过摇周边摇出来的人数、次数,点击摇周边页面的人数、次数;查询的最长时间跨度为30天。]
+     * @param int $page_id 指定页面的ID
+     * @param int $begin_date 起始日期时间戳,最长时间跨度为30 天
+     * @param int $end_date 结束日期时间戳,最长时间跨度为30 天
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+        "data": [
+            {
+                "click_pv": 0,
+                "click_uv": 0,
+                "ftime": 1425052800,
+                "shake_pv": 0,
+                "shake_uv": 0
+            },
+            {
+                "click_pv": 0,
+                "click_uv": 0,
+                "ftime": 1425139200,
+                "shake_pv": 0,
+                "shake_uv": 0
+            }
+        ],
+        "errcode": 0,
+        "errmsg": "success."
+       }
+     * 字段说明:
+     * ftime 当天0 点对应的时间戳
+     * click_pv 点击摇周边消息的次数
+     * click_uv 点击摇周边消息的人数
+     * shake_pv 摇周边的次数
+     * shake_uv 摇周边的人数
+     * @author binsee<binsee@163.com>
+     * @version 2015-4-21 00:43:00
+     */
+    public function pageShakeAroundStatistics($page_id,$begin_date,$end_date){
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data = array(
+            'page_id' => $page_id,
+            'begin_date' => $begin_date,
+            'end_date' => $end_date
+        );
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_STATISTICS_DEVICE . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+	/**
+	 * 根据订单ID获取订单详情
+	 * @param string $order_id 订单ID
+	 * @return order array|bool
+	 */
+	public function getOrderByID($order_id){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		if (!$order_id) return false;
+		$data = array(
+			'order_id'=>$order_id
+		);
+		$result = $this->http_post(self::API_BASE_URL_PREFIX.self::MERCHANT_ORDER_GETBYID.'access_token='.$this->access_token, self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (isset($json['errcode']) && $json['errcode']) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json['order'];
+		}
+		return false;
+	}
+	/**
+	 * 根据订单状态/创建时间获取订单详情
+	 * @param int $status 订单状态(不带该字段-全部状态, 2-待发货, 3-已发货, 5-已完成, 8-维权中, )
+	 * @param int $begintime 订单创建时间起始时间(不带该字段则不按照时间做筛选)
+	 * @param int $endtime 订单创建时间终止时间(不带该字段则不按照时间做筛选)
+	 * @return order list array|bool
+	 */
+	public function getOrderByFilter($status = null, $begintime = null, $endtime = null){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$data = array();
+		$valid_status = array(2, 3, 5, 8);
+		if (is_numeric($status) && in_array($status, $valid_status)) {
+			$data['status'] = $status;
+		}
+		if (is_numeric($begintime) && is_numeric($endtime)) {
+			$data['begintime'] = $begintime;
+			$data['endtime'] = $endtime;
+		}
+		$result = $this->http_post(self::API_BASE_URL_PREFIX.self::MERCHANT_ORDER_GETBYFILTER.'access_token='.$this->access_token, self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (isset($json['errcode']) && $json['errcode']) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json['order_list'];
+		}
+		return false;
+	}
+	/**
+	 * 设置订单发货信息
+	 * @param string $order_id 订单 ID
+	 * @param int $need_delivery 商品是否需要物流(0-不需要,1-需要)
+	 * @param string $delivery_company 物流公司 ID
+	 * @param string $delivery_track_no 运单 ID
+	 * @param int $is_others 是否为 6.4.5 表之外的其它物流公司(0-否,1-是)
+	 * @return bool
+	 */
+	public function setOrderDelivery($order_id, $need_delivery = 0, $delivery_company = null, $delivery_track_no = null, $is_others = 0){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		if (!$order_id) return false;
+		$data = array();
+		$data['order_id'] = $order_id;
+		if ($need_delivery) {
+			$data['delivery_company'] = $delivery_company;
+			$data['delivery_track_no'] = $delivery_track_no;
+			$data['is_others'] = $is_others;
+		}
+		else {
+			$data['need_delivery'] = $need_delivery;
+		}
+		$result = $this->http_post(self::API_BASE_URL_PREFIX.self::MERCHANT_ORDER_SETDELIVERY.'access_token='.$this->access_token, self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (isset($json['errcode']) && $json['errcode']) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return true;
+		}
+		return false;
+	}
+	/**
+	 * 关闭订单
+	 * @param string $order_id 订单 ID
+	 * @return bool
+	 */
+	public function closeOrder($order_id){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		if (!$order_id) return false;
+		$data = array(
+			'order_id'=>$order_id
+		);
+		$result = $this->http_post(self::API_BASE_URL_PREFIX.self::MERCHANT_ORDER_CLOSE.'access_token='.$this->access_token, self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (isset($json['errcode']) && $json['errcode']) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return true;
+		}
+		return false;
+	}
+	private function parseSkuInfo($skuInfo) {
+		$skuInfo = str_replace("\$", "", $skuInfo);
+		$matches = explode(";", $skuInfo);
+		$result = array();
+		foreach ($matches as $matche) {
+			$arrs = explode(":", $matche);
+			$result[$arrs[0]] = $arrs[1];
+		}
+		return $result;
+	}
+	/**
+	 * 获取订单SkuInfo - 订单付款通知
+	 * 当Event为 merchant_order(订单付款通知)
+	 * @return array|boolean
+	 */
+	public function getRevOrderSkuInfo(){
+		if (isset($this->_receive['SkuInfo']))     //订单 SkuInfo
+			return $this->parseSkuInfo($this->_receive['SkuInfo']);
+		else
+			return false;
+	}
+	}
+	/**
+	 * PKCS7Encoder class
+	 *
+	 * 提供基于PKCS7算法的加解密接口.
+	 */
+	class PKCS7Encoder
+	{
+		public static $block_size = 32;
+		/**
+		 * 对需要加密的明文进行填充补位
+		 * @param $text 需要进行填充补位操作的明文
+		 * @return 补齐明文字符串
+		 */
+		function encode($text)
+		{
+			$block_size = PKCS7Encoder::$block_size;
+			$text_length = strlen($text);
+			//计算需要填充的位数
+			$amount_to_pad = PKCS7Encoder::$block_size - ($text_length % PKCS7Encoder::$block_size);
+			if ($amount_to_pad == 0) {
+				$amount_to_pad = PKCS7Encoder::block_size;
+			}
+			//获得补位所用的字符
+			$pad_chr = chr($amount_to_pad);
+			$tmp = "";
+			for ($index = 0; $index < $amount_to_pad; $index++) {
+				$tmp .= $pad_chr;
+			}
+			return $text . $tmp;
+		}
+		/**
+		 * 对解密后的明文进行补位删除
+		 * @param decrypted 解密后的明文
+		 * @return 删除填充补位后的明文
+		 */
+		function decode($text)
+		{
+			$pad = ord(substr($text, -1));
+			if ($pad < 1 || $pad > PKCS7Encoder::$block_size) {
+				$pad = 0;
+			}
+			return substr($text, 0, (strlen($text) - $pad));
+		}
+	}
+	/**
+	 * Prpcrypt class
+	 *
+	 * 提供接收和推送给公众平台消息的加解密接口.
+	 */
+	class Prpcrypt
+	{
+		public $key;
+		function __construct($k) {
+			$this->key = base64_decode($k . "=");
+		}
+		/**
+		 * 兼容老版本php构造函数,不能在 __construct() 方法前边,否则报错
+		 */
+		function Prpcrypt($k)
+		{
+			$this->key = base64_decode($k . "=");
+		}
+		/**
+		 * 对明文进行加密
+		 * @param string $text 需要加密的明文
+		 * @return string 加密后的密文
+		 */
+		public function encrypt($text, $appid){
+			try {
+				//获得16位随机字符串,填充到明文之前
+				$random = $this->getRandomStr();
+				$text = $random . pack("N", strlen($text)) . $text . $appid;
+				$iv = substr($this->key, 0, 16);
+				$pkc_encoder = new PKCS7Encoder;
+				$text = $pkc_encoder->encode($text);
+				$encrypted = openssl_encrypt($text,'AES-256-CBC',substr($this->key, 0, 32),OPENSSL_ZERO_PADDING,$iv);
+				return array(ErrorCode::$OK, $encrypted);
+			} catch (Exception $e) {
+				//print $e;
+				return array(ErrorCode::$EncryptAESError, null);
+			}
+		}
+		/**
+		 * 对密文进行解密
+		 * @param string $encrypted 需要解密的密文
+		 * @return string 解密得到的明文
+		 */
+		public function decrypt($encrypted, $appid){
+			try {
+				$iv = substr($this->key, 0, 16);
+				$decrypted = openssl_decrypt($encrypted,'AES-256-CBC',substr($this->key, 0, 32),OPENSSL_ZERO_PADDING,$iv);
+			} catch (Exception $e) {
+				return array(ErrorCode::$DecryptAESError, null);
+			}
+			try {
+				//去除补位字符
+				$pkc_encoder = new PKCS7Encoder;
+				$result = $pkc_encoder->decode($decrypted);
+				//去除16位随机字符串,网络字节序和AppId
+				if (strlen($result) < 16)
+					return "";
+				$content = substr($result, 16, strlen($result));
+				$len_list = unpack("N", substr($content, 0, 4));
+				$xml_len = $len_list[1];
+				$xml_content = substr($content, 4, $xml_len);
+				$from_appid = substr($content, $xml_len + 4);
+				if (!$appid)
+					$appid = $from_appid;
+				//如果传入的appid是空的,则认为是订阅号,使用数据中提取出来的appid
+			} catch (Exception $e) {
+				//print $e;
+				return array(ErrorCode::$IllegalBuffer, null);
+			}
+			if ($from_appid != $appid)
+				return array(ErrorCode::$ValidateAppidError, null);
+			//不注释上边两行,避免传入appid是错误的情况
+			return array(0, $xml_content, $from_appid);
+			//增加appid,为了解决后面加密回复消息的时候没有appid的订阅号会无法回复
+		}
+		/**
+		 * 随机生成16位字符串
+		 * @return string 生成的字符串
+		 */
+		function getRandomStr()
+		{
+			$str = "";
+			$str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
+			$max = strlen($str_pol) - 1;
+			for ($i = 0; $i < 16; $i++) {
+				$str .= $str_pol[mt_rand(0, $max)];
+			}
+			return $str;
+		}
+	}
+	/**
+	 * error code
+	 * 仅用作类内部使用,不用于官方API接口的errCode码
+	 */
+	class ErrorCode
+	{
+		public static $OK = 0;
+		public static $ValidateSignatureError = 40001;
+		public static $ParseXmlError = 40002;
+		public static $ComputeSignatureError = 40003;
+		public static $IllegalAesKey = 40004;
+		public static $ValidateAppidError = 40005;
+		public static $EncryptAESError = 40006;
+		public static $DecryptAESError = 40007;
+		public static $IllegalBuffer = 40008;
+		public static $EncodeBase64Error = 40009;
+		public static $DecodeBase64Error = 40010;
+		public static $GenReturnXmlError = 40011;
+		public static $errCode=array(
+				'0' => '处理成功',
+				'40001' => '校验签名失败',
+				'40002' => '解析xml失败',
+				'40003' => '计算签名失败',
+				'40004' => '不合法的AESKey',
+				'40005' => '校验AppID失败',
+				'40006' => 'AES加密失败',
+				'40007' => 'AES解密失败',
+				'40008' => '公众平台发送的xml不合法',
+				'40009' => 'Base64编码失败',
+				'40010' => 'Base64解码失败',
+				'40011' => '公众帐号生成回包xml失败'
+		);
+		public static function getErrText($err) {
+			if (isset(self::$errCode[$err])) {
+				return self::$errCode[$err];
+			}else {
+				return false;
+			};
+		}
+}

+ 681 - 0
application/common/plugin/WechatAuth.php

@@ -0,0 +1,681 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Tinywan
+ * Date: 2016/9/11
+ * Time: 9:55
+ */
+
+namespace app\common\plugin;
+
+class WechatAuth
+{
+
+    /* 消息类型常量 */
+    const MSG_TYPE_TEXT = 'text';
+    const MSG_TYPE_IMAGE = 'image';
+    const MSG_TYPE_VOICE = 'voice';
+    const MSG_TYPE_VIDEO = 'video';
+    const MSG_TYPE_SHORTVIDEO = 'shortvideo';
+    const MSG_TYPE_LOCATION = 'location';
+    const MSG_TYPE_LINK = 'link';
+    const MSG_TYPE_MUSIC = 'music';
+    const MSG_TYPE_NEWS = 'news';
+    const MSG_TYPE_EVENT = 'event';
+
+    /* 二维码类型常量 */
+    const QR_SCENE       = 'QR_SCENE';
+    const QR_STR_SCENE   = 'QR_STR_SCENE';
+    const QR_LIMIT_SCENE = 'QR_LIMIT_SCENE';
+    const QR_LIMIT_STR_SCENE = 'QR_LIMIT_STR_SCENE';
+
+    /**
+     * 微信开发者申请的appID
+     * @var string
+     */
+    private $appId = '';
+
+    /**
+     * 微信开发者申请的appSecret
+     * @var string
+     */
+    private $appSecret = '';
+
+    /**
+     * 获取到的access_token
+     * @var string
+     */
+    private $accessToken = '';
+
+    /**
+     * 微信api根路径
+     * @var string
+     */
+    private $apiURL = 'https://api.weixin.qq.com/cgi-bin';
+
+    /**
+     * 微信二维码根路径
+     * @var string
+     */
+    private $qrcodeURL = 'https://mp.weixin.qq.com/cgi-bin';
+
+    //授权地址
+    private $requestCodeURL = 'https://open.weixin.qq.com/connect/oauth2/authorize';
+
+    private $oauthApiURL = 'https://api.weixin.qq.com/sns';
+
+    /**
+     * 构造方法,调用微信高级接口时实例化SDK
+     * @param string $appid 微信appid
+     * @param string $secret 微信appsecret
+     * @param string $token 获取到的access_token
+     */
+    public function __construct($appid, $secret, $token = null)
+    {
+        if ($appid && $secret) {
+            $this->appId = $appid;
+            $this->appSecret = $secret;
+            if (!empty($token)) $this->accessToken = $token;
+        } else {
+            throw new \Exception('缺少参数 APP_ID 和 APP_SECRET!');
+        }
+    }
+
+    // 是为了拼接成一URL地址,什么地址,拼接一个
+    public function getRequestCodeURL($redirect_uri, $state = null, $scope = 'snsapi_userinfo')
+    {
+
+        $query = array(
+            'appid' => $this->appId,
+            'redirect_uri' => $redirect_uri,
+            'response_type' => 'code',
+            'scope' => $scope,
+        );
+
+        if (!is_null($state) && preg_match('/[a-zA-Z0-9]+/', $state)) $query['state'] = $state;
+
+        //生成 URL-encode 之后的请求字符串 :foo=bar&baz=boom&cow=milk&php=hypertext+pro
+        $query = http_build_query($query);
+        return "{$this->requestCodeURL}?{$query}#wechat_redirect";
+    }
+
+    /**
+     * 获取access_token,用于后续接口访问
+     * @return array access_token信息,包含 token 和有效期
+     */
+    public function getAccessToken($type = 'client', $code = null)
+    {
+        $param = array(
+            'appid' => $this->appId,
+            'secret' => $this->appSecret
+        );
+
+        switch ($type) {
+            case 'client':
+                $param['grant_type'] = 'client_credential';
+                $url = "{$this->apiURL}/token";
+                break;
+
+            case 'code':
+                $param['code'] = $code;
+                $param['grant_type'] = 'authorization_code';
+                $url = "{$this->oauthApiURL}/oauth2/access_token";
+                break;
+
+            default:
+                throw new \Exception('不支持的grant_type类型!');
+                break;
+        }
+
+        $token = self::http($url, $param);
+        $token = json_decode($token, true);
+
+        if (is_array($token)) {
+            if (isset($token['errcode'])) {
+                throw new \Exception($token['errmsg']);
+            } else {
+                $this->accessToken = $token['access_token'];
+                return $token;
+            }
+        } else {
+            throw new \Exception('获取微信access_token失败!');
+        }
+    }
+
+    /**
+     * 获取授权用户信息
+     * @param  string $openid 用户的OpenID
+     * @param  string $lang 指定的语言
+     * @return array          用户信息数据,具体参见微信文档
+     */
+    public function getUserInfo($openid, $lang = 'zh_CN')
+    {
+        $query = array(
+            'access_token' => $this->accessToken,
+            'openid' => $openid,
+            'lang' => $lang,
+        );
+
+        $info = self::http("{$this->oauthApiURL}/userinfo", $query);
+        return json_decode($info, true);
+    }
+
+    /**
+     * 上传零时媒体资源
+     * @param  string $filename 媒体资源本地路径
+     * @param  string $type 媒体资源类型,具体请参考微信开发手册
+     */
+    public function mediaUpload($filename, $type)
+    {
+        $filename = realpath($filename);
+        if (!$filename) throw new \Exception('资源路径错误!');
+
+        $data = array(
+            'type' => $type,
+            'media' => "@{$filename}"
+        );
+
+        return $this->api('media/upload', $data, 'POST', '', false);
+    }
+
+    /**
+     * 上传永久媒体资源
+     * @param string $filename 媒体资源本地路径
+     * @param string $type 媒体资源类型,具体请参考微信开发手册
+     * @param string $description 资源描述,仅资源类型为 video 时有效
+     */
+    public function materialAddMaterial($filename, $type, $description = '')
+    {
+        $filename = realpath($filename);
+        if (!$filename) throw new \Exception('资源路径错误!');
+
+        $data = array(
+            'type' => $type,
+            'media' => "@{$filename}",
+        );
+
+        if ($type == 'video') {
+            if (is_array($description)) {
+                //保护中文,微信api不支持中文转义的json结构
+                array_walk_recursive($description, function (&$value) {
+                    $value = urlencode($value);
+                });
+                $description = urldecode(json_encode($description));
+            }
+            $data['description'] = $description;
+        }
+        return $this->api('material/add_material', $data, 'POST', '', false);
+    }
+
+    /**
+     * 获取媒体资源下载地址
+     * 注意:视频资源不允许下载
+     * @param  string $media_id 媒体资源id
+     * @return string           媒体资源下载地址
+     */
+    public function mediaGet($media_id)
+    {
+        $param = array(
+            'access_token' => $this->accessToken,
+            'media_id' => $media_id
+        );
+
+        $url = "{$this->apiURL}/media/get?";
+        return $url . http_build_query($param);
+    }
+
+    /**
+     * 给指定用户推送信息
+     * 注意:微信规则只允许给在48小时内给公众平台发送过消息的用户推送信息
+     * @param  string $openid 用户的openid
+     * @param  array $content 发送的数据,不同类型的数据结构可能不同
+     * @param  string $type 推送消息类型
+     */
+    public function messageCustomSend($openid, $content, $type = self::MSG_TYPE_TEXT)
+    {
+
+        //基础数据
+        $data = array(
+            'touser' => $openid,
+            'msgtype' => $type,
+        );
+
+        //根据类型附加额外数据
+        $data[$type] = call_user_func(array(self, $type), $content);
+
+        return $this->api('message/custom/send', $data);
+    }
+
+    /**
+     * 发送文本消息
+     * @param  string $openid 用户的openid
+     * @param  string $text 发送的文字
+     */
+    public function sendText($openid, $text)
+    {
+        return $this->messageCustomSend($openid, $text, self::MSG_TYPE_TEXT);
+    }
+
+    /**
+     * 发送图片消息
+     * @param  string $openid 用户的openid
+     * @param  string $media 图片ID
+     */
+    public function sendImage($openid, $media)
+    {
+        return $this->messageCustomSend($openid, $media, self::MSG_TYPE_IMAGE);
+    }
+
+    /**
+     * 发送语音消息
+     * @param  string $openid 用户的openid
+     * @param  string $media 音频ID
+     */
+    public function sendVoice($openid, $media)
+    {
+        return $this->messageCustomSend($openid, $media, self::MSG_TYPE_VOICE);
+    }
+
+    /**
+     * 发送视频消息
+     * @param  string $openid 用户的openid
+     * @param  string $media_id 视频ID
+     * @param  string $title 视频标题
+     * @param  string $discription 视频描述
+     */
+    public function sendVideo()
+    {
+        $video = func_get_args();
+        $openid = array_shift($video);
+        return $this->messageCustomSend($openid, $video, self::MSG_TYPE_VIDEO);
+    }
+
+    /**
+     * 发送音乐消息
+     * @param  string $openid 用户的openid
+     * @param  string $title 音乐标题
+     * @param  string $discription 音乐描述
+     * @param  string $musicurl 音乐链接
+     * @param  string $hqmusicurl 高品质音乐链接
+     * @param  string $thumb_media_id 缩略图ID
+     */
+    public function sendMusic()
+    {
+        $music = func_get_args();
+        $openid = array_shift($music);
+        return $this->messageCustomSend($openid, $music, self::MSG_TYPE_MUSIC);
+    }
+
+    /**
+     * 发送图文消息
+     * @param  string $openid 用户的openid
+     * @param  array $news 图文内容 [标题,描述,URL,缩略图]
+     * @param  array $news1 图文内容 [标题,描述,URL,缩略图]
+     * @param  array $news2 图文内容 [标题,描述,URL,缩略图]
+     *                ...     ...
+     * @param  array $news9 图文内容 [标题,描述,URL,缩略图]
+     */
+    public function sendNews()
+    {
+        $news = func_get_args();
+        $openid = array_shift($news);
+        return $this->messageCustomSend($openid, $news, self::MSG_TYPE_NEWS);
+    }
+
+    /**
+     * 发送一条图文消息
+     * @param  string $openid 用户的openid
+     * @param  string $title 文章标题
+     * @param  string $discription 文章简介
+     * @param  string $url 文章连接
+     * @param  string $picurl 文章缩略图
+     */
+    public function sendNewsOnce()
+    {
+        $news = func_get_args();
+        $openid = array_shift($news);
+        $news = array($news);
+        return $this->messageCustomSend($openid, $news, self::MSG_TYPE_NEWS);
+    }
+
+    /**
+     * 创建用户组
+     * @param  string $name 组名称
+     */
+    public function groupsCreate($name)
+    {
+        $data = array('group' => array('name' => $name));
+        return $this->api('groups/create', $data);
+    }
+
+    /**
+     * 查询所有分组
+     * @return array 分组列表
+     */
+    public function groupsGet()
+    {
+        return $this->api('groups/get', '', 'GET');
+    }
+
+    /**
+     * 查询用户所在的分组
+     * @param  string $openid 用户的OpenID
+     * @return number         分组ID
+     */
+    public function groupsGetid($openid)
+    {
+        $data = array('openid' => $openid);
+        return $this->api('groups/getid', $data);
+    }
+
+    /**
+     * 修改分组
+     * @param  number $id 分组ID
+     * @param  string $name 分组名称
+     * @return array        修改成功或失败信息
+     */
+    public function groupsUpdate($id, $name)
+    {
+        $data = array('id' => $id, 'name' => $name);
+        return $this->api('groups/update', $data);
+    }
+
+    /**
+     * 移动用户分组
+     * @param  string $openid 用户的OpenID
+     * @param  number $to_groupid 要移动到的分组ID
+     * @return array              移动成功或失败信息
+     */
+    public function groupsMemberUpdate($openid, $to_groupid)
+    {
+        $data = array('openid' => $openid, 'to_groupid' => $to_groupid);
+
+        return $this->api('groups/member/update', $data);
+    }
+
+
+    /**
+     * 用户设备注名
+     * @param  string $openid 用户的OpenID
+     * @param  string $remark 设备注名
+     * @return array          执行成功失败信息
+     */
+    public function userInfoUpdateremark($openid, $remark)
+    {
+        $data = array('openid' => $openid, 'remark' => $remark);
+
+
+        return $this->api('user/info/updateremark', $data);
+    }
+
+    /**
+     * 获取指定用户的详细信息
+     * @param  string $openid 用户的openid
+     * @param  string $lang 需要获取数据的语言
+     */
+    public function userInfo($openid, $lang = 'zh_CN')
+    {
+        $param = array('openid' => $openid, 'lang' => $lang);
+        return $this->api('user/info', '', 'GET', $param);
+    }
+
+    /**
+     * 获取关注者列表
+     * @param  string $next_openid 下一个openid,在用户数大于10000时有效
+     * @return array               用户列表
+     */
+    public function userGet($next_openid = '')
+    {
+        $param = array('next_openid' => $next_openid);
+        return $this->api('user/get', '', 'GET', $param);
+    }
+
+    /**
+     * 创建自定义菜单
+     * @param  array $button 符合规则的菜单数组,规则参见微信手册
+     */
+    public function menuCreate($button)
+    {
+        $data = array('button' => $button);
+        return $this->api('menu/create', $data);
+    }
+
+    /**
+     * 获取所有的自定义菜单
+     * @return array  自定义菜单数组
+     */
+    public function menuGet()
+    {
+        return $this->api('menu/get', '', 'GET');
+    }
+
+    /**
+     * 删除自定义菜单
+     */
+    public function menuDelete()
+    {
+        return $this->api('menu/delete', '', 'GET');
+    }
+
+    /**
+     * 创建二维码,可创建指定有效期的二维码和永久二维码
+     * @param  integer $scene_id       二维码参数
+     * @param  integer $expire_seconds 二维码有效期,0-永久有效
+     */
+    public function qrcodeCreate($q_type,$scene_id, $expire_seconds = 0){
+    	$data = array();
+    	switch($q_type){
+    		case self::QR_SCENE:
+    			$data['expire_seconds'] = $expire_seconds;
+    			$data['action_name']    = self::QR_SCENE;
+    			$data['action_info']['scene']['scene_id'] = $scene_id;
+    			break;
+    		case self::QR_STR_SCENE:
+    			$data['action_name']    = self::QR_STR_SCENE;
+    			$data['action_info']['scene']['scene_str'] = $scene_id;
+    			break;
+    		case self::QR_LIMIT_SCENE:
+    			$data['action_name']    = self::QR_LIMIT_SCENE;
+    			$data['action_info']['scene']['scene_id'] = $scene_id;
+    			break;
+    		case self::QR_LIMIT_STR_SCENE:
+    			$data['action_name']    = self::QR_LIMIT_STR_SCENE;
+    			$data['action_info']['scene']['scene_str'] = $scene_id;
+    			break;
+    	}
+    	return $this->api('qrcode/create', $data);
+    }
+    
+
+    /**
+     * 根据ticket获取二维码URL
+     * @param  string $ticket 通过 qrcodeCreate接口获取到的ticket
+     * @return string         二维码URL
+     */
+    public function showqrcode($ticket)
+    {
+        return "{$this->qrcodeURL}/showqrcode?ticket={$ticket}";
+    }
+
+    /**
+     * 长链接转短链接
+     * @param  string $long_url 长链接
+     * @return string           短链接
+     */
+    public function shorturl($long_url)
+    {
+        $data = array(
+            'action' => 'long2short',
+            'long_url' => $long_url
+        );
+
+        return $this->api('shorturl', $data);
+    }
+
+    /**
+     * 调用微信api获取响应数据
+     * @param  string $name API名称
+     * @param  string $data POST请求数据
+     * @param  string $method 请求方式
+     * @param  string $param GET请求参数
+     * @return array          api返回结果
+     */
+    protected function api($name, $data = '', $method = 'POST', $param = '', $json = true)
+    {
+        $params = array('access_token' => $this->accessToken);
+
+        if (!empty($param) && is_array($param)) {
+            $params = array_merge($params, $param);
+        }
+
+        $url = "{$this->apiURL}/{$name}";
+        if ($json && !empty($data)) {
+            //保护中文,微信api不支持中文转义的json结构
+            array_walk_recursive($data, function (&$value) {
+                $value = urlencode($value);
+            });
+            $data = urldecode(json_encode($data));
+        }
+
+        $data = self::http($url, $params, $data, $method);
+
+        return json_decode($data, true);
+    }
+
+    /**
+     * 发送HTTP请求方法,目前只支持CURL发送请求
+     * @param  string $url 请求URL
+     * @param  array $param GET参数数组
+     * @param  array $data POST的数据,GET请求时该参数无效
+     * @param  string $method 请求方法GET/POST
+     * @return array          响应数据
+     */
+    protected static function http($url, $param, $data = '', $method = 'GET')
+    {
+        $opts = array(
+            CURLOPT_TIMEOUT => 30,
+            CURLOPT_RETURNTRANSFER => 1,
+            CURLOPT_SSL_VERIFYPEER => false,
+            CURLOPT_SSL_VERIFYHOST => false,
+        );
+
+        /* 根据请求类型设置特定参数 */
+        $opts[CURLOPT_URL] = $url . '?' . http_build_query($param);
+
+        if (strtoupper($method) == 'POST') {
+            $opts[CURLOPT_POST] = 1;
+            $opts[CURLOPT_POSTFIELDS] = $data;
+
+            if (is_string($data)) { //发送JSON数据
+                $opts[CURLOPT_HTTPHEADER] = array(
+                    'Content-Type: application/json; charset=utf-8',
+                    'Content-Length: ' . strlen($data),
+                );
+            }
+        }
+
+        /* 初始化并执行curl请求 */
+        $ch = curl_init();
+        curl_setopt_array($ch, $opts);
+        $data = curl_exec($ch);
+        $error = curl_error($ch);
+        curl_close($ch);
+
+        //发生错误,抛出异常
+        if ($error) throw new \Exception('请求发生错误:' . $error);
+
+        return $data;
+    }
+
+    /**
+     * 构造文本信息
+     * @param  string $content 要回复的文本
+     */
+    private static function text($content)
+    {
+        $data['content'] = $content;
+        return $data;
+    }
+
+    /**
+     * 构造图片信息
+     * @param  integer $media 图片ID
+     */
+    private static function image($media)
+    {
+        $data['media_id'] = $media;
+        return $data;
+    }
+
+    /**
+     * 构造音频信息
+     * @param  integer $media 语音ID
+     */
+    private static function voice($media)
+    {
+        $data['media_id'] = $media;
+        return $data;
+    }
+
+    /**
+     * 构造视频信息
+     * @param  array $video 要回复的视频 [视频ID,标题,说明]
+     */
+    private static function video($video)
+    {
+        $data = array();
+        list(
+            $data['media_id'],
+            $data['title'],
+            $data['description'],
+            ) = $video;
+
+        return $data;
+    }
+
+    /**
+     * 构造音乐信息
+     * @param  array $music 要回复的音乐[标题,说明,链接,高品质链接,缩略图ID]
+     */
+    private static function music($music)
+    {
+        $data = array();
+        list(
+            $data['title'],
+            $data['description'],
+            $data['musicurl'],
+            $data['hqmusicurl'],
+            $data['thumb_media_id'],
+            ) = $music;
+
+        return $data;
+    }
+
+    /**
+     * 构造图文信息
+     * @param  array $news 要回复的图文内容
+     * [
+     *      0 => 第一条图文信息[标题,说明,图片链接,全文连接],
+     *      1 => 第二条图文信息[标题,说明,图片链接,全文连接],
+     *      2 => 第三条图文信息[标题,说明,图片链接,全文连接],
+     * ]
+     */
+    private static function news($news)
+    {
+        $articles = array();
+        foreach ($news as $key => $value) {
+            list(
+                $articles[$key]['title'],
+                $articles[$key]['description'],
+                $articles[$key]['url'],
+                $articles[$key]['picurl']
+                ) = $value;
+
+            if ($key >= 9) break; //最多只允许10条图文信息
+        }
+
+        $data['articles'] = $articles;
+        return $data;
+    }
+
+}

+ 66 - 0
application/common/plugin/WxBizDataCrypt.php

@@ -0,0 +1,66 @@
+<?php
+namespace app\common\plugin;
+include_once "Pkcs7Encoder.php";
+class WXBizDataCrypt
+{
+    private $appid;
+	private $sessionKey;
+	public static $OK = 0;
+	public static $IllegalAesKey = 41001;
+	public static $IllegalIv = 41002;
+	public static $IllegalBuffer = 41003;
+	public static $DecodeBase64Error = 41004;
+	/**
+	 * 构造函数
+	 * @param $sessionKey string 用户在小程序登录后获取的会话密钥
+	 * @param $appid string 小程序的appid
+	 */
+	public function __construct( $appid, $sessionKey)
+	{
+		$this->sessionKey = $sessionKey;
+		$this->appid = $appid;
+	}
+
+
+	/**
+	 * 检验数据的真实性,并且获取解密后的明文.
+	 * @param $encryptedData string 加密的用户数据
+	 * @param $iv string 与用户数据一同返回的初始向量
+	 * @param $data string 解密后的原文
+     *
+	 * @return int 成功0,失败返回对应的错误码
+	 */
+	public function decryptData( $encryptedData, $iv, &$data )
+	{
+		if (strlen($this->sessionKey) != 24) {
+			return self::$IllegalAesKey;
+		}
+		$aesKey=base64_decode($this->sessionKey);
+
+        
+		if (strlen($iv) != 24) {
+			return self::$IllegalIv;
+		}
+		$aesIV=base64_decode($iv);
+
+		$aesCipher=base64_decode($encryptedData);
+
+		$pc = new \Prpcrypt($aesKey);
+		$result = $pc->decrypt($aesCipher,$aesIV);
+		if ($result[0] != 0) {
+			return $result[0];
+		}
+     
+        $dataObj=json_decode( $result[1] );
+        if( $dataObj  == NULL )
+        {
+            return self::$IllegalBuffer;
+        }
+        if( $dataObj->watermark->appid != $this->appid )
+        {
+            return self::$IllegalBuffer;
+        }
+		$data = $result[1];
+		return self::$OK;
+	}
+}

+ 14 - 0
application/provider.php

@@ -0,0 +1,14 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// 应用容器绑定定义
+return [
+];

+ 28 - 0
application/tags.php

@@ -0,0 +1,28 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// 应用行为扩展定义文件
+return [
+    // 应用初始化
+    'app_init'     => [],
+    // 应用开始
+    'app_begin'    => [],
+    // 模块初始化
+    'module_init'  => [],
+    // 操作开始执行
+    'action_begin' => [],
+    // 视图内容过滤
+    'view_filter'  => [],
+    // 日志写入
+    'log_write'    => [],
+    // 应用结束
+    'app_end'      => [],
+];

+ 164 - 0
application/web/controller/CommonController.php

@@ -0,0 +1,164 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: shaoguo
+ * Date: 2020/4/24
+ * Time: 3:31 PM
+ */
+
+namespace app\web\controller;
+
+
+use model\Community;
+use model\HouseInfo;
+use model\UserInfo;
+use service\DepartmentService;
+use service\HouseMemberService;
+use service\HouseService;
+use service\PositionService;
+use think\Controller;
+use think\helper\Arr;
+
+class CommonController extends Controller
+{
+    public function province(){
+        return json([
+            'code'=>200,
+            'data'=>db('provinces')->where(['code'=>"32"])->field('code,name')->select()
+        ]);
+    }
+
+    public function city(){
+        $code=$this->request->param('code');
+        return json([
+            'code'=>200,
+            'data'=>db('cities')->where('province_code',$code)->where('code',3205)->field('code,name')->select()
+        ]);
+    }
+
+    public function area(){
+        $code=$this->request->param('code');
+        return json([
+            'code'=>200,
+            'data'=>db('areas')->where('city_code',$code)->field('code,name')->select()
+        ]);
+    }
+
+    public function street(){
+        $code=$this->request->param('code');
+        return json([
+            'code'=>200,
+            'data'=>db('streets')->where('area_code',$code)->field('code,name')->select()
+        ]);
+    }
+
+
+    public function village(){
+        $code=$this->request->param('code');
+        return json([
+            'code'=>200,
+            'data'=>db('villages')->where('street_code',$code)->field('code,name')->select()
+        ]);
+    }
+
+    public function community(){
+        $data=$this->request->get();
+        $where[]=array('is_delete','=',0);
+        if(isset($data['code']) && !empty($data['code'])){
+            $where[]=['province_code|city_code|areas_code|street_code|village_code','=',$data['code']];
+        }
+        if(isset($data['id']) && !empty($data['id']))$where[]=['id','=',$data['id']];
+
+        $communities=Community::where($where)->select();
+        return json([
+            'code'=>200,
+            'data'=>$communities
+        ]);
+
+
+    }
+
+
+
+
+    /**
+     * @name 获取家庭
+     */
+    public function getHouse(){
+        $where[]=['House.is_delete','=',0];
+        $data=$this->request->param();
+        if(isset($data['keyword']) && !empty($data['keyword'])){
+            $where[]=['House.name','like','%'.$data['keyword'].'%'];
+        }
+        $lists=HouseService::get($where,"id,name",['House.create_time'=>'desc'],'',true);
+        $count=HouseService::get($where,"id,name",['House.create_time'=>'desc'])->count();
+        $result['item']=$lists->items();
+        $result['count']=$count;
+        $result = [
+            'code' => 1,
+            'msg'  => 'ok',
+            'time' => time(),
+            'data' => $result,
+        ];
+        return json($result,200);
+    }
+
+
+    /**
+     * @name 获取家庭
+     */
+    public function getInfo(){
+        $where[]=['is_delete','=',0];
+        $data=$this->request->param();
+        if(isset($data['keyword']) && !empty($data['keyword'])){
+            $where[]=['name','like','%'.$data['keyword'].'%'];
+        }
+        $lists=HouseMemberService::get($where,"user_id as id,name",['create_time'=>'desc'],true);
+        $count=HouseMemberService::get($where,"user_id as id,name",['create_time'=>'desc'])->count();
+        $result['item']=$lists->items();
+        $result['count']=$count;
+
+        $result = [
+            'code' => 1,
+            'msg'  => 'ok',
+            'time' => time(),
+            'data' => $result,
+        ];
+        return json($result,200);
+    }
+
+    /**
+     * @name 获取员工
+     */
+    public function user_info(){
+        $with_where=array();
+        $data=$this->request->param();
+
+        if(isset($data['id']) && !empty($data['id']))$with_where[]=['pivot.department_id','=',$data['id']];
+        $userinfos=DepartmentService::getOne([['id','=',$data['id']]])->userInfo;
+        $result = [
+            'code' => 1,
+            'msg'  => 'ok',
+            'time' => time(),
+            'data' => $userinfos,
+        ];
+        return json($result,200);
+    }
+
+    public function position(){
+        $id=$this->request->param('id');
+        $where=array(['delete_time','=',0]);
+        if(!empty($id)){
+            $where[]=['id','in',$id];
+        }
+        $positions=PositionService::get($where);
+        $result = [
+            'code' => 1,
+            'msg'  => 'ok',
+            'time' => time(),
+            'data' => $positions,
+        ];
+        return json($result,200);
+    }
+
+}

+ 698 - 0
application/web/controller/IndexController.php

@@ -0,0 +1,698 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2018 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\web\controller;
+use think\Db;
+use app\common\plugin\Varbinary;
+use PhpOffice\PhpSpreadsheet\IOFactory;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Alignment;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+//产品类
+/**
+ * @author lyuis
+ *	小程序首页相应功能类
+ */
+class IndexController extends MainController
+{
+	private $pages=15;
+
+	//首页信息
+	public function index(){
+	    //站点信息
+        $primarydev=Db::table('primarydev')->select();
+        //报警设备 reportdata
+        $reportdata=Db::table('reportdata')->where(array(array('ALARMENDTIME','=','')))->select();
+
+
+        $this->api_return_json(compact('reportdata','primarydev'),1);
+
+    }
+
+
+
+
+
+	/**
+	 * 旁路电缆首页接口s
+	 */
+	public function deviceList(){
+		$page=$this->request->param('page',1);
+		$collect=empty(file_get_contents(CMF_ROOT . 'public/collect.json')) ? array():json_decode(file_get_contents(CMF_ROOT . 'public/collect.json'),true);
+		$deviceList=Db::name('device')
+		->alias('u')
+		->field('u.DeviceID as id,u.DeviceName as name,u.DeviceType as type,u.DeviceAddress as address,r.TestTime')
+		->join('Result_Last r','r.DeviceID=u.DeviceID','left')
+		->limit(($page-1)*$this->pages,$this->pages)
+		->select();
+		foreach($deviceList as &$v){
+			$v['type_text']=$v['type']==1 ? '电流巡检设备':'温度巡检设备';
+				
+			if(!empty($v['TestTime'])&&strtotime($v['TestTime'])>time()-600)
+				$v['online']=1;
+			else 
+				$v['online']=0;
+		};
+		if(!empty($collect)){
+			if(!is_array($collect))
+				$collect=json_decode($collect,true);
+			$collect_ids=array_column($collect, 'id');
+		}else 
+			$collect_ids=array();
+		foreach ($deviceList as &$val){
+			if(!empty($collect_ids)){
+				if(in_array($val['id'],$collect_ids)){
+					$val['is_collect']=1;
+				}else
+					$val['is_collect']=0;
+				}
+			else 
+				$val['is_collect']=0;
+		}
+		$pages=$this->pages;
+		$user_id=$this->userid;
+		$this->api_return_json(compact('deviceList','pages','collect','user_id'),1);
+	}
+	
+	public function collectDevice(){
+		$collect=empty(file_get_contents(CMF_ROOT . 'public/collect.json')) ? array():json_decode(file_get_contents(CMF_ROOT . 'public/collect.json'),true);
+		if(empty($collect))
+			$collect=array();
+		if(!is_array($collect))
+		$collect=json_decode($collect,true);
+		if(!empty($collect))
+		array_multisort(array_column($collect,'num'), SORT_ASC, $collect);
+		$deviceId=empty($collect) ? array() :array_column($collect, 'id');
+		$collcetDevice=Db::table('Result_Last')->where(array(array('DeviceID','in',$deviceId)))->field('TestTime,DeviceID')->select();
+		foreach($collect as &$val){
+			foreach ($collcetDevice as $v){
+				if($val['id']==$v['DeviceID']){
+					if(!empty($v['TestTime'])&&strtotime($v['TestTime'])>time()-300)
+						$val['online']=1;
+					else
+						$val['online']=0;
+				}
+			}
+			
+		};
+		$user_id=$this->userid;
+		$this->api_return_json(compact('collect','user_id'),1);
+	}
+	
+	//旁路电缆实时数据接口
+	public function dataRealTime(){
+		$collect=empty(file_get_contents(CMF_ROOT . 'public/collect.json')) ? array():json_decode(file_get_contents(CMF_ROOT . 'public/collect.json'),true);
+		if(empty($collect))
+			$collect=array();
+		if(!is_array($collect))
+			$collect=json_decode($collect,true);
+		$deviceId=empty($collect) ? array() :array_column($collect, 'id');
+		$deviceName=empty($collect) ? array() :array_column($collect, 'name','id');
+		$dataRealTime=Db::table('Result_Last')
+		->alias('u')
+		->where(array(array('u.DeviceID','in',$deviceId)))
+		->field('u.*,r.DeviceName as name,r.DeviceType as type,(u.Result_1+u.Result_2+u.Result_3)/3 as avg')
+		->join('device r','r.DeviceID=u.DeviceID','left')
+		->order('avg desc')
+		->select();
+		$dianliu=$wendu=array();
+		foreach ($dataRealTime as $key=>&$val){
+			if(!empty($val['TestTime'])&&strtotime($val['TestTime'])<time()-900){
+				unset($dataRealTime[$key]);
+				continue;
+			}
+			$val['Result_1']=sprintf('%.1f',$val['Result_1']);
+			$val['Result_2']=sprintf('%.1f',$val['Result_2']);
+			$val['Result_3']=sprintf('%.1f',$val['Result_3']);
+			$val['Result_4']=sprintf('%.1f',$val['Result_4']);
+			$val['Result_5']=sprintf('%.1f',$val['Result_5']);
+			if(!empty($deviceName[$val['DeviceID']]))
+			$val['name']=$deviceName[$val['DeviceID']];
+			if($val['type']==1)//1电流2温度
+				array_push($dianliu,$val);
+			else{
+				array_push($wendu,$val);
+			}
+		}
+		$dataRealTimes=array($wendu,$dianliu);
+		$data=array();
+		foreach ($dataRealTimes as $key=>$v){
+			if(empty($v))
+				continue;
+			$data[$key][0]=array('name'=>'A相','xAxis'=>array_column($v, 'name'),'resData'=>array_column($v, 'Result_1'));
+			$data[$key][1]=array('name'=>'B相','xAxis'=>array_column($v, 'name'),'resData'=>array_column($v, 'Result_2'));
+			$data[$key][2]=array('name'=>'C相','xAxis'=>array_column($v, 'name'),'resData'=>array_column($v, 'Result_3'));
+		}
+		$tem=array('dianliu0'=>0.1,'dianliu1'=>10,'wendu0'=>50,'wendu1'=>100);
+		if(!file_exists(CMF_ROOT . 'public/userConfig.json'))
+			file_put_contents(CMF_ROOT . 'public/userConfig.json',json_encode($tem),LOCK_EX);
+		$userConfig=file_get_contents(CMF_ROOT . 'public/userConfig.json');
+		$userConfig=empty($userConfig) ? array():json_decode($userConfig,true);
+		$this->api_return_json(compact('dataRealTime','data','userConfig'),1);
+	}
+	
+	public function dataDay(){
+		$device_id=$this->request->param('device_id');
+		$sdate=$this->request->param('sdate');
+		$edate=$this->request->param('edate');
+		$collect=empty(file_get_contents(CMF_ROOT . 'public/collect.json')) ? array():json_decode(file_get_contents(CMF_ROOT . 'public/collect.json'),true);
+		if(empty($collect))
+			$collect=array();
+		if(!is_array($collect))
+			$collect=json_decode($collect,true);
+		$elecDeviceId=0;
+		foreach ($collect as $v){
+			if($v['type']==1&&$v['id']!=$device_id)
+				$elecDeviceId=$v['id'];
+		}
+		if(empty($device_id))
+			$this->api_return_json(array('msg'=>'设备参数异常'));
+		if(empty($sdate)||empty($edate))
+			$this->api_return_json(array('msg'=>'时间参数异常'));
+		$type=Db::name('device')->field('DeviceType')->where(array('DeviceId'=>$device_id))->find()['DeviceType'];
+		$where=array(array('DeviceId','=',$device_id),array('TestTime','between',array($sdate,$edate)));
+		$dataDay=Db::name('result_day')
+		->where($where)
+		->order('TestTime asc')
+		->select();
+		foreach ($dataDay as &$val){
+			$val['time']=date('H:i:s',strtotime($val['TestTime']));
+			$val['type']=$type;
+				$val['Result_1']=sprintf('%.1f',$val['Result_1']);
+				$val['Result_2']=sprintf('%.1f',$val['Result_2']);
+				$val['Result_3']=sprintf('%.1f',$val['Result_3']);
+				$val['Result_4']=sprintf('%.1f',$val['Result_4']);
+				$val['Result_5']=sprintf('%.1f',$val['Result_5']);
+		}
+		$data=array();
+		$data['xAxis']=array_column($dataDay, 'time');
+		$data['a']=array_column($dataDay, 'Result_1');
+		$data['b']=array_column($dataDay, 'Result_2');
+		$data['c']=array_column($dataDay, 'Result_3');
+		array_multisort(array_column($dataDay,'ResultID'), SORT_DESC, $dataDay);
+		$elecData=array();
+		if($elecDeviceId){
+			$where=array(array('DeviceId','=',$elecDeviceId),array('TestTime','between',array($sdate,$edate)));
+			$dataDayElec=Db::name('result_day')
+			->where($where)
+			->order('TestTime asc')
+			->select();
+			foreach ($dataDayElec as &$val){
+				$val['time']=date('H:i:s',strtotime($val['TestTime']));
+				$val['type']=1;
+				$val['Result_1']=sprintf('%.1f',$val['Result_1']);
+				$val['Result_2']=sprintf('%.1f',$val['Result_2']);
+				$val['Result_3']=sprintf('%.1f',$val['Result_3']);
+				$val['Result_4']=sprintf('%.1f',$val['Result_4']);
+				$val['Result_5']=sprintf('%.1f',$val['Result_5']);
+			}
+			$elecData['xAxis']=array_column($dataDayElec, 'time');
+			$elecData['a']=array_column($dataDayElec, 'Result_1');
+			$elecData['b']=array_column($dataDayElec, 'Result_2');
+			$elecData['c']=array_column($dataDayElec, 'Result_3');
+		}
+		$tem=array('dianliu0'=>0.1,'dianliu1'=>10,'wendu0'=>50,'wendu1'=>100);
+		if(!file_exists(CMF_ROOT . 'public/userConfig.json'))
+			file_put_contents(CMF_ROOT . 'public/userConfig.json',json_encode($tem),LOCK_EX);
+		$userConfig=file_get_contents(CMF_ROOT . 'public/userConfig.json');
+		$userConfig=empty($userConfig) ? array():json_decode($userConfig,true);
+		$this->api_return_json(compact('dataDay','data','type','elecData','userConfig'),1);
+	}
+	
+	public function dataDevice(){
+		/* $num=$this->request->param('num',1);
+		if(!in_array($num,array(1,2,3)))
+			$this->api_return_json(array('msg'=>'参数异常')); */
+		$item=array(1,2,3);
+		$itemName=array('1'=>'A相','2'=>'B相','3'=>'C相');
+		$collect=empty(file_get_contents(CMF_ROOT . 'public/collect.json')) ? array():json_decode(file_get_contents(CMF_ROOT . 'public/collect.json'),true);
+		if(empty($collect))
+			$collect=array();
+		if(!is_array($collect))
+			$collect=json_decode($collect,true);
+		$deviceId=empty($collect) ? array() :array_column($collect, 'id');
+		$deviceName=empty($collect) ? array() :array_column($collect, 'name','id');
+		$deviceType=empty($collect) ? array() :array_column($collect, 'type','id');
+		$xTime=array();
+		$yDevice=array();
+		$dealdata=array();
+		$maxCount=array();
+		$dataDevice=array();
+			foreach ($deviceId as $k=>$v){
+				$where=array(array('DeviceId','=',$v),array('TestTime','between',array(date('Y-m-d'.' 00:00:00'),date('Y-m-d H:i:s'))));
+				$dataDay=Db::name('result_day')
+				->field('Result_1,Result_2,Result_3,Result_5,TestTime')
+				->where($where)
+				->limit(200)
+				->order('TestTime desc')
+				->select();
+				if(empty($dataDay))
+					continue;
+				$itStatus=$this->resReturnStatus($deviceType[$v],array($dataDay[0]['Result_1'],$dataDay[0]['Result_2'],$dataDay[0]['Result_3']));
+				$vStatus=$dataDay[0]['Result_5']<=6.5 ? 1 : 0;
+				$dataTem=array('name'=>$deviceName[$v],'vStatus'=>$vStatus,'itStatus'=>$itStatus,'id'=>$v,'type'=>$deviceType[$v]);
+				array_push($dataDevice,$dataTem);
+				
+				foreach ($item as $i=>$n){
+					array_push($yDevice,$deviceName[$v].$itemName[$n]);
+					
+					$tem=array();
+					foreach ($dataDay as $key=>&$val){
+						if($n==1)
+						$val['TestTime']=strtotime($val['TestTime']);
+						$zVal=sprintf('%.1f',($val['Result_'.$n]));
+						array_push($tem,array(count($dataDay)<200 ? count($dataDay)-1-$key:199-$key,count($yDevice)-1,$zVal));
+					}
+					if($n==1)
+					if(max(array_column($dataDay,'TestTime'))>=(empty($maxCount) ?0 :max(array_column($maxCount,'TestTime')))){
+						$maxCount=$dataDay;
+					}
+					array_push($dealdata,$tem);
+				}
+			}
+			foreach ($maxCount as $vmax){
+				array_unshift($xTime,date('H:i',$vmax['TestTime']));
+			}
+			$lineWidth=5;
+		$this->api_return_json(compact('xTime','yDevice','dealdata','dataDevice','lineWidth'),1);
+	}
+	
+	public function resReturnStatus($type,$data){
+		$tem=array('dianliu0'=>0.1,'dianliu1'=>10,'wendu0'=>50,'wendu1'=>100);
+		if(!file_exists(CMF_ROOT . 'public/userConfig.json')||empty(file_get_contents(CMF_ROOT . 'public/userConfig.json')))
+			file_put_contents(CMF_ROOT . 'public/userConfig.json',json_encode($tem),LOCK_EX);
+		$userConfig=file_get_contents(CMF_ROOT . 'public/userConfig.json');
+		$userConfig=empty($userConfig) ? $tem:json_decode($userConfig,true);
+		if($type==1){
+			//电流设备判断
+			$temData=array();
+			foreach($data as $d){
+				if($d>=$userConfig['dianliu1'])
+					array_push($temData,2);
+				elseif($d>=$userConfig['dianliu0'])
+					array_push($temData,1);
+				else 
+					array_push($temData,0);
+			}
+			return $temData;
+		}else{
+			//温度设备判断
+			$temData=array();
+			foreach($data as $d){
+				if($d>=$userConfig['wendu1'])
+					array_push($temData,2);
+				elseif($d>=$userConfig['wendu0'])
+					array_push($temData,1);
+				else 
+					array_push($temData,0);
+			}
+			return $temData;
+		}
+	}
+	
+	public function dataHistory(){
+		
+		$device_id=$this->request->param('device_id');
+		$sdate=$this->request->param('sdate');
+		$edate=$this->request->param('edate');
+		$collect=empty(file_get_contents(CMF_ROOT . 'public/collect.json')) ? array():json_decode(file_get_contents(CMF_ROOT . 'public/collect.json'),true);
+		if(empty($collect))
+			$collect=array();
+		if(!is_array($collect))
+			$collect=json_decode($collect,true);
+		$elecDeviceId=0;
+		foreach ($collect as $v){
+			if($v['type']==1&&$v['id']!=$device_id)
+				$elecDeviceId=$v['id'];
+		}
+		if(empty($device_id))
+			$this->api_return_json(array('msg'=>'设备参数异常'));
+		if(empty($sdate)||empty($edate))
+			$this->api_return_json(array('msg'=>'时间参数异常'));
+		$type=Db::name('device')->field('DeviceType')->where(array('DeviceId'=>$device_id))->find()['DeviceType'];
+		$where=array(array('Device_Id','=',$device_id),array('TestTime_Day','=',date('Y-m-d',strtotime($sdate))));
+		$dataHistorySql=Db::name('result_list')
+		->where($where)
+		->find();
+		$dataHistory=array();
+		$data=array();
+		if(!empty($dataHistorySql)){
+			$dataHistorySql['type']=$type;
+			$dataHistorySql['Result_1']=Varbinary::byteArrayTofloatArray(($dataHistorySql['Result_1']));
+			$dataHistorySql['Result_2']=Varbinary::byteArrayTofloatArray(($dataHistorySql['Result_2']));
+			$dataHistorySql['Result_3']=Varbinary::byteArrayTofloatArray(($dataHistorySql['Result_3']));
+			$dataHistorySql['Result_4']=Varbinary::byteArrayTofloatArray(($dataHistorySql['Result_4']));
+			$dataHistorySql['Result_5']=Varbinary::byteArrayTofloatArray(($dataHistorySql['Result_5']));
+			$dataHistorySql['TestTime']=Varbinary::byteArrayTodatetimeArray(($dataHistorySql['TestTime']));
+			foreach ($dataHistorySql['TestTime'] as $key=>$val){
+				if($val<$sdate||$val>$edate)
+					continue;
+				$dataHistoryTmp['Result_1']=$dataHistorySql['Result_1'][$key];
+				$dataHistoryTmp['Result_2']=$dataHistorySql['Result_2'][$key];
+				$dataHistoryTmp['Result_3']=$dataHistorySql['Result_3'][$key];
+				$dataHistoryTmp['Result_4']=$dataHistorySql['Result_4'][$key];;
+				$dataHistoryTmp['Result_5']=$dataHistorySql['Result_5'][$key];;
+				$dataHistoryTmp['time']=$val;
+				$dataHistoryTmp['type']=$type;
+					$dataHistoryTmp['Result_1']=sprintf('%.1f',$dataHistoryTmp['Result_1']);
+					$dataHistoryTmp['Result_2']=sprintf('%.1f',$dataHistoryTmp['Result_2']);
+					$dataHistoryTmp['Result_3']=sprintf('%.1f',$dataHistoryTmp['Result_3']);
+					$dataHistoryTmp['Result_4']=sprintf('%.1f',$dataHistoryTmp['Result_4']);
+					$dataHistoryTmp['Result_5']=sprintf('%.1f',$dataHistoryTmp['Result_5']);
+				$dataHistory[]=$dataHistoryTmp;
+			}
+		$data['xAxis']=array_column($dataHistory, 'time');
+		$data['a']=array_column($dataHistory, 'Result_1');
+		$data['b']=array_column($dataHistory, 'Result_2');
+		$data['c']=array_column($dataHistory, 'Result_3');
+		}
+		array_multisort(array_column($dataHistory,'time'), SORT_DESC, $dataHistory);
+		$elecData=array();
+		if($elecDeviceId){
+			$dataHistoryElec=array();
+			$where=array(array('Device_Id','=',$elecDeviceId),array('TestTime_Day','=',date('Y-m-d',strtotime($sdate))));
+			$dataHistorySqlElec=Db::name('result_list')
+			->where($where)
+			->find();
+			if(!empty($dataHistorySqlElec)){
+				$dataHistorySqlElec['type']=1;
+				$dataHistorySqlElec['Result_1']=Varbinary::byteArrayTofloatArray(($dataHistorySqlElec['Result_1']));
+				$dataHistorySqlElec['Result_2']=Varbinary::byteArrayTofloatArray(($dataHistorySqlElec['Result_2']));
+				$dataHistorySqlElec['Result_3']=Varbinary::byteArrayTofloatArray(($dataHistorySqlElec['Result_3']));
+				$dataHistorySqlElec['Result_4']=Varbinary::byteArrayTofloatArray(($dataHistorySqlElec['Result_4']));
+				$dataHistorySqlElec['Result_5']=Varbinary::byteArrayTofloatArray(($dataHistorySqlElec['Result_5']));
+				$dataHistorySqlElec['TestTime']=Varbinary::byteArrayTodatetimeArray(($dataHistorySqlElec['TestTime']));
+				foreach ($dataHistorySqlElec['TestTime'] as $key=>$val){
+					if($val<$sdate||$val>$edate)
+						continue;
+					$dataHistoryTmpElec['Result_1']=$dataHistorySqlElec['Result_1'][$key];
+					$dataHistoryTmpElec['Result_2']=$dataHistorySqlElec['Result_2'][$key];
+					$dataHistoryTmpElec['Result_3']=$dataHistorySqlElec['Result_3'][$key];
+					$dataHistoryTmpElec['Result_4']=$dataHistorySqlElec['Result_4'][$key];;
+					$dataHistoryTmpElec['Result_5']=$dataHistorySqlElec['Result_5'][$key];;
+					$dataHistoryTmpElec['time']=$val;
+					$dataHistoryTmpElec['type']=$type;
+					$dataHistoryTmpElec['Result_1']=sprintf('%.1f',$dataHistoryTmpElec['Result_1']);
+					$dataHistoryTmpElec['Result_2']=sprintf('%.1f',$dataHistoryTmpElec['Result_2']);
+					$dataHistoryTmpElec['Result_3']=sprintf('%.1f',$dataHistoryTmpElec['Result_3']);
+					$dataHistoryTmpElec['Result_4']=sprintf('%.1f',$dataHistoryTmpElec['Result_4']);
+					$dataHistoryTmpElec['Result_5']=sprintf('%.1f',$dataHistoryTmpElec['Result_5']);
+					$dataHistoryElec[]=$dataHistoryTmpElec;
+				}
+				$elecData['xAxis']=array_column($dataHistoryElec, 'time');
+				$elecData['a']=array_column($dataHistoryElec, 'Result_1');
+				$elecData['b']=array_column($dataHistoryElec, 'Result_2');
+				$elecData['c']=array_column($dataHistoryElec, 'Result_3');
+			}
+		}
+		$tem=array('dianliu0'=>0.1,'dianliu1'=>10,'wendu0'=>50,'wendu1'=>100);
+		if(!file_exists(CMF_ROOT . 'public/userConfig.json'))
+			file_put_contents(CMF_ROOT . 'public/userConfig.json',json_encode($tem),LOCK_EX);
+		$userConfig=file_get_contents(CMF_ROOT . 'public/userConfig.json');
+		$userConfig=empty($userConfig) ? array():json_decode($userConfig,true);
+		$this->api_return_json(compact('dataHistory','data','type','elecData','userConfig'),1);
+	}
+	
+	public function exportDay(){
+		$device_id=$this->request->param('device_id');
+		$sdate=$this->request->param('sdate');
+		$edate=$this->request->param('edate');
+		if(empty($device_id))
+			$this->api_return_json(array('msg'=>'设备参数异常'));
+		if(empty($sdate)||empty($edate))
+			$this->api_return_json(array('msg'=>'时间参数异常'));
+		$type=Db::name('device')->field('DeviceType')->where(array('DeviceId'=>$device_id))->find()['DeviceType'];
+		$where=array(array('DeviceId','=',$device_id),array('TestTime','between',array($sdate,$edate)));
+		$dataDay=Db::name('result_day')
+		->where($where)
+		->order('TestTime asc')
+		->select();
+		foreach ($dataDay as &$val){
+			$val['time']=date('H:i:s',strtotime($val['TestTime']));
+			$val['type']=$type;
+			$val['Result_1']=sprintf('%.1f',$val['Result_1']).($type==1 ? 'A':'℃');
+			$val['Result_2']=sprintf('%.1f',$val['Result_2']).($type==1 ? 'A':'℃');
+			$val['Result_3']=sprintf('%.1f',$val['Result_3']).($type==1 ? 'A':'℃');
+			$val['Result_4']=sprintf('%.1f',$val['Result_4']).($type==1 ? 'V':'℃');
+			$val['Result_5']=sprintf('%.1f',$val['Result_5']).'V';
+		}
+		array_multisort(array_column($dataDay,'ResultID'), SORT_DESC, $dataDay);
+		$collect=empty(file_get_contents(CMF_ROOT . 'public/collect.json')) ? array():json_decode(file_get_contents(CMF_ROOT . 'public/collect.json'),true);
+		if(empty($collect))
+			$collect=array();
+		if(!is_array($collect))
+			$collect=json_decode($collect,true);
+		$name='当日数据';
+		foreach ($collect as $v){
+			if($v['id']==$device_id)
+				$name=$v['name'].$name;
+		}
+		$file=$this->export($dataDay,$name,$type);
+		$this->api_return_json(array('path'=>'excel/'.$file,'file_name'=>$file),1);
+	}
+	
+	public function exportHistory(){
+		$device_id=$this->request->param('device_id');
+		$sdate=$this->request->param('sdate');
+		$edate=$this->request->param('edate');
+		if(empty($device_id))
+			$this->api_return_json(array('msg'=>'设备参数异常'));
+		if(empty($sdate)||empty($edate))
+			$this->api_return_json(array('msg'=>'时间参数异常'));
+		$type=Db::name('device')->field('DeviceType')->where(array('DeviceId'=>$device_id))->find()['DeviceType'];
+		$where=array(array('Device_Id','=',$device_id),array('TestTime_Day','=',date('Y-m-d',strtotime($sdate))));
+		$dataHistorySql=Db::name('result_list')
+		->where($where)
+		->find();
+		$dataHistory=array();
+		if(empty($dataHistorySql)){
+			$this->api_return_json(array('msg'=>'历史数据异常'));
+		}
+			$dataHistorySql['type']=$type;
+			$dataHistorySql['Result_1']=Varbinary::byteArrayTofloatArray(($dataHistorySql['Result_1']));
+			$dataHistorySql['Result_2']=Varbinary::byteArrayTofloatArray(($dataHistorySql['Result_2']));
+			$dataHistorySql['Result_3']=Varbinary::byteArrayTofloatArray(($dataHistorySql['Result_3']));
+			$dataHistorySql['Result_4']=Varbinary::byteArrayTofloatArray(($dataHistorySql['Result_4']));
+			$dataHistorySql['Result_5']=Varbinary::byteArrayTofloatArray(($dataHistorySql['Result_5']));
+			$dataHistorySql['TestTime']=Varbinary::byteArrayTodatetimeArray(($dataHistorySql['TestTime']));
+			foreach ($dataHistorySql['TestTime'] as $key=>$val){
+				if($val<$sdate||$val>$edate)
+					continue;
+				$dataHistoryTmp['Result_1']=$dataHistorySql['Result_1'][$key];
+				$dataHistoryTmp['Result_2']=$dataHistorySql['Result_2'][$key];
+				$dataHistoryTmp['Result_3']=$dataHistorySql['Result_3'][$key];
+				$dataHistoryTmp['Result_4']=$dataHistorySql['Result_4'][$key];;
+				$dataHistoryTmp['Result_5']=$dataHistorySql['Result_5'][$key];;
+				$dataHistoryTmp['time']=$val;
+				$dataHistoryTmp['type']=$type;
+				$dataHistoryTmp['Result_1']=sprintf('%.1f',$dataHistoryTmp['Result_1']).($type==1 ? 'A':'℃');
+				$dataHistoryTmp['Result_2']=sprintf('%.1f',$dataHistoryTmp['Result_2']).($type==1 ? 'A':'℃');
+				$dataHistoryTmp['Result_3']=sprintf('%.1f',$dataHistoryTmp['Result_3']).($type==1 ? 'A':'℃');
+				$dataHistoryTmp['Result_4']=sprintf('%.1f',$dataHistoryTmp['Result_4']).($type==1 ? 'V':'℃');
+				$dataHistoryTmp['Result_5']=sprintf('%.1f',$dataHistoryTmp['Result_5']).'V';
+				$dataHistory[]=$dataHistoryTmp;
+			}
+
+		array_multisort(array_column($dataHistory,'time'), SORT_DESC, $dataHistory);
+		$collect=empty(file_get_contents(CMF_ROOT . 'public/collect.json')) ? array():json_decode(file_get_contents(CMF_ROOT . 'public/collect.json'),true);
+		if(empty($collect))
+			$collect=array();
+		if(!is_array($collect))
+			$collect=json_decode($collect,true);
+		$name='历史数据';
+		foreach ($collect as $v){
+			if($v['id']==$device_id)
+				$name=$v['name'].$name;
+		}
+		$file=$this->export($dataHistory,$name,$type);
+		$this->api_return_json(array('path'=>'excel/'.$file,'file_name'=>$file),1);
+	}
+	
+	//导出数据
+	public function export($result,$name,$type){//导出Excel
+		set_time_limit(0);
+		$xlsName  = $name;
+		$xlsCell  = array(
+				array('time','时间','20'),//字段,名称,样式,图片
+				array('Result_1','A相'.($type==1 ? '电流':'温度'),'20'),
+				array('Result_2','B相'.($type==1 ? '电流':'温度'),'20'),
+				array('Result_3','C相'.($type==1 ? '电流':'温度'),'20'),
+				array('Result_4',$type==1 ? '充电电压':'地表温度','20'),
+				array('Result_5','电池电压','20')
+	
+		);
+		$file=$this->exportExcel($xlsName,$xlsCell,$result);
+		return $file;
+	}
+	
+	/**
+	 * @param $expTitle 名称
+	 * @param $expCellName 参数
+	 * @param $expTableData 内容
+	 * @throws \PHPExcel_Exception
+	 * @throws \PHPExcel_Reader_Exception
+	 */
+	public function exportExcel($expTitle,$expCellName,$expTableData){
+		set_time_limit(0);
+		$xlsTitle = $expTitle;//iconv('utf-8', 'gb2312', $expTitle);//文件名称
+		$fileName = date('YmdHis');//or $xlsTitle 文件名称可根据自己情况设定
+		$cellNum = count($expCellName);
+		$dataNum = count($expTableData);
+		 
+		$objPHPExcel = new  Spreadsheet();
+		$styleArray = [
+	
+		'borders' => [
+	
+		'allBorders' => [
+	
+		'borderStyle' => Border::BORDER_THIN //细边框
+	
+		]
+	
+		]
+	
+		];
+		$cellName = array('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
+		$cellName=array_slice($cellName, 0,$cellNum);
+		foreach ($cellName as $val){
+			//垂直左右居中
+			$objPHPExcel->setActiveSheetIndex(0)->getStyle($val)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
+			$objPHPExcel->getActiveSheet()->getStyle($val)->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
+			$objPHPExcel->getActiveSheet()->getStyle($val)->getAlignment()->setWrapText(true);
+		}
+		 
+		//        $objPHPExcel->getActiveSheet(0)->mergeCells('A1:'.$cellName[$cellNum-1].'1');//合并单元格
+		//         $objPHPExcel->setActiveSheetIndex(0)->setCellValue('A1', $expTitle.'  Export time:'.date('Y-m-d H:i:s'));
+		for($i=0;$i<$cellNum;$i++){
+			$objPHPExcel->setActiveSheetIndex(0)->setCellValue($cellName[$i].'1', $expCellName[$i][1]);
+			if(!empty($expCellName[$i][2]))//表示有样式
+				$objPHPExcel->getActiveSheet()->getColumnDimension($cellName[$i])->setWidth($expCellName[$i][2]);
+		}
+		//var_dump($expTableData);exit;
+		// Miscellaneous glyphs, UTF-8
+		for($i=0;$i<$dataNum;$i++){
+			for($j=0;$j<$cellNum;$j++){
+				$objPHPExcel->getActiveSheet(0)->setCellValue($cellName[$j].($i+2), $expTableData[$i][$expCellName[$j][0]]);
+			}
+		}
+		$objPHPExcel->getActiveSheet()->getStyle('A1:'.$cellName[$cellNum-1].'1')->applyFromArray($styleArray);
+		$objWriter=new Xlsx($objPHPExcel);
+		$file=$xlsTitle.'.xlsx';
+		$objWriter->save(CMF_ROOT . 'public/upload/excel/'.$file);
+		return $file;
+	}
+	
+	
+	
+	public function checkTime($ar){
+		$current=(int)date('H')*60+(int)date('i');
+		if(count($ar)!=4)
+			return false;
+		$ar_begin=explode(':',$ar[0]);
+		$ar_end=explode(':',$ar[1]);
+		$b=intval($ar_begin[0])*60+intval($ar_begin[1]);
+		$e=intval($ar_end[0])*60+intval($ar_end[1]);
+	
+		$ar_begin_1=explode(':',$ar[2]);
+		$ar_end_1=explode(':',$ar[3]);
+		$b_1=intval($ar_begin_1[0])*60+intval($ar_begin_1[1]);
+		$e_1=intval($ar_end_1[0])*60+intval($ar_end_1[1]);
+	
+		if($current>=$b&&$current<=$e)
+			return true;
+		else if($current>=$b_1&&$current<=$e_1)
+			return true;
+		return false;
+	}
+	
+	/**
+	 * 视频时间处理时分秒
+	 */
+	protected static function videoTimeDeal($time){
+		if($time<=60)
+			return $time.'秒';
+		elseif($time<3600)
+			return intval($time/60).'分'.$time%60 .'秒';
+		else
+			return intval($time/3600).'时'.intval(($time%3600)/60).'分'.($time%3600)%60 .'秒';
+	}
+	
+	public function shouzimu(){
+		echo $this->Getzimu($this->request->param('str'));
+	}
+	
+	public function Getzimu($str)
+	{
+		if(empty($str)){return '';}
+	
+		$fchar=ord($str{0});
+	
+		if($fchar>=ord('A')&&$fchar<=ord('z')) return strtoupper($str{0});
+	
+		$s1=iconv('UTF-8','gb2312',$str);
+	
+		$s2=iconv('gb2312','UTF-8',$s1);
+	
+		$s=$s2==$str?$s1:$str;
+	
+		$asc=ord($s{0})*256+ord($s{1})-65536;
+	
+		if($asc>=-20319&&$asc<=-20284) return 'A';
+	
+		if($asc>=-20283&&$asc<=-19776) return 'B';
+	
+		if($asc>=-19775&&$asc<=-19219) return 'C';
+	
+		if($asc>=-19218&&$asc<=-18711) return 'D';
+	
+		if($asc>=-18710&&$asc<=-18527) return 'E';
+	
+		if($asc>=-18526&&$asc<=-18240) return 'F';
+	
+		if($asc>=-18239&&$asc<=-17923) return 'G';
+	
+		if($asc>=-17922&&$asc<=-17418) return 'H';
+	
+		if($asc>=-17417&&$asc<=-16475) return 'J';
+	
+		if($asc>=-16474&&$asc<=-16213) return 'K';
+	
+		if($asc>=-16212&&$asc<=-15641) return 'L';
+	
+		if($asc>=-15640&&$asc<=-15166) return 'M';
+	
+		if($asc>=-15165&&$asc<=-14923) return 'N';
+	
+		if($asc>=-14922&&$asc<=-14915) return 'O';
+	
+		if($asc>=-14914&&$asc<=-14631) return 'P';
+	
+		if($asc>=-14630&&$asc<=-14150) return 'Q';
+	
+		if($asc>=-14149&&$asc<=-14091) return 'R';
+	
+		if($asc>=-14090&&$asc<=-13319) return 'S';
+	
+		if($asc>=-13318&&$asc<=-12839) return 'T';
+	
+		if($asc>=-12838&&$asc<=-12557) return 'W';
+	
+		if($asc>=-12556&&$asc<=-11848) return 'X';
+	
+		if($asc>=-11847&&$asc<=-11056) return 'Y';
+	
+		if($asc>=-11055&&$asc<=-10247) return 'Z';
+	
+		return "#";
+	}
+}
+

+ 530 - 0
application/web/controller/MainController.php

@@ -0,0 +1,530 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2018 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\web\controller;
+
+use think\Db;
+use app\common\plugin\Jwt;
+use app\common\plugin\Jssdk;
+use think\Controller;
+class MainController extends Controller
+{
+
+    public $userid = 0;
+    public $is_product = true;
+    protected $token;
+    protected $major;
+    protected $ip;
+    public $access_token;
+    protected $redis;
+
+    function __construct()
+    {
+        parent::__construct();
+        $token=request()->request('sid');
+        $major=request()->request('major');
+//        $redis=new \Redis();
+//        $redis->connect(config("config.redis.REDIS_HOST"),config("config.redis.REDIS_PORT"));
+//        $this->redis=$redis;
+
+
+        $ip=request()->ip();
+        $this->ip=$ip;
+        $this->token=$token;
+        $this->major='aaa';
+        $s = explode('\\', get_class($this));
+        $module=request()->module();
+        $controller = request()->controller();//控制器名
+        $action=request()->action();
+        $token='lyuis';
+        if($token=='lyuis'){
+        	$this->userid=5;
+        	return;
+        }
+
+        if (end($s) != 'ArController'&& end($s) != 'NotifyController'&& end($s) != 'LoginController' && $module!="admin"&&$action!='login'&&$action!='datarealtime'&&$action!='home' ) {
+            if(empty($token)){
+                $this->api_return_json(['msg'=>'请先登录。']);
+            }
+            $result=Jwt::verify($token);
+//            dump($token);
+//            dump($result);
+            if($result['code']!=1){
+                $this->api_return_json(['msg'=>$result['message']]);
+            }
+            $data=(array)$result['message']['data'];
+            if($data['major']!=$this->major){
+                $this->api_return_json(['msg'=>'登录过期']);
+            }
+            /* $params=$this->request->param();
+            unset($params['sign']);
+            if(!$this->checkSign($this->request->param('sign'),$params)){
+            	$this->api_return_json(array('msg'=>'签名错误'));
+            }
+            if(((int)$params['time']+60)<time()){
+            	$this->api_return_json(array('msg'=>'签名过期'));
+            } */
+            $this->userid=$data['userid'];
+        };
+
+
+
+
+//        if (empty($sid)){
+//
+//        }
+//
+//        session_id($sid);
+//        if(!isset($_SESSION)){
+//            session_start();
+//        }
+        date_default_timezone_set('Asia/Shanghai');
+//        if (isset($_SESSION['user_id'])) {
+//            $this->userid = $_SESSION['user_id'];
+//        }
+
+
+
+    }
+
+    /**
+     * @param unknown_type $return_array
+     * @param unknown_type $info_code 0代表失败 1代表成功 2代表其他
+     */
+    function api_return_json($return_array, $info_code = '0')
+    {
+        $callback = isset($_GET['callback']) ? trim($_GET['callback']) : '';
+        $return_array['code'] = $info_code;
+        if (!empty($callback)) {
+            echo $callback . '(' . json_encode($return_array,JSON_UNESCAPED_UNICODE) . ')';
+            exit();
+        } else {
+            echo json_encode($return_array,JSON_UNESCAPED_UNICODE);
+            exit();
+        }
+    }
+
+   	function curl_get($url)
+   	{
+       $ch = curl_init();
+       // 设置选项,包括URL
+       curl_setopt($ch, CURLOPT_URL, $url);
+       curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+       curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+       curl_setopt($ch, CURLOPT_HEADER, 0);
+
+       if (stripos($url, "https://") !== FALSE) {
+           curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
+           curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
+           curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
+       } else {
+           curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, TRUE);
+           curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);//严格校验
+       }
+       // 执行并获取HTML文档内容
+       $output = curl_exec($ch);
+       // 释放curl句柄
+       $err_code = curl_errno($ch);
+
+       curl_close($ch);
+
+       return json_decode($output);
+   	}
+
+    function getStartDay()
+    {
+        return strtotime(date('Y-m-d') . ' 00:00:00');
+    }
+
+    function getEndDay()
+    {
+        return strtotime(date('Y-m-d') . ' 23:59:59');
+    }
+    
+    function getWeek($index)
+    {
+    	$sdefaultDate = date("Y-m-d");
+    	//$first =1 表示每周星期一为开始日期 0表示每周日为开始日期
+    	$first=1;
+    	//获取当前周的第几天 周日是 0 周一到周六是 1 - 6
+    	$w=date('w',strtotime($sdefaultDate));
+    	//获取本周开始日期,如果$w是0,则表示周日,减去 6 天
+    	$week_start=date('Y-m-d',strtotime("$sdefaultDate -".($w ? $w - $first : 6).' days'));
+    	//本周结束日期
+    	$week_end=date('Y-m-d',strtotime("$week_start +6 days"));
+    	$week_start=strtotime($week_start . ' 00:00:00');
+    	$week_end=strtotime($week_end . ' 23:59:59');
+    	$tem=array($week_start,$week_end);
+    	return $tem[$index];
+    }
+    
+    function getFirstDateMonth($month='',$year=''){
+    	if(empty($month)){
+    		return strtotime(date('Y-m').'-01 00:00:00');
+    	}elseif(empty($year)){
+    		return strtotime(date('Y-'.$month).'-01 00:00:00');
+    	}else{
+    		return strtotime(date($year.'-'.$month).'-01 00:00:00');
+    	}
+    }
+    
+    function getEndDateMonth($month='',$year=''){
+    	if(empty($month)){
+    		$BeginDate=date('Y-m-01', strtotime(date("Y-m-d")));
+    		$date=date('Y-m-d', strtotime("$BeginDate +1 month -1 day")).' 23:59:59';
+    		return strtotime($date);
+    	}elseif(empty($year)){
+    		$BeginDate=date('Y-m-01', strtotime(date("Y-".$month."-d")));
+    		$date=date('Y-m-d', strtotime("$BeginDate +1 month -1 day")).' 23:59:59';
+    		return strtotime($date);
+    	}else{
+    		$BeginDate=date('Y-m-01', strtotime(date($year."-".$month."-d")));
+    		$date=date('Y-m-d', strtotime("$BeginDate +1 month -1 day")).' 23:59:59';
+    		return strtotime($date);
+    	}
+    }
+
+    //判断两个日期是否相连
+    public function isNextDay($day1, $day2)
+    {
+        $date1 = date('Y-m-d', $day1);
+        $date2 = date('Y-m-d', $day2);
+        $is_next = (strtotime($date1 . ' 00:00:00') - strtotime($date2 . ' 00:00:00')) / 86400;
+        return $is_next == 1;
+    }
+
+    public function getSignCount()
+    {
+        $days = Db::name('sign')->where(array('user_id' => $this->userid))->order('date desc')->limit(0, 30)->select()->toArray();
+        $count = 0;
+        if (sizeof($days) > 0)
+            $count = 1;
+        for ($i = 0; $i < sizeof($days) - 1; $i++) {
+            if ($i == 0) {
+                $yesterday = date('Y-m-d', strtotime("-1 day"));
+                $today = date('Y-m-d');
+                $sign_date = date('Y-m-d', $days[$i]['date']);
+                if ($yesterday != $sign_date && $sign_date != $today) {
+                    $count = 0;
+                    break;
+                }
+            }
+            if ($this->isNextDay($days[$i]['date'], $days[$i + 1]['date'])) {
+                $count++;
+            } else {
+                break;
+            }
+        }
+        return $count;
+    }
+
+    function curl_post($url, $data)
+    {
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_POST, 1);
+        curl_setopt($ch, CURLOPT_HEADER, 0);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+        $return = curl_exec($ch);
+        curl_close($ch);
+        return $return;
+    }
+
+
+
+
+
+    //签名验证
+    //将前端传参sign与除了sign以外的参数后台拼接成sign进行对比验证
+    public function checkSign($sign, $params)
+    {
+        ksort($params);
+        //var_dump($params);
+        $param_str = '';
+        $i = 0;
+        foreach ($params as $key => $param) {
+        	if(!$this->haveEmojiChar($param)){
+	            if ($i == 0) {
+	                $param_str .= $key . '=' . $param;
+	            } else {
+	                $param_str .= "&" . $key . '=' . $param;
+	            }
+        	}
+            $i++;
+        }
+        $signBackend = strtoupper(md5($param_str . '&key=' . md5($_SERVER['SERVER_NAME'])));
+        file_put_contents(CMF_ROOT . 'data/sign.txt', "前端签名:" . $sign . ",后台原始数据," . $param_str . "签名:" . $signBackend . "\n生成签名前的参数:".$param_str . '&key=' . config()['config']['wxconfig']['secret']);
+        if ($sign !== $signBackend) {
+            return false;
+        } else {
+            return true;
+        }
+
+    }
+    //判断是否有表情
+    function haveEmojiChar($str)
+    {
+    	$mbLen = mb_strlen($str);
+    
+    	$strArr = [];
+    	for ($i = 0; $i < $mbLen; $i++) {
+    		$strArr[] = mb_substr($str, $i, 1, 'utf-8');
+    		if (strlen($strArr[$i]) >= 4) {
+    			return true;
+    		}
+    	}
+    
+    	return false;
+    }
+
+    /**
+     * 调试输出函数
+     * @param mix $val 调试输出源数据
+     * @param bool $dump 是否启用var_dump调试
+     * @param bool $exit 是否在调试结束后设置断点
+     * @return void
+     */
+
+    public function pre($val, $dump = true, $exit = true)
+    {
+        if ($dump) {
+            $func = 'var_dump';
+        } else {
+            $func = (is_array($val) || is_object($val)) ? 'print_r' : 'printf';
+        }
+        //输出到html
+        header("Content-type:text/html;charset=utf-8");
+        echo '<pre>debug output:<hr />';
+        $func($val);
+        echo '</pre>';
+        if ($exit) {
+            exit;
+        }
+    }
+
+    /**
+     *计算某个经纬度的周围某段距离的正方形的四个点
+     *
+     * @param lng float 经度
+     * @param lat float 纬度
+     * @param 6371 地球半径
+     * @param distance float 该点所在圆的半径,该圆与此正方形内切,默认值为0.5千米
+     * @return array 正方形的四个点的经纬度坐标
+     */
+   public function returnSquarePoint($lng, $lat, $distance = 5)
+    {
+
+        $dlng = 2 * asin(sin($distance / (2 * 6371)) / cos(deg2rad($lat)));
+        $dlng = rad2deg($dlng);
+
+        $dlat = $distance / 6371;
+        $dlat = rad2deg($dlat);
+
+        return array(
+            'left-top' => array('lat' => $lat + $dlat, 'lng' => $lng - $dlng),
+            'right-top' => array('lat' => $lat + $dlat, 'lng' => $lng + $dlng),
+            'left-bottom' => array('lat' => $lat - $dlat, 'lng' => $lng - $dlng),
+            'right-bottom' => array('lat' => $lat - $dlat, 'lng' => $lng + $dlng)
+        );
+    }
+    /**
+     * 根据两点间的经纬度计算距离
+     * @param $lat1 //纬度值
+     * @param $lng1 //经度值
+     * @param $lat2
+     * @param $lng2
+     * @return float 单位为(km)
+     */
+    public function getDistance($lat1, $lng1, $lat2, $lng2)
+    {
+        //近似地球半径(米)
+        $earthRadius = 6367000;
+
+        //纬度转换
+        $lat1 = ($lat1 * pi()) / 180;
+        $lng1 = ($lng1 * pi()) / 180;
+
+        //经度转换
+        $lat2 = ($lat2 * pi()) / 180;
+        $lng2 = ($lng2 * pi()) / 180;
+
+        //计算两点间距离返回(km)
+        $differ_lng = $lng2 - $lng1;
+        $differ_lat = $lat2 - $lat1;
+        $stepOne = pow(sin($differ_lat / 2), 2) + cos($lat1) * cos($lat2) * pow(sin($differ_lng / 2), 2);
+        $stepTwo = 2 * asin(min(1, sqrt($stepOne)));
+
+        $distance = $earthRadius * $stepTwo / 1000;
+        return round($distance,2);
+    }
+
+
+    /**
+     * 拼团服务通知
+     */
+    public function noticeOrder($val,$p){
+
+    }
+    //发送统一服务消息
+    public function sendNotice($data){
+        if(empty($data))
+            return false;
+        $access_token=$this->getAccess_token();
+        if(!$access_token)
+            return false;
+        $result=$this->curl_post('https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token='.$access_token,$data);
+        return $result;
+    }
+
+
+    function getAccess_token($refresh = false){
+    	$wxconfig=config()['config']['wxconfig'];
+    	$appid =$wxconfig['appid'];
+    	$appsecret =$wxconfig['secret'];
+    	//$appid='wxa334f8548511450e';
+    	//$appsecret='ba5bd1263065929f8831ce2cf8baf5b8';
+    	$path="small_access_token.json";
+    	if(!file_exists($path)){
+    		$datas['expire_time'] = 0;
+    		$datas['access_token'] = '';
+    		file_put_contents($path, json_encode($datas));
+    		chmod($path, 0777);
+    	}
+    	$data = json_decode(file_get_contents($path));
+    	if ($data->expire_time < time() || $refresh) {
+    		$url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid='.$appid.'&corpsecret='.$appsecret;
+    		$result = $this->curl_get($url);
+    		$access_token = $result->access_token;
+    
+    		if (!$access_token) {
+    			return false;
+    		} else {
+    			$datas['expire_time'] = time() + 7000;
+    			$datas['access_token'] = $access_token;
+    			$fp = fopen($path, "w");
+    			fwrite($fp, json_encode($datas));
+    			fclose($fp);
+    
+    			$this->access_token = $access_token;
+    		}
+    	} else if (!$this->access_token){
+    		$this->access_token = $data->access_token;
+    	}
+    	return $this->access_token;
+    }
+    
+    public function FromXml($xml)
+    {
+    	if(!$xml){
+    		throw new \Exception("xml数据异常!");
+    	}
+    	//将XML转为array
+    	//禁止引用外部xml实体
+    	libxml_disable_entity_loader(true);
+    	$this->values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
+    	return $this->values;
+    }
+    
+    public function ToXml($result){
+    	if(!is_array($result)
+    	|| count($result) <= 0)
+    	{
+    		throw new \Exception("数组数据异常!");
+    	}
+    		
+    	$xml = "<xml>";
+    	foreach ($result as $key=>$val)
+    	{
+    		if (is_numeric($val)){
+    			$xml.="<".$key.">".$val."</".$key.">";
+    		}else{
+    			$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
+    		}
+    	}
+    	$xml.="</xml>";
+    	return $xml;
+    }
+
+    
+    //BD-09(百度)坐标转换成GCJ-02(火星,高德)坐标
+    //@param bd_lon 百度经度
+    //@param bd_lat 百度纬度
+    function bd_decrypt($bd_lon,$bd_lat){
+    	$x_pi = 3.14159265358979324 * 3000.0 / 180.0;
+    	$x = $bd_lon - 0.0065;
+    	$y = $bd_lat - 0.006;
+    	$z = sqrt($x * $x + $y * $y) - 0.00002 * sin($y * $x_pi);
+    	$theta = atan2($y, $x) - 0.000003 * cos($x * $x_pi);
+    	// $data['gg_lon'] = $z * cos($theta);
+    	// $data['gg_lat'] = $z * sin($theta);
+    	$gg_lon = $z * cos($theta);
+    	$gg_lat = $z * sin($theta);
+    	// 保留小数点后六位
+    	$data['gg_lon'] = round($gg_lon, 6);
+    	$data['gg_lat'] = round($gg_lat, 6);
+    	return $data;
+    }
+    
+    //GCJ-02(火星,高德)坐标转换成BD-09(百度)坐标
+    //@param bd_lon 百度经度
+    //@param bd_lat 百度纬度
+    function bd_encrypt($gg_lon,$gg_lat){
+    	$x_pi = 3.14159265358979324 * 3000.0 / 180.0;
+    	$x = $gg_lon;
+    	$y = $gg_lat;
+    	$z = sqrt($x * $x + $y * $y) - 0.00002 * sin($y * $x_pi);
+    	$theta = atan2($y, $x) - 0.000003 * cos($x * $x_pi);
+    	$bd_lon = $z * cos($theta) + 0.0065;
+    	$bd_lat = $z * sin($theta) + 0.006;
+    	// 保留小数点后六位
+    	$data['bd_lon'] = round($bd_lon, 6);
+    	$data['bd_lat'] = round($bd_lat, 6);
+    	return $data;
+    }
+    
+    /**
+     * 发送站内信息
+     */
+    public function siteMessage($content='',$siteurl='',$user_id=0,$type=1,$distributor_id=0,$membership_id=0){
+    	if(empty($content))
+    		return;
+    	if(!isset($user_id)||empty($user_id)){
+    		return;
+    	}
+    	$data['user_id']=$user_id;
+    	$data['add_time']=time();
+    	$data['content']=$content;
+    	$data['siteurl']=$siteurl;
+    	$data['type']=$type;
+    	$data['distributor_id']=$distributor_id;
+    	$data['membership_id']=$membership_id;
+    	Db::name('user_message')->insert($data);
+    	return true;
+    	//}
+    }
+
+    public function wasterJson($status='success',$msg='',array $data=[]){
+         switch ($status){
+             case 'success':
+                 $return=['status'=>$status];
+                 $return=array_merge($return,$data);
+                 break;
+             case "error":
+                 $return=['status'=>$status,'msg'=>$msg];
+                 break;
+         }
+		 file_put_contents('hyInterface.txt', '时间:'.date('Y-m-d H:i:s')."\n".json_encode($return,JSON_UNESCAPED_UNICODE),FILE_APPEND);
+         echo json_encode($return,JSON_UNESCAPED_UNICODE);
+         exit();
+    }
+
+}

+ 168 - 0
application/web/controller/NotifyController.php

@@ -0,0 +1,168 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2018 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\web\controller;
+use EasyWeChat\Factory;
+use model\UserInfo;
+use service\DepartmentService;
+use service\PositionService;
+use service\UserInfoService;
+
+//header('Access-Control-Allow-Origin:https://open.weixin.qq.com');
+
+class NotifyController extends MainController
+{
+
+    private $is_debug = true;//上线后设为false;
+//    const APPID = 'wx051828c647720b9d';
+//    const MCHID = '1502575411';
+//    const KEY = 'qazxswedcvfr1456287tgnnyu964236q';
+
+    public function __construct()
+    {
+        parent::__construct();
+        //通知微信已经收到回调了
+//        $str = '<xml>' .
+//            '<return_code><![CDATA[SUCCESS]]></return_code>' .
+//            '<return_msg><![CDATA[OK]]></return_msg>' .
+//            '</xml> ';
+//        echo $str;
+//        require_once CMF_ROOT.'vendor/wxpay/WxPayPubHelper.php';
+//        require_once CMF_ROOT.'vendor/wxpay/log.php';
+    }
+
+
+    /**
+     * @name 同步
+     */
+    public function tb(){
+        file_put_contents(WEB_ROOT.'qy1.txt',file_get_contents("php://input").date('Y-m-d H:i:s')."\n",FILE_APPEND);
+        $app = Factory::work(config('work_wechat.'));
+     //   $message=json_decode('{"ToUserName":"wwe32621127cf73e25","FromUserName":"sys","CreateTime":"1592558376","MsgType":"event","Event":"change_contact","ChangeType":"update_user","UserID":"shaoguo","Name":"\u90b5\u679c222"}',true);
+
+        $app->server->push(function ($message) {
+            // $message['FromUserName'] // 用户的 openid
+            // $message['MsgType'] // 消息类型:event, text....
+            file_put_contents(WEB_ROOT.'qy9.txt',json_encode($message)."\n",FILE_APPEND);
+            if($message['Event']=="change_contact"){
+                $new_data=array_slice($message,7);
+                switch ($message['ChangeType']){
+                    case 'update_user':
+                        $s=array_keys($new_data)[0];
+                        if($s=='NewUserID'){$new_data['UserID']==$new_data['NewUserID'];unset($new_data['UserID']);}
+                        $new_data=CamelToUnderLineArr($new_data);
+                        unset($new_data['ext_attr']);
+                        unset($new_data['department']);
+                        unset($new_data['is_leader_in_dept']);
+                        unset($new_data['status']);
+                        unset($new_data['position']);
+                        unset($new_data['is_leader']);
+
+                        if(isset($message['Department'])){
+                            $message['Department']=explode(',',$message['Department']);
+                            $message['IsLeaderInDept']=explode(',',$message['IsLeaderInDept']);
+                            if(!empty($message['Department'])){
+                                $departments=DepartmentService::get([['work_department_id','in',$message['Department']]],'id')->column('id');
+                                $user_info=UserInfoService::getOne([['user_id','=',$message['UserID']],['delete_time','=',0]]);
+                                foreach ($departments as $k=>$v){
+                                    $department_data[$k]=['department_id'=>$v,'is_leader_in_dept'=>$message['IsLeaderInDept'][$k],'is_default'=>0];
+                                }
+
+//                            dump($message);
+                                if(!empty($user_info->department))$user_info->department()->detach();
+                                dump($department_data);
+                                foreach ($department_data as $d){
+                                    $user_info->department()->attach($d['department_id'],$d);
+                                }
+
+
+                            }
+                        }
+
+                        if(!empty($message['Position'])){
+                            $position=PositionService::getOne([['name','=',$message['Position']],['delete_time','=',0]]);
+                            $new_data['position_id']=$position['id'];
+                        }
+
+                        UserInfoService::update([['user_id','=',$message['UserID']]],$new_data);
+                        break;
+
+                    case 'create_user':
+                        $new_data=array_slice($message,6);
+                        $s=array_keys($new_data)[0];
+                        if($s=='NewUserID'){$new_data['UserID']==$new_data['NewUserID'];unset($new_data['UserID']);}
+                        $new_data=CamelToUnderLineArr($new_data);
+                        unset($new_data['ext_attr']);
+                        unset($new_data['department']);
+                        unset($new_data['is_leader_in_dept']);
+                        unset($new_data['status']);
+                        unset($new_data['position']);
+                        unset($new_data['is_leader']);
+                        unset($new_data['avatar']);
+
+                        $message['Department']=explode(',',$message['Department']);
+                        $message['IsLeaderInDept']=explode(',',$message['IsLeaderInDept']);
+                        if(!empty($message['Department'])){
+                            $departments=DepartmentService::get([['work_department_id','in',$message['Department']]],'id')->column('id');
+                            $department_data=array();
+                            foreach ($departments as $k=>$v){
+                                $department_data[$k]=['department_id'=>$v,'is_leader_in_dept'=>$message['IsLeaderInDept'][$k],'is_default'=>0];
+                            }
+
+
+                        }
+
+                        if(!empty($message['Position'])){
+                            $position=PositionService::getOne([['name','=',$message['Position']],['delete_time','=',0]]);
+                            $new_data['position_id']=$position['id'];
+                        }
+
+                        $new_data['department']=$department_data;
+                        $new_data['external_profile']=json_encode(array());
+                        $new_data['enable']=1;
+
+                        $user_info=UserInfo::create($new_data);
+                        if(!empty($department_data));
+                        foreach ($department_data as $v){
+                            $user_info->department()->attach($v['department_id'],$v);
+                        }
+                        break;
+
+                    case 'delete_user':
+                        UserInfoService::update([['user_id','=',$message['UserID']],['delete_time','=',0]],['delete_time'=>time()]);
+                        break;
+                }
+            }
+
+
+//            return "您好!欢迎使用 EasyWeChat";
+        });
+
+        $response = $app->server->serve();
+
+        return $response->send();
+
+
+    }
+
+
+    public function test(){
+//        $worker=work(config('work_wechat.'));
+//        dump($worker->department->list());
+        $test=json_decode('{"ToUserName":"wwe32621127cf73e25","FromUserName":"sys","CreateTime":"1590978582","MsgType":"event","Event":"change_contact","ChangeType":"update_user","UserID":"shaoguo","Avatar":"http:\/\/wework.qpic.cn\/bizmail\/zLtxaArXppcU07OO4ZjTBBN8hBJfPBKm6ukdf633icInwFI6Av4yz4A\/0","Alias":"\u4e5d\u5343\u4e03","Address":"\u6e56\u5317\u6b66\u6c49"}',true);
+        //$test=array_slice($test,7);
+        dump(CamelToUnderLineArr($test));
+
+        $department=DepartmentService::get([['work_department_id','in',[1]]])->column('id');
+        dump($department);
+
+
+    }
+}

+ 723 - 0
application/web/controller/UserController.php

@@ -0,0 +1,723 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2018 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\web\controller;
+use service\UserService;
+use think\Db;
+use app\common\plugin\Jwt;
+
+//ar类
+class UserController extends MainController
+{
+	public $pages=20;
+	public function __construct()
+    {
+        parent::__construct();
+//        echo '这是永固构造';
+
+    }
+
+    public function userInfo(){
+		if(empty($this->userid))
+			$this->api_return_json(array('msg'=>'登录过期'),0);
+		$user=file_get_contents(CMF_ROOT . 'public/user.json');
+		if(empty($user))
+			$this->api_return_json(array('msg'=>'用户数据异常'));
+		$user=json_decode($user,true);
+		$userInfo=array();
+		foreach ($user as $val){
+			if($this->userid==$val['user_id']){
+				$userInfo=$val;
+				break;
+			}
+		}
+		if(empty($userInfo))
+			$this->api_return_json(array('msg'=>'用户信息异常'));
+		$tem=array('dianliu0'=>0.1,'dianliu1'=>10,'wendu0'=>50,'wendu1'=>100);
+		if(!file_exists(CMF_ROOT . 'public/userConfig.json'))
+			file_put_contents(CMF_ROOT . 'public/userConfig.json',json_encode($tem),LOCK_EX);
+		$userConfig=file_get_contents(CMF_ROOT . 'public/userConfig.json');
+		$userConfig=empty($userConfig) ? array():json_decode($userConfig,true);
+		$this->api_return_json(array('userInfo'=>$userInfo,'userConfig'=>$userConfig),1);
+	}
+	
+	public function editUser(){
+		if(empty($this->userid))
+			$this->api_return_json(array('msg'=>'登录过期'),0);
+		$phone=$this->request->param('phone','');
+		$nickname=$this->request->param('nickname','');
+		if(empty($nickname))
+			$this->api_return_json(array('msg'=>'昵称不能为空'));
+		if(!isset($phone)||empty($phone)||!preg_match('/^1[345789]\d{9}$/', $phone)){
+			$this->api_return_json(array('msg'=>'手机号不合法'));
+		}
+		$user=file_get_contents(CMF_ROOT . 'public/user.json');
+		if(empty($user))
+			$this->api_return_json(array('msg'=>'用户数据异常'));
+		$user=json_decode($user,true);
+		foreach ($user as &$val){
+			if($this->userid==$val['user_id']){
+				$val['phone']=$phone;
+				$val['nickname']=$nickname;
+				break;
+			}
+		}
+		file_put_contents(CMF_ROOT . 'public/user.json',json_encode($user),LOCK_EX);
+		$this->api_return_json(array('msg'=>'修改成功'),1);
+	}
+	
+	public function uploadAvatar(){
+		if(empty($this->userid))
+			$this->api_return_json(array('msg'=>'登录过期'),0);
+		$type=$this->request->param('type');
+		$file=$this->request->file('file');
+		$info = $file->validate(array('ext'=>'jpg,png'))->move(CMF_ROOT . 'public/upload'.DIRECTORY_SEPARATOR.'image'); //上传后的文件名
+		if($info){
+			$upame=$info->getSaveName(); //数据库存储文件名
+			$path='image/'.$upame;
+			$user=file_get_contents(CMF_ROOT . 'public/user.json');
+			if(empty($user))
+				$this->api_return_json(array('msg'=>'用户数据异常'));
+			$user=json_decode($user,true);
+			foreach ($user as &$val){
+				if($this->userid==$val['user_id']){
+					if($type==0)
+						$val['avatar']=$path;
+					else 
+						$val['cover']=$path;
+					break;
+				}
+			}
+			file_put_contents(CMF_ROOT . 'public/user.json',json_encode($user),LOCK_EX);
+			$this->api_return_json(array('path'=>$path),1);
+		}else{
+			$this->api_return_json(array('msg'=>$file->getError()));
+		}
+	}
+	
+	public function login(){
+		$username=$this->request->param('username');
+		$password=$this->request->param('password');
+		if(empty($username))
+			$this->api_return_json(array('msg'=>'用户不能为空'));
+		if(empty($password))
+			$this->api_return_json(array('msg'=>'密码不能为空'));
+		if(!file_exists(CMF_ROOT . 'public/user.json'))
+			file_put_contents(CMF_ROOT . 'public/user.json','',LOCK_EX);
+		$user=file_get_contents(CMF_ROOT . 'public/user.json');
+		if(empty($user)){
+			//注册
+			$this->api_return_json(array('msg'=>'用户不存在'));
+			$id=1;
+			$userData=array(array('user_id'=>$id,'username'=>$username,'password'=>$password,'phone'=>'','avatar'=>'','cover'=>'','nickname'=>'','add_time'=>time(),'update_time'=>time()));
+			file_put_contents(CMF_ROOT . 'public/user.json',json_encode($userData),LOCK_EX);
+		}else{
+			$user=json_decode($user,true);
+			$jsonUserName=array_column($user,'username');
+			if(in_array($username,$jsonUserName)){
+				//登录
+				foreach ($user as &$val){
+					if($username==$val['username']){
+						if($password!=$val['password'])
+						$this->api_return_json(array('msg'=>'密码错误'));
+						$id=$val['user_id'];
+					}
+					
+				}
+			}else{
+				//注册追加
+				$this->api_return_json(array('msg'=>'用户不存在'));
+				$id=max(array_column($user, 'user_id'))+1;
+				$userData=array('user_id'=>$id,'username'=>$username,'password'=>$password,'phone'=>'','avatar'=>'','cover'=>'','nickname'=>'','add_time'=>time(),'update_time'=>time());
+				array_push($user,$userData);
+				file_put_contents(CMF_ROOT . 'public/user.json',json_encode($user),LOCK_EX);
+			}
+		}
+		$token=JWT::getToken($id,$this->major);
+		$this->api_return_json(array('msg'=>'登录成功','sid'=>$token),1);
+	}
+	
+	public function register(){
+		if(empty($this->userid))
+			$this->api_return_json(array('msg'=>'登录过期'),0);
+		if($this->userid!=1)
+			$this->api_return_json(array('msg'=>'您没权限操作'),0);
+		$username=$this->request->param('username');
+		$password=$this->request->param('password');
+		if(empty($username))
+			$this->api_return_json(array('msg'=>'用户不能为空'));
+		if(empty($password))
+			$this->api_return_json(array('msg'=>'密码不能为空'));
+		$user=file_get_contents(CMF_ROOT . 'public/user.json');
+		$user=json_decode($user,true);
+		$jsonUserName=array_column($user,'username');
+		if(in_array($username,$jsonUserName)){
+			//登录
+			$this->api_return_json(array('msg'=>'账号已存在'));		
+		}
+		
+		$id=max(array_column($user, 'user_id'))+1;
+		$userData=array('user_id'=>$id,'username'=>$username,'password'=>$password,'phone'=>'','avatar'=>'','cover'=>'','nickname'=>'','add_time'=>time(),'update_time'=>time());
+		array_push($user,$userData);
+		file_put_contents(CMF_ROOT . 'public/user.json',json_encode($user),LOCK_EX);
+		$this->api_return_json(array('msg'=>'账号注册成功'),1);
+	}
+	
+	public function forgetSecret(){
+		if(empty($this->userid))
+			$this->api_return_json(array('msg'=>'登录过期'),0);
+		$ori_password=$this->request->param('ori_password');
+		$new_password=$this->request->param('new_password');
+		$re_password=$this->request->param('re_password');
+		if($new_password=="" ||strlen($new_password)<6 ){
+		  	$this->api_return_json(array('msg'=>'请输入不少于6位数的新密码'));
+		}else if($re_password==""){
+		    $this->api_return_json(array('msg'=>'请再次输入新密码'));
+		}else if($new_password!=$re_password){
+		  	$this->api_return_json(array('msg'=>'两次密码不一致'));
+		}else if($ori_password=="")
+			$this->api_return_json(array('msg'=>'旧密码不能为空'));
+		
+			$user=file_get_contents(CMF_ROOT . 'public/user.json');
+			if(empty($user))
+				$this->api_return_json(array('msg'=>'用户数据异常'));
+			$user=json_decode($user,true);
+			foreach ($user as &$val){
+				if($this->userid==$val['user_id']){
+					if($val['password']!=$ori_password)
+						$this->api_return_json(array('msg'=>'旧密码不正确'));
+					$val['password']=$new_password;
+					break;
+				}
+			}
+			file_put_contents(CMF_ROOT . 'public/user.json',json_encode($user),LOCK_EX);
+		$this->api_return_json(array('msg'=>'密码修改成功'),1);
+	}
+	
+	public function userConfig(){
+		$k=$this->request->param('key');
+		$v=$this->request->param('value');
+		if(empty($k)||!is_numeric($v))
+			$this->api_return_json(array('msg'=>'参数有误'),0);
+		if(empty($this->userid))
+			$this->api_return_json(array('msg'=>'登录过期'),0);
+		if($this->userid!=1)
+			$this->api_return_json(array('msg'=>'您没有权限操作'),0);
+		$tem=array('dianliu0'=>0.1,'dianliu1'=>10,'wendu0'=>50,'wendu1'=>100);
+		if(!file_exists(CMF_ROOT . 'public/userConfig.json')||empty(file_get_contents(CMF_ROOT . 'public/userConfig.json')))
+			file_put_contents(CMF_ROOT . 'public/userConfig.json',json_encode($tem),LOCK_EX);
+		$userConfig=file_get_contents(CMF_ROOT . 'public/userConfig.json');
+		$userConfig=empty($userConfig) ? array():json_decode($userConfig,true);
+		if(array_key_exists($k,$userConfig)){
+			$userConfig[$k]=$v;
+			file_put_contents(CMF_ROOT . 'public/userConfig.json',json_encode($userConfig),LOCK_EX);
+		}else 
+			$this->api_return_json(array('msg'=>'修改失败'),0);
+		$this->api_return_json(array('msg'=>'修改成功'),1);
+	}
+	
+	//关注
+	public function collect(){
+		$res=$this->request->param('res');
+		if(empty($res))
+			$this->api_return_json(array('msg'=>'数据获取失败'),0);
+		if(is_array($res)){
+			$res=array_unique($res);
+			$res=json_encode($res);
+		}
+		file_put_contents(CMF_ROOT . 'public/collect.json',$res,LOCK_EX);
+		$this->api_return_json(array('msg'=>'关注成功'),1);
+	}
+	
+	//取关
+	public function quitCollect(){
+		$id=$this->request->param('id');
+		$collect=empty(file_get_contents(CMF_ROOT . 'public/collect.json')) ? array():json_decode(file_get_contents(CMF_ROOT . 'public/collect.json'),true);
+		foreach ($collect as $key=>$val){
+			if($val['id']==$id)
+				array_splice($collect,$key,1);
+		}
+		file_put_contents(CMF_ROOT . 'public/collect.json',json_encode($collect),LOCK_EX);
+		$this->api_return_json(array('msg'=>'取关成功'),1);
+	}
+	
+	public function home(){
+		$user=file_get_contents(CMF_ROOT . 'public/user.json');
+		if(empty($user))
+			$this->api_return_json(array('msg'=>'用户数据异常'));
+		$user=json_decode($user,true);
+		$userInfo=array();
+		foreach ($user as $val){
+			if(1==$val['user_id']){
+				$userInfo=$val;
+				break;
+			}
+		}
+		$userInfo['map']=!empty($userInfo['cover']) ? $userInfo['cover']:'map.jpg';
+		$this->api_return_json(array('home'=>$userInfo),1);
+	}
+	
+	//我的消息
+	public function myMessage(){
+		$page=$this->request->param('page');
+		if(!isset($this->userid)||empty($this->userid)){
+			$this->api_return_json(array('msg'=>'登录过期'));
+		}
+		if(!isset($page)||empty($page)){
+			$page=1;
+		}
+		$where=array('c.user_id'=>$this->userid,'c.is_delete'=>0);
+		$products=Db::name('user_message')
+		->field('c.add_time,c.id,c.type,m.desc,m.url,c.user_id,c.content,c.siteurl,u.user_message_id')
+		->alias("c")
+		->join('message m','m.id=c.message_id','left')
+		->join('user_message_read u','u.user_message_id=c.id','left')
+		->where('(c.user_id=0 or c.user_id='.$this->userid.') and c.is_delete=0')
+		//->group('c.message_id')
+		->order('c.add_time desc')
+		->limit(($page-1)*$this->pages,$this->pages)
+		->select()
+		->each(function($v){
+			$v['add_time']=$this->getChatTimeStr($v['add_time']);
+			if($v['type']==1){
+				$v['url']=$v['siteurl'];
+				$v['desc']=$v['content'];
+			}
+			if(empty($v['user_message_id']))
+				$v['is_read']=0;
+			else
+				$v['is_read']=1;
+			return $v;
+		})
+		->toArray();
+		//未读总数
+		$count=Db::name('user_message')
+		->alias("c")
+		->field('user_message_id')
+		->join('user_message_read u','u.user_message_id=c.id','left')
+		->where('(c.user_id=0 or c.user_id='.$this->userid.') and c.is_delete=0')
+		//->group('c.message_id')
+		->select()
+		->toArray();
+		$i=0;
+		foreach ($count as $val){
+			if($val['user_message_id']==null)
+				$i++;
+		}
+		$this->api_return_json(array('myMessage'=>$products,'count'=>$i,'pages'=>$this->pages),1);
+	}
+	
+	//消息是否已读
+	public function isRead(){
+		$id=$this->request->param('id');
+		if(!isset($id)||empty($id))
+		$this->api_return_json(array('msg'=>'消息id异常'));
+		if(!isset($this->userid)||empty($this->userid))
+			$this->api_return_json(array('msg'=>'登录过期'));
+		$data['user_id']=$this->userid;
+		$data['user_message_id']=$id;
+		$data['add_time']=time();
+		Db::name('user_message_read')->insert($data);
+		$this->api_return_json(array('msg'=>'阅读成功'),1);
+	}
+	
+	//删除消息
+	public function deleteMessage(){
+		$id=$this->request->param('id');
+		if(!isset($id)||empty($id))
+			$this->api_return_json(array('msg'=>'消息id异常'));
+		Db::name('user_message')->where(array('id'=>$id))->update(array('is_delete'=>1));
+		$this->api_return_json(array('msg'=>'删除成功'),1);
+	}
+
+
+	
+	//更新用户信息
+	public function updateUser(){
+		//微信小程序
+		$avatar=$this->request->param('avatar','');
+		$wx_nickname=$this->request->param('nickName','');
+		$msg='未修改';
+		if(!empty($avatar)){
+			$update_date['avatar']=$avatar;
+			$update_date['wx_nickname']=$wx_nickname;
+			db('wx_user')->where('user_id',$this->userid)->update($update_date);
+			$msg='已修改';
+		}
+		$this->api_return_json(array('msg'=>$msg),1);
+	}
+
+	
+	//上传
+	public function upload(){
+		$file=$this->request->file('file');
+		$info = $file->validate(array('ext'=>'jpg,png'))->move(CMF_ROOT . 'public/upload'.DIRECTORY_SEPARATOR.'portal'); //上传后的文件名
+		if($info){
+			$upame=$info->getSaveName(); //数据库存储文件名
+			$path='portal/'.$upame;
+			$this->api_return_json(array('path'=>$path),1);
+		}else{
+			$this->api_return_json(array('msg'=>$file->getError()));
+		}
+	
+	}
+	
+	public function deleteFile(){
+		$image_url=$this->request->param('file');
+		if(!isset($image_url)||empty($image_url)){
+			$this->api_return_json(array('msg'=>'资源错误'));
+		}
+		if(file_exists(CMF_ROOT . 'public/upload/'.$image_url)){
+			unlink(CMF_ROOT . 'public/upload/'.$image_url);
+			$this->api_return_json(array('msg'=>'删除成功'),1);
+		}else{
+			$this->api_return_json(array('msg'=>'文件不存在删除失败'));
+		}
+	
+	}
+	
+	//小程序码无限制的那种
+	public function getUnlimitCode(){
+		$url='https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token='.$this->getAccess_token();
+		$data=array();
+		$data['scene']='id=1';
+		$data['page']='pages/index/index';
+		$result=$this->curl_post($url, json_encode($data));
+		$this->api_return_json(array('result'=>json_decode($result)),1);
+	} 
+	
+	/**
+	 * 微信展示时间的方法
+	 * @param $addTime
+	 * @return string
+	 */
+	public static function getChatTimeStr($addTime) {
+		$nowTime = time();
+		if($addTime > $nowTime) {
+			return "";
+		}
+	
+		//返回的时间
+		$timeStr = "";
+		//获取当前时间
+		$addTime = explode(',', date('Y,m,d,w,a,h,i,Y', $addTime));//年,月,日,星期,上下午,时,分
+		$nowTime = explode(',', date('Y,m,d,w,a,h,i,Y', $nowTime));
+	
+		$dayPerMonthAddTime = self::getDayPerMonth($addTime[0]);
+		$week = array(0=>"星期日",1=>"星期一",2=>"星期二",3=>"星期三",4=>"星期四",5=>"星期五",6=>"星期六");
+		//如果时间差小于一天的,显示(上午 时间) / (下午 时间)
+		if($addTime[0] == $nowTime[0] && $addTime[1] == $nowTime[1] && $addTime[2] == $nowTime[2]) {
+			$timeStr .= $addTime[5] . ":" . $addTime[6];
+		} else if(($addTime[0] == $nowTime[0] && $addTime[1] == $nowTime[1] && $addTime[2] == $nowTime[2]-1)
+		|| ($addTime[0] == $nowTime[0] && $nowTime[1]-$addTime[1] == 1 && $dayPerMonthAddTime[(int)$addTime[1]] == $addTime[2] && $nowTime[2] == 1)
+		|| ($nowTime[0]-$addTime[0] == 1 && $addTime[1] == 12 && $addTime[2] == 31 && $nowTime[1] == 1 && $nowTime[2] == 1)) { //如果时间差在昨天,三种情况(同一月份内跨一天、月末跨越到月初、年末跨越到年初)显示格式:昨天 时:分 上午/下午
+			$timeStr .= "昨天 " . $addTime[5] . ":" . $addTime[6] . " ";
+		} else if(($addTime[0] == $nowTime[0] && $addTime[1] == $nowTime[1] && $nowTime[2] - $addTime[2] < 7)
+		|| ($addTime[0] == $nowTime[0] && $nowTime[1]-$addTime[1] == 1 && $dayPerMonthAddTime[(int)$addTime[1]]-$addTime[2]+$nowTime[2] < 7
+				|| ($nowTime[0]-$addTime[0] == 1 && $addTime[1] == 12 && $nowTime[1] == 1 && 31-$addTime[2]+$nowTime[2] < 7))) { //如果时间差在一个星期之内的,也是三种情况,显示格式:星期 时:分 上午/下午
+	
+			$timeStr .= $week[$addTime[3]] . " " . $addTime[5] . ":" . $addTime[6];
+		} else { //显示格式:月/日/年 时:分 上午/下午
+			$timeStr .= $addTime[7]."-".$addTime[1] . "-" . $addTime[2]  . " " . $addTime[5] . ":" . $addTime[6];
+		}
+	
+		if($addTime[4] == "am") {
+			$timeStr .= " 上午";
+		} else if($addTime[4] == "pm") {
+			$timeStr .= " 下午";
+		}
+	
+		return $timeStr;
+	
+	}
+	
+	//根据年份获取每个月份的总天数和每年最后一个月的天数
+	public static function getDayPerMonth($year) {
+		$arr = array(
+				1 => 31,
+				3 => 31,
+				4 => 30,
+				5 => 31,
+				6 => 30,
+				7 => 31,
+				8 => 31,
+				9 => 30,
+				10 => 31,
+				11 => 30,
+				12 => 31
+		);
+		//闰年
+		if(($year%4==0&&$year%100!=0) || ($year%400==0)) {
+			$arr[2] = 29;
+		} else {
+			$arr[2] = 28;
+		}
+		return $arr;
+	}
+	
+	/*************** 校验身份证 start *******************/
+	function checkIdCard($idc){
+		if(empty($idc)){
+			return false;
+		}
+		$idcard = $idc;
+		$City = array(11=>"北京",12=>"天津",13=>"河北",14=>"山西",15=>"内蒙古",21=>"辽宁",22=>"吉林",23=>"黑龙江",31=>"上海",32=>"江苏",33=>"浙江",34=>"安徽",35=>"福建",36=>"江西",37=>"山东",41=>"河南",42=>"湖北",43=>"湖南",44=>"广东",45=>"广西",46=>"海南",50=>"重庆",51=>"四川",52=>"贵州",53=>"云南",54=>"西藏",61=>"陕西",62=>"甘肃",63=>"青海",64=>"宁夏",65=>"新疆",71=>"台湾",81=>"香港",82=>"澳门",91=>"国外");
+		$iSum = 0;
+		$idCardLength = strlen($idcard);
+		//长度验证
+		if(!preg_match('/^\d{17}(\d|x)$/i',$idcard) and !preg_match('/^\d{15}$/i',$idcard))
+		{
+			return false;
+		}
+		//地区验证
+		if(!array_key_exists(intval(substr($idcard,0,2)),$City))
+		{
+			return false;
+		}
+		// 15位身份证验证生日,转换为18位
+		if ($idCardLength == 15)
+		{
+			$sBirthday = '19'.substr($idcard,6,2).'-'.substr($idcard,8,2).'-'.substr($idcard,10,2);
+			//    $d = new DateTime($sBirthday);
+			//    $dd = $d->format('Y-m-d');
+			//    if($sBirthday != $dd)
+			if($sBirthday != $sBirthday)
+			{
+				return false;
+			}
+			$idcard = substr($idcard,0,6)."19".substr($idcard,6,9);//15to18
+			$Bit18 = $this->getVerifyBit($idcard);//算出第18位校验码
+			$idcard = $idcard.$Bit18;
+		}
+		// 判断是否大于2078年,小于1900年
+		$year = substr($idcard,6,4);
+		if ($year<1900 || $year>2078 )
+		{
+			return false;
+		}
+	
+		//18位身份证处理
+		$sBirthday = substr($idcard,6,4).'-'.substr($idcard,10,2).'-'.substr($idcard,12,2);
+		//    var_dump($sBirthday);
+		//    $d = new DateTime($sBirthday);
+	
+		//    $dd = $d->format('Y-m-d');
+		//    echo $dd;
+		//    die();
+		//    if($sBirthday != $dd)
+		if($sBirthday != $sBirthday)
+		{
+			return false;
+		}
+		//身份证编码规范验证
+		$idcard_base = substr($idcard,0,17);
+		if(strtoupper(substr($idcard,17,1)) != $this->getVerifyBit($idcard_base))
+		{
+			return false;
+		}
+		return true;
+	}
+	
+	// 计算身份证校验码,根据国家标准GB 11643-1999
+	function getVerifyBit($idcard_base)
+	{
+		if(strlen($idcard_base) != 17)
+		{
+			return false;
+		}
+		//加权因子
+		$factor = array(7, 9, 10, 5 , 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2);
+		//校验码对应值
+		$verify_number_list = array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2');
+		$checksum = 0;
+		for ($i = 0; $i < strlen($idcard_base); $i++)
+		{
+		$checksum += substr($idcard_base, $i, 1) * $factor[$i];
+		}
+		$mod = $checksum % 11;
+		$verify_number = $verify_number_list[$mod];
+		return $verify_number;
+	}
+
+
+
+    //全读
+    public function isAllRead(){
+        if(!isset($this->userid)||empty($this->userid))
+            $this->api_return_json(array('msg'=>'登录过期'));
+        $result=Db::name('user_message')
+            ->field('id')
+            ->where('(user_id=0 or user_id='.$this->userid.') and is_delete=0')
+            ->select()
+            ->toArray();
+        $datas=array();
+        foreach ($result as $val){
+            $res=Db::name('user_message_read')->where(array('user_message_id'=>$val['id'],'user_id'=>$this->userid))->find();
+            if($res)
+                continue;
+            $data['user_id']=$this->userid;
+            $data['user_message_id']=$val['id'];
+            $data['add_time']=time();
+            $datas[]=$data;
+        }
+        Db::name('user_message_read')->insertAll($datas);
+        $this->api_return_json(array('msg'=>'全部阅读成功'),1);
+    }
+
+    /**
+     * @name 获取城市
+     */
+    public function city(){
+        $where[]=['delete_time','=',0];
+        $name=$this->request->get('name');
+      //  $page=$this->request->get('page',1);
+        $where[]=['name','like','%苏州%'];
+        $index=City::where($where)->field('id,name')->order(['list_order'=>'asc','create_time'=>'desc'])->select();
+        $keyword=Keyword::where([['delete_time','=',0]])->field('name')->order(['list_order'=>'desc','create_time'=>'desc'])->select();
+
+       $this->api_return_json(['city'=>$index,'pages'=>$this->pages,'keyword'=>$keyword],1);
+    }
+
+
+    /**
+     * @name 分类category
+     */
+    public function category(){
+        $where[]=['delete_time','=',0];
+        $city_id=$this->request->get('city_id','','intval');
+        if(empty($city_id))$this->api_return_json(['msg'=>'请选择城市']);
+        $city=City::where([['delete_time','=',0],['id','=',$city_id]])->find();
+        if(empty($city))$this->api_return_json(['msg'=>'该城市不存在']);
+
+
+        $categories=$city->category()->field('id,name,desc,icon,table_name,detail')->order(['list_order'=>'asc','create_time'=>'desc'])->select()->each(function ($v){
+            unset($v['pivot']);
+            return $v;
+        });
+
+        $this->api_return_json(['category'=>$categories],1);
+
+    }
+
+
+    public function setLocation()
+    {//$this->redis->flushAll();
+    	$this->api_return_json(['msg' => 'ok'], 1);
+        $page = $this->request->get('page', 1);
+        if (!$this->request->isGet()) {
+            return $this->api_return_json(['msg' => '请求方式不对']);
+        }
+        if (!isset($this->userid) || empty($this->userid)) {
+            return $this->api_return_json(['msg' => '请先登录']);
+        }
+        $data = $this->request->get();
+//        if (!isset($data['lat']) || empty($data['lat'])) {
+//            $this->api_return_json(['msg' => '请传入纬度']);
+//        }
+//        if (!isset($data['long']) || empty($data['long'])) {
+//            $this->api_return_json(['msg' => '请传入经度']);
+//        }
+
+        if (!isset($data['city']) || empty($data['city'])) {
+            $this->api_return_json(['msg' => '请输入城市名']);
+        }
+        //$citys = City::where('delete_time', 0)->field('id,name')->limit(($page - 1), $this->pages)->orderRaw('',["name <>".$data['city']])->select();
+        $citys=Db::query('select id,name from rbi_city where delete_time=0 order by field(name,"'.$data['city'].'") desc,list_order desc');
+        $this->api_return_json(compact('citys'), 1);
+    }
+
+
+    /**
+     * @name
+     */
+    public function gf(){
+        $name=$this->request->post('name');
+        $city_id=$this->request->post('city_id');
+        if(empty($name))$this->api_return_json(['msg'=>'名称不能为空']);
+        if(empty($city_id))$this->api_return_json(['msg'=>'请选择城市']);
+        $GarbageForm=GarbageForm::where('name',$name)->where('city_id',$city_id)->where('delete_time',0)->find();
+        if(!empty($GarbageForm)){
+            $this->api_return_json(['msg'=>'该名称已提交']);
+        }
+
+        $search=Db::field('name,list_order,create_time,category_id')
+            ->table('rbi_recycled')
+            ->alias('r')
+            ->unionAll([
+                'SELECT name,list_order,create_time,category_id FROM rbi_harmful where '.'delete_time=0 and '.'(city_id='.$city_id.' or city_id=0) and name like "%'.$name.'%"',
+                'SELECT name,list_order,create_time,category_id FROM rbi_perishable_refuse where '.'delete_time=0 and '.'(city_id='.$city_id.' or city_id=0)  and name like "%'.$name.'%"',
+                'SELECT name,list_order,create_time,category_id FROM rbi_kitchen_waste where '.'delete_time=0 and '.'(city_id='.$city_id.' or city_id=0)and name like "%'.$name.'%"',
+                'SELECT name,list_order,create_time,category_id FROM rbi_wet_refuse where '.'delete_time=0 and '.'(city_id='.$city_id.' or city_id=0) and name like "%'.$name.'%"',
+                'SELECT name,list_order,create_time,category_id FROM rbi_other where '.'delete_time=0 and '.'(city_id='.$city_id.' or city_id=0) and name like "%'.$name.'%"',
+            ])->where(function ($query)use($city_id){
+                $query->where('r.city_id',0)->whereOr('r.city_id','=',$city_id);
+            })->where('name',$name)->find();
+
+        if(!empty($search)){
+            $this->api_return_json(['msg'=>'该名称已提交']);
+        }
+        $result=GarbageFormService::store(compact('name','city_id'));
+        if($result['code']!=0)$this->api_return_json(['msg'=>"提交失败"]);
+        $this->api_return_json(['msg'=>"提交成功"],1);
+    }
+
+    /**
+     * @name 详情
+     */
+    public function info(){
+        $data=$this->request->get();
+        if(!$this->request->isGet()){
+            return $this->api_return_json(['msg'=>'请求方式不对']);
+        }
+        if(!isset($this->userid) || empty($this->userid)){
+            return $this->api_return_json(['msg'=>'请先登录']);
+        }
+        $user=UserService::getOne([['user_id','=',$this->userid]]);
+        $member=$user->member;
+        $this->api_return_json(compact('member'),1);
+
+    }
+
+
+    /**
+     * @name 详情
+     */
+    public function store_info(){
+        $data=$this->request->post();
+        $id=isset($data['id'])&&empty($data['id'])?0:$data['id'];
+        if(!$this->request->isPost()){
+            return $this->api_return_json(['msg'=>'请求方式不对']);
+        }
+        if(!isset($this->userid) || empty($this->userid)){
+            return $this->api_return_json(['msg'=>'请先登录']);
+        }
+        $user=UserService::getOne([['user_id','=',$this->userid]]);
+        $member=$user->member;
+        if(empty($member)){
+            $result=HouseMemberService::store($data);
+        }else{
+            $result=HouseMemberService::store($data,$id);
+        }
+
+        if($result['code']==-1){
+            $return=['msg'=>$data['message']];
+        }elseif($result['code']==0){
+            $return=['msg'=>'操作失败'];
+        }else{
+            $return=['msg'=>"操作成功"];
+        }
+
+        $this->api_return_json($return,$result['code']==1?1:0);
+
+    }
+
+
+
+}

+ 411 - 0
application/web/controller/WxController.php

@@ -0,0 +1,411 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2018 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\web\controller;
+use service\DistributorService;
+use service\UserService;
+use think\Db;
+use app\common\plugin\Jwt;
+use app\common\plugin\WxBizDataCrypt;
+use app\common\plugin\Jssdk;
+
+
+//wx类
+class WxController extends MainController
+{
+	public $app_id,$app_secret,$accessToken;//公众号
+
+	public function __construct()
+	{
+		parent::__construct();
+		$wxconfig=config()['config']['wxconfig'];
+		$this->app_id=$wxconfig['appid'];
+		$this->app_secret=$wxconfig['secret'];
+	}
+
+	public function login(){
+		//微信小程序
+		$code = input("code", '', 'htmlspecialchars_decode');
+
+		$avatar=$this->request->param('avatar','');
+		$wx_nickname=$this->request->param('nickname','匿名用户');
+
+		$url = 'https://qyapi.weixin.qq.com/cgi-bin/miniprogram/jscode2session?access_token='.$this->getAccess_token().'&js_code='.$code.'&grant_type=authorization_code';
+		//通过code换取openid session _key
+		$res = $this->curl_get($url);
+		if(!$res)
+			$this->api_return_json(['msg'=>'请求微信服务器失败']);
+		$res=(array)$res;
+		if (!isset($res['session_key'])) {
+			$this->api_return_json(['msg'=>'requestTokenFailed']);
+		}
+		$user_data['wx_openid']=$res['userid'];
+		$user_data['corpid']=$res['corpid'];
+		$user_data['reg_time'] = time();
+		$user_data['ip']=ip2long($this->ip);
+		$rand_str = md5($user_data['reg_time']);
+		$user_data['username'] = 'wx' . $user_data['reg_time'] . substr($rand_str, 0, 4);
+		$user_data['major']=$this->major;
+
+		if(!empty($avatar)){
+			$user_data['avatar'] =$avatar;
+			$user_data['wx_nickname']=$wx_nickname;
+		}
+//		$res['userid']='1254565412212';$user_data=array();//测试数据
+		$user= Db::name('wx_user')->where('wx_openid',$res['userid'])->find();
+		if(empty($user)){
+			$user_id=Db::name('wx_user')->insertGetId($user_data);
+		}else{
+			$user_id=$user['user_id'];
+			$update_date=array('ip'=>ip2long($this->ip),'major'=>$this->major);
+			if(!empty($avatar)&&!empty($wx_nickname)){
+				$update_date['avatar']=$avatar;
+				$update_date['wx_nickname']=$wx_nickname;
+			}
+			db('wx_user')->where('user_id',$user['user_id'])->update($update_date);
+		}
+		$token=JWT::getToken($user_id,$this->major);
+//
+//        $distributor_id=$this->request->get('distributor_id');
+//        $app_id=$this->request->get('app_id');
+//        if(!empty($distributor_id)){
+//        $distributor=DistributorService::getOne([['id','=',$distributor_id],['status','=',1]]);
+//        $result=UserService::update([['user_id','=',$user_id]],['distributor_bind_id'=>$distributor_id]);
+//        $distributor->setInc('share_num');
+//        }
+//        if(!empty($app_id)){
+//            $distributor=DistributorService::getOne([['app_id','=',$distributor_id],['status','=',1]]);
+//            $result=UserService::update([['user_id','=',$user_id]],['distributor_bind_id'=>$distributor['id']]);
+//            $distributor->setInc('share_num');
+//        }
+
+
+
+        $this->api_return_json(array('user_data'=>$user_data,'session_id'=>$token,'is_audit'=>config('config.is_audit')),1);
+	}
+
+	public function getRunData(){
+		$code = $this->request->param('code');
+		$encryptedData = $this->request->param('encryptedData');
+		$iv = $this->request->param('iv');
+		if(!isset($code)||empty($code)){
+			$this->api_return_json(array('msg'=>'code异常'));
+		}
+		if(!isset($encryptedData)||empty($encryptedData)){
+			$this->api_return_json(array('msg'=>'encryptedData异常'));
+		}
+		if(!isset($iv)||empty($iv)){
+			$this->api_return_json(array('msg'=>'iv异常'));
+		}
+		$url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' . $this->app_id . '&secret=' . $this->app_secret . '&js_code='.$code.'&grant_type=authorization_code';
+		//通过code换取openid session _key
+		$res = $this->curl_get($url);
+		if(!$res)
+			$this->api_return_json(['msg'=>'请求微信服务器失败']);
+		$res=(array)$res;
+		if (!isset($res['session_key'])) {
+			$this->api_return_json(['msg'=>'requestTokenFailed']);
+		}
+
+		$pc=new WXBizDataCrypt($this->app_id, $res['session_key']);
+		$errCode = $pc->decryptData($encryptedData, $iv, $data);
+		if ($errCode == 0||true) {
+			$res = json_decode($data, true);
+			$stepInfoList=$res['stepInfoList'];
+			$time=array();
+			$data=array();
+			foreach ($stepInfoList as &$val){
+				$val['timestamp']=date('Y-m-d',$val['timestamp']);
+				array_push($time, $val['timestamp']);
+				array_push($data, $val['step']);
+			}
+			$result=array('time'=>$time,'data'=>$data);
+			$this->api_return_json(array('stepInfoList'=>$result),1);
+
+		} else {
+			$this->api_return_json(array('msg'=>'解密失败'));
+		}
+	}
+
+	public function getPhone(){
+		$code = $this->request->param('code');
+		$encryptedData = $this->request->param('encryptedData');
+		$iv = $this->request->param('iv');
+		if(!isset($code)||empty($code)){
+			$this->api_return_json(array('msg'=>'code异常'));
+		}
+		if(!isset($encryptedData)||empty($encryptedData)){
+			$this->api_return_json(array('msg'=>'encryptedData异常'));
+		}
+		if(!isset($iv)||empty($iv)){
+			$this->api_return_json(array('msg'=>'iv异常'));
+		}
+
+		$url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' . $this->app_id . '&secret=' . $this->app_secret . '&js_code='.$code.'&grant_type=authorization_code';
+		//通过code换取openid session _key
+		$res = $this->curl_get($url);
+		if(!$res)
+			$this->api_return_json(['msg'=>'请求微信服务器失败']);
+		$res=(array)$res;
+		if (!isset($res['session_key'])) {
+			$this->api_return_json(['msg'=>'requestTokenFailed']);
+		}
+
+		$pc=new WXBizDataCrypt($this->app_id, $res['session_key']);
+		$errCode = $pc->decryptData($encryptedData, $iv, $data);
+		if ($errCode == 0||true) {
+			$res = json_decode($data, true);
+			if(!isset($res['phoneNumber'])||empty($res['phoneNumber'])){
+				$this->api_return_json(array('msg'=>'手机号获取失败'));
+			}
+			//查询该小程序用户是否已经存在
+			$user=Db::name('wx_user')->where('user_id',$this->userid)->find();
+			if ($user) {
+				//存在就更新登录时间,检查是否登录过期
+				db('wx_user')->where('user_id',$this->userid)->update(array('phone'=>$res['phoneNumber'],'is_tied'=>1));
+				$this->api_return_json(array('phone'=>$res['phoneNumber']),1);
+			}
+			$this->api_return_json(array('msg'=>'用户不存在'));
+		} else {
+			$this->api_return_json(array('msg'=>'解密失败'));
+		}
+	}
+
+	//小程序卡券
+	function cardCreate(){
+		$jssdk=new Jssdk();
+		$token=$jssdk->getAccessToken();//测试公众号的token
+		$package=$jssdk->GetSignPackage();//测试公众号的token
+		$url='https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token='.$token;
+		//log图
+		$data=array('buffer'=>new \CURLFile(realpath(CMF_ROOT.'public/static/images/headicon.png')));
+		$image_url_log=$this->curl_post($url, $data);
+		$image_url_log=json_decode($image_url_log)->url;
+
+		//某一测试素材图
+		$data=array('buffer'=>new \CURLFile(realpath(CMF_ROOT.'public/static/images/bg.jpg')));
+		$image_url_one=$this->curl_post($url, $data);
+		$image_url_one=json_decode($image_url_one)->url;
+		//起止截至时间
+		$begin_timestamp=time()-30;
+		$end_timestamp=time()+30*24*3600;
+		//创建卡券json数据 团购券
+		$type='member_card';
+		switch ($type){
+			case 'groupon':
+				$des='"deal_detail":"以下锅底2选1(有菌王锅、麻辣锅、大骨锅、番茄锅、清补 凉锅、酸菜鱼锅可选):\n大锅1份 12元\n小锅2份 16元 "';
+				$memdes='';
+				break;
+			case 'cash':
+				$des='"least_cost": 1000,"reduce_cost": 100';
+				$memdes='';
+				break;
+			case 'discount':
+				$des='"discount": 30';
+				$memdes='';
+				break;
+			case 'gift':
+				$des='"gift":"可兑换白色bra一个"';
+				$memdes='';
+				break;
+			case 'general_coupon':
+				$des='"default_detail":"优惠券专用,填写优惠详情"';
+				$memdes='';
+				break;
+			case 'member_card':
+				$des='"discount": 10';
+				$memdes='"supply_bonus": true,
+			            "supply_balance": false,
+			            "prerogative": "test_prerogative",
+			            "auto_activate": true,
+			            "custom_field1": {
+			                "name_type": "FIELD_NAME_TYPE_LEVEL",
+			                "url": "http://www.qq.com"
+			            },
+			            "activate_url": "http://www.qq.com",
+			            "custom_cell1": {
+			                "name": "使用入口2",
+			                "tips": "激活后显示",
+			                "url": "http://www.qq.com"
+			            },
+			            "bonus_rule": {
+			                "cost_money_unit": 100,
+			                "increase_bonus": 1,
+			                "max_increase_bonus": 200,
+			                "init_increase_bonus": 10,
+			                "cost_bonus_unit": 5,
+			                "reduce_money": 100,
+			                "least_money_to_use_bonus": 1000,
+			                "max_reduce_bonus": 50
+			            },';
+				break;
+			default:
+				$des='';
+				$memdes='';
+				break;
+		}
+
+		$card_res=$this->card_type($type);
+		$card_type=$card_res['card_type'];
+		$card_name=$card_res['card_name'];
+		$data='{
+    		"card": {
+	    		"card_type": "'.$card_type.'",
+	    		"'.$card_name.'": {
+		    		"base_info": {
+			    		"logo_url":  "'.$image_url_log.'",
+			    		"brand_name": "微信餐厅",
+			    		"code_type": "CODE_TYPE_TEXT",
+			    		"title": "132元双人火锅套餐",
+			    		"color": "Color010",
+			    		"notice": "使用时向服务员出示此券",
+			    		"service_phone": "020-88888888",
+			    		"description": "不可与其他优惠同享\n如需团购券发票,请在消费时向商户提出\n店内均可使用,仅限堂食",
+			    		"date_info": {
+				    		"type": "DATE_TYPE_FIX_TIME_RANGE",
+				    		"begin_timestamp": "'.$begin_timestamp.'",
+				    		"end_timestamp": "'.$end_timestamp.'"
+			    		},
+				    	"sku": {
+				    		"quantity": 500000
+				    	},
+				    	"use_limit":100,
+				    	"get_limit": 3,
+				    	"use_custom_code": false,
+				    	"bind_openid": false,
+				    	"can_share": true,
+				    	"can_give_friend": true,
+				    	"location_id_list": [
+					    	123,
+					    	12321,
+					    	345345
+				    	],
+				    	"center_title": "云货舱",
+				    	"center_sub_title": "立享优惠",
+				    	"center_app_brand_user_name": "gh_83c581f00e36@app",
+				    	"center_app_brand_pass":"pages/template/tabbar/tabbar",
+				    	"custom_url_name": "立即使用",
+				    	"custom_app_brand_user_name": "gh_83c581f00e36@app",
+				    	"custom_app_brand_pass":"pages/template/tabbar/tabbar",
+				    	"custom_url_sub_title": "前往使用",
+				    	"promotion_url_name": "更多优惠",
+				    	"promotion_url": "http://www.qq.com",
+				    	"source": "大众点评"
+	    			},
+	    			"advanced_info": {
+			    		"use_condition": {
+			    		"accept_category": "鞋类",
+			    		"reject_category": "阿迪达斯",
+			    		"can_use_with_other_discount": true
+			    		},
+			    		"abstract": {
+			    		"abstract": "微信餐厅推出多种新季菜品,期待您的光临",
+			    		"icon_url_list": [
+			    		"'.$image_url_log.'"
+			    				]
+			    		},
+			    		"text_image_list": [
+			    		{
+			    			"image_url": "'.$image_url_one.'",
+			    			"text": "此菜品精选食材,以独特的烹饪方法,最大程度地刺激食 客的味蕾"
+			    		},
+			    		{
+			    			"image_url": "'.$image_url_one.'",
+			    			"text": "此菜品迎合大众口味,老少皆宜,营养均衡"
+			    		}
+			    		],
+			    		"time_limit": [
+				    		{
+				    			"type": "MONDAY",
+				    			"begin_hour":0,
+				    			"end_hour":10,
+				    			"begin_minute":10,
+				    			"end_minute":59
+				    		},
+				    		{
+				    			"type": "HOLIDAY"
+				    		}
+			    		],
+		    			"business_service": [
+				    		"BIZ_SERVICE_FREE_WIFI",
+				    		"BIZ_SERVICE_WITH_PET",
+				    		"BIZ_SERVICE_FREE_PARK",
+				    		"BIZ_SERVICE_DELIVER"
+		    			]
+		    		},'.$memdes.'
+	    			'.$des.'
+	    		}
+	    	}
+	    }';
+		$url='https://api.weixin.qq.com/card/create?access_token='.$token;
+		$res=$this->curl_post($url, $data);
+		$card_id=json_decode($res)->card_id;
+		$ret=array();
+		$ret['cardId']=$card_id;
+		$ret['time']=time();
+		//$arr['code'] = 1434008071;
+    	$arr['timestamp'] = $ret['time'];
+   	 	$arr['ticket'] = $package['ticket'];
+    	$arr['nonce_str'] = '123';
+    	$arr['card_id'] = $card_id;
+
+	    sort($arr,SORT_STRING);
+
+	    $str = '';
+	    foreach ($arr as $v) {
+	        $str .= $v;
+	    }
+
+    	$sign = sha1($str);
+		$ret['signature']=$sign;
+		$ret['ticket']=$package['ticket'];
+		$this->api_return_json(array('url'=>$ret),1);
+		$url='https://api.weixin.qq.com/card/qrcode/create?access_token='.$token;
+		$data='{
+			"action_name": "QR_CARD",
+			"expire_seconds": 1800,
+			"action_info": {
+			"card": {
+			"card_id": "'.$card_id.'",
+			"is_unique_code": false ,
+			"outer_str":"12b"
+			  }
+			 }
+			}';
+		$res=$this->curl_post($url, $data);
+		$show_qrcode_url=json_decode($res)->show_qrcode_url;
+		//设置体验白名单,用于开发者调试用
+		$url='https://api.weixin.qq.com/card/testwhitelist/set?access_token='.$token;
+		$data='{
+		  "username": [
+		      "fcy736337824",
+		      "empcity"
+		  ]
+		 }';
+		$this->curl_post($url, $data);
+		//白名单ending...
+		if(!isset($show_qrcode_url)||empty($show_qrcode_url)){
+			exit('二维码生成失败');
+		}
+		$this->api_return_json(array('url'=>$show_qrcode_url),1);
+	}
+
+	function card_type($type){
+		//团购券
+		$data=array('groupon'=>array('card_type'=>'GROUPON','card_name'=>'groupon'),
+				'cash'=>array('card_type'=>'CASH','card_name'=>'cash'),
+				'discount'=>array('card_type'=>'DISCOUNT','card_name'=>'discount'),
+				'gift'=>array('card_type'=>'GIFT','card_name'=>'gift'),
+				'general_coupon'=>array('card_type'=>'GENERAL_COUPON','card_name'=>'general_coupon'),
+				'member_card'=>array('card_type'=>'MEMBER_CARD','card_name'=>'member_card')
+		);
+		return $data[$type];
+	}
+}

+ 261 - 0
application/web/controller/WxpayController.php

@@ -0,0 +1,261 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2018/5/19
+ * Time: 16:37
+ */
+namespace app\web\controller;
+use think\Db;
+
+header('Access-Control-Allow-Origin:https://open.weixin.qq.com');
+class WxpayController extends MainController{
+
+    public function __construct()
+    {
+        parent::__construct();
+        require_once CMF_ROOT.'vendor/wxpay/lib/WxPayApi.php';
+        require_once CMF_ROOT.'vendor/wxpay/WxPayJsApiPay.php';
+        require_once CMF_ROOT.'vendor/wxpay/log.php';
+        require_once CMF_ROOT.'vendor/wxpay/lib/WxPay.Config.php';
+    }
+
+
+    //统一下单
+    public function pay_order()
+    {
+        $id=$this->request->param('id');
+        if(!isset($id)||empty($id)){
+            $this->api_return_json(array('msg'=>'该订单id不存在')) ;
+        }
+        $order=Db::name('orders')
+        ->field('o.order_sn,o.goods_id,o.hospital_id,o.appointment_time,o.pay_price,p.title,u.wx_openid')
+        ->alias('o')
+        ->join('goods p', 'p.id=o.goods_id', 'left')
+        ->join('wx_user u', 'u.user_id=o.user_id', 'left')
+        ->where(array('o.id'=>$id,'o.is_delete'=>0))
+        ->find();
+        if(empty($order))
+        	$this->api_return_json(array('msg'=>'订单不存在'));
+        //检测库存
+        //总库存
+        $res=db('hospital_good')->where(['good_id'=>$order['goods_id'],'hospital_id'=>$order['hospital_id'],'deleted_at'=>null])->find();
+        if(empty($res))
+        	$this->api_return_json(array('msg'=>'医院疫苗信息不存在'));
+        //已预约库存
+        $has=db('orders')->where(['goods_id'=>$order['goods_id'],'hospital_id'=>$order['hospital_id'],'appointment_time'=>$order['appointment_time'],'pay_status'=>array(1,2)])->count();
+        if((int)$res['stock']<=$has)
+        	$this->api_return_json(array('msg'=>'您预约时间的疫苗库存不足,请重新换个时间预约'));
+        
+        $order_money=$order['pay_price'];
+        /*------------------------支付流程----------------------------*/
+         $tools = new \JsApiPay();
+	
+	        //②、统一下单
+	        $input = new \WxPayUnifiedOrder();
+	        $input->SetBody($order['title']);
+	        $input->SetOut_trade_no($order['order_sn']);
+	        if ($this->is_product) {
+	            $input->SetTotal_fee(((double)($order_money) * 100));
+	        } else {
+	            $input->SetTotal_fee(((double)(0.01) * 100));
+	        }
+	        //
+	        $input->SetTime_start(date("YmdHis"));
+	        $input->SetTime_expire(date("YmdHis", time() + 600));
+	        $input->SetGoods_tag('预约疫苗');
+	        $input->SetNotify_url(url('web/Notify/orderNotify','','html',true));
+	        $input->SetTrade_type("JSAPI");
+	
+	        $input->SetSpbill_create_ip(get_client_ip());
+	        $input->SetOpenid($order['wx_openid']);
+	        $orders = \WxPayApi::unifiedOrder($input);
+	
+	        $jsApiParameters = json_decode($tools->GetJsApiParameters($orders));
+        $this->api_return_json(array('pay_key' => $jsApiParameters), 1);
+    }
+    
+    //统一下单会员
+    public function pay_member_order()
+    {
+    	$id=$this->request->param('id');
+    	if(!isset($id)||empty($id)){
+    		$this->api_return_json(array('msg'=>'该订单id不存在')) ;
+    	}
+    	$order=Db::name('orders')
+    	->field('o.order_sn,o.goods_id,o.hospital_id,o.appointment_time,o.pay_price,p.title,u.wx_openid')
+    	->alias('o')
+    	->join('goods p', 'p.id=o.goods_id', 'left')
+    	->join('wx_user u', 'u.user_id=o.user_id', 'left')
+    	->where(array('o.id'=>$id,'o.is_delete'=>0))
+    	->find();
+    	if(empty($order))
+    		$this->api_return_json(array('msg'=>'订单不存在'));
+    	//检测库存
+    	//总库存
+    
+    	$order_money=$order['pay_price'];
+    	/*------------------------支付流程----------------------------*/
+    	$tools = new \JsApiPay();
+    
+    	//②、统一下单
+    	$input = new \WxPayUnifiedOrder();
+    	$input->SetBody('会员服务');
+    	$input->SetOut_trade_no($order['order_sn']);
+    	if ($this->is_product) {
+    		$input->SetTotal_fee(((double)($order_money) * 100));
+    	} else {
+    		$input->SetTotal_fee(((double)(0.01) * 100));
+    	}
+    	//
+    	$input->SetTime_start(date("YmdHis"));
+    	$input->SetTime_expire(date("YmdHis", time() + 600));
+    	$input->SetGoods_tag('会员');
+    	$input->SetNotify_url(url('web/Notify/memberOrderNotify','','html',true));
+    	$input->SetTrade_type("JSAPI");
+    
+    	$input->SetSpbill_create_ip(get_client_ip());
+    	$input->SetOpenid($order['wx_openid']);
+    	$orders = \WxPayApi::unifiedOrder($input);
+    
+    	$jsApiParameters = json_decode($tools->GetJsApiParameters($orders));
+    	$this->api_return_json(array('pay_key' => $jsApiParameters), 1);
+    }
+
+    //申请退款
+	public function apply_refund($out_trade_no='t_2018060218250996720000000001',$total_fee=1,$refund_fee=1){
+		/*------------------------退款流程----------------------------*/
+        file_put_contents(CMF_ROOT . "data/refund_log/refund_log.txt", date('Y-m-d H:i:s')."\n进入退款,订单号:".$out_trade_no."订单金额:".$total_fee."退款金额:".$refund_fee."\n", FILE_APPEND);
+        $input = new \WxPayRefund();
+			$input->SetOut_trade_no($out_trade_no);
+			$input->SetTotal_fee($total_fee);
+			$input->SetRefund_fee($refund_fee);
+		    $input->SetOut_refund_no('1529522091'.date("YmdHis"));
+		    $input->SetOp_user_id('1529522091');
+			$result=\WxPayApi::refund($input);
+			file_put_contents(CMF_ROOT . "data/refund_log/refund_log.txt", date('Y-m-d H:i:s')."\n退款的结果:" . json_encode($result).'退款单号:'.$out_trade_no.'退款金额:'.$refund_fee.'付款金额:'.$total_fee."\n", FILE_APPEND);
+			
+			if($result['return_code']=='SUCCESS' && $result['result_code']=='SUCCESS'){
+				return array('return_code'=>$result['return_code'],'result_code'=>$result['result_code']);
+			}else{
+				return array('return_code'=>$result['return_code'],'result_code'=>$result['result_code'],'err_code_des'=>$result['err_code_des'],'return_msg'=>$result['return_msg']);
+			}
+			exit();
+	}
+	
+	//发红包
+	public function sendRedpackage($money,$user_id){
+		header("content-type:text/html;charset=utf-8");
+		$url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack';
+		$user=Db::name('wx_user')->field('wx_openid2 as wx_openid')->where(array('user_id'=>$user_id))->find();
+		$params = array();
+		$account_key = \WxPayConfig::KEY;
+		$params['nonce_str'] = md5(time() . rand(1, 9999));
+		$params['wxappid'] = config('mp_wxconfig')['appid'];//wxd6053c3091d8aaaf
+		$params['mch_id'] = \WxPayConfig::MCHID;
+		$params['mch_billno'] = 'red'.date("YmdHis",time()).mt_rand(10,99);
+		$params['send_name'] = '云货舱';
+		$params['scene_id']='PRODUCT_5';
+		$params['re_openid'] =$user['wx_openid'];//
+		$params['total_amount'] =(int)$money*100;//分
+		$params['total_num'] = 1;
+		$params['wishing'] = '恭喜您提现成功';
+		$params['client_ip'] = $this->real_ip();
+		$params['act_name'] = '提现';
+		$params['remark'] = '由于商户号3个月交易记录才能开放提现,故先采取发红包方式';
+		ksort($params);
+		$param_str = '';
+		foreach ($params as $key => $param) {
+			$param_str .= $key . '=' . $param . '&';
+		}
+		$sign = strtoupper(md5($param_str . 'key=' . $account_key));
+		$params['sign'] = $sign;
+		ksort($params);
+		$xml = $this->ToXml($params);
+		//$result = $this->curl_post_xml($url, $xml);
+		//  echo BASEPATH.'../wx_pay/cert/apiclient_key.pem';exit;
+		$result = \WxPayApi::postXmlCurl($xml, $url, true);
+		$result = $this->FromXml($result);
+		try{
+			if($result['result_code']=='SUCCESS')
+				return true;
+			else 
+				return false;
+		}catch (\Exception $e){
+			return false;
+		}
+		
+	}
+	
+	 /**
+	 * 获得用户的真实IP地址
+	 *
+	 * @access  public
+	 * @return  string
+	 */
+	function real_ip()
+	{
+		static $realip = NULL;
+	
+		if ($realip !== NULL)
+		{
+			return $realip;
+		}
+	
+		if (isset($_SERVER))
+		{
+			if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
+			{
+				$arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
+	
+				/* 取X-Forwarded-For中第一个非unknown的有效IP字符串 */
+				foreach ($arr AS $ip)
+				{
+					$ip = trim($ip);
+	
+					if ($ip != 'unknown')
+					{
+						$realip = $ip;
+	
+						break;
+					}
+				}
+			}
+			elseif (isset($_SERVER['HTTP_CLIENT_IP']))
+			{
+				$realip = $_SERVER['HTTP_CLIENT_IP'];
+			}
+			else
+			{
+				if (isset($_SERVER['REMOTE_ADDR']))
+				{
+					$realip = $_SERVER['REMOTE_ADDR'];
+				}
+				else
+				{
+					$realip = '0.0.0.0';
+				}
+			}
+		}
+		else
+		{
+			if (getenv('HTTP_X_FORWARDED_FOR'))
+			{
+				$realip = getenv('HTTP_X_FORWARDED_FOR');
+			}
+			elseif (getenv('HTTP_CLIENT_IP'))
+			{
+				$realip = getenv('HTTP_CLIENT_IP');
+			}
+			else
+			{
+				$realip = getenv('REMOTE_ADDR');
+			}
+		}
+	
+		preg_match("/[\d\.]{7,15}/", $realip, $onlineip);
+		$realip = !empty($onlineip[0]) ? $onlineip[0] : '0.0.0.0';
+	
+		return $realip;
+	}
+}

+ 168 - 0
application/web/view/map/index.html

@@ -0,0 +1,168 @@
+<!doctype html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
+    <title>轨迹回放</title>
+    <link rel="stylesheet" href="https://a.amap.com/jsapi_demos/static/demo-center/css/demo-center.css"/>
+    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
+    <style>
+        html, body, #container {
+            height: 100%;
+            width: 100%;
+        }
+
+        .input-card .btn{
+            margin-right: 1.2rem;
+            width: 9rem;
+        }
+
+        .input-card .btn:last-child{
+            margin-right: 0;
+        }
+    </style>
+</head>
+<body>
+<div id="container"></div>
+<div class="input-card">
+    <h4>轨迹回放控制</h4>
+    <div class="input-item">
+        <input type="button" class="btn" value="开始动画" id="start" onclick="startAnimation()"/>
+        <input type="button" class="btn" value="暂停动画" id="pause" onclick="pauseAnimation()"/>
+    </div>
+    <div class="input-item">
+        <input type="button" class="btn" value="继续动画" id="resume" onclick="resumeAnimation()"/>
+        <input type="button" class="btn" value="停止动画" id="stop" onclick="stopAnimation()"/>
+    </div>
+</div>
+<script type="text/javascript" src="https://webapi.amap.com/maps?v=1.4.15&key=093a59e5f88f22a7fcee1f4708b98878"></script>
+<script>
+    var marker, lineArr = [
+       // [116.478935,39.997761],[116.478939,39.997825],[116.478912,39.998549],[116.478912,39.998549],[116.478998,39.998555],[116.478998,39.998555],[116.479282,39.99856],[116.479658,39.998528],[116.480151,39.998453],[116.480784,39.998302],[116.480784,39.998302],[116.481149,39.998184],[116.481573,39.997997],[116.481863,39.997846],[116.482072,39.997718],[116.482362,39.997718],[116.483633,39.998935],[116.48367,39.998968],[116.484648,39.999861]
+    ];
+
+    var map = new AMap.Map("container", {
+        resizeEnable: true,
+        // center: [116.397428, 39.90923],
+        zoom: 17
+    });
+
+    marker = new AMap.Marker({
+        map: map,
+      //  position: [116.478935,39.997761],
+        icon: "https://webapi.amap.com/images/car.png",
+        offset: new AMap.Pixel(-26, -13),
+        autoRotation: true,
+        angle:-90,
+    });
+
+    // 绘制轨迹
+    var polyline = new AMap.Polyline({
+        map: map,
+     //   path: lineArr,
+        showDir:true,
+        strokeColor: "#28F",  //线颜色
+        // strokeOpacity: 1,     //线透明度
+        strokeWeight: 6,      //线宽
+        // strokeStyle: "solid"  //线样式
+    });
+
+    var passedPolyline = new AMap.Polyline({
+        map: map,
+        // path: lineArr,
+        strokeColor: "#AF5",  //线颜色
+        // strokeOpacity: 1,     //线透明度
+        strokeWeight: 6,      //线宽
+        // strokeStyle: "solid"  //线样式
+    });
+
+
+    marker.on('moving', function (e) {
+         //marker.setPosition(new AMap.LngLat(e.passedPath[0].lat,e.passedPath[0].lng));
+       // passedPolyline.setPath(e.passedPath);
+    });
+
+    map.setFitView();
+
+    function startAnimation () {
+        // test();
+
+        // marker.moveAlong(lineArr, 200);
+        // marker.moveTo([116.478912,39.998549],200)
+        // marker.moveTo([116.478998,39.998555],200)
+    }
+
+    function pauseAnimation () {
+        marker.pauseMove();
+    }
+
+    function resumeAnimation () {
+        marker.resumeMove();
+    }
+
+    function stopAnimation () {
+        marker.stopMove();
+    }
+
+    // AMap.event.addListener(marker, 'dragging', function(e){
+    //     var lat = e.lnglat.lat,
+    //         lng = e.lnglat.lng;
+    //     marker.setPosition(new AMap.LngLat(lng,lat));
+    //
+    // });
+
+    setInterval("test()",1000);
+
+
+    var i=0;
+    var test2=new Array();
+    function test() {
+        map.setZoom(map.getZoom());
+        $.ajax({
+            url:'/web/map/getLocation',
+           // data:{'user_id':"{$user_id}"},
+            success:function (res) {
+
+               // res[i];
+               //  a=new AMap.LngLat(res[0][0],res[0][1])
+
+
+                // console.log()
+                // map.setCenter(new AMap.LngLat(res[0].lat,res[0].lat));
+
+                //passedPolyline.setPath(res);
+                // console.log(res[i]);
+                // console.log(new AMap.LngLat(res[0][0],res[0][1]));
+                if(res!=null){
+                    if(i<res.length){
+                        console.log(res[i]);
+
+                        //marker.setPosition(new AMap.LngLat(res[i].lng,res[i].lat));
+                        if(i==0){
+                            marker.setPosition(res[0]);
+                        }
+
+                        console.log(res.length-1);
+                        //  map.setCenter(new AMap.LngLat(res[i].lng,res[i].lat));
+                        map.setCenter(res[i]);
+
+                        //marker.moveTo([res[i].lng,res[i].lat],1000);
+                        marker.moveTo([res[i][0],res[i][1]],200);
+                        test2.push(res[i]);
+                        passedPolyline.setPath(test2);
+
+                        i+=1;
+
+                    }
+
+                }
+
+
+            }
+        })
+
+    }
+</script>
+</body>
+</html>

+ 74 - 0
application/web/view/map/test.html

@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
+    <title>位置经纬度 + 步行路线规划</title>
+    <style type="text/css">
+        html,
+        body,
+        #container {
+            width: 100%;
+            height: 100%;
+        }
+        #panel {
+            position: fixed;
+            background-color: white;
+            max-height: 90%;
+            overflow-y: auto;
+            top: 10px;
+            right: 10px;
+            width: 280px;
+        }
+        #panel .amap-call {
+            background-color: #009cf9;
+            border-top-left-radius: 4px;
+            border-top-right-radius: 4px;
+        }
+        #panel .amap-lib-walking {
+            border-bottom-left-radius: 4px;
+            border-bottom-right-radius: 4px;
+            overflow: hidden;
+        }
+    </style>
+    <link rel="stylesheet" href="https://a.amap.com/jsapi_demos/static/demo-center/css/demo-center.css" />
+    <script type="text/javascript" src="https://webapi.amap.com/maps?v=1.4.15&key=116471cff4206df879cc353c2b63c91e&plugin=AMap.Walking"></script>
+    <script src="https://a.amap.com/jsapi_demos/static/demo-center/js/demoutils.js"></script>
+    <script type="text/javascript" src="https://cache.amap.com/lbs/static/addToolbar.js"></script>
+</head>
+<body>
+<div id="container"></div>
+<div id="panel"></div>
+<script type="text/javascript">
+    var map = new AMap.Map("container", {
+        resizeEnable: true,
+        center: [116.397428, 39.90923],//地图中心点
+        zoom: 13 //地图显示的缩放级别
+    });
+    //步行导航
+    var walking = new AMap.Walking({
+        map: map,
+        panel: "panel"
+    });
+    //根据起终点坐标规划步行路线
+    walking.search([116.399028, 39.845042], [116.436281, 39.880719], function(status, result) {
+        // result即是对应的步行路线数据信息,相关数据结构文档请参考  https://lbs.amap.com/api/javascript-api/reference/route-search#m_WalkingResult
+        if (status === 'complete') {
+            log.success('绘制步行路线完成')
+        } else {
+            log.error('步行路线数据查询失败' + result)
+        }
+    });
+    var t1=new Array();
+    AMap.event.addListener(walking,'complete',function (v) {
+        v.routes[0].steps.forEach(function (v1,k) {
+            v1.path.forEach(function (v2,k1) {
+                console.log([v2.lng,v2.lat]);
+            })
+        });
+    })
+    // console.log(t1);
+</script>
+</body>
+</html>

+ 69 - 0
application/web/view/test/v.html

@@ -0,0 +1,69 @@
+<html>
+<head>
+    <meta http-equiv="content-type" content="text/html;charset=utf-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1"/>
+    <title>微信支付样例-支付</title>
+    <script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.3.0.js"></script>
+
+    <script type="text/javascript">
+        window.onload=function () {
+            wx.miniProgram.redirectTo({url: '/pages/success/success'})
+
+        }
+        //调用微信JS api 支付
+        function jsApiCall()
+        {
+            WeixinJSBridge.invoke(
+                'getBrandWCPayRequest',{"appId":"wx17b60e8bcb1f9018","timeStamp":"1564468116","nonceStr":"5d3fe394419cc","package":"prepay_id=wx30142836212143ed7724a4ac1121113500","signType":"MD5","paySign":"46A4A0E8DD5357FC61FF710CA33FF74D"},
+
+            function(res){
+                if(res.err_msg == "get_brand_wcpay_request:ok" ){
+                    wx.miniProgram.redirectTo({url: '/pages/success/success'})
+                }
+                WeixinJSBridge.log(res.err_msg);
+                alert(res.err_code+res.err_desc+res.err_msg);
+                }
+
+            );
+        }
+
+        function callpay()
+        {
+            if (typeof WeixinJSBridge == "undefined"){
+                if( document.addEventListener ){
+                    document.addEventListener('WeixinJSBridgeReady', jsApiCall, false);
+                }else if (document.attachEvent){
+                    document.attachEvent('WeixinJSBridgeReady', jsApiCall);
+                    document.attachEvent('onWeixinJSBridgeReady', jsApiCall);
+                }
+            }else{
+                jsApiCall();
+            }
+        }
+    </script>
+    <script type="text/javascript">
+
+
+        window.onload = function(){
+            if (typeof WeixinJSBridge == "undefined"){
+                if( document.addEventListener ){
+                    document.addEventListener('WeixinJSBridgeReady', editAddress, false);
+                }else if (document.attachEvent){
+                    document.attachEvent('WeixinJSBridgeReady', editAddress);
+                    document.attachEvent('onWeixinJSBridgeReady', editAddress);
+                }
+            }else{
+                /*editAddress();*/
+            }
+        };
+
+    </script>
+</head>
+<body>
+<br/>
+<font color="#9ACD32"><b>该笔订单支付金额为<span style="color:#f00;font-size:50px">1分</span>钱</b></font><br/><br/>
+<div align="center">
+    <button style="width:210px; height:50px; border-radius: 15px;background-color:#FE6714; border:0px #FE6714 solid; cursor: pointer;  color:white;  font-size:16px;" type="button" onclick="callpay()" >立即支付</button>
+</div>
+</body>
+</html>

+ 26 - 0
build.php

@@ -0,0 +1,26 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+return [
+    // 生成应用公共文件
+    '__file__' => ['common.php'],
+
+    // 定义demo模块的自动生成 (按照实际定义的文件名生成)
+    'demo'     => [
+        '__file__'   => ['common.php'],
+        '__dir__'    => ['behavior', 'controller', 'model', 'view'],
+        'controller' => ['Index', 'Test', 'UserType'],
+        'model'      => ['User', 'UserType'],
+        'view'       => ['index/index'],
+    ],
+
+    // 其他更多的模块定义
+];

+ 35 - 0
composer.json

@@ -0,0 +1,35 @@
+{
+    "name": "topthink/think",
+    "description": "the new thinkphp framework",
+    "type": "project",
+    "keywords": [
+        "framework",
+        "thinkphp",
+        "ORM"
+    ],
+    "homepage": "http://thinkphp.cn/",
+    "license": "Apache-2.0",
+    "authors": [
+        {
+            "name": "liu21st",
+            "email": "liu21st@gmail.com"
+        }
+    ],
+    "require": {
+        "php": ">=5.6.0",
+        "topthink/framework": "5.1.*",
+        "firebase/php-jwt": "^5.2",
+        "phpoffice/phpspreadsheet": "^1.14"
+    },
+    "autoload": {
+        "psr-4": {
+            "app\\": "application"
+        }
+    },
+    "extra": {
+        "think-path": "thinkphp"
+    },
+    "config": {
+        "preferred-install": "dist"
+    }
+}

+ 869 - 0
composer.lock

@@ -0,0 +1,869 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "2025fa5ef1f1aab5ad86076a8f9f4849",
+    "packages": [
+        {
+            "name": "firebase/php-jwt",
+            "version": "v5.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/firebase/php-jwt.git",
+                "reference": "feb0e820b8436873675fd3aca04f3728eb2185cb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/firebase/php-jwt/zipball/feb0e820b8436873675fd3aca04f3728eb2185cb",
+                "reference": "feb0e820b8436873675fd3aca04f3728eb2185cb",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": ">=4.8 <=9"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Firebase\\JWT\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Neuman Vong",
+                    "email": "neuman+pear@twilio.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Anant Narayanan",
+                    "email": "anant@php.net",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
+            "homepage": "https://github.com/firebase/php-jwt",
+            "keywords": [
+                "jwt",
+                "php"
+            ],
+            "time": "2020-03-25T18:49:23+00:00"
+        },
+        {
+            "name": "maennchen/zipstream-php",
+            "version": "2.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/maennchen/ZipStream-PHP.git",
+                "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/c4c5803cc1f93df3d2448478ef79394a5981cc58",
+                "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "myclabs/php-enum": "^1.5",
+                "php": ">= 7.1",
+                "psr/http-message": "^1.0",
+                "symfony/polyfill-mbstring": "^1.0"
+            },
+            "require-dev": {
+                "ext-zip": "*",
+                "guzzlehttp/guzzle": ">= 6.3",
+                "mikey179/vfsstream": "^1.6",
+                "phpunit/phpunit": ">= 7.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "ZipStream\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Paul Duncan",
+                    "email": "pabs@pablotron.org"
+                },
+                {
+                    "name": "Jonatan Männchen",
+                    "email": "jonatan@maennchen.ch"
+                },
+                {
+                    "name": "Jesse Donat",
+                    "email": "donatj@gmail.com"
+                },
+                {
+                    "name": "András Kolesár",
+                    "email": "kolesar@kolesar.hu"
+                }
+            ],
+            "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
+            "keywords": [
+                "stream",
+                "zip"
+            ],
+            "time": "2020-05-30T13:11:16+00:00"
+        },
+        {
+            "name": "markbaker/complex",
+            "version": "1.4.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/MarkBaker/PHPComplex.git",
+                "reference": "8eaa40cceec7bf0518187530b2e63871be661b72"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/8eaa40cceec7bf0518187530b2e63871be661b72",
+                "reference": "8eaa40cceec7bf0518187530b2e63871be661b72",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^5.6.0|^7.0.0"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
+                "phpcompatibility/php-compatibility": "^9.0",
+                "phpdocumentor/phpdocumentor": "2.*",
+                "phploc/phploc": "2.*",
+                "phpmd/phpmd": "2.*",
+                "phpunit/phpunit": "^4.8.35|^5.4.0",
+                "sebastian/phpcpd": "2.*",
+                "squizlabs/php_codesniffer": "^3.4.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Complex\\": "classes/src/"
+                },
+                "files": [
+                    "classes/src/functions/abs.php",
+                    "classes/src/functions/acos.php",
+                    "classes/src/functions/acosh.php",
+                    "classes/src/functions/acot.php",
+                    "classes/src/functions/acoth.php",
+                    "classes/src/functions/acsc.php",
+                    "classes/src/functions/acsch.php",
+                    "classes/src/functions/argument.php",
+                    "classes/src/functions/asec.php",
+                    "classes/src/functions/asech.php",
+                    "classes/src/functions/asin.php",
+                    "classes/src/functions/asinh.php",
+                    "classes/src/functions/atan.php",
+                    "classes/src/functions/atanh.php",
+                    "classes/src/functions/conjugate.php",
+                    "classes/src/functions/cos.php",
+                    "classes/src/functions/cosh.php",
+                    "classes/src/functions/cot.php",
+                    "classes/src/functions/coth.php",
+                    "classes/src/functions/csc.php",
+                    "classes/src/functions/csch.php",
+                    "classes/src/functions/exp.php",
+                    "classes/src/functions/inverse.php",
+                    "classes/src/functions/ln.php",
+                    "classes/src/functions/log2.php",
+                    "classes/src/functions/log10.php",
+                    "classes/src/functions/negative.php",
+                    "classes/src/functions/pow.php",
+                    "classes/src/functions/rho.php",
+                    "classes/src/functions/sec.php",
+                    "classes/src/functions/sech.php",
+                    "classes/src/functions/sin.php",
+                    "classes/src/functions/sinh.php",
+                    "classes/src/functions/sqrt.php",
+                    "classes/src/functions/tan.php",
+                    "classes/src/functions/tanh.php",
+                    "classes/src/functions/theta.php",
+                    "classes/src/operations/add.php",
+                    "classes/src/operations/subtract.php",
+                    "classes/src/operations/multiply.php",
+                    "classes/src/operations/divideby.php",
+                    "classes/src/operations/divideinto.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mark Baker",
+                    "email": "mark@lange.demon.co.uk"
+                }
+            ],
+            "description": "PHP Class for working with complex numbers",
+            "homepage": "https://github.com/MarkBaker/PHPComplex",
+            "keywords": [
+                "complex",
+                "mathematics"
+            ],
+            "time": "2020-03-11T20:15:49+00:00"
+        },
+        {
+            "name": "markbaker/matrix",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/MarkBaker/PHPMatrix.git",
+                "reference": "5348c5a67e3b75cd209d70103f916a93b1f1ed21"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/5348c5a67e3b75cd209d70103f916a93b1f1ed21",
+                "reference": "5348c5a67e3b75cd209d70103f916a93b1f1ed21",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^5.6.0|^7.0.0"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "dev-master",
+                "phpcompatibility/php-compatibility": "dev-master",
+                "phploc/phploc": "^4",
+                "phpmd/phpmd": "dev-master",
+                "phpunit/phpunit": "^5.7",
+                "sebastian/phpcpd": "^3.0",
+                "squizlabs/php_codesniffer": "^3.0@dev"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Matrix\\": "classes/src/"
+                },
+                "files": [
+                    "classes/src/functions/adjoint.php",
+                    "classes/src/functions/antidiagonal.php",
+                    "classes/src/functions/cofactors.php",
+                    "classes/src/functions/determinant.php",
+                    "classes/src/functions/diagonal.php",
+                    "classes/src/functions/identity.php",
+                    "classes/src/functions/inverse.php",
+                    "classes/src/functions/minors.php",
+                    "classes/src/functions/trace.php",
+                    "classes/src/functions/transpose.php",
+                    "classes/src/operations/add.php",
+                    "classes/src/operations/directsum.php",
+                    "classes/src/operations/subtract.php",
+                    "classes/src/operations/multiply.php",
+                    "classes/src/operations/divideby.php",
+                    "classes/src/operations/divideinto.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mark Baker",
+                    "email": "mark@lange.demon.co.uk"
+                }
+            ],
+            "description": "PHP Class for working with matrices",
+            "homepage": "https://github.com/MarkBaker/PHPMatrix",
+            "keywords": [
+                "mathematics",
+                "matrix",
+                "vector"
+            ],
+            "time": "2019-10-06T11:29:25+00:00"
+        },
+        {
+            "name": "myclabs/php-enum",
+            "version": "1.7.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/myclabs/php-enum.git",
+                "reference": "5f36467c7a87e20fbdc51e524fd8f9d1de80187c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/myclabs/php-enum/zipball/5f36467c7a87e20fbdc51e524fd8f9d1de80187c",
+                "reference": "5f36467c7a87e20fbdc51e524fd8f9d1de80187c",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-json": "*",
+                "php": ">=7.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7",
+                "squizlabs/php_codesniffer": "1.*",
+                "vimeo/psalm": "^3.8"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "MyCLabs\\Enum\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP Enum contributors",
+                    "homepage": "https://github.com/myclabs/php-enum/graphs/contributors"
+                }
+            ],
+            "description": "PHP Enum implementation",
+            "homepage": "http://github.com/myclabs/php-enum",
+            "keywords": [
+                "enum"
+            ],
+            "time": "2020-02-14T08:15:52+00:00"
+        },
+        {
+            "name": "phpoffice/phpspreadsheet",
+            "version": "1.14.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
+                "reference": "2383aad5689778470491581442aab38cec41bf1d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/2383aad5689778470491581442aab38cec41bf1d",
+                "reference": "2383aad5689778470491581442aab38cec41bf1d",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-dom": "*",
+                "ext-fileinfo": "*",
+                "ext-gd": "*",
+                "ext-iconv": "*",
+                "ext-libxml": "*",
+                "ext-mbstring": "*",
+                "ext-simplexml": "*",
+                "ext-xml": "*",
+                "ext-xmlreader": "*",
+                "ext-xmlwriter": "*",
+                "ext-zip": "*",
+                "ext-zlib": "*",
+                "maennchen/zipstream-php": "^2.1",
+                "markbaker/complex": "^1.4",
+                "markbaker/matrix": "^1.2",
+                "php": "^7.2",
+                "psr/http-client": "^1.0",
+                "psr/http-factory": "^1.0",
+                "psr/simple-cache": "^1.0"
+            },
+            "require-dev": {
+                "dompdf/dompdf": "^0.8.5",
+                "friendsofphp/php-cs-fixer": "^2.16",
+                "jpgraph/jpgraph": "^4.0",
+                "mpdf/mpdf": "^8.0",
+                "phpcompatibility/php-compatibility": "^9.3",
+                "phpunit/phpunit": "^8.5",
+                "squizlabs/php_codesniffer": "^3.5",
+                "tecnickcom/tcpdf": "^6.3"
+            },
+            "suggest": {
+                "dompdf/dompdf": "Option for rendering PDF with PDF Writer",
+                "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
+                "mpdf/mpdf": "Option for rendering PDF with PDF Writer",
+                "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Maarten Balliauw",
+                    "homepage": "https://blog.maartenballiauw.be"
+                },
+                {
+                    "name": "Mark Baker",
+                    "homepage": "https://markbakeruk.net"
+                },
+                {
+                    "name": "Franck Lefevre",
+                    "homepage": "https://rootslabs.net"
+                },
+                {
+                    "name": "Erik Tilt"
+                },
+                {
+                    "name": "Adrien Crivelli"
+                }
+            ],
+            "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
+            "homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
+            "keywords": [
+                "OpenXML",
+                "excel",
+                "gnumeric",
+                "ods",
+                "php",
+                "spreadsheet",
+                "xls",
+                "xlsx"
+            ],
+            "time": "2020-07-19T09:51:35+00:00"
+        },
+        {
+            "name": "psr/http-client",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-client.git",
+                "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
+                "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.0 || ^8.0",
+                "psr/http-message": "^1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Client\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP clients",
+            "homepage": "https://github.com/php-fig/http-client",
+            "keywords": [
+                "http",
+                "http-client",
+                "psr",
+                "psr-18"
+            ],
+            "time": "2020-06-29T06:28:15+00:00"
+        },
+        {
+            "name": "psr/http-factory",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-factory.git",
+                "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
+                "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.0.0",
+                "psr/http-message": "^1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for PSR-7 HTTP message factories",
+            "keywords": [
+                "factory",
+                "http",
+                "message",
+                "psr",
+                "psr-17",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "time": "2019-04-30T12:38:16+00:00"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "time": "2016-08-06T14:39:51+00:00"
+        },
+        {
+            "name": "psr/simple-cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/simple-cache.git",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\SimpleCache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for simple caching",
+            "keywords": [
+                "cache",
+                "caching",
+                "psr",
+                "psr-16",
+                "simple-cache"
+            ],
+            "time": "2017-10-23T01:57:42+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.18.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a",
+                "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.18-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2020-07-14T12:35:20+00:00"
+        },
+        {
+            "name": "topthink/framework",
+            "version": "v5.1.39",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/framework.git",
+                "reference": "5762858f3d58faafb3a39427f8788884b2927007"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/framework/zipball/5762858f3d58faafb3a39427f8788884b2927007",
+                "reference": "5762858f3d58faafb3a39427f8788884b2927007",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.6.0",
+                "topthink/think-installer": "2.*"
+            },
+            "require-dev": {
+                "johnkary/phpunit-speedtrap": "^1.0",
+                "mikey179/vfsstream": "~1.6",
+                "phpdocumentor/reflection-docblock": "^2.0",
+                "phploc/phploc": "2.*",
+                "phpunit/phpunit": "^5.0|^6.0",
+                "sebastian/phpcpd": "2.*",
+                "squizlabs/php_codesniffer": "2.*"
+            },
+            "type": "think-framework",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                },
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "the new thinkphp framework",
+            "homepage": "http://thinkphp.cn/",
+            "keywords": [
+                "framework",
+                "orm",
+                "thinkphp"
+            ],
+            "time": "2019-11-17T23:22:02+00:00"
+        },
+        {
+            "name": "topthink/think-installer",
+            "version": "v2.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-installer.git",
+                "reference": "f5400a12c60e513911aef41fe443fa6920952675"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-installer/zipball/f5400a12c60e513911aef41fe443fa6920952675",
+                "reference": "f5400a12c60e513911aef41fe443fa6920952675",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "composer-plugin-api": "^1.0"
+            },
+            "require-dev": {
+                "composer/composer": "1.0.*@dev"
+            },
+            "type": "composer-plugin",
+            "extra": {
+                "class": "think\\composer\\Plugin"
+            },
+            "autoload": {
+                "psr-4": {
+                    "think\\composer\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "time": "2018-05-11T06:45:42+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=5.6.0"
+    },
+    "platform-dev": []
+}

+ 147 - 0
config/app.php

@@ -0,0 +1,147 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// +----------------------------------------------------------------------
+// | 应用设置
+// +----------------------------------------------------------------------
+
+return [
+    // 应用名称
+    'app_name'               => '',
+    // 应用地址
+    'app_host'               => '',
+    // 应用调试模式
+    'app_debug'              => true,
+    // 应用Trace
+    'app_trace'              => false,
+    // 是否支持多模块
+    'app_multi_module'       => true,
+    // 入口自动绑定模块
+    'auto_bind_module'       => false,
+    // 注册的根命名空间
+    'root_namespace'         => [],
+    // 默认输出类型
+    'default_return_type'    => 'html',
+    // 默认AJAX 数据返回格式,可选json xml ...
+    'default_ajax_return'    => 'json',
+    // 默认JSONP格式返回的处理方法
+    'default_jsonp_handler'  => 'jsonpReturn',
+    // 默认JSONP处理方法
+    'var_jsonp_handler'      => 'callback',
+    // 默认时区
+    'default_timezone'       => 'Asia/Shanghai',
+    // 是否开启多语言
+    'lang_switch_on'         => false,
+    // 默认全局过滤方法 用逗号分隔多个
+    'default_filter'         => '',
+    // 默认语言
+    'default_lang'           => 'zh-cn',
+
+    // 应用类库后缀
+    'class_suffix'           => true,
+    // 控制器类后缀
+    'controller_suffix'      => "Controller",
+
+    // +----------------------------------------------------------------------
+    // | 模块设置
+    // +----------------------------------------------------------------------
+
+    // 默认模块名
+    'default_module'         => 'web',
+    // 禁止访问模块
+    'deny_module_list'       => ['common'],
+    // 默认控制器名
+    'default_controller'     => 'Index',
+    // 默认操作名
+    'default_action'         => 'index',
+    // 默认验证器
+    'default_validate'       => '',
+    // 默认的空模块名
+    'empty_module'           => '',
+    // 默认的空控制器名
+    'empty_controller'       => 'Error',
+    // 操作方法前缀
+    'use_action_prefix'      => false,
+    // 操作方法后缀
+    'action_suffix'          => '',
+    // 自动搜索控制器
+    'controller_auto_search' => false,
+
+    // +----------------------------------------------------------------------
+    // | URL设置
+    // +----------------------------------------------------------------------
+
+    // PATHINFO变量名 用于兼容模式
+    'var_pathinfo'           => 's',
+    // 兼容PATH_INFO获取
+    'pathinfo_fetch'         => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
+    // pathinfo分隔符
+    'pathinfo_depr'          => '/',
+    // HTTPS代理标识
+    'https_agent_name'       => '',
+    // IP代理获取标识
+    'http_agent_ip'          => 'X-REAL-IP',
+    // URL伪静态后缀
+    'url_html_suffix'        => 'html',
+    // URL普通方式参数 用于自动生成
+    'url_common_param'       => false,
+    // URL参数方式 0 按名称成对解析 1 按顺序解析
+    'url_param_type'         => 0,
+    // 是否开启路由延迟解析
+    'url_lazy_route'         => false,
+    // 是否强制使用路由
+    'url_route_must'         => false,
+    // 合并路由规则
+    'route_rule_merge'       => false,
+    // 路由是否完全匹配
+    'route_complete_match'   => false,
+    // 使用注解路由
+    'route_annotation'       => false,
+    // 域名根,如thinkphp.cn
+    'url_domain_root'        => '',
+    // 是否自动转换URL中的控制器和操作名
+    'url_convert'            => true,
+    // 默认的访问控制器层
+    'url_controller_layer'   => 'controller',
+    // 表单请求类型伪装变量
+    'var_method'             => '_method',
+    // 表单ajax伪装变量
+    'var_ajax'               => '_ajax',
+    // 表单pjax伪装变量
+    'var_pjax'               => '_pjax',
+    // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
+    'request_cache'          => false,
+    // 请求缓存有效期
+    'request_cache_expire'   => null,
+    // 全局请求缓存排除规则
+    'request_cache_except'   => [],
+    // 是否开启路由缓存
+    'route_check_cache'      => false,
+    // 路由缓存的Key自定义设置(闭包),默认为当前URL和请求类型的md5
+    'route_check_cache_key'  => '',
+    // 路由缓存类型及参数
+    'route_cache_option'     => [],
+
+    // 默认跳转页面对应的模板文件
+    'dispatch_success_tmpl'  => Env::get('think_path') . 'tpl/dispatch_jump.tpl',
+    'dispatch_error_tmpl'    => Env::get('think_path') . 'tpl/dispatch_jump.tpl',
+
+    // 异常页面的模板文件
+    'exception_tmpl'         => Env::get('think_path') . 'tpl/think_exception.tpl',
+
+    // 错误显示信息,非调试模式有效
+    'error_message'          => '页面错误!请稍后再试~',
+    // 显示错误信息
+    'show_error_msg'         => false,
+    // 异常处理handle类 留空使用 \think\exception\Handle
+    'exception_handle'       => '',
+
+];

+ 25 - 0
config/cache.php

@@ -0,0 +1,25 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// +----------------------------------------------------------------------
+// | 缓存设置
+// +----------------------------------------------------------------------
+
+return [
+    // 驱动方式
+    'type'   => 'File',
+    // 缓存保存目录
+    'path'   => '',
+    // 缓存前缀
+    'prefix' => '',
+    // 缓存有效期 0表示永久缓存
+    'expire' => 0,
+];

+ 20 - 0
config/console.php

@@ -0,0 +1,20 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// +----------------------------------------------------------------------
+// | 控制台配置
+// +----------------------------------------------------------------------
+return [
+    'name'      => 'Think Console',
+    'version'   => '0.1',
+    'user'      => null,
+    'auto_path' => env('app_path') . 'command' . DIRECTORY_SEPARATOR,
+];

+ 30 - 0
config/cookie.php

@@ -0,0 +1,30 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// +----------------------------------------------------------------------
+// | Cookie设置
+// +----------------------------------------------------------------------
+return [
+    // cookie 名称前缀
+    'prefix'    => '',
+    // cookie 保存时间
+    'expire'    => 0,
+    // cookie 保存路径
+    'path'      => '/',
+    // cookie 有效域名
+    'domain'    => '',
+    //  cookie 启用安全传输
+    'secure'    => false,
+    // httponly设置
+    'httponly'  => '',
+    // 是否使用 setcookie
+    'setcookie' => true,
+];

+ 38 - 0
config/database.php

@@ -0,0 +1,38 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+/**
+ * 配置文件
+ */
+
+return [
+    // 数据库类型
+    'type'     => 'sqlsrv',
+    // 服务器地址
+    'hostname' => 'localhost',
+    // 数据库名
+    'database' => 'first',
+    // 用户名
+    'username' => 'sa',
+    // 密码
+    'password' => 'dream1008',//密码
+    // 端口
+    'hostport' => '1433',
+    // 数据库编码默认采用utf8
+    'charset'  => 'utf8',
+    /* 'dsn' => 'odbc:Driver={SQL Server};Server=wuhanxindian.vicp.net;Database=bypass_cable', */
+    // 数据库表前缀
+    'prefix'   => '',
+    "authcode" => '5PYFjWzx0YXjvJpa0q',
+    //#COOKIE_PREFIX#
+
+    // 自动写入时间戳字段
+    'auto_timestamp'  => true
+];

+ 30 - 0
config/log.php

@@ -0,0 +1,30 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// +----------------------------------------------------------------------
+// | 日志设置
+// +----------------------------------------------------------------------
+return [
+    // 日志记录方式,内置 file socket 支持扩展
+    'type'        => 'File',
+    // 日志保存目录
+    'path'        => '',
+    // 日志记录级别
+    'level'       => [],
+    // 单文件日志写入
+    'single'      => false,
+    // 独立日志级别
+    'apart_level' => [],
+    // 最大日志文件数量
+    'max_files'   => 0,
+    // 是否关闭日志写入
+    'close'       => false,
+];

+ 18 - 0
config/middleware.php

@@ -0,0 +1,18 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// +----------------------------------------------------------------------
+// | 中间件配置
+// +----------------------------------------------------------------------
+return [
+    // 默认中间件命名空间
+    'default_namespace' => 'app\\http\\middleware\\',
+];

+ 26 - 0
config/session.php

@@ -0,0 +1,26 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// +----------------------------------------------------------------------
+// | 会话设置
+// +----------------------------------------------------------------------
+
+return [
+    'id'             => '',
+    // SESSION_ID的提交变量,解决flash上传跨域
+    'var_session_id' => '',
+    // SESSION 前缀
+    'prefix'         => 'think',
+    // 驱动方式 支持redis memcache memcached
+    'type'           => '',
+    // 是否自动开启 SESSION
+    'auto_start'     => true,
+];

+ 35 - 0
config/template.php

@@ -0,0 +1,35 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// +----------------------------------------------------------------------
+// | 模板设置
+// +----------------------------------------------------------------------
+
+return [
+    // 模板引擎类型 支持 php think 支持扩展
+    'type'         => 'Think',
+    // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法
+    'auto_rule'    => 1,
+    // 模板路径
+    'view_path'    => '',
+    // 模板后缀
+    'view_suffix'  => 'html',
+    // 模板文件名分隔符
+    'view_depr'    => DIRECTORY_SEPARATOR,
+    // 模板引擎普通标签开始标记
+    'tpl_begin'    => '{',
+    // 模板引擎普通标签结束标记
+    'tpl_end'      => '}',
+    // 标签库标签开始标记
+    'taglib_begin' => '{',
+    // 标签库标签结束标记
+    'taglib_end'   => '}',
+];

+ 18 - 0
config/trace.php

@@ -0,0 +1,18 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// +----------------------------------------------------------------------
+// | Trace设置 开启 app_trace 后 有效
+// +----------------------------------------------------------------------
+return [
+    // 内置Html Console 支持扩展
+    'type' => 'Html',
+];

+ 2 - 0
extend/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 8 - 0
public/.htaccess

@@ -0,0 +1,8 @@
+<IfModule mod_rewrite.c>
+  Options +FollowSymlinks -Multiviews
+  RewriteEngine On
+
+  RewriteCond %{REQUEST_FILENAME} !-d
+  RewriteCond %{REQUEST_FILENAME} !-f
+  RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
+</IfModule>

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 6 - 0
public/buttonLoading/ladda-themeless.min.css


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
public/buttonLoading/ladda.min.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
public/buttonLoading/spin.min.js


binární
public/cable.apk


+ 1 - 0
public/collect.json

@@ -0,0 +1 @@
+[{"id":"3","name":"3号接头","type":"2","address":"3","TestTime":"2020-08-15 09:35:00.0000000","ROW_NUMBER":"1","type_text":"温度巡检设备","online":1,"is_collect":0,"num":"3"},{"id":"4","name":"4号接头","type":"2","address":"4","TestTime":"2020-08-15 09:35:00.0000000","ROW_NUMBER":"2","type_text":"温度巡检设备","online":1,"is_collect":0,"num":"4"},{"id":"6","name":"6号接头","type":"2","address":"6","TestTime":"2020-08-15 09:35:00.0000000","ROW_NUMBER":"4","type_text":"温度巡检设备","online":1,"is_collect":0,"num":"6"},{"id":"7","name":"7号接头","type":"2","address":"7","TestTime":"2020-08-17 08:12:00.0000000","ROW_NUMBER":"5","type_text":"温度巡检设备","online":1,"is_collect":0,"num":"7"},{"id":"8","name":"8号接头","type":"2","address":"8","TestTime":"2020-08-17 08:11:00.0000000","ROW_NUMBER":"6","type_text":"温度巡检设备","online":1,"is_collect":0,"num":"8"},{"id":"2","name":"2号接头","type":"2","address":"2","TestTime":"2020-08-17 08:12:00.0000000","ROW_NUMBER":"8","type_text":"温度巡检设备","online":1,"is_collect":0,"num":"2"},{"id":"5","name":"5号接头","type":"2","address":"5","TestTime":"2020-09-07 10:55:00.0000000","ROW_NUMBER":"3","type_text":"温度巡检设备","online":1,"is_collect":0,"num":"5"},{"id":"11","name":"1号电流设备","type":"1","address":"11","TestTime":"2020-09-29 15:51:00.0000000","ROW_NUMBER":"9","type_text":"电流巡检设备","online":1,"is_collect":0,"num":"1"}]

binární
public/favicon.ico


+ 23 - 0
public/index.php

@@ -0,0 +1,23 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// [ 应用入口文件 ]
+namespace think;
+
+// 加载基础文件
+require __DIR__ . '/../thinkphp/base.php';
+
+// 支持事先使用静态方法设置Request对象和Config对象
+define('APP_PATH', __DIR__ . '/../application/');
+define('CMF_ROOT', dirname(__DIR__) . '/');
+
+// 执行应用并响应
+Container::get('app')->path(APP_PATH)->run()->send();

+ 3 - 0
public/phpinfo.php

@@ -0,0 +1,3 @@
+<?php
+ phpinfo(); 
+?>

+ 2 - 0
public/robots.txt

@@ -0,0 +1,2 @@
+User-agent: *
+Disallow:

+ 17 - 0
public/router.php

@@ -0,0 +1,17 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+// $Id$
+
+if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) {
+    return false;
+} else {
+    require __DIR__ . "/index.php";
+}

+ 2 - 0
public/static/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

binární
public/upload/map.jpg


+ 1 - 0
public/user.json

@@ -0,0 +1 @@
+[{"user_id":1,"username":"admins","password":"123456","phone":"","avatar":"","nickname":"","add_time":1596508091,"update_time":1596508091},{"user_id":2,"username":"lyuis003","password":"123456","phone":"","avatar":"","nickname":"","add_time":1596508112,"update_time":1596508112}]

+ 1 - 0
public/userConfig.json

@@ -0,0 +1 @@
+{"dianliu0":0.10000000000000001,"dianliu1":10,"wendu0":50,"wendu1":100}

+ 225 - 0
public/wx_share.php

@@ -0,0 +1,225 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<meta charset="utf-8">
+    <meta name="viewport" content="maximum-scale=1.0,minimum-scale=1.0,user-scalable=0,width=device-width,initial-scale=1.0"/>
+    <meta name="format-detection" content="telephone=no,email=no,date=no,address=no">
+    <title>下载旁路电缆监测</title>
+    <script type="text/javascript">
+		 var resetWin=1;
+		 (function (doc, win) {
+				var docEl = doc.documentElement,
+					resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
+					recalc = function () {
+						var clientWidth = docEl.clientWidth;
+						if (!clientWidth) return;
+						if(clientWidth>=640){
+							docEl.style.fontSize = '100px';
+						}else{
+							docEl.style.fontSize = 100 * (clientWidth / 640) + 'px';
+						}
+					};
+				resetWin=recalc;
+				if (!doc.addEventListener) return;
+				win.addEventListener(resizeEvt, recalc, false);
+				doc.addEventListener('DOMContentLoaded', recalc, false);
+			})(document, window);
+	</script>
+    <style>
+    		body{
+	    		width:100%;
+			    font-weight:400;
+				color:rgb(153,153,153);
+				position:relative;
+				font-family: 'PingFangSC-Regular','微软雅黑',"Microsoft YaHei";
+	    	}
+			 img{width: 100%}
+			 body,div,p,img{padding: 0;margin: 0;}
+			.images{
+				width: 1024px;
+				margin: 0 auto;
+				position:relative
+			}   
+			
+	    	.title{
+	    	    position: absolute;
+			    top: 210px;
+			    width: 100%;
+			    text-align: center;
+			    font-size: 60px;
+			    color: rgb(246,46,59);
+	    	}
+			.content{
+				font-size: 28px;
+		   		text-align: center;
+		    	padding: 0 0 20px;
+				color:#999;
+			}
+			.contentFirst{
+				font-size: 42px;
+		   		text-align: center;
+		    	padding: 0 ;
+				color:#333;
+			}
+			.download{
+			    width: 1024px;
+			    margin:0 -512px;
+				left: 50%; 
+			 }
+			.confing{
+			    width: 48%;
+				margin: 0 0 20px;
+				background: linear-gradient( 145deg, #ffac23 10%, #ffca48 100%);
+				background-size: 100%;
+				height: 65px;
+				line-height: 65px;
+				text-align: center;
+				border-radius: 30px;
+				font-size:36px;
+				color: #fff;
+				float: left
+			}
+			.confingRight{
+			   float: right
+			}
+	@media screen and (min-width:600px) and (max-width:1024px){	/*当屏幕宽度在600~1024px*/
+		   .images{
+	    		width: 80%;
+				margin: 0 auto;
+				position:relative
+	    	}
+	    	.title{
+	    	    position: absolute;
+			    top: 110px;
+			    width: 100%;
+			    text-align: center;
+			    font-size: 40px;
+			    color:rgb(246,46,59);
+	    	}
+			.download{
+	    		width: 76%;
+	    		padding:0 12%;
+			 }  
+			.content{
+				font-size: 28px;
+		   		text-align: center;
+		    	padding: 0 0 10px;
+				color:#999;
+			}
+			.contentFirst{
+				font-size: 42px;
+		   		text-align: center;
+		    	padding: 0;
+				color:#333;
+			}
+			.confing{
+			    width: 48%;
+				margin: 0 0 20px;
+				background: linear-gradient( 145deg, #ffac23 10%, #ffca48 100%);
+				background-size: 100%;
+				height: 50px;
+				line-height: 50px;
+				text-align: center;
+				border-radius: 30px;
+				font-size:28px;
+				color: #fff;
+				float: left
+			}
+			.confingRight{
+			   float: right
+			   }
+	}
+    @media screen and (max-width:600px){	/*屏幕宽度在小于等于600px*/
+	    	.images{
+	    		margin: 0rem 0 0;
+	    		width: 100%;
+				position:relative;
+	    		float: left
+	    	}
+	    	.title{
+	    	    position: absolute;
+			    top: 1.5rem;
+			    width: 100%;
+			    text-align: center;
+			    font-size: 0.5rem;
+			    color: rgb(246,46,59);
+	    	}
+	    	.download{
+	    		width: 76%;
+	    		padding:0 12%;
+	    		float: left;
+	    		margin:0 auto;
+				left: 0; 
+	    	}
+	    	.confing{
+	    	    width: 48%;
+			    margin: 0 0 0.2rem;
+			    background: linear-gradient( 145deg, #ffac23 10%, #ffca48 100%);
+				background-size: 100%;
+				height: 0.7rem;
+			    line-height: 0.7rem;
+			    text-align: center;
+			    font-size: 0.28rem;
+			    border-radius: 0.5rem;
+			    color: #fff;
+			    float: left;
+			   }
+			.confingRight{
+			   float: right
+			   }
+			.content{
+			   	width: 93%;
+			   	float: left;
+			   	font-size: 0.28rem;
+			   	padding: 0 3.5% 0.2rem;
+			   	text-align: center;
+				color:#999;
+			   	line-height:0.5rem;
+			   }
+			.contentFirst{
+			   	width: 93%;
+			   	float: left;
+				font-size: 0.42rem;
+		   		text-align: center;
+			   	padding: 0 3.5% ;
+				color:#333;
+			}
+		   }
+    </style>
+	<link rel="stylesheet" href="buttonLoading/ladda-themeless.min.css">
+</head>
+<body>
+	<div class="images">
+		<img src="static/cover.png" style="display:block">
+		<?php if(!empty($_REQUEST["code"])&&isset($_REQUEST["code"])){?>
+		<p class="contentFirst">邀请码:  <?php echo $_REQUEST["code"];?></p>
+		<p class="content">安装登录后注册请填写以上邀请码,该app请无视</p>
+		<?php  } ?>
+	</div>
+	<div class="download" style="position:fixed;bottom:10px;">
+		<div id="ios" style="display:none;float:none;margin:0px auto;width:80%;" class="confing" onclick="alert('ios还未上架');return;location.href='https://itunes.apple.com/us/app/%E5%A4%A9%E5%A4%A9%E5%90%91%E5%B0%9A/id1375914709?l=zh&ls=1&mt=8'">下载APP</div>
+		<div id="android" style="display:none;float:none;margin:0px auto;width:80%;" class="confing confingRight ladda-button"  onclick="location.href='cable.apk'" data-style="expand-left">下载APP</div>
+	</div>
+</body>
+<script src="buttonLoading/spin.min.js"></script>
+<script src="buttonLoading/ladda.min.js"></script>
+<script type="text/javascript">
+var u = navigator.userAgent
+var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
+document.getElementsByTagName('img')[0].style.height=document.documentElement.clientHeight+'px';
+if(isiOS){
+	document.getElementById('ios').style.display="block";
+}
+else{
+	document.getElementById('android').style.display="block";
+}
+Ladda.bind( '#android', {
+				callback: function( instance ) {
+
+					setTimeout( function() {
+instance.stop();
+}, 2000 );
+				}
+			} );
+</script>
+</html>

+ 20 - 0
route/route.php

@@ -0,0 +1,20 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+Route::get('think', function () {
+    return 'hello,ThinkPHP5!';
+});
+
+Route::get('hello/:name', 'index/hello');
+
+return [
+
+];

+ 2 - 0
runtime/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 22 - 0
think

@@ -0,0 +1,22 @@
+#!/usr/bin/env php
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+// 加载基础文件
+require __DIR__ . '/thinkphp/base.php';
+
+// 应用初始化
+Container::get('app')->path(__DIR__ . '/application/')->initialize();
+
+// 控制台初始化
+Console::init();

+ 8 - 0
thinkphp/.gitignore

@@ -0,0 +1,8 @@
+/vendor
+composer.phar
+composer.lock
+.DS_Store
+Thumbs.db
+/phpunit.xml
+/.idea
+/.vscode

+ 1 - 0
thinkphp/.htaccess

@@ -0,0 +1 @@
+deny from all

+ 119 - 0
thinkphp/CONTRIBUTING.md

@@ -0,0 +1,119 @@
+如何贡献我的源代码
+===
+
+此文档介绍了 ThinkPHP 团队的组成以及运转机制,您提交的代码将给 ThinkPHP 项目带来什么好处,以及如何才能加入我们的行列。
+
+## 通过 Github 贡献代码
+
+ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡献源代码,请先大致了解 Git 的使用方法。我们目前把项目托管在 GitHub 上,任何 GitHub 用户都可以向我们贡献代码。
+
+参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请并。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。
+
+我们希望你贡献的代码符合:
+
+* ThinkPHP 的编码规范
+* 适当的注释,能让其他人读懂
+* 遵循 Apache2 开源协议
+
+**如果想要了解更多细节或有任何疑问,请继续阅读下面的内容**
+
+### 注意事项
+
+* 本项目代码格式化标准选用 [**PSR-2**](http://www.kancloud.cn/thinkphp/php-fig-psr/3141);
+* 类名和类文件名遵循 [**PSR-4**](http://www.kancloud.cn/thinkphp/php-fig-psr/3144);
+* 对于 Issues 的处理,请使用诸如 `fix #xxx(Issue ID)` 的 commit title 直接关闭 issue。
+* 系统会自动在 PHP 5.4 5.5 5.6 7.0 和 HHVM 上测试修改,其中 HHVM 下的测试容许报错,请确保你的修改符合 PHP 5.4 ~ 5.6 和 PHP 7.0 的语法规范;
+* 管理员不会合并造成 CI faild 的修改,若出现 CI faild 请检查自己的源代码或修改相应的[单元测试文件](tests);
+
+## GitHub Issue
+
+GitHub 提供了 Issue 功能,该功能可以用于:
+
+* 提出 bug
+* 提出功能改进
+* 反馈使用体验
+
+该功能不应该用于:
+
+ * 提出修改意见(涉及代码署名和修订追溯问题)
+ * 不友善的言论
+
+## 快速修改
+
+**GitHub 提供了快速编辑文件的功能**
+
+1. 登录 GitHub 帐号;
+2. 浏览项目文件,找到要进行修改的文件;
+3. 点击右上角铅笔图标进行修改;
+4. 填写 `Commit changes` 相关内容(Title 必填);
+5. 提交修改,等待 CI 验证和管理员合并。
+
+**若您需要一次提交大量修改,请继续阅读下面的内容**
+
+## 完整流程
+
+1. `fork`本项目;
+2. 克隆(`clone`)你 `fork` 的项目到本地;
+3. 新建分支(`branch`)并检出(`checkout`)新分支;
+4. 添加本项目到你的本地 git 仓库作为上游(`upstream`);
+5. 进行修改,若你的修改包含方法或函数的增减,请记得修改[单元测试文件](tests);
+6. 变基(衍合 `rebase`)你的分支到上游 master 分支;
+7. `push` 你的本地仓库到 GitHub;
+8. 提交 `pull request`;
+9. 等待 CI 验证(若不通过则重复 5~7,GitHub 会自动更新你的 `pull request`);
+10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。
+
+*若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`*
+
+*绝对不可以使用 `git push -f` 强行推送修改到上游*
+
+### 注意事项
+
+* 若对上述流程有任何不清楚的地方,请查阅 GIT 教程,如 [这个](http://backlogtool.com/git-guide/cn/);
+* 对于代码**不同方面**的修改,请在自己 `fork` 的项目中**创建不同的分支**(原因参见`完整流程`第9条备注部分);
+* 变基及交互式变基操作参见 [Git 交互式变基](http://pakchoi.me/2015/03/17/git-interactive-rebase/)
+
+## 推荐资源
+
+### 开发环境
+
+* XAMPP for Windows 5.5.x
+* WampServer (for Windows)
+* upupw Apache PHP5.4 ( for Windows)
+
+或自行安装
+
+- Apache / Nginx
+- PHP 5.4 ~ 5.6
+- MySQL / MariaDB
+
+*Windows 用户推荐添加 PHP bin 目录到 PATH,方便使用 composer*
+
+*Linux 用户自行配置环境, Mac 用户推荐使用内置 Apache 配合 Homebrew 安装 PHP 和 MariaDB*
+
+### 编辑器
+
+Sublime Text 3 + phpfmt 插件
+
+phpfmt 插件参数
+
+```json
+{
+	"autocomplete": true,
+	"enable_auto_align": true,
+	"format_on_save": true,
+	"indent_with_space": true,
+	"psr1_naming": false,
+	"psr2": true,
+	"version": 4
+}
+```
+
+或其他 编辑器 / IDE 配合 PSR2 自动格式化工具
+
+### Git GUI
+
+* SourceTree
+* GitHub Desktop
+
+或其他 Git 图形界面客户端

+ 32 - 0
thinkphp/LICENSE.txt

@@ -0,0 +1,32 @@
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn)
+All rights reserved。
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+Apache Licence是著名的非盈利开源组织Apache采用的协议。
+该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,
+允许代码修改,再作为开源或商业软件发布。需要满足
+的条件: 
+1. 需要给代码的用户一份Apache Licence ;
+2. 如果你修改了代码,需要在被修改的文件中说明;
+3. 在延伸的代码中(修改和有源代码衍生的代码中)需要
+带有原来代码中的协议,商标,专利声明和其他原来作者规
+定需要包含的说明;
+4. 如果再发布的产品中包含一个Notice文件,则在Notice文
+件中需要带有本协议内容。你可以在Notice中增加自己的
+许可,但不可以表现为对Apache Licence构成更改。 
+具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0
+
+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.

+ 93 - 0
thinkphp/README.md

@@ -0,0 +1,93 @@
+![](https://box.kancloud.cn/5a0aaa69a5ff42657b5c4715f3d49221) 
+
+ThinkPHP 5.1(LTS) —— 12载初心,你值得信赖的PHP框架
+===============
+
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/top-think/framework/badges/quality-score.png?b=5.1)](https://scrutinizer-ci.com/g/top-think/framework/?branch=5.1)
+[![Build Status](https://travis-ci.org/top-think/framework.svg?branch=master)](https://travis-ci.org/top-think/framework)
+[![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework)
+[![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework)
+[![PHP Version](https://img.shields.io/badge/php-%3E%3D5.6-8892BF.svg)](http://www.php.net/)
+[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework)
+
+ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特性包括:
+
+ + 采用容器统一管理对象
+ + 支持Facade
+ + 更易用的路由
+ + 注解路由支持
+ + 路由跨域请求支持
+ + 验证类增强
+ + 配置和路由目录独立
+ + 取消系统常量
+ + 类库别名机制
+ + 模型和数据库增强
+ + 依赖注入完善
+ + 支持PSR-3日志规范
+ + 中间件支持(`V5.1.6+`)
+ + 支持`Swoole`/`Workerman`运行(`V5.1.18+`)
+
+官方已经正式宣布`5.1.27`版本为LTS版本。
+
+### 废除的功能:
+
+ + 聚合模型
+ + 内置控制器扩展类
+ + 模型自动验证
+
+> ThinkPHP5.1的运行环境要求PHP5.6+。
+
+
+## 安装
+
+使用composer安装
+
+~~~
+composer create-project topthink/think tp
+~~~
+
+启动服务
+
+~~~
+cd tp
+php think run
+~~~
+
+然后就可以在浏览器中访问
+
+~~~
+http://localhost:8000
+~~~
+
+更新框架
+~~~
+composer update topthink/framework
+~~~
+
+
+## 在线手册
+
++ [完全开发手册](https://www.kancloud.cn/manual/thinkphp5_1/content)
++ [升级指导](https://www.kancloud.cn/manual/thinkphp5_1/354155) 
+
+## 命名规范
+
+`ThinkPHP5.1`遵循PSR-2命名规范和PSR-4自动加载规范。
+
+## 参与开发
+
+请参阅 [ThinkPHP5 核心框架包](https://github.com/top-think/framework)。
+
+## 版权信息
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+
+本项目包含的第三方源码和二进制文件之版权信息另行标注。
+
+版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn)
+
+All rights reserved。
+
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+更多细节参阅 [LICENSE.txt](LICENSE.txt)

+ 52 - 0
thinkphp/base.php

@@ -0,0 +1,52 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+namespace think;
+
+// 载入Loader类
+require __DIR__ . '/library/think/Loader.php';
+
+// 注册自动加载
+Loader::register();
+
+// 注册错误和异常处理机制
+Error::register();
+
+// 实现日志接口
+if (interface_exists('Psr\Log\LoggerInterface')) {
+    interface LoggerInterface extends \Psr\Log\LoggerInterface
+    {}
+} else {
+    interface LoggerInterface
+    {}
+}
+
+// 注册类库别名
+Loader::addClassAlias([
+    'App'      => facade\App::class,
+    'Build'    => facade\Build::class,
+    'Cache'    => facade\Cache::class,
+    'Config'   => facade\Config::class,
+    'Cookie'   => facade\Cookie::class,
+    'Db'       => Db::class,
+    'Debug'    => facade\Debug::class,
+    'Env'      => facade\Env::class,
+    'Facade'   => Facade::class,
+    'Hook'     => facade\Hook::class,
+    'Lang'     => facade\Lang::class,
+    'Log'      => facade\Log::class,
+    'Request'  => facade\Request::class,
+    'Response' => facade\Response::class,
+    'Route'    => facade\Route::class,
+    'Session'  => facade\Session::class,
+    'Url'      => facade\Url::class,
+    'Validate' => facade\Validate::class,
+    'View'     => facade\View::class,
+]);

+ 35 - 0
thinkphp/composer.json

@@ -0,0 +1,35 @@
+{
+    "name": "topthink/framework",
+    "description": "the new thinkphp framework",
+    "type": "think-framework",
+    "keywords": [
+        "framework",
+        "thinkphp",
+        "ORM"
+    ],
+    "homepage": "http://thinkphp.cn/",
+    "license": "Apache-2.0",
+    "authors": [
+        {
+            "name": "liu21st",
+            "email": "liu21st@gmail.com"
+        },
+        {
+            "name": "yunwuxin",
+            "email": "448901948@qq.com"
+        }
+    ],
+    "require": {
+        "php": ">=5.6.0",
+        "topthink/think-installer": "2.*"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "^5.0|^6.0",
+        "johnkary/phpunit-speedtrap": "^1.0",
+        "mikey179/vfsstream": "~1.6",
+        "phploc/phploc": "2.*",
+        "sebastian/phpcpd": "2.*",
+        "squizlabs/php_codesniffer": "2.*",
+        "phpdocumentor/reflection-docblock": "^2.0"
+    }
+}

+ 327 - 0
thinkphp/convention.php

@@ -0,0 +1,327 @@
+<?php
+
+return [
+    // +----------------------------------------------------------------------
+    // | 应用设置
+    // +----------------------------------------------------------------------
+    'app'        => [
+        // 应用名称
+        'app_name'               => '',
+        // 应用地址
+        'app_host'               => '',
+        // 应用调试模式
+        'app_debug'              => false,
+        // 应用Trace
+        'app_trace'              => false,
+        // 应用模式状态
+        'app_status'             => '',
+        // 是否HTTPS
+        'is_https'               => false,
+        // 入口自动绑定模块
+        'auto_bind_module'       => false,
+        // 注册的根命名空间
+        'root_namespace'         => [],
+        // 默认输出类型
+        'default_return_type'    => 'html',
+        // 默认AJAX 数据返回格式,可选json xml ...
+        'default_ajax_return'    => 'json',
+        // 默认JSONP格式返回的处理方法
+        'default_jsonp_handler'  => 'jsonpReturn',
+        // 默认JSONP处理方法
+        'var_jsonp_handler'      => 'callback',
+        // 默认时区
+        'default_timezone'       => 'Asia/Shanghai',
+        // 是否开启多语言
+        'lang_switch_on'         => false,
+        // 默认验证器
+        'default_validate'       => '',
+        // 默认语言
+        'default_lang'           => 'zh-cn',
+
+        // +----------------------------------------------------------------------
+        // | 模块设置
+        // +----------------------------------------------------------------------
+
+        // 自动搜索控制器
+        'controller_auto_search' => false,
+        // 操作方法前缀
+        'use_action_prefix'      => false,
+        // 操作方法后缀
+        'action_suffix'          => '',
+        // 默认的空控制器名
+        'empty_controller'       => 'Error',
+        // 默认的空模块名
+        'empty_module'           => '',
+        // 默认模块名
+        'default_module'         => 'index',
+        // 是否支持多模块
+        'app_multi_module'       => true,
+        // 禁止访问模块
+        'deny_module_list'       => ['common'],
+        // 默认控制器名
+        'default_controller'     => 'Index',
+        // 默认操作名
+        'default_action'         => 'index',
+        // 是否自动转换URL中的控制器和操作名
+        'url_convert'            => true,
+        // 默认的访问控制器层
+        'url_controller_layer'   => 'controller',
+        // 应用类库后缀
+        'class_suffix'           => false,
+        // 控制器类后缀
+        'controller_suffix'      => false,
+
+        // +----------------------------------------------------------------------
+        // | URL请求设置
+        // +----------------------------------------------------------------------
+
+        // 默认全局过滤方法 用逗号分隔多个
+        'default_filter'         => '',
+        // PATHINFO变量名 用于兼容模式
+        'var_pathinfo'           => 's',
+        // 兼容PATH_INFO获取
+        'pathinfo_fetch'         => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
+        // HTTPS代理标识
+        'https_agent_name'       => '',
+        // IP代理获取标识
+        'http_agent_ip'          => 'HTTP_X_REAL_IP',
+        // URL伪静态后缀
+        'url_html_suffix'        => 'html',
+        // 域名根,如thinkphp.cn
+        'url_domain_root'        => '',
+        // 表单请求类型伪装变量
+        'var_method'             => '_method',
+        // 表单ajax伪装变量
+        'var_ajax'               => '_ajax',
+        // 表单pjax伪装变量
+        'var_pjax'               => '_pjax',
+        // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
+        'request_cache'          => false,
+        // 请求缓存有效期
+        'request_cache_expire'   => null,
+        // 全局请求缓存排除规则
+        'request_cache_except'   => [],
+
+        // +----------------------------------------------------------------------
+        // | 路由设置
+        // +----------------------------------------------------------------------
+
+        // pathinfo分隔符
+        'pathinfo_depr'          => '/',
+        // URL普通方式参数 用于自动生成
+        'url_common_param'       => false,
+        // URL参数方式 0 按名称成对解析 1 按顺序解析
+        'url_param_type'         => 0,
+        // 是否开启路由延迟解析
+        'url_lazy_route'         => false,
+        // 是否强制使用路由
+        'url_route_must'         => false,
+        // 合并路由规则
+        'route_rule_merge'       => false,
+        // 路由是否完全匹配
+        'route_complete_match'   => false,
+        // 使用注解路由
+        'route_annotation'       => false,
+        // 默认的路由变量规则
+        'default_route_pattern'  => '\w+',
+        // 是否开启路由缓存
+        'route_check_cache'      => false,
+        // 路由缓存的Key自定义设置(闭包),默认为当前URL和请求类型的md5
+        'route_check_cache_key'  => '',
+        // 路由缓存的设置
+        'route_cache_option'     => [],
+
+        // +----------------------------------------------------------------------
+        // | 异常及错误设置
+        // +----------------------------------------------------------------------
+
+        // 默认跳转页面对应的模板文件
+        'dispatch_success_tmpl'  => __DIR__ . '/tpl/dispatch_jump.tpl',
+        'dispatch_error_tmpl'    => __DIR__ . '/tpl/dispatch_jump.tpl',
+        // 异常页面的模板文件
+        'exception_tmpl'         => __DIR__ . '/tpl/think_exception.tpl',
+        // 错误显示信息,非调试模式有效
+        'error_message'          => '页面错误!请稍后再试~',
+        // 显示错误信息
+        'show_error_msg'         => false,
+        // 异常处理handle类 留空使用 \think\exception\Handle
+        'exception_handle'       => '',
+    ],
+
+    // +----------------------------------------------------------------------
+    // | 模板设置
+    // +----------------------------------------------------------------------
+
+    'template'   => [
+        // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写
+        'auto_rule'    => 1,
+        // 模板引擎类型 支持 php think 支持扩展
+        'type'         => 'Think',
+        // 视图基础目录,配置目录为所有模块的视图起始目录
+        'view_base'    => '',
+        // 当前模板的视图目录 留空为自动获取
+        'view_path'    => '',
+        // 模板后缀
+        'view_suffix'  => 'html',
+        // 模板文件名分隔符
+        'view_depr'    => DIRECTORY_SEPARATOR,
+        // 模板引擎普通标签开始标记
+        'tpl_begin'    => '{',
+        // 模板引擎普通标签结束标记
+        'tpl_end'      => '}',
+        // 标签库标签开始标记
+        'taglib_begin' => '{',
+        // 标签库标签结束标记
+        'taglib_end'   => '}',
+    ],
+
+    // +----------------------------------------------------------------------
+    // | 日志设置
+    // +----------------------------------------------------------------------
+
+    'log'        => [
+        // 日志记录方式,内置 file socket 支持扩展
+        'type'         => 'File',
+        // 日志保存目录
+        //'path'  => LOG_PATH,
+        // 日志记录级别
+        'level'        => [],
+        // 是否记录trace信息到日志
+        'record_trace' => false,
+        // 是否JSON格式记录
+        'json'         => false,
+    ],
+
+    // +----------------------------------------------------------------------
+    // | Trace设置 开启 app_trace 后 有效
+    // +----------------------------------------------------------------------
+
+    'trace'      => [
+        // 内置Html Console 支持扩展
+        'type' => 'Html',
+        'file' => __DIR__ . '/tpl/page_trace.tpl',
+    ],
+
+    // +----------------------------------------------------------------------
+    // | 缓存设置
+    // +----------------------------------------------------------------------
+
+    'cache'      => [
+        // 驱动方式
+        'type'   => 'File',
+        // 缓存保存目录
+        //'path'   => CACHE_PATH,
+        // 缓存前缀
+        'prefix' => '',
+        // 缓存有效期 0表示永久缓存
+        'expire' => 0,
+    ],
+
+    // +----------------------------------------------------------------------
+    // | 会话设置
+    // +----------------------------------------------------------------------
+
+    'session'    => [
+        'id'             => '',
+        // SESSION_ID的提交变量,解决flash上传跨域
+        'var_session_id' => '',
+        // SESSION 前缀
+        'prefix'         => 'think',
+        // 驱动方式 支持redis memcache memcached
+        'type'           => '',
+        // 是否自动开启 SESSION
+        'auto_start'     => true,
+        'httponly'       => true,
+        'secure'         => false,
+    ],
+
+    // +----------------------------------------------------------------------
+    // | Cookie设置
+    // +----------------------------------------------------------------------
+
+    'cookie'     => [
+        // cookie 名称前缀
+        'prefix'    => '',
+        // cookie 保存时间
+        'expire'    => 0,
+        // cookie 保存路径
+        'path'      => '/',
+        // cookie 有效域名
+        'domain'    => '',
+        //  cookie 启用安全传输
+        'secure'    => false,
+        // httponly设置
+        'httponly'  => '',
+        // 是否使用 setcookie
+        'setcookie' => true,
+    ],
+
+    // +----------------------------------------------------------------------
+    // | 数据库设置
+    // +----------------------------------------------------------------------
+
+    'database'   => [
+        // 数据库类型
+        'type'            => 'mysql',
+        // 数据库连接DSN配置
+        'dsn'             => '',
+        // 服务器地址
+        'hostname'        => '127.0.0.1',
+        // 数据库名
+        'database'        => '',
+        // 数据库用户名
+        'username'        => 'root',
+        // 数据库密码
+        'password'        => '',
+        // 数据库连接端口
+        'hostport'        => '',
+        // 数据库连接参数
+        'params'          => [],
+        // 数据库编码默认采用utf8
+        'charset'         => 'utf8',
+        // 数据库表前缀
+        'prefix'          => '',
+        // 数据库调试模式
+        'debug'           => false,
+        // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
+        'deploy'          => 0,
+        // 数据库读写是否分离 主从式有效
+        'rw_separate'     => false,
+        // 读写分离后 主服务器数量
+        'master_num'      => 1,
+        // 指定从服务器序号
+        'slave_no'        => '',
+        // 是否严格检查字段是否存在
+        'fields_strict'   => true,
+        // 数据集返回类型
+        'resultset_type'  => 'array',
+        // 自动写入时间戳字段
+        'auto_timestamp'  => false,
+        // 时间字段取出后的默认时间格式
+        'datetime_format' => 'Y-m-d H:i:s',
+        // 是否需要进行SQL性能分析
+        'sql_explain'     => false,
+        // 查询对象
+        'query'           => '\\think\\db\\Query',
+    ],
+
+    //分页配置
+    'paginate'   => [
+        'type'      => 'bootstrap',
+        'var_page'  => 'page',
+        'list_rows' => 15,
+    ],
+
+    //控制台配置
+    'console'    => [
+        'name'      => 'Think Console',
+        'version'   => '0.1',
+        'user'      => null,
+        'auto_path' => '',
+    ],
+
+    // 中间件配置
+    'middleware' => [
+        'default_namespace' => 'app\\http\\middleware\\',
+    ],
+];

+ 726 - 0
thinkphp/helper.php

@@ -0,0 +1,726 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+//------------------------
+// ThinkPHP 助手函数
+//-------------------------
+
+use think\Container;
+use think\Db;
+use think\exception\HttpException;
+use think\exception\HttpResponseException;
+use think\facade\Cache;
+use think\facade\Config;
+use think\facade\Cookie;
+use think\facade\Debug;
+use think\facade\Env;
+use think\facade\Hook;
+use think\facade\Lang;
+use think\facade\Log;
+use think\facade\Request;
+use think\facade\Route;
+use think\facade\Session;
+use think\facade\Url;
+use think\Response;
+use think\route\RuleItem;
+
+if (!function_exists('abort')) {
+    /**
+     * 抛出HTTP异常
+     * @param integer|Response      $code 状态码 或者 Response对象实例
+     * @param string                $message 错误信息
+     * @param array                 $header 参数
+     */
+    function abort($code, $message = null, $header = [])
+    {
+        if ($code instanceof Response) {
+            throw new HttpResponseException($code);
+        } else {
+            throw new HttpException($code, $message, null, $header);
+        }
+    }
+}
+
+if (!function_exists('action')) {
+    /**
+     * 调用模块的操作方法 参数格式 [模块/控制器/]操作
+     * @param string        $url 调用地址
+     * @param string|array  $vars 调用参数 支持字符串和数组
+     * @param string        $layer 要调用的控制层名称
+     * @param bool          $appendSuffix 是否添加类名后缀
+     * @return mixed
+     */
+    function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
+    {
+        return app()->action($url, $vars, $layer, $appendSuffix);
+    }
+}
+
+if (!function_exists('app')) {
+    /**
+     * 快速获取容器中的实例 支持依赖注入
+     * @param string    $name 类名或标识 默认获取当前应用实例
+     * @param array     $args 参数
+     * @param bool      $newInstance    是否每次创建新的实例
+     * @return mixed|\think\App
+     */
+    function app($name = 'think\App', $args = [], $newInstance = false)
+    {
+        return Container::get($name, $args, $newInstance);
+    }
+}
+
+if (!function_exists('behavior')) {
+    /**
+     * 执行某个行为(run方法) 支持依赖注入
+     * @param mixed $behavior   行为类名或者别名
+     * @param mixed $args       参数
+     * @return mixed
+     */
+    function behavior($behavior, $args = null)
+    {
+        return Hook::exec($behavior, $args);
+    }
+}
+
+if (!function_exists('bind')) {
+    /**
+     * 绑定一个类到容器
+     * @access public
+     * @param string  $abstract    类标识、接口
+     * @param mixed   $concrete    要绑定的类、闭包或者实例
+     * @return Container
+     */
+    function bind($abstract, $concrete = null)
+    {
+        return Container::getInstance()->bindTo($abstract, $concrete);
+    }
+}
+
+if (!function_exists('cache')) {
+    /**
+     * 缓存管理
+     * @param mixed     $name 缓存名称,如果为数组表示进行缓存设置
+     * @param mixed     $value 缓存值
+     * @param mixed     $options 缓存参数
+     * @param string    $tag 缓存标签
+     * @return mixed
+     */
+    function cache($name, $value = '', $options = null, $tag = null)
+    {
+        if (is_array($options)) {
+            // 缓存操作的同时初始化
+            Cache::connect($options);
+        } elseif (is_array($name)) {
+            // 缓存初始化
+            return Cache::connect($name);
+        }
+
+        if ('' === $value) {
+            // 获取缓存
+            return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name);
+        } elseif (is_null($value)) {
+            // 删除缓存
+            return Cache::rm($name);
+        }
+
+        // 缓存数据
+        if (is_array($options)) {
+            $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间
+        } else {
+            $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间
+        }
+
+        if (is_null($tag)) {
+            return Cache::set($name, $value, $expire);
+        } else {
+            return Cache::tag($tag)->set($name, $value, $expire);
+        }
+    }
+}
+
+if (!function_exists('call')) {
+    /**
+     * 调用反射执行callable 支持依赖注入
+     * @param mixed $callable   支持闭包等callable写法
+     * @param array $args       参数
+     * @return mixed
+     */
+    function call($callable, $args = [])
+    {
+        return Container::getInstance()->invoke($callable, $args);
+    }
+}
+
+if (!function_exists('class_basename')) {
+    /**
+     * 获取类名(不包含命名空间)
+     *
+     * @param  string|object $class
+     * @return string
+     */
+    function class_basename($class)
+    {
+        $class = is_object($class) ? get_class($class) : $class;
+        return basename(str_replace('\\', '/', $class));
+    }
+}
+
+if (!function_exists('class_uses_recursive')) {
+    /**
+     *获取一个类里所有用到的trait,包括父类的
+     *
+     * @param $class
+     * @return array
+     */
+    function class_uses_recursive($class)
+    {
+        if (is_object($class)) {
+            $class = get_class($class);
+        }
+
+        $results = [];
+        $classes = array_merge([$class => $class], class_parents($class));
+        foreach ($classes as $class) {
+            $results += trait_uses_recursive($class);
+        }
+
+        return array_unique($results);
+    }
+}
+
+if (!function_exists('config')) {
+    /**
+     * 获取和设置配置参数
+     * @param string|array  $name 参数名
+     * @param mixed         $value 参数值
+     * @return mixed
+     */
+    function config($name = '', $value = null)
+    {
+        if (is_null($value) && is_string($name)) {
+            if ('.' == substr($name, -1)) {
+                return Config::pull(substr($name, 0, -1));
+            }
+
+            return 0 === strpos($name, '?') ? Config::has(substr($name, 1)) : Config::get($name);
+        } else {
+            return Config::set($name, $value);
+        }
+    }
+}
+
+if (!function_exists('container')) {
+    /**
+     * 获取容器对象实例
+     * @return Container
+     */
+    function container()
+    {
+        return Container::getInstance();
+    }
+}
+
+if (!function_exists('controller')) {
+    /**
+     * 实例化控制器 格式:[模块/]控制器
+     * @param string    $name 资源地址
+     * @param string    $layer 控制层名称
+     * @param bool      $appendSuffix 是否添加类名后缀
+     * @return \think\Controller
+     */
+    function controller($name, $layer = 'controller', $appendSuffix = false)
+    {
+        return app()->controller($name, $layer, $appendSuffix);
+    }
+}
+
+if (!function_exists('cookie')) {
+    /**
+     * Cookie管理
+     * @param string|array  $name cookie名称,如果为数组表示进行cookie设置
+     * @param mixed         $value cookie值
+     * @param mixed         $option 参数
+     * @return mixed
+     */
+    function cookie($name, $value = '', $option = null)
+    {
+        if (is_array($name)) {
+            // 初始化
+            Cookie::init($name);
+        } elseif (is_null($name)) {
+            // 清除
+            Cookie::clear($value);
+        } elseif ('' === $value) {
+            // 获取
+            return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name);
+        } elseif (is_null($value)) {
+            // 删除
+            return Cookie::delete($name);
+        } else {
+            // 设置
+            return Cookie::set($name, $value, $option);
+        }
+    }
+}
+
+if (!function_exists('db')) {
+    /**
+     * 实例化数据库类
+     * @param string        $name 操作的数据表名称(不含前缀)
+     * @param array|string  $config 数据库配置参数
+     * @param bool          $force 是否强制重新连接
+     * @return \think\db\Query
+     */
+    function db($name = '', $config = [], $force = true)
+    {
+        return Db::connect($config, $force)->name($name);
+    }
+}
+
+if (!function_exists('debug')) {
+    /**
+     * 记录时间(微秒)和内存使用情况
+     * @param string            $start 开始标签
+     * @param string            $end 结束标签
+     * @param integer|string    $dec 小数位 如果是m 表示统计内存占用
+     * @return mixed
+     */
+    function debug($start, $end = '', $dec = 6)
+    {
+        if ('' == $end) {
+            Debug::remark($start);
+        } else {
+            return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec);
+        }
+    }
+}
+
+if (!function_exists('download')) {
+    /**
+     * 获取\think\response\Download对象实例
+     * @param string  $filename 要下载的文件
+     * @param string  $name 显示文件名
+     * @param bool    $content 是否为内容
+     * @param integer $expire 有效期(秒)
+     * @return \think\response\Download
+     */
+    function download($filename, $name = '', $content = false, $expire = 360, $openinBrowser = false)
+    {
+        return Response::create($filename, 'download')->name($name)->isContent($content)->expire($expire)->openinBrowser($openinBrowser);
+    }
+}
+
+if (!function_exists('dump')) {
+    /**
+     * 浏览器友好的变量输出
+     * @param mixed     $var 变量
+     * @param boolean   $echo 是否输出 默认为true 如果为false 则返回输出字符串
+     * @param string    $label 标签 默认为空
+     * @return void|string
+     */
+    function dump($var, $echo = true, $label = null)
+    {
+        return Debug::dump($var, $echo, $label);
+    }
+}
+
+if (!function_exists('env')) {
+    /**
+     * 获取环境变量值
+     * @access public
+     * @param  string    $name 环境变量名(支持二级 .号分割)
+     * @param  string    $default  默认值
+     * @return mixed
+     */
+    function env($name = null, $default = null)
+    {
+        return Env::get($name, $default);
+    }
+}
+
+if (!function_exists('exception')) {
+    /**
+     * 抛出异常处理
+     *
+     * @param string    $msg  异常消息
+     * @param integer   $code 异常代码 默认为0
+     * @param string    $exception 异常类
+     *
+     * @throws Exception
+     */
+    function exception($msg, $code = 0, $exception = '')
+    {
+        $e = $exception ?: '\think\Exception';
+        throw new $e($msg, $code);
+    }
+}
+
+if (!function_exists('halt')) {
+    /**
+     * 调试变量并且中断输出
+     * @param mixed      $var 调试变量或者信息
+     */
+    function halt($var)
+    {
+        dump($var);
+
+        throw new HttpResponseException(new Response);
+    }
+}
+
+if (!function_exists('input')) {
+    /**
+     * 获取输入数据 支持默认值和过滤
+     * @param string    $key 获取的变量名
+     * @param mixed     $default 默认值
+     * @param string    $filter 过滤方法
+     * @return mixed
+     */
+    function input($key = '', $default = null, $filter = '')
+    {
+        if (0 === strpos($key, '?')) {
+            $key = substr($key, 1);
+            $has = true;
+        }
+
+        if ($pos = strpos($key, '.')) {
+            // 指定参数来源
+            $method = substr($key, 0, $pos);
+            if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) {
+                $key = substr($key, $pos + 1);
+            } else {
+                $method = 'param';
+            }
+        } else {
+            // 默认为自动判断
+            $method = 'param';
+        }
+
+        if (isset($has)) {
+            return request()->has($key, $method, $default);
+        } else {
+            return request()->$method($key, $default, $filter);
+        }
+    }
+}
+
+if (!function_exists('json')) {
+    /**
+     * 获取\think\response\Json对象实例
+     * @param mixed   $data 返回的数据
+     * @param integer $code 状态码
+     * @param array   $header 头部
+     * @param array   $options 参数
+     * @return \think\response\Json
+     */
+    function json($data = [], $code = 200, $header = [], $options = [])
+    {
+        return Response::create($data, 'json', $code, $header, $options);
+    }
+}
+
+if (!function_exists('jsonp')) {
+    /**
+     * 获取\think\response\Jsonp对象实例
+     * @param mixed   $data    返回的数据
+     * @param integer $code    状态码
+     * @param array   $header 头部
+     * @param array   $options 参数
+     * @return \think\response\Jsonp
+     */
+    function jsonp($data = [], $code = 200, $header = [], $options = [])
+    {
+        return Response::create($data, 'jsonp', $code, $header, $options);
+    }
+}
+
+if (!function_exists('lang')) {
+    /**
+     * 获取语言变量值
+     * @param string    $name 语言变量名
+     * @param array     $vars 动态变量值
+     * @param string    $lang 语言
+     * @return mixed
+     */
+    function lang($name, $vars = [], $lang = '')
+    {
+        return Lang::get($name, $vars, $lang);
+    }
+}
+
+if (!function_exists('model')) {
+    /**
+     * 实例化Model
+     * @param string    $name Model名称
+     * @param string    $layer 业务层名称
+     * @param bool      $appendSuffix 是否添加类名后缀
+     * @return \think\Model
+     */
+    function model($name = '', $layer = 'model', $appendSuffix = false)
+    {
+        return app()->model($name, $layer, $appendSuffix);
+    }
+}
+
+if (!function_exists('parse_name')) {
+    /**
+     * 字符串命名风格转换
+     * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
+     * @param string  $name 字符串
+     * @param integer $type 转换类型
+     * @param bool    $ucfirst 首字母是否大写(驼峰规则)
+     * @return string
+     */
+    function parse_name($name, $type = 0, $ucfirst = true)
+    {
+        if ($type) {
+            $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
+                return strtoupper($match[1]);
+            }, $name);
+
+            return $ucfirst ? ucfirst($name) : lcfirst($name);
+        } else {
+            return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
+        }
+    }
+}
+
+if (!function_exists('redirect')) {
+    /**
+     * 获取\think\response\Redirect对象实例
+     * @param mixed         $url 重定向地址 支持Url::build方法的地址
+     * @param array|integer $params 额外参数
+     * @param integer       $code 状态码
+     * @return \think\response\Redirect
+     */
+    function redirect($url = [], $params = [], $code = 302)
+    {
+        if (is_integer($params)) {
+            $code   = $params;
+            $params = [];
+        }
+
+        return Response::create($url, 'redirect', $code)->params($params);
+    }
+}
+
+if (!function_exists('request')) {
+    /**
+     * 获取当前Request对象实例
+     * @return Request
+     */
+    function request()
+    {
+        return app('request');
+    }
+}
+
+if (!function_exists('response')) {
+    /**
+     * 创建普通 Response 对象实例
+     * @param mixed      $data   输出数据
+     * @param int|string $code   状态码
+     * @param array      $header 头信息
+     * @param string     $type
+     * @return Response
+     */
+    function response($data = '', $code = 200, $header = [], $type = 'html')
+    {
+        return Response::create($data, $type, $code, $header);
+    }
+}
+
+if (!function_exists('route')) {
+    /**
+     * 路由注册
+     * @param  string    $rule       路由规则
+     * @param  mixed     $route      路由地址
+     * @param  array     $option     路由参数
+     * @param  array     $pattern    变量规则
+     * @return RuleItem
+     */
+    function route($rule, $route, $option = [], $pattern = [])
+    {
+        return Route::rule($rule, $route, '*', $option, $pattern);
+    }
+}
+
+if (!function_exists('session')) {
+    /**
+     * Session管理
+     * @param string|array  $name session名称,如果为数组表示进行session设置
+     * @param mixed         $value session值
+     * @param string        $prefix 前缀
+     * @return mixed
+     */
+    function session($name, $value = '', $prefix = null)
+    {
+        if (is_array($name)) {
+            // 初始化
+            Session::init($name);
+        } elseif (is_null($name)) {
+            // 清除
+            Session::clear($value);
+        } elseif ('' === $value) {
+            // 判断或获取
+            return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix);
+        } elseif (is_null($value)) {
+            // 删除
+            return Session::delete($name, $prefix);
+        } else {
+            // 设置
+            return Session::set($name, $value, $prefix);
+        }
+    }
+}
+
+if (!function_exists('token')) {
+    /**
+     * 生成表单令牌
+     * @param string $name 令牌名称
+     * @param mixed  $type 令牌生成方法
+     * @return string
+     */
+    function token($name = '__token__', $type = 'md5')
+    {
+        $token = Request::token($name, $type);
+
+        return '<input type="hidden" name="' . $name . '" value="' . $token . '" />';
+    }
+}
+
+if (!function_exists('trace')) {
+    /**
+     * 记录日志信息
+     * @param mixed     $log log信息 支持字符串和数组
+     * @param string    $level 日志级别
+     * @return array|void
+     */
+    function trace($log = '[think]', $level = 'log')
+    {
+        if ('[think]' === $log) {
+            return Log::getLog();
+        } else {
+            Log::record($log, $level);
+        }
+    }
+}
+
+if (!function_exists('trait_uses_recursive')) {
+    /**
+     * 获取一个trait里所有引用到的trait
+     *
+     * @param  string $trait
+     * @return array
+     */
+    function trait_uses_recursive($trait)
+    {
+        $traits = class_uses($trait);
+        foreach ($traits as $trait) {
+            $traits += trait_uses_recursive($trait);
+        }
+
+        return $traits;
+    }
+}
+
+if (!function_exists('url')) {
+    /**
+     * Url生成
+     * @param string        $url 路由地址
+     * @param string|array  $vars 变量
+     * @param bool|string   $suffix 生成的URL后缀
+     * @param bool|string   $domain 域名
+     * @return string
+     */
+    function url($url = '', $vars = '', $suffix = true, $domain = false)
+    {
+        return Url::build($url, $vars, $suffix, $domain);
+    }
+}
+
+if (!function_exists('validate')) {
+    /**
+     * 实例化验证器
+     * @param string    $name 验证器名称
+     * @param string    $layer 业务层名称
+     * @param bool      $appendSuffix 是否添加类名后缀
+     * @return \think\Validate
+     */
+    function validate($name = '', $layer = 'validate', $appendSuffix = false)
+    {
+        return app()->validate($name, $layer, $appendSuffix);
+    }
+}
+
+if (!function_exists('view')) {
+    /**
+     * 渲染模板输出
+     * @param string    $template 模板文件
+     * @param array     $vars 模板变量
+     * @param integer   $code 状态码
+     * @param callable  $filter 内容过滤
+     * @return \think\response\View
+     */
+    function view($template = '', $vars = [], $code = 200, $filter = null)
+    {
+        return Response::create($template, 'view', $code)->assign($vars)->filter($filter);
+    }
+}
+
+if (!function_exists('widget')) {
+    /**
+     * 渲染输出Widget
+     * @param string    $name Widget名称
+     * @param array     $data 传入的参数
+     * @return mixed
+     */
+    function widget($name, $data = [])
+    {
+        $result = app()->action($name, $data, 'widget');
+
+        if (is_object($result)) {
+            $result = $result->getContent();
+        }
+
+        return $result;
+    }
+}
+
+if (!function_exists('xml')) {
+    /**
+     * 获取\think\response\Xml对象实例
+     * @param mixed   $data    返回的数据
+     * @param integer $code    状态码
+     * @param array   $header  头部
+     * @param array   $options 参数
+     * @return \think\response\Xml
+     */
+    function xml($data = [], $code = 200, $header = [], $options = [])
+    {
+        return Response::create($data, 'xml', $code, $header, $options);
+    }
+}
+
+if (!function_exists('yaconf')) {
+    /**
+     * 获取yaconf配置
+     *
+     * @param  string    $name 配置参数名
+     * @param  mixed     $default   默认值
+     * @return mixed
+     */
+    function yaconf($name, $default = null)
+    {
+        return Config::yaconf($name, $default);
+    }
+}

+ 144 - 0
thinkphp/lang/zh-cn.php

@@ -0,0 +1,144 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// 核心中文语言包
+return [
+    // 系统错误提示
+    'Undefined variable'                                        => '未定义变量',
+    'Undefined index'                                           => '未定义数组索引',
+    'Undefined offset'                                          => '未定义数组下标',
+    'Parse error'                                               => '语法解析错误',
+    'Type error'                                                => '类型错误',
+    'Fatal error'                                               => '致命错误',
+    'syntax error'                                              => '语法错误',
+
+    // 框架核心错误提示
+    'dispatch type not support'                                 => '不支持的调度类型',
+    'method param miss'                                         => '方法参数错误',
+    'method not exists'                                         => '方法不存在',
+    'function not exists'                                       => '函数不存在',
+    'file not exists'                                           => '文件不存在',
+    'module not exists'                                         => '模块不存在',
+    'controller not exists'                                     => '控制器不存在',
+    'class not exists'                                          => '类不存在',
+    'property not exists'                                       => '类的属性不存在',
+    'template not exists'                                       => '模板文件不存在',
+    'illegal controller name'                                   => '非法的控制器名称',
+    'illegal action name'                                       => '非法的操作名称',
+    'url suffix deny'                                           => '禁止的URL后缀访问',
+    'Route Not Found'                                           => '当前访问路由未定义或不匹配',
+    'Undefined db type'                                         => '未定义数据库类型',
+    'variable type error'                                       => '变量类型错误',
+    'PSR-4 error'                                               => 'PSR-4 规范错误',
+    'not support total'                                         => '简洁模式下不能获取数据总数',
+    'not support last'                                          => '简洁模式下不能获取最后一页',
+    'error session handler'                                     => '错误的SESSION处理器类',
+    'not allow php tag'                                         => '模板不允许使用PHP语法',
+    'not support'                                               => '不支持',
+    'redisd master'                                             => 'Redisd 主服务器错误',
+    'redisd slave'                                              => 'Redisd 从服务器错误',
+    'must run at sae'                                           => '必须在SAE运行',
+    'memcache init error'                                       => '未开通Memcache服务,请在SAE管理平台初始化Memcache服务',
+    'KVDB init error'                                           => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务',
+    'fields not exists'                                         => '数据表字段不存在',
+    'where express error'                                       => '查询表达式错误',
+    'order express error'                                       => '排序表达式错误',
+    'no data to update'                                         => '没有任何数据需要更新',
+    'miss data to insert'                                       => '缺少需要写入的数据',
+    'not support data'                                          => '不支持的数据表达式',
+    'miss complex primary data'                                 => '缺少复合主键数据',
+    'miss update condition'                                     => '缺少更新条件',
+    'model data Not Found'                                      => '模型数据不存在',
+    'table data not Found'                                      => '表数据不存在',
+    'delete without condition'                                  => '没有条件不会执行删除操作',
+    'miss relation data'                                        => '缺少关联表数据',
+    'tag attr must'                                             => '模板标签属性必须',
+    'tag error'                                                 => '模板标签错误',
+    'cache write error'                                         => '缓存写入失败',
+    'sae mc write error'                                        => 'SAE mc 写入错误',
+    'route name not exists'                                     => '路由标识不存在(或参数不够)',
+    'invalid request'                                           => '非法请求',
+    'bind attr has exists'                                      => '模型的属性已经存在',
+    'relation data not exists'                                  => '关联数据不存在',
+    'relation not support'                                      => '关联不支持',
+    'chunk not support order'                                   => 'Chunk不支持调用order方法',
+    'route pattern error'                                       => '路由变量规则定义错误',
+    'route behavior will not support'                           => '路由行为废弃(使用中间件替代)',
+    'closure not support cache(true)'                           => '使用闭包查询不支持cache(true),请指定缓存Key',
+
+    // 上传错误信息
+    'unknown upload error'                                      => '未知上传错误!',
+    'file write error'                                          => '文件写入失败!',
+    'upload temp dir not found'                                 => '找不到临时文件夹!',
+    'no file to uploaded'                                       => '没有文件被上传!',
+    'only the portion of file is uploaded'                      => '文件只有部分被上传!',
+    'upload File size exceeds the maximum value'                => '上传文件大小超过了最大值!',
+    'upload write error'                                        => '文件上传保存错误!',
+    'has the same filename: {:filename}'                        => '存在同名文件:{:filename}',
+    'upload illegal files'                                      => '非法上传文件',
+    'illegal image files'                                       => '非法图片文件',
+    'extensions to upload is not allowed'                       => '上传文件后缀不允许',
+    'mimetype to upload is not allowed'                         => '上传文件MIME类型不允许!',
+    'filesize not match'                                        => '上传文件大小不符!',
+    'directory {:path} creation failed'                         => '目录 {:path} 创建失败!',
+
+    'The middleware must return Response instance'              => '中间件方法必须返回Response对象实例',
+    'The queue was exhausted, with no response returned'        => '中间件队列为空',
+    // Validate Error Message
+    ':attribute require'                                        => ':attribute不能为空',
+    ':attribute must'                                           => ':attribute必须',
+    ':attribute must be numeric'                                => ':attribute必须是数字',
+    ':attribute must be integer'                                => ':attribute必须是整数',
+    ':attribute must be float'                                  => ':attribute必须是浮点数',
+    ':attribute must be bool'                                   => ':attribute必须是布尔值',
+    ':attribute not a valid email address'                      => ':attribute格式不符',
+    ':attribute not a valid mobile'                             => ':attribute格式不符',
+    ':attribute must be a array'                                => ':attribute必须是数组',
+    ':attribute must be yes,on or 1'                            => ':attribute必须是yes、on或者1',
+    ':attribute not a valid datetime'                           => ':attribute不是一个有效的日期或时间格式',
+    ':attribute not a valid file'                               => ':attribute不是有效的上传文件',
+    ':attribute not a valid image'                              => ':attribute不是有效的图像文件',
+    ':attribute must be alpha'                                  => ':attribute只能是字母',
+    ':attribute must be alpha-numeric'                          => ':attribute只能是字母和数字',
+    ':attribute must be alpha-numeric, dash, underscore'        => ':attribute只能是字母、数字和下划线_及破折号-',
+    ':attribute not a valid domain or ip'                       => ':attribute不是有效的域名或者IP',
+    ':attribute must be chinese'                                => ':attribute只能是汉字',
+    ':attribute must be chinese or alpha'                       => ':attribute只能是汉字、字母',
+    ':attribute must be chinese,alpha-numeric'                  => ':attribute只能是汉字、字母和数字',
+    ':attribute must be chinese,alpha-numeric,underscore, dash' => ':attribute只能是汉字、字母、数字和下划线_及破折号-',
+    ':attribute not a valid url'                                => ':attribute不是有效的URL地址',
+    ':attribute not a valid ip'                                 => ':attribute不是有效的IP地址',
+    ':attribute must be dateFormat of :rule'                    => ':attribute必须使用日期格式 :rule',
+    ':attribute must be in :rule'                               => ':attribute必须在 :rule 范围内',
+    ':attribute be notin :rule'                                 => ':attribute不能在 :rule 范围内',
+    ':attribute must between :1 - :2'                           => ':attribute只能在 :1 - :2 之间',
+    ':attribute not between :1 - :2'                            => ':attribute不能在 :1 - :2 之间',
+    'size of :attribute must be :rule'                          => ':attribute长度不符合要求 :rule',
+    'max size of :attribute must be :rule'                      => ':attribute长度不能超过 :rule',
+    'min size of :attribute must be :rule'                      => ':attribute长度不能小于 :rule',
+    ':attribute cannot be less than :rule'                      => ':attribute日期不能小于 :rule',
+    ':attribute cannot exceed :rule'                            => ':attribute日期不能超过 :rule',
+    ':attribute not within :rule'                               => '不在有效期内 :rule',
+    'access IP is not allowed'                                  => '不允许的IP访问',
+    'access IP denied'                                          => '禁止的IP访问',
+    ':attribute out of accord with :2'                          => ':attribute和确认字段:2不一致',
+    ':attribute cannot be same with :2'                         => ':attribute和比较字段:2不能相同',
+    ':attribute must greater than or equal :rule'               => ':attribute必须大于等于 :rule',
+    ':attribute must greater than :rule'                        => ':attribute必须大于 :rule',
+    ':attribute must less than or equal :rule'                  => ':attribute必须小于等于 :rule',
+    ':attribute must less than :rule'                           => ':attribute必须小于 :rule',
+    ':attribute must equal :rule'                               => ':attribute必须等于 :rule',
+    ':attribute has exists'                                     => ':attribute已存在',
+    ':attribute not conform to the rules'                       => ':attribute不符合指定规则',
+    'invalid Request method'                                    => '无效的请求类型',
+    'invalid token'                                             => '令牌数据无效',
+    'not conform to the rules'                                  => '规则错误',
+];

+ 981 - 0
thinkphp/library/think/App.php

@@ -0,0 +1,981 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\exception\ClassNotFoundException;
+use think\exception\HttpResponseException;
+use think\route\Dispatch;
+
+/**
+ * App 应用管理
+ */
+class App extends Container
+{
+    const VERSION = '5.1.39 LTS';
+
+    /**
+     * 当前模块路径
+     * @var string
+     */
+    protected $modulePath;
+
+    /**
+     * 应用调试模式
+     * @var bool
+     */
+    protected $appDebug = true;
+
+    /**
+     * 应用开始时间
+     * @var float
+     */
+    protected $beginTime;
+
+    /**
+     * 应用内存初始占用
+     * @var integer
+     */
+    protected $beginMem;
+
+    /**
+     * 应用类库命名空间
+     * @var string
+     */
+    protected $namespace = 'app';
+
+    /**
+     * 应用类库后缀
+     * @var bool
+     */
+    protected $suffix = false;
+
+    /**
+     * 严格路由检测
+     * @var bool
+     */
+    protected $routeMust;
+
+    /**
+     * 应用类库目录
+     * @var string
+     */
+    protected $appPath;
+
+    /**
+     * 框架目录
+     * @var string
+     */
+    protected $thinkPath;
+
+    /**
+     * 应用根目录
+     * @var string
+     */
+    protected $rootPath;
+
+    /**
+     * 运行时目录
+     * @var string
+     */
+    protected $runtimePath;
+
+    /**
+     * 配置目录
+     * @var string
+     */
+    protected $configPath;
+
+    /**
+     * 路由目录
+     * @var string
+     */
+    protected $routePath;
+
+    /**
+     * 配置后缀
+     * @var string
+     */
+    protected $configExt;
+
+    /**
+     * 应用调度实例
+     * @var Dispatch
+     */
+    protected $dispatch;
+
+    /**
+     * 绑定模块(控制器)
+     * @var string
+     */
+    protected $bindModule;
+
+    /**
+     * 初始化
+     * @var bool
+     */
+    protected $initialized = false;
+
+    public function __construct($appPath = '')
+    {
+        $this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR;
+        $this->path($appPath);
+    }
+
+    /**
+     * 绑定模块或者控制器
+     * @access public
+     * @param  string $bind
+     * @return $this
+     */
+    public function bind($bind)
+    {
+        $this->bindModule = $bind;
+        return $this;
+    }
+
+    /**
+     * 设置应用类库目录
+     * @access public
+     * @param  string $path 路径
+     * @return $this
+     */
+    public function path($path)
+    {
+        $this->appPath = $path ? realpath($path) . DIRECTORY_SEPARATOR : $this->getAppPath();
+
+        return $this;
+    }
+
+    /**
+     * 初始化应用
+     * @access public
+     * @return void
+     */
+    public function initialize()
+    {
+        if ($this->initialized) {
+            return;
+        }
+
+        $this->initialized = true;
+        $this->beginTime   = microtime(true);
+        $this->beginMem    = memory_get_usage();
+
+        $this->rootPath    = dirname($this->appPath) . DIRECTORY_SEPARATOR;
+        $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
+        $this->routePath   = $this->rootPath . 'route' . DIRECTORY_SEPARATOR;
+        $this->configPath  = $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
+
+        static::setInstance($this);
+
+        $this->instance('app', $this);
+
+        // 加载环境变量配置文件
+        if (is_file($this->rootPath . '.env')) {
+            $this->env->load($this->rootPath . '.env');
+        }
+
+        $this->configExt = $this->env->get('config_ext', '.php');
+
+        // 加载惯例配置文件
+        $this->config->set(include $this->thinkPath . 'convention.php');
+
+        // 设置路径环境变量
+        $this->env->set([
+            'think_path'   => $this->thinkPath,
+            'root_path'    => $this->rootPath,
+            'app_path'     => $this->appPath,
+            'config_path'  => $this->configPath,
+            'route_path'   => $this->routePath,
+            'runtime_path' => $this->runtimePath,
+            'extend_path'  => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR,
+            'vendor_path'  => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR,
+        ]);
+
+        $this->namespace = $this->env->get('app_namespace', $this->namespace);
+        $this->env->set('app_namespace', $this->namespace);
+
+        // 注册应用命名空间
+        Loader::addNamespace($this->namespace, $this->appPath);
+
+        // 初始化应用
+        $this->init();
+
+        // 开启类名后缀
+        $this->suffix = $this->config('app.class_suffix');
+
+        // 应用调试模式
+        $this->appDebug = $this->env->get('app_debug', $this->config('app.app_debug'));
+        $this->env->set('app_debug', $this->appDebug);
+
+        if (!$this->appDebug) {
+            ini_set('display_errors', 'Off');
+        } elseif (PHP_SAPI != 'cli') {
+            //重新申请一块比较大的buffer
+            if (ob_get_level() > 0) {
+                $output = ob_get_clean();
+            }
+            ob_start();
+            if (!empty($output)) {
+                echo $output;
+            }
+        }
+
+        // 注册异常处理类
+        if ($this->config('app.exception_handle')) {
+            Error::setExceptionHandler($this->config('app.exception_handle'));
+        }
+
+        // 注册根命名空间
+        if (!empty($this->config('app.root_namespace'))) {
+            Loader::addNamespace($this->config('app.root_namespace'));
+        }
+
+        // 加载composer autofile文件
+        Loader::loadComposerAutoloadFiles();
+
+        // 注册类库别名
+        Loader::addClassAlias($this->config->pull('alias'));
+
+        // 数据库配置初始化
+        Db::init($this->config->pull('database'));
+
+        // 设置系统时区
+        date_default_timezone_set($this->config('app.default_timezone'));
+
+        // 读取语言包
+        $this->loadLangPack();
+
+        // 路由初始化
+        $this->routeInit();
+    }
+
+    /**
+     * 初始化应用或模块
+     * @access public
+     * @param  string $module 模块名
+     * @return void
+     */
+    public function init($module = '')
+    {
+        // 定位模块目录
+        $module = $module ? $module . DIRECTORY_SEPARATOR : '';
+        $path   = $this->appPath . $module;
+
+        // 加载初始化文件
+        if (is_file($path . 'init.php')) {
+            include $path . 'init.php';
+        } elseif (is_file($this->runtimePath . $module . 'init.php')) {
+            include $this->runtimePath . $module . 'init.php';
+        } else {
+            // 加载行为扩展文件
+            if (is_file($path . 'tags.php')) {
+                $tags = include $path . 'tags.php';
+                if (is_array($tags)) {
+                    $this->hook->import($tags);
+                }
+            }
+
+            // 加载公共文件
+            if (is_file($path . 'common.php')) {
+                include_once $path . 'common.php';
+            }
+
+            if ('' == $module) {
+                // 加载系统助手函数
+                include $this->thinkPath . 'helper.php';
+            }
+
+            // 加载中间件
+            if (is_file($path . 'middleware.php')) {
+                $middleware = include $path . 'middleware.php';
+                if (is_array($middleware)) {
+                    $this->middleware->import($middleware);
+                }
+            }
+
+            // 注册服务的容器对象实例
+            if (is_file($path . 'provider.php')) {
+                $provider = include $path . 'provider.php';
+                if (is_array($provider)) {
+                    $this->bindTo($provider);
+                }
+            }
+
+            // 自动读取配置文件
+            if (is_dir($path . 'config')) {
+                $dir = $path . 'config' . DIRECTORY_SEPARATOR;
+            } elseif (is_dir($this->configPath . $module)) {
+                $dir = $this->configPath . $module;
+            }
+
+            $files = isset($dir) ? scandir($dir) : [];
+
+            foreach ($files as $file) {
+                if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) {
+                    $this->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME));
+                }
+            }
+        }
+
+        $this->setModulePath($path);
+
+        if ($module) {
+            // 对容器中的对象实例进行配置更新
+            $this->containerConfigUpdate($module);
+        }
+    }
+
+    protected function containerConfigUpdate($module)
+    {
+        $config = $this->config->get();
+
+        // 注册异常处理类
+        if ($config['app']['exception_handle']) {
+            Error::setExceptionHandler($config['app']['exception_handle']);
+        }
+
+        Db::init($config['database']);
+        $this->middleware->setConfig($config['middleware']);
+        $this->route->setConfig($config['app']);
+        $this->request->init($config['app']);
+        $this->cookie->init($config['cookie']);
+        $this->view->init($config['template']);
+        $this->log->init($config['log']);
+        $this->session->setConfig($config['session']);
+        $this->debug->setConfig($config['trace']);
+        $this->cache->init($config['cache'], true);
+
+        // 加载当前模块语言包
+        $this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php');
+
+        // 模块请求缓存检查
+        $this->checkRequestCache(
+            $config['app']['request_cache'],
+            $config['app']['request_cache_expire'],
+            $config['app']['request_cache_except']
+        );
+    }
+
+    /**
+     * 执行应用程序
+     * @access public
+     * @return Response
+     * @throws Exception
+     */
+    public function run()
+    {
+        try {
+            // 初始化应用
+            $this->initialize();
+
+            // 监听app_init
+            $this->hook->listen('app_init');
+
+            if ($this->bindModule) {
+                // 模块/控制器绑定
+                $this->route->bind($this->bindModule);
+            } elseif ($this->config('app.auto_bind_module')) {
+                // 入口自动绑定
+                $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME);
+                if ($name && 'index' != $name && is_dir($this->appPath . $name)) {
+                    $this->route->bind($name);
+                }
+            }
+
+            // 监听app_dispatch
+            $this->hook->listen('app_dispatch');
+
+            $dispatch = $this->dispatch;
+
+            if (empty($dispatch)) {
+                // 路由检测
+                $dispatch = $this->routeCheck()->init();
+            }
+
+            // 记录当前调度信息
+            $this->request->dispatch($dispatch);
+
+            // 记录路由和请求信息
+            if ($this->appDebug) {
+                $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true));
+                $this->log('[ HEADER ] ' . var_export($this->request->header(), true));
+                $this->log('[ PARAM ] ' . var_export($this->request->param(), true));
+            }
+
+            // 监听app_begin
+            $this->hook->listen('app_begin');
+
+            // 请求缓存检查
+            $this->checkRequestCache(
+                $this->config('request_cache'),
+                $this->config('request_cache_expire'),
+                $this->config('request_cache_except')
+            );
+
+            $data = null;
+        } catch (HttpResponseException $exception) {
+            $dispatch = null;
+            $data     = $exception->getResponse();
+        }
+
+        $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) {
+            return is_null($data) ? $dispatch->run() : $data;
+        });
+
+        $response = $this->middleware->dispatch($this->request);
+
+        // 监听app_end
+        $this->hook->listen('app_end', $response);
+
+        return $response;
+    }
+
+    protected function getRouteCacheKey()
+    {
+        if ($this->config->get('route_check_cache_key')) {
+            $closure  = $this->config->get('route_check_cache_key');
+            $routeKey = $closure($this->request);
+        } else {
+            $routeKey = md5($this->request->baseUrl(true) . ':' . $this->request->method());
+        }
+
+        return $routeKey;
+    }
+
+    protected function loadLangPack()
+    {
+        // 读取默认语言
+        $this->lang->range($this->config('app.default_lang'));
+
+        if ($this->config('app.lang_switch_on')) {
+            // 开启多语言机制 检测当前语言
+            $this->lang->detect();
+        }
+
+        $this->request->setLangset($this->lang->range());
+
+        // 加载系统语言包
+        $this->lang->load([
+            $this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php',
+            $this->appPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php',
+        ]);
+    }
+
+    /**
+     * 设置当前地址的请求缓存
+     * @access public
+     * @param  string $key 缓存标识,支持变量规则 ,例如 item/:name/:id
+     * @param  mixed  $expire 缓存有效期
+     * @param  array  $except 缓存排除
+     * @param  string $tag    缓存标签
+     * @return void
+     */
+    public function checkRequestCache($key, $expire = null, $except = [], $tag = null)
+    {
+        $cache = $this->request->cache($key, $expire, $except, $tag);
+
+        if ($cache) {
+            $this->setResponseCache($cache);
+        }
+    }
+
+    public function setResponseCache($cache)
+    {
+        list($key, $expire, $tag) = $cache;
+
+        if (strtotime($this->request->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $this->request->server('REQUEST_TIME')) {
+            // 读取缓存
+            $response = Response::create()->code(304);
+            throw new HttpResponseException($response);
+        } elseif ($this->cache->has($key)) {
+            list($content, $header) = $this->cache->get($key);
+
+            $response = Response::create($content)->header($header);
+            throw new HttpResponseException($response);
+        }
+    }
+
+    /**
+     * 设置当前请求的调度信息
+     * @access public
+     * @param  Dispatch  $dispatch 调度信息
+     * @return $this
+     */
+    public function dispatch(Dispatch $dispatch)
+    {
+        $this->dispatch = $dispatch;
+        return $this;
+    }
+
+    /**
+     * 记录调试信息
+     * @access public
+     * @param  mixed  $msg  调试信息
+     * @param  string $type 信息类型
+     * @return void
+     */
+    public function log($msg, $type = 'info')
+    {
+        $this->appDebug && $this->log->record($msg, $type);
+    }
+
+    /**
+     * 获取配置参数 为空则获取所有配置
+     * @access public
+     * @param  string    $name 配置参数名(支持二级配置 .号分割)
+     * @return mixed
+     */
+    public function config($name = '')
+    {
+        return $this->config->get($name);
+    }
+
+    /**
+     * 路由初始化 导入路由定义规则
+     * @access public
+     * @return void
+     */
+    public function routeInit()
+    {
+        // 路由检测
+        $files = scandir($this->routePath);
+        foreach ($files as $file) {
+            if (strpos($file, '.php')) {
+                $filename = $this->routePath . $file;
+                // 导入路由配置
+                $rules = include $filename;
+                if (is_array($rules)) {
+                    $this->route->import($rules);
+                }
+            }
+        }
+
+        if ($this->route->config('route_annotation')) {
+            // 自动生成路由定义
+            if ($this->appDebug) {
+                $suffix = $this->route->config('controller_suffix') || $this->route->config('class_suffix');
+                $this->build->buildRoute($suffix);
+            }
+
+            $filename = $this->runtimePath . 'build_route.php';
+
+            if (is_file($filename)) {
+                include $filename;
+            }
+        }
+    }
+
+    /**
+     * URL路由检测(根据PATH_INFO)
+     * @access public
+     * @return Dispatch
+     */
+    public function routeCheck()
+    {
+        // 检测路由缓存
+        if (!$this->appDebug && $this->config->get('route_check_cache')) {
+            $routeKey = $this->getRouteCacheKey();
+            $option   = $this->config->get('route_cache_option');
+
+            if ($option && $this->cache->connect($option)->has($routeKey)) {
+                return $this->cache->connect($option)->get($routeKey);
+            } elseif ($this->cache->has($routeKey)) {
+                return $this->cache->get($routeKey);
+            }
+        }
+
+        // 获取应用调度信息
+        $path = $this->request->path();
+
+        // 是否强制路由模式
+        $must = !is_null($this->routeMust) ? $this->routeMust : $this->route->config('url_route_must');
+
+        // 路由检测 返回一个Dispatch对象
+        $dispatch = $this->route->check($path, $must);
+
+        if (!empty($routeKey)) {
+            try {
+                if ($option) {
+                    $this->cache->connect($option)->tag('route_cache')->set($routeKey, $dispatch);
+                } else {
+                    $this->cache->tag('route_cache')->set($routeKey, $dispatch);
+                }
+            } catch (\Exception $e) {
+                // 存在闭包的时候缓存无效
+            }
+        }
+
+        return $dispatch;
+    }
+
+    /**
+     * 设置应用的路由检测机制
+     * @access public
+     * @param  bool $must  是否强制检测路由
+     * @return $this
+     */
+    public function routeMust($must = false)
+    {
+        $this->routeMust = $must;
+        return $this;
+    }
+
+    /**
+     * 解析模块和类名
+     * @access protected
+     * @param  string $name         资源地址
+     * @param  string $layer        验证层名称
+     * @param  bool   $appendSuffix 是否添加类名后缀
+     * @return array
+     */
+    protected function parseModuleAndClass($name, $layer, $appendSuffix)
+    {
+        if (false !== strpos($name, '\\')) {
+            $class  = $name;
+            $module = $this->request->module();
+        } else {
+            if (strpos($name, '/')) {
+                list($module, $name) = explode('/', $name, 2);
+            } else {
+                $module = $this->request->module();
+            }
+
+            $class = $this->parseClass($module, $layer, $name, $appendSuffix);
+        }
+
+        return [$module, $class];
+    }
+
+    /**
+     * 实例化应用类库
+     * @access public
+     * @param  string $name         类名称
+     * @param  string $layer        业务层名称
+     * @param  bool   $appendSuffix 是否添加类名后缀
+     * @param  string $common       公共模块名
+     * @return object
+     * @throws ClassNotFoundException
+     */
+    public function create($name, $layer, $appendSuffix = false, $common = 'common')
+    {
+        $guid = $name . $layer;
+
+        if ($this->__isset($guid)) {
+            return $this->__get($guid);
+        }
+
+        list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix);
+
+        if (class_exists($class)) {
+            $object = $this->__get($class);
+        } else {
+            $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class);
+            if (class_exists($class)) {
+                $object = $this->__get($class);
+            } else {
+                throw new ClassNotFoundException('class not exists:' . $class, $class);
+            }
+        }
+
+        $this->__set($guid, $class);
+
+        return $object;
+    }
+
+    /**
+     * 实例化(分层)模型
+     * @access public
+     * @param  string $name         Model名称
+     * @param  string $layer        业务层名称
+     * @param  bool   $appendSuffix 是否添加类名后缀
+     * @param  string $common       公共模块名
+     * @return Model
+     * @throws ClassNotFoundException
+     */
+    public function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common')
+    {
+        return $this->create($name, $layer, $appendSuffix, $common);
+    }
+
+    /**
+     * 实例化(分层)控制器 格式:[模块名/]控制器名
+     * @access public
+     * @param  string $name              资源地址
+     * @param  string $layer             控制层名称
+     * @param  bool   $appendSuffix      是否添加类名后缀
+     * @param  string $empty             空控制器名称
+     * @return object
+     * @throws ClassNotFoundException
+     */
+    public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
+    {
+        list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix);
+
+        if (class_exists($class)) {
+            return $this->make($class, true);
+        } elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) {
+            return $this->make($emptyClass, true);
+        }
+
+        throw new ClassNotFoundException('class not exists:' . $class, $class);
+    }
+
+    /**
+     * 实例化验证类 格式:[模块名/]验证器名
+     * @access public
+     * @param  string $name         资源地址
+     * @param  string $layer        验证层名称
+     * @param  bool   $appendSuffix 是否添加类名后缀
+     * @param  string $common       公共模块名
+     * @return Validate
+     * @throws ClassNotFoundException
+     */
+    public function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common')
+    {
+        $name = $name ?: $this->config('default_validate');
+
+        if (empty($name)) {
+            return new Validate;
+        }
+
+        return $this->create($name, $layer, $appendSuffix, $common);
+    }
+
+    /**
+     * 数据库初始化
+     * @access public
+     * @param  mixed         $config 数据库配置
+     * @param  bool|string   $name 连接标识 true 强制重新连接
+     * @return \think\db\Query
+     */
+    public function db($config = [], $name = false)
+    {
+        return Db::connect($config, $name);
+    }
+
+    /**
+     * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作
+     * @access public
+     * @param  string       $url          调用地址
+     * @param  string|array $vars         调用参数 支持字符串和数组
+     * @param  string       $layer        要调用的控制层名称
+     * @param  bool         $appendSuffix 是否添加类名后缀
+     * @return mixed
+     * @throws ClassNotFoundException
+     */
+    public function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
+    {
+        $info   = pathinfo($url);
+        $action = $info['basename'];
+        $module = '.' != $info['dirname'] ? $info['dirname'] : $this->request->controller();
+        $class  = $this->controller($module, $layer, $appendSuffix);
+
+        if (is_scalar($vars)) {
+            if (strpos($vars, '=')) {
+                parse_str($vars, $vars);
+            } else {
+                $vars = [$vars];
+            }
+        }
+
+        return $this->invokeMethod([$class, $action . $this->config('action_suffix')], $vars);
+    }
+
+    /**
+     * 解析应用类的类名
+     * @access public
+     * @param  string $module 模块名
+     * @param  string $layer  层名 controller model ...
+     * @param  string $name   类名
+     * @param  bool   $appendSuffix
+     * @return string
+     */
+    public function parseClass($module, $layer, $name, $appendSuffix = false)
+    {
+        $name  = str_replace(['/', '.'], '\\', $name);
+        $array = explode('\\', $name);
+        $class = Loader::parseName(array_pop($array), 1) . ($this->suffix || $appendSuffix ? ucfirst($layer) : '');
+        $path  = $array ? implode('\\', $array) . '\\' : '';
+
+        return $this->namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $path . $class;
+    }
+
+    /**
+     * 获取框架版本
+     * @access public
+     * @return string
+     */
+    public function version()
+    {
+        return static::VERSION;
+    }
+
+    /**
+     * 是否为调试模式
+     * @access public
+     * @return bool
+     */
+    public function isDebug()
+    {
+        return $this->appDebug;
+    }
+
+    /**
+     * 获取模块路径
+     * @access public
+     * @return string
+     */
+    public function getModulePath()
+    {
+        return $this->modulePath;
+    }
+
+    /**
+     * 设置模块路径
+     * @access public
+     * @param  string $path 路径
+     * @return void
+     */
+    public function setModulePath($path)
+    {
+        $this->modulePath = $path;
+        $this->env->set('module_path', $path);
+    }
+
+    /**
+     * 获取应用根目录
+     * @access public
+     * @return string
+     */
+    public function getRootPath()
+    {
+        return $this->rootPath;
+    }
+
+    /**
+     * 获取应用类库目录
+     * @access public
+     * @return string
+     */
+    public function getAppPath()
+    {
+        if (is_null($this->appPath)) {
+            $this->appPath = Loader::getRootPath() . 'application' . DIRECTORY_SEPARATOR;
+        }
+
+        return $this->appPath;
+    }
+
+    /**
+     * 获取应用运行时目录
+     * @access public
+     * @return string
+     */
+    public function getRuntimePath()
+    {
+        return $this->runtimePath;
+    }
+
+    /**
+     * 获取核心框架目录
+     * @access public
+     * @return string
+     */
+    public function getThinkPath()
+    {
+        return $this->thinkPath;
+    }
+
+    /**
+     * 获取路由目录
+     * @access public
+     * @return string
+     */
+    public function getRoutePath()
+    {
+        return $this->routePath;
+    }
+
+    /**
+     * 获取应用配置目录
+     * @access public
+     * @return string
+     */
+    public function getConfigPath()
+    {
+        return $this->configPath;
+    }
+
+    /**
+     * 获取配置后缀
+     * @access public
+     * @return string
+     */
+    public function getConfigExt()
+    {
+        return $this->configExt;
+    }
+
+    /**
+     * 获取应用类库命名空间
+     * @access public
+     * @return string
+     */
+    public function getNamespace()
+    {
+        return $this->namespace;
+    }
+
+    /**
+     * 设置应用类库命名空间
+     * @access public
+     * @param  string $namespace 命名空间名称
+     * @return $this
+     */
+    public function setNamespace($namespace)
+    {
+        $this->namespace = $namespace;
+        return $this;
+    }
+
+    /**
+     * 是否启用类库后缀
+     * @access public
+     * @return bool
+     */
+    public function getSuffix()
+    {
+        return $this->suffix;
+    }
+
+    /**
+     * 获取应用开启时间
+     * @access public
+     * @return float
+     */
+    public function getBeginTime()
+    {
+        return $this->beginTime;
+    }
+
+    /**
+     * 获取应用初始内存占用
+     * @access public
+     * @return integer
+     */
+    public function getBeginMem()
+    {
+        return $this->beginMem;
+    }
+
+}

+ 415 - 0
thinkphp/library/think/Build.php

@@ -0,0 +1,415 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Build
+{
+    /**
+     * 应用对象
+     * @var App
+     */
+    protected $app;
+
+    /**
+     * 应用目录
+     * @var string
+     */
+    protected $basePath;
+
+    public function __construct(App $app)
+    {
+        $this->app      = $app;
+        $this->basePath = $this->app->getAppPath();
+    }
+
+    /**
+     * 根据传入的build资料创建目录和文件
+     * @access public
+     * @param  array  $build build列表
+     * @param  string $namespace 应用类库命名空间
+     * @param  bool   $suffix 类库后缀
+     * @return void
+     */
+    public function run(array $build = [], $namespace = 'app', $suffix = false)
+    {
+        // 锁定
+        $lockfile = $this->basePath . 'build.lock';
+
+        if (is_writable($lockfile)) {
+            return;
+        } elseif (!touch($lockfile)) {
+            throw new Exception('应用目录[' . $this->basePath . ']不可写,目录无法自动生成!<BR>请手动生成项目目录~', 10006);
+        }
+
+        foreach ($build as $module => $list) {
+            if ('__dir__' == $module) {
+                // 创建目录列表
+                $this->buildDir($list);
+            } elseif ('__file__' == $module) {
+                // 创建文件列表
+                $this->buildFile($list);
+            } else {
+                // 创建模块
+                $this->module($module, $list, $namespace, $suffix);
+            }
+        }
+
+        // 解除锁定
+        unlink($lockfile);
+    }
+
+    /**
+     * 创建目录
+     * @access protected
+     * @param  array $list 目录列表
+     * @return void
+     */
+    protected function buildDir($list)
+    {
+        foreach ($list as $dir) {
+            $this->checkDirBuild($this->basePath . $dir);
+        }
+    }
+
+    /**
+     * 创建文件
+     * @access protected
+     * @param  array $list 文件列表
+     * @return void
+     */
+    protected function buildFile($list)
+    {
+        foreach ($list as $file) {
+            if (!is_dir($this->basePath . dirname($file))) {
+                // 创建目录
+                mkdir($this->basePath . dirname($file), 0755, true);
+            }
+
+            if (!is_file($this->basePath . $file)) {
+                file_put_contents($this->basePath . $file, 'php' == pathinfo($file, PATHINFO_EXTENSION) ? "<?php\n" : '');
+            }
+        }
+    }
+
+    /**
+     * 创建模块
+     * @access public
+     * @param  string $module 模块名
+     * @param  array  $list build列表
+     * @param  string $namespace 应用类库命名空间
+     * @param  bool   $suffix 类库后缀
+     * @return void
+     */
+    public function module($module = '', $list = [], $namespace = 'app', $suffix = false)
+    {
+        $module = $module ? $module : '';
+
+        if (!is_dir($this->basePath . $module)) {
+            // 创建模块目录
+            mkdir($this->basePath . $module);
+        }
+
+        if (basename($this->app->getRuntimePath()) != $module) {
+            // 创建配置文件和公共文件
+            $this->buildCommon($module);
+            // 创建模块的默认页面
+            $this->buildHello($module, $namespace, $suffix);
+        }
+
+        if (empty($list)) {
+            // 创建默认的模块目录和文件
+            $list = [
+                '__file__' => ['common.php'],
+                '__dir__'  => ['controller', 'model', 'view', 'config'],
+            ];
+        }
+
+        // 创建子目录和文件
+        foreach ($list as $path => $file) {
+            $modulePath = $this->basePath . $module . DIRECTORY_SEPARATOR;
+            if ('__dir__' == $path) {
+                // 生成子目录
+                foreach ($file as $dir) {
+                    $this->checkDirBuild($modulePath . $dir);
+                }
+            } elseif ('__file__' == $path) {
+                // 生成(空白)文件
+                foreach ($file as $name) {
+                    if (!is_file($modulePath . $name)) {
+                        file_put_contents($modulePath . $name, 'php' == pathinfo($name, PATHINFO_EXTENSION) ? "<?php\n" : '');
+                    }
+                }
+            } else {
+                // 生成相关MVC文件
+                foreach ($file as $val) {
+                    $val      = trim($val);
+                    $filename = $modulePath . $path . DIRECTORY_SEPARATOR . $val . ($suffix ? ucfirst($path) : '') . '.php';
+                    $space    = $namespace . '\\' . ($module ? $module . '\\' : '') . $path;
+                    $class    = $val . ($suffix ? ucfirst($path) : '');
+                    switch ($path) {
+                        case 'controller': // 控制器
+                            $content = "<?php\nnamespace {$space};\n\nclass {$class}\n{\n\n}";
+                            break;
+                        case 'model': // 模型
+                            $content = "<?php\nnamespace {$space};\n\nuse think\Model;\n\nclass {$class} extends Model\n{\n\n}";
+                            break;
+                        case 'view': // 视图
+                            $filename = $modulePath . $path . DIRECTORY_SEPARATOR . $val . '.html';
+                            $this->checkDirBuild(dirname($filename));
+                            $content = '';
+                            break;
+                        default:
+                            // 其他文件
+                            $content = "<?php\nnamespace {$space};\n\nclass {$class}\n{\n\n}";
+                    }
+
+                    if (!is_file($filename)) {
+                        file_put_contents($filename, $content);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 根据注释自动生成路由规则
+     * @access public
+     * @param  bool   $suffix 类库后缀
+     * @param  string $layer  控制器层目录名
+     * @return string
+     */
+    public function buildRoute($suffix = false, $layer = '')
+    {
+        $namespace = $this->app->getNameSpace();
+        $content   = '<?php ' . PHP_EOL . '//根据 Annotation 自动生成的路由规则';
+
+        if (!$layer) {
+            $layer = $this->app->config('app.url_controller_layer');
+        }
+
+        if ($this->app->config('app.app_multi_module')) {
+            $modules = glob($this->basePath . '*', GLOB_ONLYDIR);
+
+            foreach ($modules as $module) {
+                $module = basename($module);
+
+                if (in_array($module, $this->app->config('app.deny_module_list'))) {
+                    continue;
+                }
+
+                $path = $this->basePath . $module . DIRECTORY_SEPARATOR . $layer . DIRECTORY_SEPARATOR;
+                $content .= $this->buildDirRoute($path, $namespace, $module, $suffix, $layer);
+            }
+        } else {
+            $path = $this->basePath . $layer . DIRECTORY_SEPARATOR;
+            $content .= $this->buildDirRoute($path, $namespace, '', $suffix, $layer);
+        }
+
+        $filename = $this->app->getRuntimePath() . 'build_route.php';
+        file_put_contents($filename, $content);
+
+        return $filename;
+    }
+
+    /**
+     * 生成子目录控制器类的路由规则
+     * @access protected
+     * @param  string $path  控制器目录
+     * @param  string $namespace 应用命名空间
+     * @param  string $module 模块
+     * @param  bool   $suffix 类库后缀
+     * @param  string $layer 控制器层目录名
+     * @return string
+     */
+    protected function buildDirRoute($path, $namespace, $module, $suffix, $layer)
+    {
+        $content     = '';
+        $controllers = glob($path . '*.php');
+
+        foreach ($controllers as $controller) {
+            $controller = basename($controller, '.php');
+
+            $class = new \ReflectionClass($namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $controller);
+
+            if (strpos($layer, '\\')) {
+                // 多级控制器
+                $level      = str_replace(DIRECTORY_SEPARATOR, '.', substr($layer, 11));
+                $controller = $level . '.' . $controller;
+                $length     = strlen(strstr($layer, '\\', true));
+            } else {
+                $length = strlen($layer);
+            }
+
+            if ($suffix) {
+                $controller = substr($controller, 0, -$length);
+            }
+
+            $content .= $this->getControllerRoute($class, $module, $controller);
+        }
+
+        $subDir = glob($path . '*', GLOB_ONLYDIR);
+
+        foreach ($subDir as $dir) {
+            $content .= $this->buildDirRoute($dir . DIRECTORY_SEPARATOR, $namespace, $module, $suffix, $layer . '\\' . basename($dir));
+        }
+
+        return $content;
+    }
+
+    /**
+     * 生成控制器类的路由规则
+     * @access protected
+     * @param  string $class        控制器完整类名
+     * @param  string $module       模块名
+     * @param  string $controller   控制器名
+     * @return string
+     */
+    protected function getControllerRoute($class, $module, $controller)
+    {
+        $content = '';
+        $comment = $class->getDocComment();
+
+        if (false !== strpos($comment, '@route(')) {
+            $comment = $this->parseRouteComment($comment);
+            $route   = ($module ? $module . '/' : '') . $controller;
+            $comment = preg_replace('/route\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::resource(\1,\'' . $route . '\')', $comment);
+            $content .= PHP_EOL . $comment;
+        } elseif (false !== strpos($comment, '@alias(')) {
+            $comment = $this->parseRouteComment($comment, '@alias(');
+            $route   = ($module ? $module . '/' : '') . $controller;
+            $comment = preg_replace('/alias\(\s?([\'\"][\-\_\/\w]+[\'\"])\s?\)/is', 'Route::alias(\1,\'' . $route . '\')', $comment);
+            $content .= PHP_EOL . $comment;
+        }
+
+        $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
+
+        foreach ($methods as $method) {
+            $comment = $this->getMethodRouteComment($module, $controller, $method);
+            if ($comment) {
+                $content .= PHP_EOL . $comment;
+            }
+        }
+
+        return $content;
+    }
+
+    /**
+     * 解析路由注释
+     * @access protected
+     * @param  string $comment
+     * @param  string $tag
+     * @return string
+     */
+    protected function parseRouteComment($comment, $tag = '@route(')
+    {
+        $comment = substr($comment, 3, -2);
+        $comment = explode(PHP_EOL, substr(strstr(trim($comment), $tag), 1));
+        $comment = array_map(function ($item) {return trim(trim($item), ' \t*');}, $comment);
+
+        if (count($comment) > 1) {
+            $key     = array_search('', $comment);
+            $comment = array_slice($comment, 0, false === $key ? 1 : $key);
+        }
+
+        $comment = implode(PHP_EOL . "\t", $comment) . ';';
+
+        if (strpos($comment, '{')) {
+            $comment = preg_replace_callback('/\{\s?.*?\s?\}/s', function ($matches) {
+                return false !== strpos($matches[0], '"') ? '[' . substr(var_export(json_decode($matches[0], true), true), 7, -1) . ']' : $matches[0];
+            }, $comment);
+        }
+        return $comment;
+    }
+
+    /**
+     * 获取方法的路由注释
+     * @access protected
+     * @param  string           $module 模块
+     * @param  string           $controller 控制器名
+     * @param  \ReflectMethod   $reflectMethod
+     * @return string|void
+     */
+    protected function getMethodRouteComment($module, $controller, $reflectMethod)
+    {
+        $comment = $reflectMethod->getDocComment();
+
+        if (false !== strpos($comment, '@route(')) {
+            $comment = $this->parseRouteComment($comment);
+            $action  = $reflectMethod->getName();
+
+            if ($suffix = $this->app->config('app.action_suffix')) {
+                $action = substr($action, 0, -strlen($suffix));
+            }
+
+            $route   = ($module ? $module . '/' : '') . $controller . '/' . $action;
+            $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\,?\s?[\'\"]?(\w+?)[\'\"]?\s?\)/is', 'Route::\2(\1,\'' . $route . '\')', $comment);
+            $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::rule(\1,\'' . $route . '\')', $comment);
+
+            return $comment;
+        }
+    }
+
+    /**
+     * 创建模块的欢迎页面
+     * @access protected
+     * @param  string $module 模块名
+     * @param  string $namespace 应用类库命名空间
+     * @param  bool   $suffix 类库后缀
+     * @return void
+     */
+    protected function buildHello($module, $namespace, $suffix = false)
+    {
+        $filename = $this->basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'controller' . DIRECTORY_SEPARATOR . 'Index' . ($suffix ? 'Controller' : '') . '.php';
+        if (!is_file($filename)) {
+            $content = file_get_contents($this->app->getThinkPath() . 'tpl' . DIRECTORY_SEPARATOR . 'default_index.tpl');
+            $content = str_replace(['{$app}', '{$module}', '{layer}', '{$suffix}'], [$namespace, $module ? $module . '\\' : '', 'controller', $suffix ? 'Controller' : ''], $content);
+            $this->checkDirBuild(dirname($filename));
+
+            file_put_contents($filename, $content);
+        }
+    }
+
+    /**
+     * 创建模块的公共文件
+     * @access protected
+     * @param  string $module 模块名
+     * @return void
+     */
+    protected function buildCommon($module)
+    {
+        $filename = $this->app->getConfigPath() . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'app.php';
+        $this->checkDirBuild(dirname($filename));
+
+        if (!is_file($filename)) {
+            file_put_contents($filename, "<?php\n//配置文件\nreturn [\n\n];");
+        }
+
+        $filename = $this->basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'common.php';
+
+        if (!is_file($filename)) {
+            file_put_contents($filename, "<?php\n");
+        }
+    }
+
+    /**
+     * 创建目录
+     * @access protected
+     * @param  string $dirname 目录名称
+     * @return void
+     */
+    protected function checkDirBuild($dirname)
+    {
+        if (!is_dir($dirname)) {
+            mkdir($dirname, 0755, true);
+        }
+    }
+}

+ 133 - 0
thinkphp/library/think/Cache.php

@@ -0,0 +1,133 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\cache\Driver;
+
+/**
+ * Class Cache
+ *
+ * @package think
+ *
+ * @mixin Driver
+ * @mixin \think\cache\driver\File
+ */
+class Cache
+{
+    /**
+     * 缓存实例
+     * @var array
+     */
+    protected $instance = [];
+
+    /**
+     * 缓存配置
+     * @var array
+     */
+    protected $config = [];
+
+    /**
+     * 操作句柄
+     * @var object
+     */
+    protected $handler;
+
+    public function __construct(array $config = [])
+    {
+        $this->config = $config;
+        $this->init($config);
+    }
+
+    /**
+     * 连接缓存
+     * @access public
+     * @param  array         $options  配置数组
+     * @param  bool|string   $name 缓存连接标识 true 强制重新连接
+     * @return Driver
+     */
+    public function connect(array $options = [], $name = false)
+    {
+        if (false === $name) {
+            $name = md5(serialize($options));
+        }
+
+        if (true === $name || !isset($this->instance[$name])) {
+            $type = !empty($options['type']) ? $options['type'] : 'File';
+
+            if (true === $name) {
+                $name = md5(serialize($options));
+            }
+
+            $this->instance[$name] = Loader::factory($type, '\\think\\cache\\driver\\', $options);
+        }
+
+        return $this->instance[$name];
+    }
+
+    /**
+     * 自动初始化缓存
+     * @access public
+     * @param  array         $options  配置数组
+     * @param  bool          $force    强制更新
+     * @return Driver
+     */
+    public function init(array $options = [], $force = false)
+    {
+        if (is_null($this->handler) || $force) {
+
+            if ('complex' == $options['type']) {
+                $default = $options['default'];
+                $options = isset($options[$default['type']]) ? $options[$default['type']] : $default;
+            }
+
+            $this->handler = $this->connect($options);
+        }
+
+        return $this->handler;
+    }
+
+    public static function __make(Config $config)
+    {
+        return new static($config->pull('cache'));
+    }
+
+    public function getConfig()
+    {
+        return $this->config;
+    }
+
+    public function setConfig(array $config)
+    {
+        $this->config = array_merge($this->config, $config);
+    }
+
+    /**
+     * 切换缓存类型 需要配置 cache.type 为 complex
+     * @access public
+     * @param  string $name 缓存标识
+     * @return Driver
+     */
+    public function store($name = '')
+    {
+        if ('' !== $name && 'complex' == $this->config['type']) {
+            return $this->connect($this->config[$name], strtolower($name));
+        }
+
+        return $this->init();
+    }
+
+    public function __call($method, $args)
+    {
+        return call_user_func_array([$this->init(), $method], $args);
+    }
+
+}

+ 552 - 0
thinkphp/library/think/Collection.php

@@ -0,0 +1,552 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use ArrayAccess;
+use ArrayIterator;
+use Countable;
+use IteratorAggregate;
+use JsonSerializable;
+
+class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable
+{
+    /**
+     * 数据集数据
+     * @var array
+     */
+    protected $items = [];
+
+    public function __construct($items = [])
+    {
+        $this->items = $this->convertToArray($items);
+    }
+
+    public static function make($items = [])
+    {
+        return new static($items);
+    }
+
+    /**
+     * 是否为空
+     * @access public
+     * @return bool
+     */
+    public function isEmpty()
+    {
+        return empty($this->items);
+    }
+
+    public function toArray()
+    {
+        return array_map(function ($value) {
+            return ($value instanceof Model || $value instanceof self) ? $value->toArray() : $value;
+        }, $this->items);
+    }
+
+    public function all()
+    {
+        return $this->items;
+    }
+
+    /**
+     * 合并数组
+     *
+     * @access public
+     * @param  mixed $items
+     * @return static
+     */
+    public function merge($items)
+    {
+        return new static(array_merge($this->items, $this->convertToArray($items)));
+    }
+
+    /**
+     * 交换数组中的键和值
+     *
+     * @access public
+     * @return static
+     */
+    public function flip()
+    {
+        return new static(array_flip($this->items));
+    }
+
+    /**
+     * 按指定键整理数据
+     *
+     * @access public
+     * @param  mixed    $items      数据
+     * @param  string   $indexKey   键名
+     * @return array
+     */
+    public function dictionary($items = null, &$indexKey = null)
+    {
+        if ($items instanceof self || $items instanceof Paginator) {
+            $items = $items->all();
+        }
+
+        $items = is_null($items) ? $this->items : $items;
+
+        if ($items && empty($indexKey)) {
+            $indexKey = is_array($items[0]) ? 'id' : $items[0]->getPk();
+        }
+
+        if (isset($indexKey) && is_string($indexKey)) {
+            return array_column($items, null, $indexKey);
+        }
+
+        return $items;
+    }
+
+    /**
+     * 比较数组,返回差集
+     *
+     * @access public
+     * @param  mixed    $items      数据
+     * @param  string   $indexKey   指定比较的键名
+     * @return static
+     */
+    public function diff($items, $indexKey = null)
+    {
+        if ($this->isEmpty() || is_scalar($this->items[0])) {
+            return new static(array_diff($this->items, $this->convertToArray($items)));
+        }
+
+        $diff       = [];
+        $dictionary = $this->dictionary($items, $indexKey);
+
+        if (is_string($indexKey)) {
+            foreach ($this->items as $item) {
+                if (!isset($dictionary[$item[$indexKey]])) {
+                    $diff[] = $item;
+                }
+            }
+        }
+
+        return new static($diff);
+    }
+
+    /**
+     * 比较数组,返回交集
+     *
+     * @access public
+     * @param  mixed    $items      数据
+     * @param  string   $indexKey   指定比较的键名
+     * @return static
+     */
+    public function intersect($items, $indexKey = null)
+    {
+        if ($this->isEmpty() || is_scalar($this->items[0])) {
+            return new static(array_diff($this->items, $this->convertToArray($items)));
+        }
+
+        $intersect  = [];
+        $dictionary = $this->dictionary($items, $indexKey);
+
+        if (is_string($indexKey)) {
+            foreach ($this->items as $item) {
+                if (isset($dictionary[$item[$indexKey]])) {
+                    $intersect[] = $item;
+                }
+            }
+        }
+
+        return new static($intersect);
+    }
+
+    /**
+     * 返回数组中所有的键名
+     *
+     * @access public
+     * @return array
+     */
+    public function keys()
+    {
+        $current = current($this->items);
+
+        if (is_scalar($current)) {
+            $array = $this->items;
+        } elseif (is_array($current)) {
+            $array = $current;
+        } else {
+            $array = $current->toArray();
+        }
+
+        return array_keys($array);
+    }
+
+    /**
+     * 删除数组的最后一个元素(出栈)
+     *
+     * @access public
+     * @return mixed
+     */
+    public function pop()
+    {
+        return array_pop($this->items);
+    }
+
+    /**
+     * 通过使用用户自定义函数,以字符串返回数组
+     *
+     * @access public
+     * @param  callable $callback
+     * @param  mixed    $initial
+     * @return mixed
+     */
+    public function reduce(callable $callback, $initial = null)
+    {
+        return array_reduce($this->items, $callback, $initial);
+    }
+
+    /**
+     * 以相反的顺序返回数组。
+     *
+     * @access public
+     * @return static
+     */
+    public function reverse()
+    {
+        return new static(array_reverse($this->items));
+    }
+
+    /**
+     * 删除数组中首个元素,并返回被删除元素的值
+     *
+     * @access public
+     * @return mixed
+     */
+    public function shift()
+    {
+        return array_shift($this->items);
+    }
+
+    /**
+     * 在数组结尾插入一个元素
+     * @access public
+     * @param  mixed  $value
+     * @param  mixed  $key
+     * @return void
+     */
+    public function push($value, $key = null)
+    {
+        if (is_null($key)) {
+            $this->items[] = $value;
+        } else {
+            $this->items[$key] = $value;
+        }
+    }
+
+    /**
+     * 把一个数组分割为新的数组块.
+     *
+     * @access public
+     * @param  int  $size
+     * @param  bool $preserveKeys
+     * @return static
+     */
+    public function chunk($size, $preserveKeys = false)
+    {
+        $chunks = [];
+
+        foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) {
+            $chunks[] = new static($chunk);
+        }
+
+        return new static($chunks);
+    }
+
+    /**
+     * 在数组开头插入一个元素
+     * @access public
+     * @param mixed  $value
+     * @param mixed  $key
+     * @return void
+     */
+    public function unshift($value, $key = null)
+    {
+        if (is_null($key)) {
+            array_unshift($this->items, $value);
+        } else {
+            $this->items = [$key => $value] + $this->items;
+        }
+    }
+
+    /**
+     * 给每个元素执行个回调
+     *
+     * @access public
+     * @param  callable $callback
+     * @return $this
+     */
+    public function each(callable $callback)
+    {
+        foreach ($this->items as $key => $item) {
+            $result = $callback($item, $key);
+
+            if (false === $result) {
+                break;
+            } elseif (!is_object($item)) {
+                $this->items[$key] = $result;
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * 用回调函数处理数组中的元素
+     * @access public
+     * @param  callable|null $callback
+     * @return static
+     */
+    public function map(callable $callback)
+    {
+        return new static(array_map($callback, $this->items));
+    }
+
+    /**
+     * 用回调函数过滤数组中的元素
+     * @access public
+     * @param  callable|null $callback
+     * @return static
+     */
+    public function filter(callable $callback = null)
+    {
+        if ($callback) {
+            return new static(array_filter($this->items, $callback));
+        }
+
+        return new static(array_filter($this->items));
+    }
+
+    /**
+     * 根据字段条件过滤数组中的元素
+     * @access public
+     * @param  string   $field 字段名
+     * @param  mixed    $operator 操作符
+     * @param  mixed    $value 数据
+     * @return static
+     */
+    public function where($field, $operator, $value = null)
+    {
+        if (is_null($value)) {
+            $value    = $operator;
+            $operator = '=';
+        }
+
+        return $this->filter(function ($data) use ($field, $operator, $value) {
+            if (strpos($field, '.')) {
+                list($field, $relation) = explode('.', $field);
+
+                $result = isset($data[$field][$relation]) ? $data[$field][$relation] : null;
+            } else {
+                $result = isset($data[$field]) ? $data[$field] : null;
+            }
+
+            switch (strtolower($operator)) {
+                case '===':
+                    return $result === $value;
+                case '!==':
+                    return $result !== $value;
+                case '!=':
+                case '<>':
+                    return $result != $value;
+                case '>':
+                    return $result > $value;
+                case '>=':
+                    return $result >= $value;
+                case '<':
+                    return $result < $value;
+                case '<=':
+                    return $result <= $value;
+                case 'like':
+                    return is_string($result) && false !== strpos($result, $value);
+                case 'not like':
+                    return is_string($result) && false === strpos($result, $value);
+                case 'in':
+                    return is_scalar($result) && in_array($result, $value, true);
+                case 'not in':
+                    return is_scalar($result) && !in_array($result, $value, true);
+                case 'between':
+                    list($min, $max) = is_string($value) ? explode(',', $value) : $value;
+                    return is_scalar($result) && $result >= $min && $result <= $max;
+                case 'not between':
+                    list($min, $max) = is_string($value) ? explode(',', $value) : $value;
+                    return is_scalar($result) && $result > $max || $result < $min;
+                case '==':
+                case '=':
+                default:
+                    return $result == $value;
+            }
+        });
+    }
+
+    /**
+     * 返回数据中指定的一列
+     * @access public
+     * @param mixed $columnKey 键名
+     * @param mixed $indexKey  作为索引值的列
+     * @return array
+     */
+    public function column($columnKey, $indexKey = null)
+    {
+        return array_column($this->items, $columnKey, $indexKey);
+    }
+
+    /**
+     * 对数组排序
+     *
+     * @access public
+     * @param  callable|null $callback
+     * @return static
+     */
+    public function sort(callable $callback = null)
+    {
+        $items = $this->items;
+
+        $callback = $callback ?: function ($a, $b) {
+            return $a == $b ? 0 : (($a < $b) ? -1 : 1);
+
+        };
+
+        uasort($items, $callback);
+
+        return new static($items);
+    }
+
+    /**
+     * 指定字段排序
+     * @access public
+     * @param  string       $field 排序字段
+     * @param  string       $order 排序
+     * @param  bool         $intSort 是否为数字排序
+     * @return $this
+     */
+    public function order($field, $order = null, $intSort = true)
+    {
+        return $this->sort(function ($a, $b) use ($field, $order, $intSort) {
+            $fieldA = isset($a[$field]) ? $a[$field] : null;
+            $fieldB = isset($b[$field]) ? $b[$field] : null;
+
+            if ($intSort) {
+                return 'desc' == strtolower($order) ? $fieldB >= $fieldA : $fieldA >= $fieldB;
+            } else {
+                return 'desc' == strtolower($order) ? strcmp($fieldB, $fieldA) : strcmp($fieldA, $fieldB);
+            }
+        });
+    }
+
+    /**
+     * 将数组打乱
+     *
+     * @access public
+     * @return static
+     */
+    public function shuffle()
+    {
+        $items = $this->items;
+
+        shuffle($items);
+
+        return new static($items);
+    }
+
+    /**
+     * 截取数组
+     *
+     * @access public
+     * @param  int  $offset
+     * @param  int  $length
+     * @param  bool $preserveKeys
+     * @return static
+     */
+    public function slice($offset, $length = null, $preserveKeys = false)
+    {
+        return new static(array_slice($this->items, $offset, $length, $preserveKeys));
+    }
+
+    // ArrayAccess
+    public function offsetExists($offset)
+    {
+        return array_key_exists($offset, $this->items);
+    }
+
+    public function offsetGet($offset)
+    {
+        return $this->items[$offset];
+    }
+
+    public function offsetSet($offset, $value)
+    {
+        if (is_null($offset)) {
+            $this->items[] = $value;
+        } else {
+            $this->items[$offset] = $value;
+        }
+    }
+
+    public function offsetUnset($offset)
+    {
+        unset($this->items[$offset]);
+    }
+
+    //Countable
+    public function count()
+    {
+        return count($this->items);
+    }
+
+    //IteratorAggregate
+    public function getIterator()
+    {
+        return new ArrayIterator($this->items);
+    }
+
+    //JsonSerializable
+    public function jsonSerialize()
+    {
+        return $this->toArray();
+    }
+
+    /**
+     * 转换当前数据集为JSON字符串
+     * @access public
+     * @param  integer $options json参数
+     * @return string
+     */
+    public function toJson($options = JSON_UNESCAPED_UNICODE)
+    {
+        return json_encode($this->toArray(), $options);
+    }
+
+    public function __toString()
+    {
+        return $this->toJson();
+    }
+
+    /**
+     * 转换成数组
+     *
+     * @access public
+     * @param  mixed $items
+     * @return array
+     */
+    protected function convertToArray($items)
+    {
+        if ($items instanceof self) {
+            return $items->all();
+        }
+
+        return (array) $items;
+    }
+}

+ 398 - 0
thinkphp/library/think/Config.php

@@ -0,0 +1,398 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use Yaconf;
+
+class Config implements \ArrayAccess
+{
+    /**
+     * 配置参数
+     * @var array
+     */
+    protected $config = [];
+
+    /**
+     * 配置前缀
+     * @var string
+     */
+    protected $prefix = 'app';
+
+    /**
+     * 配置文件目录
+     * @var string
+     */
+    protected $path;
+
+    /**
+     * 配置文件后缀
+     * @var string
+     */
+    protected $ext;
+
+    /**
+     * 是否支持Yaconf
+     * @var bool
+     */
+    protected $yaconf;
+
+    /**
+     * 构造方法
+     * @access public
+     */
+    public function __construct($path = '', $ext = '.php')
+    {
+        $this->path   = $path;
+        $this->ext    = $ext;
+        $this->yaconf = class_exists('Yaconf');
+    }
+
+    public static function __make(App $app)
+    {
+        $path = $app->getConfigPath();
+        $ext  = $app->getConfigExt();
+        return new static($path, $ext);
+    }
+
+    /**
+     * 设置开启Yaconf
+     * @access public
+     * @param  bool|string    $yaconf  是否使用Yaconf
+     * @return void
+     */
+    public function setYaconf($yaconf)
+    {
+        if ($this->yaconf) {
+            $this->yaconf = $yaconf;
+        }
+    }
+
+    /**
+     * 设置配置参数默认前缀
+     * @access public
+     * @param string    $prefix 前缀
+     * @return void
+     */
+    public function setDefaultPrefix($prefix)
+    {
+        $this->prefix = $prefix;
+    }
+
+    /**
+     * 解析配置文件或内容
+     * @access public
+     * @param  string    $config 配置文件路径或内容
+     * @param  string    $type 配置解析类型
+     * @param  string    $name 配置名(如设置即表示二级配置)
+     * @return mixed
+     */
+    public function parse($config, $type = '', $name = '')
+    {
+        if (empty($type)) {
+            $type = pathinfo($config, PATHINFO_EXTENSION);
+        }
+
+        $object = Loader::factory($type, '\\think\\config\\driver\\', $config);
+
+        return $this->set($object->parse(), $name);
+    }
+
+    /**
+     * 加载配置文件(多种格式)
+     * @access public
+     * @param  string    $file 配置文件名
+     * @param  string    $name 一级配置名
+     * @return mixed
+     */
+    public function load($file, $name = '')
+    {
+        if (is_file($file)) {
+            $filename = $file;
+        } elseif (is_file($this->path . $file . $this->ext)) {
+            $filename = $this->path . $file . $this->ext;
+        }
+
+        if (isset($filename)) {
+            return $this->loadFile($filename, $name);
+        } elseif ($this->yaconf && Yaconf::has($file)) {
+            return $this->set(Yaconf::get($file), $name);
+        }
+
+        return $this->config;
+    }
+
+    /**
+     * 获取实际的yaconf配置参数
+     * @access protected
+     * @param  string    $name 配置参数名
+     * @return string
+     */
+    protected function getYaconfName($name)
+    {
+        if ($this->yaconf && is_string($this->yaconf)) {
+            return $this->yaconf . '.' . $name;
+        }
+
+        return $name;
+    }
+
+    /**
+     * 获取yaconf配置
+     * @access public
+     * @param  string    $name 配置参数名
+     * @param  mixed     $default   默认值
+     * @return mixed
+     */
+    public function yaconf($name, $default = null)
+    {
+        if ($this->yaconf) {
+            $yaconfName = $this->getYaconfName($name);
+
+            if (Yaconf::has($yaconfName)) {
+                return Yaconf::get($yaconfName);
+            }
+        }
+
+        return $default;
+    }
+
+    protected function loadFile($file, $name)
+    {
+        $name = strtolower($name);
+        $type = pathinfo($file, PATHINFO_EXTENSION);
+
+        if ('php' == $type) {
+            return $this->set(include $file, $name);
+        } elseif ('yaml' == $type && function_exists('yaml_parse_file')) {
+            return $this->set(yaml_parse_file($file), $name);
+        }
+
+        return $this->parse($file, $type, $name);
+    }
+
+    /**
+     * 检测配置是否存在
+     * @access public
+     * @param  string    $name 配置参数名(支持多级配置 .号分割)
+     * @return bool
+     */
+    public function has($name)
+    {
+        if (false === strpos($name, '.')) {
+            $name = $this->prefix . '.' . $name;
+        }
+
+        return !is_null($this->get($name));
+    }
+
+    /**
+     * 获取一级配置
+     * @access public
+     * @param  string    $name 一级配置名
+     * @return array
+     */
+    public function pull($name)
+    {
+        $name = strtolower($name);
+
+        if ($this->yaconf) {
+            $yaconfName = $this->getYaconfName($name);
+
+            if (Yaconf::has($yaconfName)) {
+                $config = Yaconf::get($yaconfName);
+                return isset($this->config[$name]) ? array_merge($this->config[$name], $config) : $config;
+            }
+        }
+
+        return isset($this->config[$name]) ? $this->config[$name] : [];
+    }
+
+    /**
+     * 获取配置参数 为空则获取所有配置
+     * @access public
+     * @param  string    $name      配置参数名(支持多级配置 .号分割)
+     * @param  mixed     $default   默认值
+     * @return mixed
+     */
+    public function get($name = null, $default = null)
+    {
+        if ($name && false === strpos($name, '.')) {
+            $name = $this->prefix . '.' . $name;
+        }
+
+        // 无参数时获取所有
+        if (empty($name)) {
+            return $this->config;
+        }
+
+        if ('.' == substr($name, -1)) {
+            return $this->pull(substr($name, 0, -1));
+        }
+
+        if ($this->yaconf) {
+            $yaconfName = $this->getYaconfName($name);
+
+            if (Yaconf::has($yaconfName)) {
+                return Yaconf::get($yaconfName);
+            }
+        }
+
+        $name    = explode('.', $name);
+        $name[0] = strtolower($name[0]);
+        $config  = $this->config;
+
+        // 按.拆分成多维数组进行判断
+        foreach ($name as $val) {
+            if (isset($config[$val])) {
+                $config = $config[$val];
+            } else {
+                return $default;
+            }
+        }
+
+        return $config;
+    }
+
+    /**
+     * 设置配置参数 name为数组则为批量设置
+     * @access public
+     * @param  string|array  $name 配置参数名(支持三级配置 .号分割)
+     * @param  mixed         $value 配置值
+     * @return mixed
+     */
+    public function set($name, $value = null)
+    {
+        if (is_string($name)) {
+            if (false === strpos($name, '.')) {
+                $name = $this->prefix . '.' . $name;
+            }
+
+            $name = explode('.', $name, 3);
+
+            if (count($name) == 2) {
+                $this->config[strtolower($name[0])][$name[1]] = $value;
+            } else {
+                $this->config[strtolower($name[0])][$name[1]][$name[2]] = $value;
+            }
+
+            return $value;
+        } elseif (is_array($name)) {
+            // 批量设置
+            if (!empty($value)) {
+                if (isset($this->config[$value])) {
+                    $result = array_merge($this->config[$value], $name);
+                } else {
+                    $result = $name;
+                }
+
+                $this->config[$value] = $result;
+            } else {
+                $result = $this->config = array_merge($this->config, $name);
+            }
+        } else {
+            // 为空直接返回 已有配置
+            $result = $this->config;
+        }
+
+        return $result;
+    }
+
+    /**
+     * 移除配置
+     * @access public
+     * @param  string  $name 配置参数名(支持三级配置 .号分割)
+     * @return void
+     */
+    public function remove($name)
+    {
+        if (false === strpos($name, '.')) {
+            $name = $this->prefix . '.' . $name;
+        }
+
+        $name = explode('.', $name, 3);
+
+        if (count($name) == 2) {
+            unset($this->config[strtolower($name[0])][$name[1]]);
+        } else {
+            unset($this->config[strtolower($name[0])][$name[1]][$name[2]]);
+        }
+    }
+
+    /**
+     * 重置配置参数
+     * @access public
+     * @param  string    $prefix  配置前缀名
+     * @return void
+     */
+    public function reset($prefix = '')
+    {
+        if ('' === $prefix) {
+            $this->config = [];
+        } else {
+            $this->config[$prefix] = [];
+        }
+    }
+
+    /**
+     * 设置配置
+     * @access public
+     * @param  string    $name  参数名
+     * @param  mixed     $value 值
+     */
+    public function __set($name, $value)
+    {
+        return $this->set($name, $value);
+    }
+
+    /**
+     * 获取配置参数
+     * @access public
+     * @param  string $name 参数名
+     * @return mixed
+     */
+    public function __get($name)
+    {
+        return $this->get($name);
+    }
+
+    /**
+     * 检测是否存在参数
+     * @access public
+     * @param  string $name 参数名
+     * @return bool
+     */
+    public function __isset($name)
+    {
+        return $this->has($name);
+    }
+
+    // ArrayAccess
+    public function offsetSet($name, $value)
+    {
+        $this->set($name, $value);
+    }
+
+    public function offsetExists($name)
+    {
+        return $this->has($name);
+    }
+
+    public function offsetUnset($name)
+    {
+        $this->remove($name);
+    }
+
+    public function offsetGet($name)
+    {
+        return $this->get($name);
+    }
+}

+ 829 - 0
thinkphp/library/think/Console.php

@@ -0,0 +1,829 @@
+<?php
+// +----------------------------------------------------------------------
+// | TopThink [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2015 http://www.topthink.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\console\Command;
+use think\console\command\Help as HelpCommand;
+use think\console\Input;
+use think\console\input\Argument as InputArgument;
+use think\console\input\Definition as InputDefinition;
+use think\console\input\Option as InputOption;
+use think\console\Output;
+use think\console\output\driver\Buffer;
+
+class Console
+{
+
+    private $name;
+    private $version;
+
+    /** @var Command[] */
+    private $commands = [];
+
+    private $wantHelps = false;
+
+    private $catchExceptions = true;
+    private $autoExit        = true;
+    private $definition;
+    private $defaultCommand;
+
+    private static $defaultCommands = [
+        'help'              => "think\\console\\command\\Help",
+        'list'              => "think\\console\\command\\Lists",
+        'build'             => "think\\console\\command\\Build",
+        'clear'             => "think\\console\\command\\Clear",
+        'make:command'      => "think\\console\\command\\make\\Command",
+        'make:controller'   => "think\\console\\command\\make\\Controller",
+        'make:model'        => "think\\console\\command\\make\\Model",
+        'make:middleware'   => "think\\console\\command\\make\\Middleware",
+        'make:validate'     => "think\\console\\command\\make\\Validate",
+        'optimize:autoload' => "think\\console\\command\\optimize\\Autoload",
+        'optimize:config'   => "think\\console\\command\\optimize\\Config",
+        'optimize:schema'   => "think\\console\\command\\optimize\\Schema",
+        'optimize:route'    => "think\\console\\command\\optimize\\Route",
+        'run'               => "think\\console\\command\\RunServer",
+        'version'           => "think\\console\\command\\Version",
+        'route:list'        => "think\\console\\command\\RouteList",
+    ];
+
+    /**
+     * Console constructor.
+     * @access public
+     * @param  string     $name    名称
+     * @param  string     $version 版本
+     * @param null|string $user    执行用户
+     */
+    public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN', $user = null)
+    {
+        $this->name    = $name;
+        $this->version = $version;
+
+        if ($user) {
+            $this->setUser($user);
+        }
+
+        $this->defaultCommand = 'list';
+        $this->definition     = $this->getDefaultInputDefinition();
+    }
+
+    /**
+     * 设置执行用户
+     * @param $user
+     */
+    public function setUser($user)
+    {
+        if (DIRECTORY_SEPARATOR == '\\') {
+            return;
+        }
+
+        $user = posix_getpwnam($user);
+        if ($user) {
+            posix_setuid($user['uid']);
+            posix_setgid($user['gid']);
+        }
+    }
+
+    /**
+     * 初始化 Console
+     * @access public
+     * @param  bool $run 是否运行 Console
+     * @return int|Console
+     */
+    public static function init($run = true)
+    {
+        static $console;
+
+        if (!$console) {
+            $config  = Container::get('config')->pull('console');
+            $console = new self($config['name'], $config['version'], $config['user']);
+
+            $commands = $console->getDefinedCommands($config);
+
+            // 添加指令集
+            $console->addCommands($commands);
+        }
+
+        if ($run) {
+            // 运行
+            return $console->run();
+        } else {
+            return $console;
+        }
+    }
+
+    /**
+     * @access public
+     * @param  array $config
+     * @return array
+     */
+    public function getDefinedCommands(array $config = [])
+    {
+        $commands = self::$defaultCommands;
+
+        if (!empty($config['auto_path']) && is_dir($config['auto_path'])) {
+            // 自动加载指令类
+            $files = scandir($config['auto_path']);
+
+            if (count($files) > 2) {
+                $beforeClass = get_declared_classes();
+
+                foreach ($files as $file) {
+                    if (pathinfo($file, PATHINFO_EXTENSION) == 'php') {
+                        include $config['auto_path'] . $file;
+                    }
+                }
+
+                $afterClass = get_declared_classes();
+                $commands   = array_merge($commands, array_diff($afterClass, $beforeClass));
+            }
+        }
+
+        $file = Container::get('env')->get('app_path') . 'command.php';
+
+        if (is_file($file)) {
+            $appCommands = include $file;
+
+            if (is_array($appCommands)) {
+                $commands = array_merge($commands, $appCommands);
+            }
+        }
+
+        return $commands;
+    }
+
+    /**
+     * @access public
+     * @param  string $command
+     * @param  array  $parameters
+     * @param  string $driver
+     * @return Output|Buffer
+     */
+    public static function call($command, array $parameters = [], $driver = 'buffer')
+    {
+        $console = self::init(false);
+
+        array_unshift($parameters, $command);
+
+        $input  = new Input($parameters);
+        $output = new Output($driver);
+
+        $console->setCatchExceptions(false);
+        $console->find($command)->run($input, $output);
+
+        return $output;
+    }
+
+    /**
+     * 执行当前的指令
+     * @access public
+     * @return int
+     * @throws \Exception
+     * @api
+     */
+    public function run()
+    {
+        $input  = new Input();
+        $output = new Output();
+
+        $this->configureIO($input, $output);
+
+        try {
+            $exitCode = $this->doRun($input, $output);
+        } catch (\Exception $e) {
+            if (!$this->catchExceptions) {
+                throw $e;
+            }
+
+            $output->renderException($e);
+
+            $exitCode = $e->getCode();
+            if (is_numeric($exitCode)) {
+                $exitCode = (int) $exitCode;
+                if (0 === $exitCode) {
+                    $exitCode = 1;
+                }
+            } else {
+                $exitCode = 1;
+            }
+        }
+
+        if ($this->autoExit) {
+            if ($exitCode > 255) {
+                $exitCode = 255;
+            }
+
+            exit($exitCode);
+        }
+
+        return $exitCode;
+    }
+
+    /**
+     * 执行指令
+     * @access public
+     * @param  Input  $input
+     * @param  Output $output
+     * @return int
+     */
+    public function doRun(Input $input, Output $output)
+    {
+        if (true === $input->hasParameterOption(['--version', '-V'])) {
+            $output->writeln($this->getLongVersion());
+
+            return 0;
+        }
+
+        $name = $this->getCommandName($input);
+
+        if (true === $input->hasParameterOption(['--help', '-h'])) {
+            if (!$name) {
+                $name  = 'help';
+                $input = new Input(['help']);
+            } else {
+                $this->wantHelps = true;
+            }
+        }
+
+        if (!$name) {
+            $name  = $this->defaultCommand;
+            $input = new Input([$this->defaultCommand]);
+        }
+
+        $command = $this->find($name);
+
+        $exitCode = $this->doRunCommand($command, $input, $output);
+
+        return $exitCode;
+    }
+
+    /**
+     * 设置输入参数定义
+     * @access public
+     * @param  InputDefinition $definition
+     */
+    public function setDefinition(InputDefinition $definition)
+    {
+        $this->definition = $definition;
+    }
+
+    /**
+     * 获取输入参数定义
+     * @access public
+     * @return InputDefinition The InputDefinition instance
+     */
+    public function getDefinition()
+    {
+        return $this->definition;
+    }
+
+    /**
+     * Gets the help message.
+     * @access public
+     * @return string A help message.
+     */
+    public function getHelp()
+    {
+        return $this->getLongVersion();
+    }
+
+    /**
+     * 是否捕获异常
+     * @access public
+     * @param  bool $boolean
+     * @api
+     */
+    public function setCatchExceptions($boolean)
+    {
+        $this->catchExceptions = (bool) $boolean;
+    }
+
+    /**
+     * 是否自动退出
+     * @access public
+     * @param  bool $boolean
+     * @api
+     */
+    public function setAutoExit($boolean)
+    {
+        $this->autoExit = (bool) $boolean;
+    }
+
+    /**
+     * 获取名称
+     * @access public
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * 设置名称
+     * @access public
+     * @param  string $name
+     */
+    public function setName($name)
+    {
+        $this->name = $name;
+    }
+
+    /**
+     * 获取版本
+     * @access public
+     * @return string
+     * @api
+     */
+    public function getVersion()
+    {
+        return $this->version;
+    }
+
+    /**
+     * 设置版本
+     * @access public
+     * @param  string $version
+     */
+    public function setVersion($version)
+    {
+        $this->version = $version;
+    }
+
+    /**
+     * 获取完整的版本号
+     * @access public
+     * @return string
+     */
+    public function getLongVersion()
+    {
+        if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
+            return sprintf('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
+        }
+
+        return '<info>Console Tool</info>';
+    }
+
+    /**
+     * 注册一个指令 (便于动态创建指令)
+     * @access public
+     * @param  string $name     指令名
+     * @return Command
+     */
+    public function register($name)
+    {
+        return $this->add(new Command($name));
+    }
+
+    /**
+     * 添加指令集
+     * @access public
+     * @param  array $commands
+     */
+    public function addCommands(array $commands)
+    {
+        foreach ($commands as $key => $command) {
+            if (is_subclass_of($command, "\\think\\console\\Command")) {
+                // 注册指令
+                $this->add($command, is_numeric($key) ? '' : $key);
+            }
+        }
+    }
+
+    /**
+     * 注册一个指令(对象)
+     * @access public
+     * @param  mixed    $command    指令对象或者指令类名
+     * @param  string   $name       指令名 留空则自动获取
+     * @return mixed
+     */
+    public function add($command, $name)
+    {
+        if ($name) {
+            $this->commands[$name] = $command;
+            return;
+        }
+
+        if (is_string($command)) {
+            $command = new $command();
+        }
+
+        $command->setConsole($this);
+
+        if (!$command->isEnabled()) {
+            $command->setConsole(null);
+            return;
+        }
+
+        if (null === $command->getDefinition()) {
+            throw new \LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
+        }
+
+        $this->commands[$command->getName()] = $command;
+
+        foreach ($command->getAliases() as $alias) {
+            $this->commands[$alias] = $command;
+        }
+
+        return $command;
+    }
+
+    /**
+     * 获取指令
+     * @access public
+     * @param  string $name 指令名称
+     * @return Command
+     * @throws \InvalidArgumentException
+     */
+    public function get($name)
+    {
+        if (!isset($this->commands[$name])) {
+            throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
+        }
+
+        $command = $this->commands[$name];
+
+        if (is_string($command)) {
+            $command = new $command();
+        }
+
+        $command->setConsole($this);
+
+        if ($this->wantHelps) {
+            $this->wantHelps = false;
+
+            /** @var HelpCommand $helpCommand */
+            $helpCommand = $this->get('help');
+            $helpCommand->setCommand($command);
+
+            return $helpCommand;
+        }
+
+        return $command;
+    }
+
+    /**
+     * 某个指令是否存在
+     * @access public
+     * @param  string $name 指令名称
+     * @return bool
+     */
+    public function has($name)
+    {
+        return isset($this->commands[$name]);
+    }
+
+    /**
+     * 获取所有的命名空间
+     * @access public
+     * @return array
+     */
+    public function getNamespaces()
+    {
+        $namespaces = [];
+        foreach ($this->commands as $name => $command) {
+            if (is_string($command)) {
+                $namespaces = array_merge($namespaces, $this->extractAllNamespaces($name));
+            } else {
+                $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
+
+                foreach ($command->getAliases() as $alias) {
+                    $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
+                }
+            }
+
+        }
+
+        return array_values(array_unique(array_filter($namespaces)));
+    }
+
+    /**
+     * 查找注册命名空间中的名称或缩写。
+     * @access public
+     * @param  string $namespace
+     * @return string
+     * @throws \InvalidArgumentException
+     */
+    public function findNamespace($namespace)
+    {
+        $allNamespaces = $this->getNamespaces();
+        $expr          = preg_replace_callback('{([^:]+|)}', function ($matches) {
+            return preg_quote($matches[1]) . '[^:]*';
+        }, $namespace);
+        $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces);
+
+        if (empty($namespaces)) {
+            $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
+
+            if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
+                if (1 == count($alternatives)) {
+                    $message .= "\n\nDid you mean this?\n    ";
+                } else {
+                    $message .= "\n\nDid you mean one of these?\n    ";
+                }
+
+                $message .= implode("\n    ", $alternatives);
+            }
+
+            throw new \InvalidArgumentException($message);
+        }
+
+        $exact = in_array($namespace, $namespaces, true);
+        if (count($namespaces) > 1 && !$exact) {
+            throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))));
+        }
+
+        return $exact ? $namespace : reset($namespaces);
+    }
+
+    /**
+     * 查找指令
+     * @access public
+     * @param  string $name 名称或者别名
+     * @return Command
+     * @throws \InvalidArgumentException
+     */
+    public function find($name)
+    {
+        $allCommands = array_keys($this->commands);
+
+        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
+            return preg_quote($matches[1]) . '[^:]*';
+        }, $name);
+
+        $commands = preg_grep('{^' . $expr . '}', $allCommands);
+
+        if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) {
+            if (false !== $pos = strrpos($name, ':')) {
+                $this->findNamespace(substr($name, 0, $pos));
+            }
+
+            $message = sprintf('Command "%s" is not defined.', $name);
+
+            if ($alternatives = $this->findAlternatives($name, $allCommands)) {
+                if (1 == count($alternatives)) {
+                    $message .= "\n\nDid you mean this?\n    ";
+                } else {
+                    $message .= "\n\nDid you mean one of these?\n    ";
+                }
+                $message .= implode("\n    ", $alternatives);
+            }
+
+            throw new \InvalidArgumentException($message);
+        }
+
+        $exact = in_array($name, $commands, true);
+        if (count($commands) > 1 && !$exact) {
+            $suggestions = $this->getAbbreviationSuggestions(array_values($commands));
+
+            throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
+        }
+
+        return $this->get($exact ? $name : reset($commands));
+    }
+
+    /**
+     * 获取所有的指令
+     * @access public
+     * @param  string $namespace 命名空间
+     * @return Command[]
+     * @api
+     */
+    public function all($namespace = null)
+    {
+        if (null === $namespace) {
+            return $this->commands;
+        }
+
+        $commands = [];
+        foreach ($this->commands as $name => $command) {
+            if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) {
+                $commands[$name] = $command;
+            }
+        }
+
+        return $commands;
+    }
+
+    /**
+     * 获取可能的指令名
+     * @access public
+     * @param  array $names
+     * @return array
+     */
+    public static function getAbbreviations($names)
+    {
+        $abbrevs = [];
+        foreach ($names as $name) {
+            for ($len = strlen($name); $len > 0; --$len) {
+                $abbrev             = substr($name, 0, $len);
+                $abbrevs[$abbrev][] = $name;
+            }
+        }
+
+        return $abbrevs;
+    }
+
+    /**
+     * 配置基于用户的参数和选项的输入和输出实例。
+     * @access protected
+     * @param  Input  $input  输入实例
+     * @param  Output $output 输出实例
+     */
+    protected function configureIO(Input $input, Output $output)
+    {
+        if (true === $input->hasParameterOption(['--ansi'])) {
+            $output->setDecorated(true);
+        } elseif (true === $input->hasParameterOption(['--no-ansi'])) {
+            $output->setDecorated(false);
+        }
+
+        if (true === $input->hasParameterOption(['--no-interaction', '-n'])) {
+            $input->setInteractive(false);
+        }
+
+        if (true === $input->hasParameterOption(['--quiet', '-q'])) {
+            $output->setVerbosity(Output::VERBOSITY_QUIET);
+        } else {
+            if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) {
+                $output->setVerbosity(Output::VERBOSITY_DEBUG);
+            } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) {
+                $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE);
+            } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
+                $output->setVerbosity(Output::VERBOSITY_VERBOSE);
+            }
+        }
+    }
+
+    /**
+     * 执行指令
+     * @access protected
+     * @param  Command $command 指令实例
+     * @param  Input   $input   输入实例
+     * @param  Output  $output  输出实例
+     * @return int
+     * @throws \Exception
+     */
+    protected function doRunCommand(Command $command, Input $input, Output $output)
+    {
+        return $command->run($input, $output);
+    }
+
+    /**
+     * 获取指令的基础名称
+     * @access protected
+     * @param  Input $input
+     * @return string
+     */
+    protected function getCommandName(Input $input)
+    {
+        return $input->getFirstArgument();
+    }
+
+    /**
+     * 获取默认输入定义
+     * @access protected
+     * @return InputDefinition
+     */
+    protected function getDefaultInputDefinition()
+    {
+        return new InputDefinition([
+            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
+            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
+            new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'),
+            new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
+            new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
+            new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
+            new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
+            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
+        ]);
+    }
+
+    public static function addDefaultCommands(array $classnames)
+    {
+        self::$defaultCommands = array_merge(self::$defaultCommands, $classnames);
+    }
+
+    /**
+     * 获取可能的建议
+     * @access private
+     * @param  array $abbrevs
+     * @return string
+     */
+    private function getAbbreviationSuggestions($abbrevs)
+    {
+        return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
+    }
+
+    /**
+     * 返回命名空间部分
+     * @access public
+     * @param  string $name  指令
+     * @param  string $limit 部分的命名空间的最大数量
+     * @return string
+     */
+    public function extractNamespace($name, $limit = null)
+    {
+        $parts = explode(':', $name);
+        array_pop($parts);
+
+        return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
+    }
+
+    /**
+     * 查找可替代的建议
+     * @access private
+     * @param  string             $name
+     * @param  array|\Traversable $collection
+     * @return array
+     */
+    private function findAlternatives($name, $collection)
+    {
+        $threshold    = 1e3;
+        $alternatives = [];
+
+        $collectionParts = [];
+        foreach ($collection as $item) {
+            $collectionParts[$item] = explode(':', $item);
+        }
+
+        foreach (explode(':', $name) as $i => $subname) {
+            foreach ($collectionParts as $collectionName => $parts) {
+                $exists = isset($alternatives[$collectionName]);
+                if (!isset($parts[$i]) && $exists) {
+                    $alternatives[$collectionName] += $threshold;
+                    continue;
+                } elseif (!isset($parts[$i])) {
+                    continue;
+                }
+
+                $lev = levenshtein($subname, $parts[$i]);
+                if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
+                    $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
+                } elseif ($exists) {
+                    $alternatives[$collectionName] += $threshold;
+                }
+            }
+        }
+
+        foreach ($collection as $item) {
+            $lev = levenshtein($name, $item);
+            if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
+                $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
+            }
+        }
+
+        $alternatives = array_filter($alternatives, function ($lev) use ($threshold) {
+            return $lev < 2 * $threshold;
+        });
+        asort($alternatives);
+
+        return array_keys($alternatives);
+    }
+
+    /**
+     * 设置默认的指令
+     * @access public
+     * @param  string $commandName The Command name
+     */
+    public function setDefaultCommand($commandName)
+    {
+        $this->defaultCommand = $commandName;
+    }
+
+    /**
+     * 返回所有的命名空间
+     * @access private
+     * @param  string $name
+     * @return array
+     */
+    private function extractAllNamespaces($name)
+    {
+        $parts      = explode(':', $name, -1);
+        $namespaces = [];
+
+        foreach ($parts as $part) {
+            if (count($namespaces)) {
+                $namespaces[] = end($namespaces) . ':' . $part;
+            } else {
+                $namespaces[] = $part;
+            }
+        }
+
+        return $namespaces;
+    }
+
+    public function __debugInfo()
+    {
+        $data = get_object_vars($this);
+        unset($data['commands'], $data['definition']);
+
+        return $data;
+    }
+}

+ 568 - 0
thinkphp/library/think/Container.php

@@ -0,0 +1,568 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use ArrayAccess;
+use ArrayIterator;
+use Closure;
+use Countable;
+use InvalidArgumentException;
+use IteratorAggregate;
+use ReflectionClass;
+use ReflectionException;
+use ReflectionFunction;
+use ReflectionMethod;
+use think\exception\ClassNotFoundException;
+
+/**
+ * @package think
+ * @property Build          $build
+ * @property Cache          $cache
+ * @property Config         $config
+ * @property Cookie         $cookie
+ * @property Debug          $debug
+ * @property Env            $env
+ * @property Hook           $hook
+ * @property Lang           $lang
+ * @property Middleware     $middleware
+ * @property Request        $request
+ * @property Response       $response
+ * @property Route          $route
+ * @property Session        $session
+ * @property Template       $template
+ * @property Url            $url
+ * @property Validate       $validate
+ * @property View           $view
+ * @property route\RuleName $rule_name
+ * @property Log            $log
+ */
+class Container implements ArrayAccess, IteratorAggregate, Countable
+{
+    /**
+     * 容器对象实例
+     * @var Container
+     */
+    protected static $instance;
+
+    /**
+     * 容器中的对象实例
+     * @var array
+     */
+    protected $instances = [];
+
+    /**
+     * 容器绑定标识
+     * @var array
+     */
+    protected $bind = [
+        'app'                   => App::class,
+        'build'                 => Build::class,
+        'cache'                 => Cache::class,
+        'config'                => Config::class,
+        'cookie'                => Cookie::class,
+        'debug'                 => Debug::class,
+        'env'                   => Env::class,
+        'hook'                  => Hook::class,
+        'lang'                  => Lang::class,
+        'log'                   => Log::class,
+        'middleware'            => Middleware::class,
+        'request'               => Request::class,
+        'response'              => Response::class,
+        'route'                 => Route::class,
+        'session'               => Session::class,
+        'template'              => Template::class,
+        'url'                   => Url::class,
+        'validate'              => Validate::class,
+        'view'                  => View::class,
+        'rule_name'             => route\RuleName::class,
+        // 接口依赖注入
+        'think\LoggerInterface' => Log::class,
+    ];
+
+    /**
+     * 容器标识别名
+     * @var array
+     */
+    protected $name = [];
+
+    /**
+     * 获取当前容器的实例(单例)
+     * @access public
+     * @return static
+     */
+    public static function getInstance()
+    {
+        if (is_null(static::$instance)) {
+            static::$instance = new static;
+        }
+
+        return static::$instance;
+    }
+
+    /**
+     * 设置当前容器的实例
+     * @access public
+     * @param  object        $instance
+     * @return void
+     */
+    public static function setInstance($instance)
+    {
+        static::$instance = $instance;
+    }
+
+    /**
+     * 获取容器中的对象实例
+     * @access public
+     * @param  string        $abstract       类名或者标识
+     * @param  array|true    $vars           变量
+     * @param  bool          $newInstance    是否每次创建新的实例
+     * @return object
+     */
+    public static function get($abstract, $vars = [], $newInstance = false)
+    {
+        return static::getInstance()->make($abstract, $vars, $newInstance);
+    }
+
+    /**
+     * 绑定一个类、闭包、实例、接口实现到容器
+     * @access public
+     * @param  string  $abstract    类标识、接口
+     * @param  mixed   $concrete    要绑定的类、闭包或者实例
+     * @return Container
+     */
+    public static function set($abstract, $concrete = null)
+    {
+        return static::getInstance()->bindTo($abstract, $concrete);
+    }
+
+    /**
+     * 移除容器中的对象实例
+     * @access public
+     * @param  string  $abstract    类标识、接口
+     * @return void
+     */
+    public static function remove($abstract)
+    {
+        return static::getInstance()->delete($abstract);
+    }
+
+    /**
+     * 清除容器中的对象实例
+     * @access public
+     * @return void
+     */
+    public static function clear()
+    {
+        return static::getInstance()->flush();
+    }
+
+    /**
+     * 绑定一个类、闭包、实例、接口实现到容器
+     * @access public
+     * @param  string|array  $abstract    类标识、接口
+     * @param  mixed         $concrete    要绑定的类、闭包或者实例
+     * @return $this
+     */
+    public function bindTo($abstract, $concrete = null)
+    {
+        if (is_array($abstract)) {
+            $this->bind = array_merge($this->bind, $abstract);
+        } elseif ($concrete instanceof Closure) {
+            $this->bind[$abstract] = $concrete;
+        } elseif (is_object($concrete)) {
+            if (isset($this->bind[$abstract])) {
+                $abstract = $this->bind[$abstract];
+            }
+            $this->instances[$abstract] = $concrete;
+        } else {
+            $this->bind[$abstract] = $concrete;
+        }
+
+        return $this;
+    }
+
+    /**
+     * 绑定一个类实例当容器
+     * @access public
+     * @param  string           $abstract    类名或者标识
+     * @param  object|\Closure  $instance    类的实例
+     * @return $this
+     */
+    public function instance($abstract, $instance)
+    {
+        if ($instance instanceof \Closure) {
+            $this->bind[$abstract] = $instance;
+        } else {
+            if (isset($this->bind[$abstract])) {
+                $abstract = $this->bind[$abstract];
+            }
+
+            $this->instances[$abstract] = $instance;
+        }
+
+        return $this;
+    }
+
+    /**
+     * 判断容器中是否存在类及标识
+     * @access public
+     * @param  string    $abstract    类名或者标识
+     * @return bool
+     */
+    public function bound($abstract)
+    {
+        return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
+    }
+
+    /**
+     * 判断容器中是否存在对象实例
+     * @access public
+     * @param  string    $abstract    类名或者标识
+     * @return bool
+     */
+    public function exists($abstract)
+    {
+        if (isset($this->bind[$abstract])) {
+            $abstract = $this->bind[$abstract];
+        }
+
+        return isset($this->instances[$abstract]);
+    }
+
+    /**
+     * 判断容器中是否存在类及标识
+     * @access public
+     * @param  string    $name    类名或者标识
+     * @return bool
+     */
+    public function has($name)
+    {
+        return $this->bound($name);
+    }
+
+    /**
+     * 创建类的实例
+     * @access public
+     * @param  string        $abstract       类名或者标识
+     * @param  array|true    $vars           变量
+     * @param  bool          $newInstance    是否每次创建新的实例
+     * @return object
+     */
+    public function make($abstract, $vars = [], $newInstance = false)
+    {
+        if (true === $vars) {
+            // 总是创建新的实例化对象
+            $newInstance = true;
+            $vars        = [];
+        }
+
+        $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;
+
+        if (isset($this->instances[$abstract]) && !$newInstance) {
+            return $this->instances[$abstract];
+        }
+
+        if (isset($this->bind[$abstract])) {
+            $concrete = $this->bind[$abstract];
+
+            if ($concrete instanceof Closure) {
+                $object = $this->invokeFunction($concrete, $vars);
+            } else {
+                $this->name[$abstract] = $concrete;
+                return $this->make($concrete, $vars, $newInstance);
+            }
+        } else {
+            $object = $this->invokeClass($abstract, $vars);
+        }
+
+        if (!$newInstance) {
+            $this->instances[$abstract] = $object;
+        }
+
+        return $object;
+    }
+
+    /**
+     * 删除容器中的对象实例
+     * @access public
+     * @param  string|array    $abstract    类名或者标识
+     * @return void
+     */
+    public function delete($abstract)
+    {
+        foreach ((array) $abstract as $name) {
+            $name = isset($this->name[$name]) ? $this->name[$name] : $name;
+
+            if (isset($this->instances[$name])) {
+                unset($this->instances[$name]);
+            }
+        }
+    }
+
+    /**
+     * 获取容器中的对象实例
+     * @access public
+     * @return array
+     */
+    public function all()
+    {
+        return $this->instances;
+    }
+
+    /**
+     * 清除容器中的对象实例
+     * @access public
+     * @return void
+     */
+    public function flush()
+    {
+        $this->instances = [];
+        $this->bind      = [];
+        $this->name      = [];
+    }
+
+    /**
+     * 执行函数或者闭包方法 支持参数调用
+     * @access public
+     * @param  mixed  $function 函数或者闭包
+     * @param  array  $vars     参数
+     * @return mixed
+     */
+    public function invokeFunction($function, $vars = [])
+    {
+        try {
+            $reflect = new ReflectionFunction($function);
+
+            $args = $this->bindParams($reflect, $vars);
+
+            return call_user_func_array($function, $args);
+        } catch (ReflectionException $e) {
+            throw new Exception('function not exists: ' . $function . '()');
+        }
+    }
+
+    /**
+     * 调用反射执行类的方法 支持参数绑定
+     * @access public
+     * @param  mixed   $method 方法
+     * @param  array   $vars   参数
+     * @return mixed
+     */
+    public function invokeMethod($method, $vars = [])
+    {
+        try {
+            if (is_array($method)) {
+                $class   = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]);
+                $reflect = new ReflectionMethod($class, $method[1]);
+            } else {
+                // 静态方法
+                $reflect = new ReflectionMethod($method);
+            }
+
+            $args = $this->bindParams($reflect, $vars);
+
+            return $reflect->invokeArgs(isset($class) ? $class : null, $args);
+        } catch (ReflectionException $e) {
+            if (is_array($method) && is_object($method[0])) {
+                $method[0] = get_class($method[0]);
+            }
+
+            throw new Exception('method not exists: ' . (is_array($method) ? $method[0] . '::' . $method[1] : $method) . '()');
+        }
+    }
+
+    /**
+     * 调用反射执行类的方法 支持参数绑定
+     * @access public
+     * @param  object  $instance 对象实例
+     * @param  mixed   $reflect 反射类
+     * @param  array   $vars   参数
+     * @return mixed
+     */
+    public function invokeReflectMethod($instance, $reflect, $vars = [])
+    {
+        $args = $this->bindParams($reflect, $vars);
+
+        return $reflect->invokeArgs($instance, $args);
+    }
+
+    /**
+     * 调用反射执行callable 支持参数绑定
+     * @access public
+     * @param  mixed $callable
+     * @param  array $vars   参数
+     * @return mixed
+     */
+    public function invoke($callable, $vars = [])
+    {
+        if ($callable instanceof Closure) {
+            return $this->invokeFunction($callable, $vars);
+        }
+
+        return $this->invokeMethod($callable, $vars);
+    }
+
+    /**
+     * 调用反射执行类的实例化 支持依赖注入
+     * @access public
+     * @param  string    $class 类名
+     * @param  array     $vars  参数
+     * @return mixed
+     */
+    public function invokeClass($class, $vars = [])
+    {
+        try {
+            $reflect = new ReflectionClass($class);
+
+            if ($reflect->hasMethod('__make')) {
+                $method = new ReflectionMethod($class, '__make');
+
+                if ($method->isPublic() && $method->isStatic()) {
+                    $args = $this->bindParams($method, $vars);
+                    return $method->invokeArgs(null, $args);
+                }
+            }
+
+            $constructor = $reflect->getConstructor();
+
+            $args = $constructor ? $this->bindParams($constructor, $vars) : [];
+
+            return $reflect->newInstanceArgs($args);
+
+        } catch (ReflectionException $e) {
+            throw new ClassNotFoundException('class not exists: ' . $class, $class);
+        }
+    }
+
+    /**
+     * 绑定参数
+     * @access protected
+     * @param  \ReflectionMethod|\ReflectionFunction $reflect 反射类
+     * @param  array                                 $vars    参数
+     * @return array
+     */
+    protected function bindParams($reflect, $vars = [])
+    {
+        if ($reflect->getNumberOfParameters() == 0) {
+            return [];
+        }
+
+        // 判断数组类型 数字数组时按顺序绑定参数
+        reset($vars);
+        $type   = key($vars) === 0 ? 1 : 0;
+        $params = $reflect->getParameters();
+
+        foreach ($params as $param) {
+            $name      = $param->getName();
+            $lowerName = Loader::parseName($name);
+            $class     = $param->getClass();
+
+            if ($class) {
+                $args[] = $this->getObjectParam($class->getName(), $vars);
+            } elseif (1 == $type && !empty($vars)) {
+                $args[] = array_shift($vars);
+            } elseif (0 == $type && isset($vars[$name])) {
+                $args[] = $vars[$name];
+            } elseif (0 == $type && isset($vars[$lowerName])) {
+                $args[] = $vars[$lowerName];
+            } elseif ($param->isDefaultValueAvailable()) {
+                $args[] = $param->getDefaultValue();
+            } else {
+                throw new InvalidArgumentException('method param miss:' . $name);
+            }
+        }
+
+        return $args;
+    }
+
+    /**
+     * 获取对象类型的参数值
+     * @access protected
+     * @param  string   $className  类名
+     * @param  array    $vars       参数
+     * @return mixed
+     */
+    protected function getObjectParam($className, &$vars)
+    {
+        $array = $vars;
+        $value = array_shift($array);
+
+        if ($value instanceof $className) {
+            $result = $value;
+            array_shift($vars);
+        } else {
+            $result = $this->make($className);
+        }
+
+        return $result;
+    }
+
+    public function __set($name, $value)
+    {
+        $this->bindTo($name, $value);
+    }
+
+    public function __get($name)
+    {
+        return $this->make($name);
+    }
+
+    public function __isset($name)
+    {
+        return $this->bound($name);
+    }
+
+    public function __unset($name)
+    {
+        $this->delete($name);
+    }
+
+    public function offsetExists($key)
+    {
+        return $this->__isset($key);
+    }
+
+    public function offsetGet($key)
+    {
+        return $this->__get($key);
+    }
+
+    public function offsetSet($key, $value)
+    {
+        $this->__set($key, $value);
+    }
+
+    public function offsetUnset($key)
+    {
+        $this->__unset($key);
+    }
+
+    //Countable
+    public function count()
+    {
+        return count($this->instances);
+    }
+
+    //IteratorAggregate
+    public function getIterator()
+    {
+        return new ArrayIterator($this->instances);
+    }
+
+    public function __debugInfo()
+    {
+        $data = get_object_vars($this);
+        unset($data['instances'], $data['instance']);
+
+        return $data;
+    }
+}

+ 287 - 0
thinkphp/library/think/Controller.php

@@ -0,0 +1,287 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\exception\ValidateException;
+use traits\controller\Jump;
+
+class Controller
+{
+    use Jump;
+
+    /**
+     * 视图类实例
+     * @var \think\View
+     */
+    protected $view;
+
+    /**
+     * Request实例
+     * @var \think\Request
+     */
+    protected $request;
+
+    /**
+     * 验证失败是否抛出异常
+     * @var bool
+     */
+    protected $failException = false;
+
+    /**
+     * 是否批量验证
+     * @var bool
+     */
+    protected $batchValidate = false;
+
+    /**
+     * 前置操作方法列表(即将废弃)
+     * @var array $beforeActionList
+     */
+    protected $beforeActionList = [];
+
+    /**
+     * 控制器中间件
+     * @var array
+     */
+    protected $middleware = [];
+
+    /**
+     * 构造方法
+     * @access public
+     */
+    public function __construct(App $app = null)
+    {
+        $this->app     = $app ?: Container::get('app');
+        $this->request = $this->app['request'];
+        $this->view    = $this->app['view'];
+
+        // 控制器初始化
+        $this->initialize();
+
+        $this->registerMiddleware();
+
+        // 前置操作方法 即将废弃
+        foreach ((array) $this->beforeActionList as $method => $options) {
+            is_numeric($method) ?
+            $this->beforeAction($options) :
+            $this->beforeAction($method, $options);
+        }
+    }
+
+    // 初始化
+    protected function initialize()
+    {}
+
+    // 注册控制器中间件
+    public function registerMiddleware()
+    {
+        foreach ($this->middleware as $key => $val) {
+            if (!is_int($key)) {
+                $only = $except = null;
+
+                if (isset($val['only'])) {
+                    $only = array_map(function ($item) {
+                        return strtolower($item);
+                    }, $val['only']);
+                } elseif (isset($val['except'])) {
+                    $except = array_map(function ($item) {
+                        return strtolower($item);
+                    }, $val['except']);
+                }
+
+                if (isset($only) && !in_array($this->request->action(), $only)) {
+                    continue;
+                } elseif (isset($except) && in_array($this->request->action(), $except)) {
+                    continue;
+                } else {
+                    $val = $key;
+                }
+            }
+
+            $this->app['middleware']->controller($val);
+        }
+    }
+
+    /**
+     * 前置操作
+     * @access protected
+     * @param  string $method  前置操作方法名
+     * @param  array  $options 调用参数 ['only'=>[...]] 或者['except'=>[...]]
+     */
+    protected function beforeAction($method, $options = [])
+    {
+        if (isset($options['only'])) {
+            if (is_string($options['only'])) {
+                $options['only'] = explode(',', $options['only']);
+            }
+
+            $only = array_map(function ($val) {
+                return strtolower($val);
+            }, $options['only']);
+
+            if (!in_array($this->request->action(), $only)) {
+                return;
+            }
+        } elseif (isset($options['except'])) {
+            if (is_string($options['except'])) {
+                $options['except'] = explode(',', $options['except']);
+            }
+
+            $except = array_map(function ($val) {
+                return strtolower($val);
+            }, $options['except']);
+
+            if (in_array($this->request->action(), $except)) {
+                return;
+            }
+        }
+
+        call_user_func([$this, $method]);
+    }
+
+    /**
+     * 加载模板输出
+     * @access protected
+     * @param  string $template 模板文件名
+     * @param  array  $vars     模板输出变量
+     * @param  array  $config   模板参数
+     * @return mixed
+     */
+    protected function fetch($template = '', $vars = [], $config = [])
+    {
+        return Response::create($template, 'view')->assign($vars)->config($config);
+    }
+
+    /**
+     * 渲染内容输出
+     * @access protected
+     * @param  string $content 模板内容
+     * @param  array  $vars    模板输出变量
+     * @param  array  $config  模板参数
+     * @return mixed
+     */
+    protected function display($content = '', $vars = [], $config = [])
+    {
+        return Response::create($content, 'view')->assign($vars)->config($config)->isContent(true);
+    }
+
+    /**
+     * 模板变量赋值
+     * @access protected
+     * @param  mixed $name  要显示的模板变量
+     * @param  mixed $value 变量的值
+     * @return $this
+     */
+    protected function assign($name, $value = '')
+    {
+        $this->view->assign($name, $value);
+
+        return $this;
+    }
+
+    /**
+     * 视图过滤
+     * @access protected
+     * @param  Callable $filter 过滤方法或闭包
+     * @return $this
+     */
+    protected function filter($filter)
+    {
+        $this->view->filter($filter);
+
+        return $this;
+    }
+
+    /**
+     * 初始化模板引擎
+     * @access protected
+     * @param  array|string $engine 引擎参数
+     * @return $this
+     */
+    protected function engine($engine)
+    {
+        $this->view->engine($engine);
+
+        return $this;
+    }
+
+    /**
+     * 设置验证失败后是否抛出异常
+     * @access protected
+     * @param  bool $fail 是否抛出异常
+     * @return $this
+     */
+    protected function validateFailException($fail = true)
+    {
+        $this->failException = $fail;
+
+        return $this;
+    }
+
+    /**
+     * 验证数据
+     * @access protected
+     * @param  array        $data     数据
+     * @param  string|array $validate 验证器名或者验证规则数组
+     * @param  array        $message  提示信息
+     * @param  bool         $batch    是否批量验证
+     * @param  mixed        $callback 回调方法(闭包)
+     * @return array|string|true
+     * @throws ValidateException
+     */
+    protected function validate($data, $validate, $message = [], $batch = false, $callback = null)
+    {
+        if (is_array($validate)) {
+            $v = $this->app->validate();
+            $v->rule($validate);
+        } else {
+            if (strpos($validate, '.')) {
+                // 支持场景
+                list($validate, $scene) = explode('.', $validate);
+            }
+            $v = $this->app->validate($validate);
+            if (!empty($scene)) {
+                $v->scene($scene);
+            }
+        }
+
+        // 是否批量验证
+        if ($batch || $this->batchValidate) {
+            $v->batch(true);
+        }
+
+        if (is_array($message)) {
+            $v->message($message);
+        }
+
+        if ($callback && is_callable($callback)) {
+            call_user_func_array($callback, [$v, &$data]);
+        }
+
+        if (!$v->check($data)) {
+            if ($this->failException) {
+                throw new ValidateException($v->getError());
+            }
+            return $v->getError();
+        }
+
+        return true;
+    }
+
+    public function __debugInfo()
+    {
+        $data = get_object_vars($this);
+        unset($data['app'], $data['request']);
+
+        return $data;
+    }
+}

+ 268 - 0
thinkphp/library/think/Cookie.php

@@ -0,0 +1,268 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Cookie
+{
+    /**
+     * 配置参数
+     * @var array
+     */
+    protected $config = [
+        // cookie 名称前缀
+        'prefix'    => '',
+        // cookie 保存时间
+        'expire'    => 0,
+        // cookie 保存路径
+        'path'      => '/',
+        // cookie 有效域名
+        'domain'    => '',
+        //  cookie 启用安全传输
+        'secure'    => false,
+        // httponly设置
+        'httponly'  => false,
+        // 是否使用 setcookie
+        'setcookie' => true,
+    ];
+
+    /**
+     * 构造方法
+     * @access public
+     */
+    public function __construct(array $config = [])
+    {
+        $this->init($config);
+    }
+
+    /**
+     * Cookie初始化
+     * @access public
+     * @param  array $config
+     * @return void
+     */
+    public function init(array $config = [])
+    {
+        $this->config = array_merge($this->config, array_change_key_case($config));
+
+        if (!empty($this->config['httponly']) && PHP_SESSION_ACTIVE != session_status()) {
+            ini_set('session.cookie_httponly', 1);
+        }
+    }
+
+    public static function __make(Config $config)
+    {
+        return new static($config->pull('cookie'));
+    }
+
+    /**
+     * 设置或者获取cookie作用域(前缀)
+     * @access public
+     * @param  string $prefix
+     * @return string|void
+     */
+    public function prefix($prefix = '')
+    {
+        if (empty($prefix)) {
+            return $this->config['prefix'];
+        }
+
+        $this->config['prefix'] = $prefix;
+    }
+
+    /**
+     * Cookie 设置、获取、删除
+     *
+     * @access public
+     * @param  string $name  cookie名称
+     * @param  mixed  $value cookie值
+     * @param  mixed  $option 可选参数 可能会是 null|integer|string
+     * @return void
+     */
+    public function set($name, $value = '', $option = null)
+    {
+        // 参数设置(会覆盖黙认设置)
+        if (!is_null($option)) {
+            if (is_numeric($option)) {
+                $option = ['expire' => $option];
+            } elseif (is_string($option)) {
+                parse_str($option, $option);
+            }
+
+            $config = array_merge($this->config, array_change_key_case($option));
+        } else {
+            $config = $this->config;
+        }
+
+        $name = $config['prefix'] . $name;
+
+        // 设置cookie
+        if (is_array($value)) {
+            array_walk_recursive($value, [$this, 'jsonFormatProtect'], 'encode');
+            $value = 'think:' . json_encode($value);
+        }
+
+        $expire = !empty($config['expire']) ? $_SERVER['REQUEST_TIME'] + intval($config['expire']) : 0;
+
+        if ($config['setcookie']) {
+            $this->setCookie($name, $value, $expire, $config);
+        }
+
+        $_COOKIE[$name] = $value;
+    }
+
+    /**
+     * Cookie 设置保存
+     *
+     * @access public
+     * @param  string $name  cookie名称
+     * @param  mixed  $value cookie值
+     * @param  array  $option 可选参数
+     * @return void
+     */
+    protected function setCookie($name, $value, $expire, $option = [])
+    {
+        setcookie($name, $value, $expire, $option['path'], $option['domain'], $option['secure'], $option['httponly']);
+    }
+
+    /**
+     * 永久保存Cookie数据
+     * @access public
+     * @param  string $name  cookie名称
+     * @param  mixed  $value cookie值
+     * @param  mixed  $option 可选参数 可能会是 null|integer|string
+     * @return void
+     */
+    public function forever($name, $value = '', $option = null)
+    {
+        if (is_null($option) || is_numeric($option)) {
+            $option = [];
+        }
+
+        $option['expire'] = 315360000;
+
+        $this->set($name, $value, $option);
+    }
+
+    /**
+     * 判断Cookie数据
+     * @access public
+     * @param  string        $name cookie名称
+     * @param  string|null   $prefix cookie前缀
+     * @return bool
+     */
+    public function has($name, $prefix = null)
+    {
+        $prefix = !is_null($prefix) ? $prefix : $this->config['prefix'];
+        $name   = $prefix . $name;
+
+        return isset($_COOKIE[$name]);
+    }
+
+    /**
+     * Cookie获取
+     * @access public
+     * @param  string        $name cookie名称 留空获取全部
+     * @param  string|null   $prefix cookie前缀
+     * @return mixed
+     */
+    public function get($name = '', $prefix = null)
+    {
+        $prefix = !is_null($prefix) ? $prefix : $this->config['prefix'];
+        $key    = $prefix . $name;
+
+        if ('' == $name) {
+            if ($prefix) {
+                $value = [];
+                foreach ($_COOKIE as $k => $val) {
+                    if (0 === strpos($k, $prefix)) {
+                        $value[$k] = $val;
+                    }
+                }
+            } else {
+                $value = $_COOKIE;
+            }
+        } elseif (isset($_COOKIE[$key])) {
+            $value = $_COOKIE[$key];
+
+            if (0 === strpos($value, 'think:')) {
+                $value = substr($value, 6);
+                $value = json_decode($value, true);
+                array_walk_recursive($value, [$this, 'jsonFormatProtect'], 'decode');
+            }
+        } else {
+            $value = null;
+        }
+
+        return $value;
+    }
+
+    /**
+     * Cookie删除
+     * @access public
+     * @param  string        $name cookie名称
+     * @param  string|null   $prefix cookie前缀
+     * @return void
+     */
+    public function delete($name, $prefix = null)
+    {
+        $config = $this->config;
+        $prefix = !is_null($prefix) ? $prefix : $config['prefix'];
+        $name   = $prefix . $name;
+
+        if ($config['setcookie']) {
+            $this->setcookie($name, '', $_SERVER['REQUEST_TIME'] - 3600, $config);
+        }
+
+        // 删除指定cookie
+        unset($_COOKIE[$name]);
+    }
+
+    /**
+     * Cookie清空
+     * @access public
+     * @param  string|null $prefix cookie前缀
+     * @return void
+     */
+    public function clear($prefix = null)
+    {
+        // 清除指定前缀的所有cookie
+        if (empty($_COOKIE)) {
+            return;
+        }
+
+        // 要删除的cookie前缀,不指定则删除config设置的指定前缀
+        $config = $this->config;
+        $prefix = !is_null($prefix) ? $prefix : $config['prefix'];
+
+        if ($prefix) {
+            // 如果前缀为空字符串将不作处理直接返回
+            foreach ($_COOKIE as $key => $val) {
+                if (0 === strpos($key, $prefix)) {
+                    if ($config['setcookie']) {
+                        $this->setcookie($key, '', $_SERVER['REQUEST_TIME'] - 3600, $config);
+                    }
+                    unset($_COOKIE[$key]);
+                }
+            }
+        }
+
+        return;
+    }
+
+    private function jsonFormatProtect(&$val, $key, $type = 'encode')
+    {
+        if (!empty($val) && true !== $val) {
+            $val = 'decode' == $type ? urldecode($val) : urlencode($val);
+        }
+    }
+
+}

+ 197 - 0
thinkphp/library/think/Db.php

@@ -0,0 +1,197 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\db\Connection;
+
+/**
+ * Class Db
+ * @package think
+ * @method \think\db\Query master() static 从主服务器读取数据
+ * @method \think\db\Query readMaster(bool $all = false) static 后续从主服务器读取数据
+ * @method \think\db\Query table(string $table) static 指定数据表(含前缀)
+ * @method \think\db\Query name(string $name) static 指定数据表(不含前缀)
+ * @method \think\db\Expression raw(string $value) static 使用表达式设置数据
+ * @method \think\db\Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件
+ * @method \think\db\Query whereRaw(string $where, array $bind = []) static 表达式查询
+ * @method \think\db\Query whereExp(string $field, string $condition, array $bind = []) static 字段表达式查询
+ * @method \think\db\Query when(mixed $condition, mixed $query, mixed $otherwise = null) static 条件查询
+ * @method \think\db\Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询
+ * @method \think\db\Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询
+ * @method \think\db\Query field(mixed $field, boolean $except = false) static 指定查询字段
+ * @method \think\db\Query fieldRaw(string $field, array $bind = []) static 指定查询字段
+ * @method \think\db\Query union(mixed $union, boolean $all = false) static UNION查询
+ * @method \think\db\Query limit(mixed $offset, integer $length = null) static 查询LIMIT
+ * @method \think\db\Query order(mixed $field, string $order = null) static 查询ORDER
+ * @method \think\db\Query orderRaw(string $field, array $bind = []) static 查询ORDER
+ * @method \think\db\Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存
+ * @method \think\db\Query withAttr(string $name,callable $callback = null) static 使用获取器获取数据
+ * @method mixed value(string $field) static 获取某个字段的值
+ * @method array column(string $field, string $key = '') static 获取某个列的值
+ * @method mixed find(mixed $data = null) static 查询单个记录
+ * @method mixed select(mixed $data = null) static 查询多个记录
+ * @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录
+ * @method integer insertGetId(array $data, boolean $replace = false, string $sequence = null) static 插入一条记录并返回自增ID
+ * @method integer insertAll(array $dataSet) static 插入多条记录
+ * @method integer update(array $data) static 更新记录
+ * @method integer delete(mixed $data = null) static 删除记录
+ * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据
+ * @method \Generator cursor(mixed $data = null) static 使用游标查找记录
+ * @method mixed query(string $sql, array $bind = [], boolean $master = false, bool $pdo = false) static SQL查询
+ * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行
+ * @method \think\Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询
+ * @method mixed transaction(callable $callback) static 执行数据库事务
+ * @method void startTrans() static 启动事务
+ * @method void commit() static 用于非自动提交状态下面的查询提交
+ * @method void rollback() static 事务回滚
+ * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句
+ * @method string getLastInsID(string $sequence = null) static 获取最近插入的ID
+ */
+class Db
+{
+    /**
+     * 当前数据库连接对象
+     * @var Connection
+     */
+    protected static $connection;
+
+    /**
+     * 数据库配置
+     * @var array
+     */
+    protected static $config = [];
+
+    /**
+     * 查询次数
+     * @var integer
+     */
+    public static $queryTimes = 0;
+
+    /**
+     * 执行次数
+     * @var integer
+     */
+    public static $executeTimes = 0;
+
+    /**
+     * 配置
+     * @access public
+     * @param  mixed $config
+     * @return void
+     */
+    public static function init($config = [])
+    {
+        self::$config = $config;
+
+        if (empty($config['query'])) {
+            self::$config['query'] = '\\think\\db\\Query';
+        }
+    }
+
+    /**
+     * 获取数据库配置
+     * @access public
+     * @param  string $config 配置名称
+     * @return mixed
+     */
+    public static function getConfig($name = '')
+    {
+        if ('' === $name) {
+            return self::$config;
+        }
+
+        return isset(self::$config[$name]) ? self::$config[$name] : null;
+    }
+
+    /**
+     * 切换数据库连接
+     * @access public
+     * @param  mixed         $config 连接配置
+     * @param  bool|string   $name 连接标识 true 强制重新连接
+     * @param  string        $query 查询对象类名
+     * @return mixed 返回查询对象实例
+     * @throws Exception
+     */
+    public static function connect($config = [], $name = false, $query = '')
+    {
+        // 解析配置参数
+        $options = self::parseConfig($config ?: self::$config);
+
+        $query = $query ?: $options['query'];
+
+        // 创建数据库连接对象实例
+        self::$connection = Connection::instance($options, $name);
+
+        return new $query(self::$connection);
+    }
+
+    /**
+     * 数据库连接参数解析
+     * @access private
+     * @param  mixed $config
+     * @return array
+     */
+    private static function parseConfig($config)
+    {
+        if (is_string($config) && false === strpos($config, '/')) {
+            // 支持读取配置参数
+            $config = isset(self::$config[$config]) ? self::$config[$config] : self::$config;
+        }
+
+        $result = is_string($config) ? self::parseDsnConfig($config) : $config;
+
+        if (empty($result['query'])) {
+            $result['query'] = self::$config['query'];
+        }
+
+        return $result;
+    }
+
+    /**
+     * DSN解析
+     * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1&param2=val2#utf8
+     * @access private
+     * @param  string $dsnStr
+     * @return array
+     */
+    private static function parseDsnConfig($dsnStr)
+    {
+        $info = parse_url($dsnStr);
+
+        if (!$info) {
+            return [];
+        }
+
+        $dsn = [
+            'type'     => $info['scheme'],
+            'username' => isset($info['user']) ? $info['user'] : '',
+            'password' => isset($info['pass']) ? $info['pass'] : '',
+            'hostname' => isset($info['host']) ? $info['host'] : '',
+            'hostport' => isset($info['port']) ? $info['port'] : '',
+            'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '',
+            'charset'  => isset($info['fragment']) ? $info['fragment'] : 'utf8',
+        ];
+
+        if (isset($info['query'])) {
+            parse_str($info['query'], $dsn['params']);
+        } else {
+            $dsn['params'] = [];
+        }
+
+        return $dsn;
+    }
+
+    public static function __callStatic($method, $args)
+    {
+        return call_user_func_array([static::connect(), $method], $args);
+    }
+}

+ 278 - 0
thinkphp/library/think/Debug.php

@@ -0,0 +1,278 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\model\Collection as ModelCollection;
+use think\response\Redirect;
+
+class Debug
+{
+    /**
+     * 配置参数
+     * @var array
+     */
+    protected $config = [];
+
+    /**
+     * 区间时间信息
+     * @var array
+     */
+    protected $info = [];
+
+    /**
+     * 区间内存信息
+     * @var array
+     */
+    protected $mem = [];
+
+    /**
+     * 应用对象
+     * @var App
+     */
+    protected $app;
+
+    public function __construct(App $app, array $config = [])
+    {
+        $this->app    = $app;
+        $this->config = $config;
+    }
+
+    public static function __make(App $app, Config $config)
+    {
+        return new static($app, $config->pull('trace'));
+    }
+
+    public function setConfig(array $config)
+    {
+        $this->config = array_merge($this->config, $config);
+    }
+
+    /**
+     * 记录时间(微秒)和内存使用情况
+     * @access public
+     * @param  string    $name 标记位置
+     * @param  mixed     $value 标记值 留空则取当前 time 表示仅记录时间 否则同时记录时间和内存
+     * @return void
+     */
+    public function remark($name, $value = '')
+    {
+        // 记录时间和内存使用
+        $this->info[$name] = is_float($value) ? $value : microtime(true);
+
+        if ('time' != $value) {
+            $this->mem['mem'][$name]  = is_float($value) ? $value : memory_get_usage();
+            $this->mem['peak'][$name] = memory_get_peak_usage();
+        }
+    }
+
+    /**
+     * 统计某个区间的时间(微秒)使用情况
+     * @access public
+     * @param  string            $start 开始标签
+     * @param  string            $end 结束标签
+     * @param  integer|string    $dec 小数位
+     * @return integer
+     */
+    public function getRangeTime($start, $end, $dec = 6)
+    {
+        if (!isset($this->info[$end])) {
+            $this->info[$end] = microtime(true);
+        }
+
+        return number_format(($this->info[$end] - $this->info[$start]), $dec);
+    }
+
+    /**
+     * 统计从开始到统计时的时间(微秒)使用情况
+     * @access public
+     * @param  integer|string $dec 小数位
+     * @return integer
+     */
+    public function getUseTime($dec = 6)
+    {
+        return number_format((microtime(true) - $this->app->getBeginTime()), $dec);
+    }
+
+    /**
+     * 获取当前访问的吞吐率情况
+     * @access public
+     * @return string
+     */
+    public function getThroughputRate()
+    {
+        return number_format(1 / $this->getUseTime(), 2) . 'req/s';
+    }
+
+    /**
+     * 记录区间的内存使用情况
+     * @access public
+     * @param  string            $start 开始标签
+     * @param  string            $end 结束标签
+     * @param  integer|string    $dec 小数位
+     * @return string
+     */
+    public function getRangeMem($start, $end, $dec = 2)
+    {
+        if (!isset($this->mem['mem'][$end])) {
+            $this->mem['mem'][$end] = memory_get_usage();
+        }
+
+        $size = $this->mem['mem'][$end] - $this->mem['mem'][$start];
+        $a    = ['B', 'KB', 'MB', 'GB', 'TB'];
+        $pos  = 0;
+
+        while ($size >= 1024) {
+            $size /= 1024;
+            $pos++;
+        }
+
+        return round($size, $dec) . " " . $a[$pos];
+    }
+
+    /**
+     * 统计从开始到统计时的内存使用情况
+     * @access public
+     * @param  integer|string $dec 小数位
+     * @return string
+     */
+    public function getUseMem($dec = 2)
+    {
+        $size = memory_get_usage() - $this->app->getBeginMem();
+        $a    = ['B', 'KB', 'MB', 'GB', 'TB'];
+        $pos  = 0;
+
+        while ($size >= 1024) {
+            $size /= 1024;
+            $pos++;
+        }
+
+        return round($size, $dec) . " " . $a[$pos];
+    }
+
+    /**
+     * 统计区间的内存峰值情况
+     * @access public
+     * @param  string            $start 开始标签
+     * @param  string            $end 结束标签
+     * @param  integer|string    $dec 小数位
+     * @return string
+     */
+    public function getMemPeak($start, $end, $dec = 2)
+    {
+        if (!isset($this->mem['peak'][$end])) {
+            $this->mem['peak'][$end] = memory_get_peak_usage();
+        }
+
+        $size = $this->mem['peak'][$end] - $this->mem['peak'][$start];
+        $a    = ['B', 'KB', 'MB', 'GB', 'TB'];
+        $pos  = 0;
+
+        while ($size >= 1024) {
+            $size /= 1024;
+            $pos++;
+        }
+
+        return round($size, $dec) . " " . $a[$pos];
+    }
+
+    /**
+     * 获取文件加载信息
+     * @access public
+     * @param  bool  $detail 是否显示详细
+     * @return integer|array
+     */
+    public function getFile($detail = false)
+    {
+        if ($detail) {
+            $files = get_included_files();
+            $info  = [];
+
+            foreach ($files as $key => $file) {
+                $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )';
+            }
+
+            return $info;
+        }
+
+        return count(get_included_files());
+    }
+
+    /**
+     * 浏览器友好的变量输出
+     * @access public
+     * @param  mixed         $var 变量
+     * @param  boolean       $echo 是否输出 默认为true 如果为false 则返回输出字符串
+     * @param  string        $label 标签 默认为空
+     * @param  integer       $flags htmlspecialchars flags
+     * @return void|string
+     */
+    public function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE)
+    {
+        $label = (null === $label) ? '' : rtrim($label) . ':';
+        if ($var instanceof Model || $var instanceof ModelCollection) {
+            $var = $var->toArray();
+        }
+
+        ob_start();
+        var_dump($var);
+
+        $output = ob_get_clean();
+        $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output);
+
+        if (PHP_SAPI == 'cli') {
+            $output = PHP_EOL . $label . $output . PHP_EOL;
+        } else {
+            if (!extension_loaded('xdebug')) {
+                $output = htmlspecialchars($output, $flags);
+            }
+            $output = '<pre>' . $label . $output . '</pre>';
+        }
+        if ($echo) {
+            echo($output);
+            return;
+        }
+        return $output;
+    }
+
+    public function inject(Response $response, &$content)
+    {
+        $config = $this->config;
+        $type   = isset($config['type']) ? $config['type'] : 'Html';
+
+        unset($config['type']);
+
+        $trace = Loader::factory($type, '\\think\\debug\\', $config);
+
+        if ($response instanceof Redirect) {
+            //TODO 记录
+        } else {
+            $output = $trace->output($response, $this->app['log']->getLog());
+            if (is_string($output)) {
+                // trace调试信息注入
+                $pos = strripos($content, '</body>');
+                if (false !== $pos) {
+                    $content = substr($content, 0, $pos) . $output . substr($content, $pos);
+                } else {
+                    $content = $content . $output;
+                }
+            }
+        }
+    }
+
+    public function __debugInfo()
+    {
+        $data = get_object_vars($this);
+        unset($data['app']);
+
+        return $data;
+    }
+}

+ 113 - 0
thinkphp/library/think/Env.php

@@ -0,0 +1,113 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Env
+{
+    /**
+     * 环境变量数据
+     * @var array
+     */
+    protected $data = [];
+
+    public function __construct()
+    {
+        $this->data = $_ENV;
+    }
+
+    /**
+     * 读取环境变量定义文件
+     * @access public
+     * @param  string    $file  环境变量定义文件
+     * @return void
+     */
+    public function load($file)
+    {
+        $env = parse_ini_file($file, true);
+        $this->set($env);
+    }
+
+    /**
+     * 获取环境变量值
+     * @access public
+     * @param  string    $name 环境变量名
+     * @param  mixed     $default  默认值
+     * @return mixed
+     */
+    public function get($name = null, $default = null, $php_prefix = true)
+    {
+        if (is_null($name)) {
+            return $this->data;
+        }
+
+        $name = strtoupper(str_replace('.', '_', $name));
+
+        if (isset($this->data[$name])) {
+            return $this->data[$name];
+        }
+
+        return $this->getEnv($name, $default, $php_prefix);
+    }
+
+    protected function getEnv($name, $default = null, $php_prefix = true)
+    {
+        if ($php_prefix) {
+            $name = 'PHP_' . $name;
+        }
+
+        $result = getenv($name);
+
+        if (false === $result) {
+            return $default;
+        }
+
+        if ('false' === $result) {
+            $result = false;
+        } elseif ('true' === $result) {
+            $result = true;
+        }
+
+        if (!isset($this->data[$name])) {
+            $this->data[$name] = $result;
+        }
+
+        return $result;
+    }
+
+    /**
+     * 设置环境变量值
+     * @access public
+     * @param  string|array  $env   环境变量
+     * @param  mixed         $value  值
+     * @return void
+     */
+    public function set($env, $value = null)
+    {
+        if (is_array($env)) {
+            $env = array_change_key_case($env, CASE_UPPER);
+
+            foreach ($env as $key => $val) {
+                if (is_array($val)) {
+                    foreach ($val as $k => $v) {
+                        $this->data[$key . '_' . strtoupper($k)] = $v;
+                    }
+                } else {
+                    $this->data[$key] = $val;
+                }
+            }
+        } else {
+            $name = strtoupper(str_replace('.', '_', $env));
+
+            $this->data[$name] = $value;
+        }
+    }
+}

+ 147 - 0
thinkphp/library/think/Error.php

@@ -0,0 +1,147 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\console\Output as ConsoleOutput;
+use think\exception\ErrorException;
+use think\exception\Handle;
+use think\exception\ThrowableError;
+
+class Error
+{
+    /**
+     * 配置参数
+     * @var array
+     */
+    protected static $exceptionHandler;
+
+    /**
+     * 注册异常处理
+     * @access public
+     * @return void
+     */
+    public static function register()
+    {
+        error_reporting(E_ALL);
+        set_error_handler([__CLASS__, 'appError']);
+        set_exception_handler([__CLASS__, 'appException']);
+        register_shutdown_function([__CLASS__, 'appShutdown']);
+    }
+
+    /**
+     * Exception Handler
+     * @access public
+     * @param  \Exception|\Throwable $e
+     */
+    public static function appException($e)
+    {
+        if (!$e instanceof \Exception) {
+            $e = new ThrowableError($e);
+        }
+
+        self::getExceptionHandler()->report($e);
+
+        if (PHP_SAPI == 'cli') {
+            self::getExceptionHandler()->renderForConsole(new ConsoleOutput, $e);
+        } else {
+            self::getExceptionHandler()->render($e)->send();
+        }
+    }
+
+    /**
+     * Error Handler
+     * @access public
+     * @param  integer $errno   错误编号
+     * @param  integer $errstr  详细错误信息
+     * @param  string  $errfile 出错的文件
+     * @param  integer $errline 出错行号
+     * @throws ErrorException
+     */
+    public static function appError($errno, $errstr, $errfile = '', $errline = 0)
+    {
+        $exception = new ErrorException($errno, $errstr, $errfile, $errline);
+        if (error_reporting() & $errno) {
+            // 将错误信息托管至 think\exception\ErrorException
+            throw $exception;
+        }
+
+        self::getExceptionHandler()->report($exception);
+    }
+
+    /**
+     * Shutdown Handler
+     * @access public
+     */
+    public static function appShutdown()
+    {
+        if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) {
+            // 将错误信息托管至think\ErrorException
+            $exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']);
+
+            self::appException($exception);
+        }
+
+        // 写入日志
+        Container::get('log')->save();
+    }
+
+    /**
+     * 确定错误类型是否致命
+     *
+     * @access protected
+     * @param  int $type
+     * @return bool
+     */
+    protected static function isFatal($type)
+    {
+        return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]);
+    }
+
+    /**
+     * 设置异常处理类
+     *
+     * @access public
+     * @param  mixed $handle
+     * @return void
+     */
+    public static function setExceptionHandler($handle)
+    {
+        self::$exceptionHandler = $handle;
+    }
+
+    /**
+     * Get an instance of the exception handler.
+     *
+     * @access public
+     * @return Handle
+     */
+    public static function getExceptionHandler()
+    {
+        static $handle;
+
+        if (!$handle) {
+            // 异常处理handle
+            $class = self::$exceptionHandler;
+
+            if ($class && is_string($class) && class_exists($class) && is_subclass_of($class, "\\think\\exception\\Handle")) {
+                $handle = new $class;
+            } else {
+                $handle = new Handle;
+                if ($class instanceof \Closure) {
+                    $handle->setRender($class);
+                }
+            }
+        }
+
+        return $handle;
+    }
+}

+ 56 - 0
thinkphp/library/think/Exception.php

@@ -0,0 +1,56 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Exception extends \Exception
+{
+
+    /**
+     * 保存异常页面显示的额外Debug数据
+     * @var array
+     */
+    protected $data = [];
+
+    /**
+     * 设置异常额外的Debug数据
+     * 数据将会显示为下面的格式
+     *
+     * Exception Data
+     * --------------------------------------------------
+     * Label 1
+     *   key1      value1
+     *   key2      value2
+     * Label 2
+     *   key1      value1
+     *   key2      value2
+     *
+     * @access protected
+     * @param  string $label 数据分类,用于异常页面显示
+     * @param  array  $data  需要显示的数据,必须为关联数组
+     */
+    final protected function setData($label, array $data)
+    {
+        $this->data[$label] = $data;
+    }
+
+    /**
+     * 获取异常额外Debug数据
+     * 主要用于输出到异常页面便于调试
+     * @access public
+     * @return array 由setData设置的Debug数据
+     */
+    final public function getData()
+    {
+        return $this->data;
+    }
+
+}

+ 125 - 0
thinkphp/library/think/Facade.php

@@ -0,0 +1,125 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Facade
+{
+    /**
+     * 绑定对象
+     * @var array
+     */
+    protected static $bind = [];
+
+    /**
+     * 始终创建新的对象实例
+     * @var bool
+     */
+    protected static $alwaysNewInstance;
+
+    /**
+     * 绑定类的静态代理
+     * @static
+     * @access public
+     * @param  string|array  $name    类标识
+     * @param  string        $class   类名
+     * @return object
+     */
+    public static function bind($name, $class = null)
+    {
+        if (__CLASS__ != static::class) {
+            return self::__callStatic('bind', func_get_args());
+        }
+
+        if (is_array($name)) {
+            self::$bind = array_merge(self::$bind, $name);
+        } else {
+            self::$bind[$name] = $class;
+        }
+    }
+
+    /**
+     * 创建Facade实例
+     * @static
+     * @access protected
+     * @param  string    $class          类名或标识
+     * @param  array     $args           变量
+     * @param  bool      $newInstance    是否每次创建新的实例
+     * @return object
+     */
+    protected static function createFacade($class = '', $args = [], $newInstance = false)
+    {
+        $class = $class ?: static::class;
+
+        $facadeClass = static::getFacadeClass();
+
+        if ($facadeClass) {
+            $class = $facadeClass;
+        } elseif (isset(self::$bind[$class])) {
+            $class = self::$bind[$class];
+        }
+
+        if (static::$alwaysNewInstance) {
+            $newInstance = true;
+        }
+
+        return Container::getInstance()->make($class, $args, $newInstance);
+    }
+
+    /**
+     * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+     * @access protected
+     * @return string
+     */
+    protected static function getFacadeClass()
+    {}
+
+    /**
+     * 带参数实例化当前Facade类
+     * @access public
+     * @return mixed
+     */
+    public static function instance(...$args)
+    {
+        if (__CLASS__ != static::class) {
+            return self::createFacade('', $args);
+        }
+    }
+
+    /**
+     * 调用类的实例
+     * @access public
+     * @param  string        $class          类名或者标识
+     * @param  array|true    $args           变量
+     * @param  bool          $newInstance    是否每次创建新的实例
+     * @return mixed
+     */
+    public static function make($class, $args = [], $newInstance = false)
+    {
+        if (__CLASS__ != static::class) {
+            return self::__callStatic('make', func_get_args());
+        }
+
+        if (true === $args) {
+            // 总是创建新的实例化对象
+            $newInstance = true;
+            $args        = [];
+        }
+
+        return self::createFacade($class, $args, $newInstance);
+    }
+
+    // 调用实际类的方法
+    public static function __callStatic($method, $params)
+    {
+        return call_user_func_array([static::createFacade(), $method], $params);
+    }
+}

+ 496 - 0
thinkphp/library/think/File.php

@@ -0,0 +1,496 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use SplFileObject;
+
+class File extends SplFileObject
+{
+    /**
+     * 错误信息
+     * @var string
+     */
+    private $error = '';
+
+    /**
+     * 当前完整文件名
+     * @var string
+     */
+    protected $filename;
+
+    /**
+     * 上传文件名
+     * @var string
+     */
+    protected $saveName;
+
+    /**
+     * 上传文件命名规则
+     * @var string
+     */
+    protected $rule = 'date';
+
+    /**
+     * 上传文件验证规则
+     * @var array
+     */
+    protected $validate = [];
+
+    /**
+     * 是否单元测试
+     * @var bool
+     */
+    protected $isTest;
+
+    /**
+     * 上传文件信息
+     * @var array
+     */
+    protected $info = [];
+
+    /**
+     * 文件hash规则
+     * @var array
+     */
+    protected $hash = [];
+
+    public function __construct($filename, $mode = 'r')
+    {
+        parent::__construct($filename, $mode);
+
+        $this->filename = $this->getRealPath() ?: $this->getPathname();
+    }
+
+    /**
+     * 是否测试
+     * @access public
+     * @param  bool   $test 是否测试
+     * @return $this
+     */
+    public function isTest($test = false)
+    {
+        $this->isTest = $test;
+
+        return $this;
+    }
+
+    /**
+     * 设置上传信息
+     * @access public
+     * @param  array   $info 上传文件信息
+     * @return $this
+     */
+    public function setUploadInfo($info)
+    {
+        $this->info = $info;
+
+        return $this;
+    }
+
+    /**
+     * 获取上传文件的信息
+     * @access public
+     * @param  string   $name
+     * @return array|string
+     */
+    public function getInfo($name = '')
+    {
+        return isset($this->info[$name]) ? $this->info[$name] : $this->info;
+    }
+
+    /**
+     * 获取上传文件的文件名
+     * @access public
+     * @return string
+     */
+    public function getSaveName()
+    {
+        return $this->saveName;
+    }
+
+    /**
+     * 设置上传文件的保存文件名
+     * @access public
+     * @param  string   $saveName
+     * @return $this
+     */
+    public function setSaveName($saveName)
+    {
+        $this->saveName = $saveName;
+
+        return $this;
+    }
+
+    /**
+     * 获取文件的哈希散列值
+     * @access public
+     * @param  string $type
+     * @return string
+     */
+    public function hash($type = 'sha1')
+    {
+        if (!isset($this->hash[$type])) {
+            $this->hash[$type] = hash_file($type, $this->filename);
+        }
+
+        return $this->hash[$type];
+    }
+
+    /**
+     * 检查目录是否可写
+     * @access protected
+     * @param  string   $path    目录
+     * @return boolean
+     */
+    protected function checkPath($path)
+    {
+        if (is_dir($path)) {
+            return true;
+        }
+
+        if (mkdir($path, 0755, true)) {
+            return true;
+        }
+
+        $this->error = ['directory {:path} creation failed', ['path' => $path]];
+        return false;
+    }
+
+    /**
+     * 获取文件类型信息
+     * @access public
+     * @return string
+     */
+    public function getMime()
+    {
+        $finfo = finfo_open(FILEINFO_MIME_TYPE);
+
+        return finfo_file($finfo, $this->filename);
+    }
+
+    /**
+     * 设置文件的命名规则
+     * @access public
+     * @param  string   $rule    文件命名规则
+     * @return $this
+     */
+    public function rule($rule)
+    {
+        $this->rule = $rule;
+
+        return $this;
+    }
+
+    /**
+     * 设置上传文件的验证规则
+     * @access public
+     * @param  array   $rule    验证规则
+     * @return $this
+     */
+    public function validate($rule = [])
+    {
+        $this->validate = $rule;
+
+        return $this;
+    }
+
+    /**
+     * 检测是否合法的上传文件
+     * @access public
+     * @return bool
+     */
+    public function isValid()
+    {
+        if ($this->isTest) {
+            return is_file($this->filename);
+        }
+
+        return is_uploaded_file($this->filename);
+    }
+
+    /**
+     * 检测上传文件
+     * @access public
+     * @param  array   $rule    验证规则
+     * @return bool
+     */
+    public function check($rule = [])
+    {
+        $rule = $rule ?: $this->validate;
+
+        if ((isset($rule['size']) && !$this->checkSize($rule['size']))
+            || (isset($rule['type']) && !$this->checkMime($rule['type']))
+            || (isset($rule['ext']) && !$this->checkExt($rule['ext']))
+            || !$this->checkImg()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 检测上传文件后缀
+     * @access public
+     * @param  array|string   $ext    允许后缀
+     * @return bool
+     */
+    public function checkExt($ext)
+    {
+        if (is_string($ext)) {
+            $ext = explode(',', $ext);
+        }
+
+        $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION));
+
+        if (!in_array($extension, $ext)) {
+            $this->error = 'extensions to upload is not allowed';
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 检测图像文件
+     * @access public
+     * @return bool
+     */
+    public function checkImg()
+    {
+        $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION));
+
+        /* 对图像文件进行严格检测 */
+        if (in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) && !in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13])) {
+            $this->error = 'illegal image files';
+            return false;
+        }
+
+        return true;
+    }
+
+    // 判断图像类型
+    protected function getImageType($image)
+    {
+        if (function_exists('exif_imagetype')) {
+            return exif_imagetype($image);
+        }
+
+        try {
+            $info = getimagesize($image);
+            return $info ? $info[2] : false;
+        } catch (\Exception $e) {
+            return false;
+        }
+    }
+
+    /**
+     * 检测上传文件大小
+     * @access public
+     * @param  integer   $size    最大大小
+     * @return bool
+     */
+    public function checkSize($size)
+    {
+        if ($this->getSize() > (int) $size) {
+            $this->error = 'filesize not match';
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 检测上传文件类型
+     * @access public
+     * @param  array|string   $mime    允许类型
+     * @return bool
+     */
+    public function checkMime($mime)
+    {
+        if (is_string($mime)) {
+            $mime = explode(',', $mime);
+        }
+
+        if (!in_array(strtolower($this->getMime()), $mime)) {
+            $this->error = 'mimetype to upload is not allowed';
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 移动文件
+     * @access public
+     * @param  string           $path    保存路径
+     * @param  string|bool      $savename    保存的文件名 默认自动生成
+     * @param  boolean          $replace 同名文件是否覆盖
+     * @param  bool             $autoAppendExt     自动补充扩展名
+     * @return false|File       false-失败 否则返回File实例
+     */
+    public function move($path, $savename = true, $replace = true, $autoAppendExt = true)
+    {
+        // 文件上传失败,捕获错误代码
+        if (!empty($this->info['error'])) {
+            $this->error($this->info['error']);
+            return false;
+        }
+
+        // 检测合法性
+        if (!$this->isValid()) {
+            $this->error = 'upload illegal files';
+            return false;
+        }
+
+        // 验证上传
+        if (!$this->check()) {
+            return false;
+        }
+
+        $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+        // 文件保存命名规则
+        $saveName = $this->buildSaveName($savename, $autoAppendExt);
+        $filename = $path . $saveName;
+
+        // 检测目录
+        if (false === $this->checkPath(dirname($filename))) {
+            return false;
+        }
+
+        /* 不覆盖同名文件 */
+        if (!$replace && is_file($filename)) {
+            $this->error = ['has the same filename: {:filename}', ['filename' => $filename]];
+            return false;
+        }
+
+        /* 移动文件 */
+        if ($this->isTest) {
+            rename($this->filename, $filename);
+        } elseif (!move_uploaded_file($this->filename, $filename)) {
+            $this->error = 'upload write error';
+            return false;
+        }
+
+        // 返回 File对象实例
+        $file = new self($filename);
+        $file->setSaveName($saveName);
+        $file->setUploadInfo($this->info);
+
+        return $file;
+    }
+
+    /**
+     * 获取保存文件名
+     * @access protected
+     * @param  string|bool   $savename    保存的文件名 默认自动生成
+     * @param  bool          $autoAppendExt     自动补充扩展名
+     * @return string
+     */
+    protected function buildSaveName($savename, $autoAppendExt = true)
+    {
+        if (true === $savename) {
+            // 自动生成文件名
+            $savename = $this->autoBuildName();
+        } elseif ('' === $savename || false === $savename) {
+            // 保留原文件名
+            $savename = $this->getInfo('name');
+        }
+
+        if ($autoAppendExt && false === strpos($savename, '.')) {
+            $savename .= '.' . pathinfo($this->getInfo('name'), PATHINFO_EXTENSION);
+        }
+
+        return $savename;
+    }
+
+    /**
+     * 自动生成文件名
+     * @access protected
+     * @return string
+     */
+    protected function autoBuildName()
+    {
+        if ($this->rule instanceof \Closure) {
+            $savename = call_user_func_array($this->rule, [$this]);
+        } else {
+            switch ($this->rule) {
+                case 'date':
+                    $savename = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true));
+                    break;
+                default:
+                    if (in_array($this->rule, hash_algos())) {
+                        $hash     = $this->hash($this->rule);
+                        $savename = substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2);
+                    } elseif (is_callable($this->rule)) {
+                        $savename = call_user_func($this->rule);
+                    } else {
+                        $savename = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true));
+                    }
+            }
+        }
+
+        return $savename;
+    }
+
+    /**
+     * 获取错误代码信息
+     * @access private
+     * @param  int $errorNo  错误号
+     */
+    private function error($errorNo)
+    {
+        switch ($errorNo) {
+            case 1:
+            case 2:
+                $this->error = 'upload File size exceeds the maximum value';
+                break;
+            case 3:
+                $this->error = 'only the portion of file is uploaded';
+                break;
+            case 4:
+                $this->error = 'no file to uploaded';
+                break;
+            case 6:
+                $this->error = 'upload temp dir not found';
+                break;
+            case 7:
+                $this->error = 'file write error';
+                break;
+            default:
+                $this->error = 'unknown upload error';
+        }
+    }
+
+    /**
+     * 获取错误信息(支持多语言)
+     * @access public
+     * @return string
+     */
+    public function getError()
+    {
+        $lang = Container::get('lang');
+
+        if (is_array($this->error)) {
+            list($msg, $vars) = $this->error;
+        } else {
+            $msg  = $this->error;
+            $vars = [];
+        }
+
+        return $lang->has($msg) ? $lang->get($msg, $vars) : $msg;
+    }
+
+    public function __call($method, $args)
+    {
+        return $this->hash($method);
+    }
+}

+ 220 - 0
thinkphp/library/think/Hook.php

@@ -0,0 +1,220 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Hook
+{
+    /**
+     * 钩子行为定义
+     * @var array
+     */
+    private $tags = [];
+
+    /**
+     * 绑定行为列表
+     * @var array
+     */
+    protected $bind = [];
+
+    /**
+     * 入口方法名称
+     * @var string
+     */
+    private static $portal = 'run';
+
+    /**
+     * 应用对象
+     * @var App
+     */
+    protected $app;
+
+    public function __construct(App $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * 指定入口方法名称
+     * @access public
+     * @param  string  $name     方法名
+     * @return $this
+     */
+    public function portal($name)
+    {
+        self::$portal = $name;
+        return $this;
+    }
+
+    /**
+     * 指定行为标识 便于调用
+     * @access public
+     * @param  string|array  $name     行为标识
+     * @param  mixed         $behavior 行为
+     * @return $this
+     */
+    public function alias($name, $behavior = null)
+    {
+        if (is_array($name)) {
+            $this->bind = array_merge($this->bind, $name);
+        } else {
+            $this->bind[$name] = $behavior;
+        }
+
+        return $this;
+    }
+
+    /**
+     * 动态添加行为扩展到某个标签
+     * @access public
+     * @param  string    $tag 标签名称
+     * @param  mixed     $behavior 行为名称
+     * @param  bool      $first 是否放到开头执行
+     * @return void
+     */
+    public function add($tag, $behavior, $first = false)
+    {
+        isset($this->tags[$tag]) || $this->tags[$tag] = [];
+
+        if (is_array($behavior) && !is_callable($behavior)) {
+            if (!array_key_exists('_overlay', $behavior)) {
+                $this->tags[$tag] = array_merge($this->tags[$tag], $behavior);
+            } else {
+                unset($behavior['_overlay']);
+                $this->tags[$tag] = $behavior;
+            }
+        } elseif ($first) {
+            array_unshift($this->tags[$tag], $behavior);
+        } else {
+            $this->tags[$tag][] = $behavior;
+        }
+    }
+
+    /**
+     * 批量导入插件
+     * @access public
+     * @param  array     $tags 插件信息
+     * @param  bool      $recursive 是否递归合并
+     * @return void
+     */
+    public function import(array $tags, $recursive = true)
+    {
+        if ($recursive) {
+            foreach ($tags as $tag => $behavior) {
+                $this->add($tag, $behavior);
+            }
+        } else {
+            $this->tags = $tags + $this->tags;
+        }
+    }
+
+    /**
+     * 获取插件信息
+     * @access public
+     * @param  string $tag 插件位置 留空获取全部
+     * @return array
+     */
+    public function get($tag = '')
+    {
+        if (empty($tag)) {
+            //获取全部的插件信息
+            return $this->tags;
+        }
+
+        return array_key_exists($tag, $this->tags) ? $this->tags[$tag] : [];
+    }
+
+    /**
+     * 监听标签的行为
+     * @access public
+     * @param  string $tag    标签名称
+     * @param  mixed  $params 传入参数
+     * @param  bool   $once   只获取一个有效返回值
+     * @return mixed
+     */
+    public function listen($tag, $params = null, $once = false)
+    {
+        $results = [];
+        $tags    = $this->get($tag);
+
+        foreach ($tags as $key => $name) {
+            $results[$key] = $this->execTag($name, $tag, $params);
+
+            if (false === $results[$key] || (!is_null($results[$key]) && $once)) {
+                break;
+            }
+        }
+
+        return $once ? end($results) : $results;
+    }
+
+    /**
+     * 执行行为
+     * @access public
+     * @param  mixed     $class  行为
+     * @param  mixed     $params 参数
+     * @return mixed
+     */
+    public function exec($class, $params = null)
+    {
+        if ($class instanceof \Closure || is_array($class)) {
+            $method = $class;
+        } else {
+            if (isset($this->bind[$class])) {
+                $class = $this->bind[$class];
+            }
+            $method = [$class, self::$portal];
+        }
+
+        return $this->app->invoke($method, [$params]);
+    }
+
+    /**
+     * 执行某个标签的行为
+     * @access protected
+     * @param  mixed     $class  要执行的行为
+     * @param  string    $tag    方法名(标签名)
+     * @param  mixed     $params 参数
+     * @return mixed
+     */
+    protected function execTag($class, $tag = '', $params = null)
+    {
+        $method = Loader::parseName($tag, 1, false);
+
+        if ($class instanceof \Closure) {
+            $call  = $class;
+            $class = 'Closure';
+        } elseif (is_array($class) || strpos($class, '::')) {
+            $call = $class;
+        } else {
+            $obj = Container::get($class);
+
+            if (!is_callable([$obj, $method])) {
+                $method = self::$portal;
+            }
+
+            $call  = [$class, $method];
+            $class = $class . '->' . $method;
+        }
+
+        $result = $this->app->invoke($call, [$params]);
+
+        return $result;
+    }
+
+    public function __debugInfo()
+    {
+        $data = get_object_vars($this);
+        unset($data['app']);
+
+        return $data;
+    }
+}

+ 284 - 0
thinkphp/library/think/Lang.php

@@ -0,0 +1,284 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Lang
+{
+    /**
+     * 多语言信息
+     * @var array
+     */
+    private $lang = [];
+
+    /**
+     * 当前语言
+     * @var string
+     */
+    private $range = 'zh-cn';
+
+    /**
+     * 多语言自动侦测变量名
+     * @var string
+     */
+    protected $langDetectVar = 'lang';
+
+    /**
+     * 多语言cookie变量
+     * @var string
+     */
+    protected $langCookieVar = 'think_var';
+
+    /**
+     * 允许的多语言列表
+     * @var array
+     */
+    protected $allowLangList = [];
+
+    /**
+     * Accept-Language转义为对应语言包名称 系统默认配置
+     * @var string
+     */
+    protected $acceptLanguage = [
+        'zh-hans-cn' => 'zh-cn',
+    ];
+
+    /**
+     * 应用对象
+     * @var App
+     */
+    protected $app;
+
+    public function __construct(App $app)
+    {
+        $this->app = $app;
+    }
+
+    // 设定当前的语言
+    public function range($range = '')
+    {
+        if ('' == $range) {
+            return $this->range;
+        } else {
+            $this->range = $range;
+        }
+    }
+
+    /**
+     * 设置语言定义(不区分大小写)
+     * @access public
+     * @param  string|array  $name 语言变量
+     * @param  string        $value 语言值
+     * @param  string        $range 语言作用域
+     * @return mixed
+     */
+    public function set($name, $value = null, $range = '')
+    {
+        $range = $range ?: $this->range;
+        // 批量定义
+        if (!isset($this->lang[$range])) {
+            $this->lang[$range] = [];
+        }
+
+        if (is_array($name)) {
+            return $this->lang[$range] = array_change_key_case($name) + $this->lang[$range];
+        }
+
+        return $this->lang[$range][strtolower($name)] = $value;
+    }
+
+    /**
+     * 加载语言定义(不区分大小写)
+     * @access public
+     * @param  string|array  $file   语言文件
+     * @param  string        $range  语言作用域
+     * @return array
+     */
+    public function load($file, $range = '')
+    {
+        $range = $range ?: $this->range;
+        if (!isset($this->lang[$range])) {
+            $this->lang[$range] = [];
+        }
+
+        // 批量定义
+        if (is_string($file)) {
+            $file = [$file];
+        }
+
+        $lang = [];
+
+        foreach ($file as $_file) {
+            if (is_file($_file)) {
+                // 记录加载信息
+                $this->app->log('[ LANG ] ' . $_file);
+                $_lang = include $_file;
+                if (is_array($_lang)) {
+                    $lang = array_change_key_case($_lang) + $lang;
+                }
+            }
+        }
+
+        if (!empty($lang)) {
+            $this->lang[$range] = $lang + $this->lang[$range];
+        }
+
+        return $this->lang[$range];
+    }
+
+    /**
+     * 获取语言定义(不区分大小写)
+     * @access public
+     * @param  string|null   $name 语言变量
+     * @param  string        $range 语言作用域
+     * @return bool
+     */
+    public function has($name, $range = '')
+    {
+        $range = $range ?: $this->range;
+
+        return isset($this->lang[$range][strtolower($name)]);
+    }
+
+    /**
+     * 获取语言定义(不区分大小写)
+     * @access public
+     * @param  string|null   $name 语言变量
+     * @param  array         $vars 变量替换
+     * @param  string        $range 语言作用域
+     * @return mixed
+     */
+    public function get($name = null, $vars = [], $range = '')
+    {
+        $range = $range ?: $this->range;
+
+        // 空参数返回所有定义
+        if (is_null($name)) {
+            return $this->lang[$range];
+        }
+
+        $key   = strtolower($name);
+        $value = isset($this->lang[$range][$key]) ? $this->lang[$range][$key] : $name;
+
+        // 变量解析
+        if (!empty($vars) && is_array($vars)) {
+            /**
+             * Notes:
+             * 为了检测的方便,数字索引的判断仅仅是参数数组的第一个元素的key为数字0
+             * 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数
+             */
+            if (key($vars) === 0) {
+                // 数字索引解析
+                array_unshift($vars, $value);
+                $value = call_user_func_array('sprintf', $vars);
+            } else {
+                // 关联索引解析
+                $replace = array_keys($vars);
+                foreach ($replace as &$v) {
+                    $v = "{:{$v}}";
+                }
+                $value = str_replace($replace, $vars, $value);
+            }
+        }
+
+        return $value;
+    }
+
+    /**
+     * 自动侦测设置获取语言选择
+     * @access public
+     * @return string
+     */
+    public function detect()
+    {
+        // 自动侦测设置获取语言选择
+        $langSet = '';
+
+        if (isset($_GET[$this->langDetectVar])) {
+            // url中设置了语言变量
+            $langSet = strtolower($_GET[$this->langDetectVar]);
+        } elseif (isset($_COOKIE[$this->langCookieVar])) {
+            // Cookie中设置了语言变量
+            $langSet = strtolower($_COOKIE[$this->langCookieVar]);
+        } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+            // 自动侦测浏览器语言
+            preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches);
+            $langSet = strtolower($matches[1]);
+            if (isset($this->acceptLanguage[$langSet])) {
+                $langSet = $this->acceptLanguage[$langSet];
+            }
+        }
+
+        if (empty($this->allowLangList) || in_array($langSet, $this->allowLangList)) {
+            // 合法的语言
+            $this->range = $langSet ?: $this->range;
+        }
+
+        return $this->range;
+    }
+
+    /**
+     * 设置当前语言到Cookie
+     * @access public
+     * @param  string $lang 语言
+     * @return void
+     */
+    public function saveToCookie($lang = null)
+    {
+        $range = $lang ?: $this->range;
+
+        $_COOKIE[$this->langCookieVar] = $range;
+    }
+
+    /**
+     * 设置语言自动侦测的变量
+     * @access public
+     * @param  string $var 变量名称
+     * @return void
+     */
+    public function setLangDetectVar($var)
+    {
+        $this->langDetectVar = $var;
+    }
+
+    /**
+     * 设置语言的cookie保存变量
+     * @access public
+     * @param  string $var 变量名称
+     * @return void
+     */
+    public function setLangCookieVar($var)
+    {
+        $this->langCookieVar = $var;
+    }
+
+    /**
+     * 设置允许的语言列表
+     * @access public
+     * @param  array $list 语言列表
+     * @return void
+     */
+    public function setAllowLangList(array $list)
+    {
+        $this->allowLangList = $list;
+    }
+
+    /**
+     * 设置转义的语言列表
+     * @access public
+     * @param  array $list 语言列表
+     * @return void
+     */
+    public function setAcceptLanguage(array $list)
+    {
+        $this->acceptLanguage = array_merge($this->acceptLanguage, $list);
+    }
+}

+ 417 - 0
thinkphp/library/think/Loader.php

@@ -0,0 +1,417 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\exception\ClassNotFoundException;
+
+class Loader
+{
+    /**
+     * 类名映射信息
+     * @var array
+     */
+    protected static $classMap = [];
+
+    /**
+     * 类库别名
+     * @var array
+     */
+    protected static $classAlias = [];
+
+    /**
+     * PSR-4
+     * @var array
+     */
+    private static $prefixLengthsPsr4 = [];
+    private static $prefixDirsPsr4    = [];
+    private static $fallbackDirsPsr4  = [];
+
+    /**
+     * PSR-0
+     * @var array
+     */
+    private static $prefixesPsr0     = [];
+    private static $fallbackDirsPsr0 = [];
+
+    /**
+     * 需要加载的文件
+     * @var array
+     */
+    private static $files = [];
+
+    /**
+     * Composer安装路径
+     * @var string
+     */
+    private static $composerPath;
+
+    // 获取应用根目录
+    public static function getRootPath()
+    {
+        if ('cli' == PHP_SAPI) {
+            $scriptName = realpath($_SERVER['argv'][0]);
+        } else {
+            $scriptName = $_SERVER['SCRIPT_FILENAME'];
+        }
+
+        $path = realpath(dirname($scriptName));
+
+        if (!is_file($path . DIRECTORY_SEPARATOR . 'think')) {
+            $path = dirname($path);
+        }
+
+        return $path . DIRECTORY_SEPARATOR;
+    }
+
+    // 注册自动加载机制
+    public static function register($autoload = '')
+    {
+        // 注册系统自动加载
+        spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
+
+        $rootPath = self::getRootPath();
+
+        self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR;
+
+        // Composer自动加载支持
+        if (is_dir(self::$composerPath)) {
+            if (is_file(self::$composerPath . 'autoload_static.php')) {
+                require self::$composerPath . 'autoload_static.php';
+
+                $declaredClass = get_declared_classes();
+                $composerClass = array_pop($declaredClass);
+
+                foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) {
+                    if (property_exists($composerClass, $attr)) {
+                        self::${$attr} = $composerClass::${$attr};
+                    }
+                }
+            } else {
+                self::registerComposerLoader(self::$composerPath);
+            }
+        }
+
+        // 注册命名空间定义
+        self::addNamespace([
+            'think'  => __DIR__,
+            'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits',
+        ]);
+
+        // 加载类库映射文件
+        if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) {
+            self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php'));
+        }
+
+        // 自动加载extend目录
+        self::addAutoLoadDir($rootPath . 'extend');
+    }
+
+    // 自动加载
+    public static function autoload($class)
+    {
+        if (isset(self::$classAlias[$class])) {
+            return class_alias(self::$classAlias[$class], $class);
+        }
+
+        if ($file = self::findFile($class)) {
+
+            // Win环境严格区分大小写
+            if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) {
+                return false;
+            }
+
+            __include_file($file);
+            return true;
+        }
+    }
+
+    /**
+     * 查找文件
+     * @access private
+     * @param  string $class
+     * @return string|false
+     */
+    private static function findFile($class)
+    {
+        if (!empty(self::$classMap[$class])) {
+            // 类库映射
+            return self::$classMap[$class];
+        }
+
+        // 查找 PSR-4
+        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php';
+
+        $first = $class[0];
+        if (isset(self::$prefixLengthsPsr4[$first])) {
+            foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) {
+                if (0 === strpos($class, $prefix)) {
+                    foreach (self::$prefixDirsPsr4[$prefix] as $dir) {
+                        if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // 查找 PSR-4 fallback dirs
+        foreach (self::$fallbackDirsPsr4 as $dir) {
+            if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+                return $file;
+            }
+        }
+
+        // 查找 PSR-0
+        if (false !== $pos = strrpos($class, '\\')) {
+            // namespaced class name
+            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+            . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+        } else {
+            // PEAR-like class name
+            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . '.php';
+        }
+
+        if (isset(self::$prefixesPsr0[$first])) {
+            foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) {
+                if (0 === strpos($class, $prefix)) {
+                    foreach ($dirs as $dir) {
+                        if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // 查找 PSR-0 fallback dirs
+        foreach (self::$fallbackDirsPsr0 as $dir) {
+            if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                return $file;
+            }
+        }
+
+        return self::$classMap[$class] = false;
+    }
+
+    // 注册classmap
+    public static function addClassMap($class, $map = '')
+    {
+        if (is_array($class)) {
+            self::$classMap = array_merge(self::$classMap, $class);
+        } else {
+            self::$classMap[$class] = $map;
+        }
+    }
+
+    // 注册命名空间
+    public static function addNamespace($namespace, $path = '')
+    {
+        if (is_array($namespace)) {
+            foreach ($namespace as $prefix => $paths) {
+                self::addPsr4($prefix . '\\', rtrim($paths, DIRECTORY_SEPARATOR), true);
+            }
+        } else {
+            self::addPsr4($namespace . '\\', rtrim($path, DIRECTORY_SEPARATOR), true);
+        }
+    }
+
+    // 添加Ps0空间
+    private static function addPsr0($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            if ($prepend) {
+                self::$fallbackDirsPsr0 = array_merge(
+                    (array) $paths,
+                    self::$fallbackDirsPsr0
+                );
+            } else {
+                self::$fallbackDirsPsr0 = array_merge(
+                    self::$fallbackDirsPsr0,
+                    (array) $paths
+                );
+            }
+
+            return;
+        }
+
+        $first = $prefix[0];
+        if (!isset(self::$prefixesPsr0[$first][$prefix])) {
+            self::$prefixesPsr0[$first][$prefix] = (array) $paths;
+
+            return;
+        }
+
+        if ($prepend) {
+            self::$prefixesPsr0[$first][$prefix] = array_merge(
+                (array) $paths,
+                self::$prefixesPsr0[$first][$prefix]
+            );
+        } else {
+            self::$prefixesPsr0[$first][$prefix] = array_merge(
+                self::$prefixesPsr0[$first][$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    // 添加Psr4空间
+    private static function addPsr4($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            // Register directories for the root namespace.
+            if ($prepend) {
+                self::$fallbackDirsPsr4 = array_merge(
+                    (array) $paths,
+                    self::$fallbackDirsPsr4
+                );
+            } else {
+                self::$fallbackDirsPsr4 = array_merge(
+                    self::$fallbackDirsPsr4,
+                    (array) $paths
+                );
+            }
+        } elseif (!isset(self::$prefixDirsPsr4[$prefix])) {
+            // Register directories for a new namespace.
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+
+            self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            self::$prefixDirsPsr4[$prefix]                = (array) $paths;
+        } elseif ($prepend) {
+            // Prepend directories for an already registered namespace.
+            self::$prefixDirsPsr4[$prefix] = array_merge(
+                (array) $paths,
+                self::$prefixDirsPsr4[$prefix]
+            );
+        } else {
+            // Append directories for an already registered namespace.
+            self::$prefixDirsPsr4[$prefix] = array_merge(
+                self::$prefixDirsPsr4[$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    // 注册自动加载类库目录
+    public static function addAutoLoadDir($path)
+    {
+        self::$fallbackDirsPsr4[] = $path;
+    }
+
+    // 注册类别名
+    public static function addClassAlias($alias, $class = null)
+    {
+        if (is_array($alias)) {
+            self::$classAlias = array_merge(self::$classAlias, $alias);
+        } else {
+            self::$classAlias[$alias] = $class;
+        }
+    }
+
+    // 注册composer自动加载
+    public static function registerComposerLoader($composerPath)
+    {
+        if (is_file($composerPath . 'autoload_namespaces.php')) {
+            $map = require $composerPath . 'autoload_namespaces.php';
+            foreach ($map as $namespace => $path) {
+                self::addPsr0($namespace, $path);
+            }
+        }
+
+        if (is_file($composerPath . 'autoload_psr4.php')) {
+            $map = require $composerPath . 'autoload_psr4.php';
+            foreach ($map as $namespace => $path) {
+                self::addPsr4($namespace, $path);
+            }
+        }
+
+        if (is_file($composerPath . 'autoload_classmap.php')) {
+            $classMap = require $composerPath . 'autoload_classmap.php';
+            if ($classMap) {
+                self::addClassMap($classMap);
+            }
+        }
+
+        if (is_file($composerPath . 'autoload_files.php')) {
+            self::$files = require $composerPath . 'autoload_files.php';
+        }
+    }
+
+    // 加载composer autofile文件
+    public static function loadComposerAutoloadFiles()
+    {
+        foreach (self::$files as $fileIdentifier => $file) {
+            if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+                __require_file($file);
+
+                $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+            }
+        }
+    }
+
+    /**
+     * 字符串命名风格转换
+     * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
+     * @access public
+     * @param  string  $name 字符串
+     * @param  integer $type 转换类型
+     * @param  bool    $ucfirst 首字母是否大写(驼峰规则)
+     * @return string
+     */
+    public static function parseName($name, $type = 0, $ucfirst = true)
+    {
+        if ($type) {
+            $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
+                return strtoupper($match[1]);
+            }, $name);
+            return $ucfirst ? ucfirst($name) : lcfirst($name);
+        }
+
+        return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
+    }
+
+    /**
+     * 创建工厂对象实例
+     * @access public
+     * @param  string $name         工厂类名
+     * @param  string $namespace    默认命名空间
+     * @return mixed
+     */
+    public static function factory($name, $namespace = '', ...$args)
+    {
+        $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name);
+
+        if (class_exists($class)) {
+            return Container::getInstance()->invokeClass($class, $args);
+        } else {
+            throw new ClassNotFoundException('class not exists:' . $class, $class);
+        }
+    }
+}
+
+/**
+ * 作用范围隔离
+ *
+ * @param $file
+ * @return mixed
+ */
+function __include_file($file)
+{
+    return include $file;
+}
+
+function __require_file($file)
+{
+    return require $file;
+}

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů