chendeben 8 lat temu
commit
919131d1fd
67 zmienionych plików z 9623 dodań i 0 usunięć
  1. 8 0
      .idea/modules.xml
  2. 4 0
      .idea/php.xml
  3. 8 0
      .idea/sphinx.iml
  4. 678 0
      .idea/workspace.xml
  5. 80 0
      App/Api/Controller.php
  6. 43 0
      App/Api/Controller/Search.php
  7. 21 0
      App/Api/Package/Database/File.php
  8. 20 0
      App/Api/Package/Database/Hash.php
  9. 29 0
      Autoload.php
  10. 15 0
      Config/MySQL/Core.php
  11. 36 0
      Config/Sphinx/Core.php
  12. 100 0
      Frame/Config.php
  13. 159 0
      Frame/Controller.php
  14. 132 0
      Frame/Dispatcher.php
  15. 20 0
      Frame/Exception.php
  16. 13 0
      Frame/Exception/Database.php
  17. 12 0
      Frame/Exception/Validate.php
  18. 251 0
      Frame/Filter.php
  19. 748 0
      Frame/Helper.php
  20. 67 0
      Frame/Helper/Date.php
  21. 39 0
      Frame/Helper/File.php
  22. 92 0
      Frame/Helper/Format.php
  23. 269 0
      Frame/Helper/Html.php
  24. 31 0
      Frame/Helper/Location.php
  25. 11 0
      Frame/Helper/Network.php
  26. 54 0
      Frame/Helper/Validate.php
  27. 92 0
      Frame/Init.php
  28. 58 0
      Frame/Library.php
  29. 83 0
      Frame/Library/AntiSpam.php
  30. 161 0
      Frame/Library/BaseConverter.php
  31. 8 0
      Frame/Library/Benchmark.php
  32. 19 0
      Frame/Library/Curl.php
  33. 44 0
      Frame/Library/Memcache.php
  34. 41 0
      Frame/Library/Memcached.php
  35. 252 0
      Frame/Library/MySQL.php
  36. 402 0
      Frame/Library/MySQL/Query.php
  37. 63 0
      Frame/Library/Redis.php
  38. 79 0
      Frame/Library/Ticket.php
  39. 414 0
      Frame/Logger.php
  40. 85 0
      Frame/Model.php
  41. 59 0
      Frame/Object.php
  42. 56 0
      Frame/Package.php
  43. 11 0
      Frame/Package/Cache.php
  44. 152 0
      Frame/Package/Cache/Memcache.php
  45. 179 0
      Frame/Package/Cache/Redis.php
  46. 11 0
      Frame/Package/Counter.php
  47. 148 0
      Frame/Package/Counter/Hash.php
  48. 141 0
      Frame/Package/Counter/Single.php
  49. 13 0
      Frame/Package/Database.php
  50. 275 0
      Frame/Package/Database/Common.php
  51. 342 0
      Frame/Package/Database/Sharding.php
  52. 421 0
      Frame/Package/Database/Single.php
  53. 62 0
      Frame/Package/Queue.php
  54. 82 0
      Frame/Package/RedisQueue.php
  55. 291 0
      Frame/Package/Set.php
  56. 203 0
      Frame/Request.php
  57. 39 0
      Frame/Router.php
  58. 148 0
      Frame/Session.php
  59. 234 0
      Frame/Template.php
  60. 47 0
      Library/Sphinx.php
  61. 1840 0
      Library/sphinxapi.php
  62. 22 0
      constants.php
  63. 22 0
      env/product/mysql/core.json
  64. 4 0
      env/product/sphinx/core.json
  65. 58 0
      functions.php
  66. 7 0
      public/api/index.php
  67. 15 0
      search.php

+ 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/sphinx.iml" filepath="$PROJECT_DIR$/.idea/sphinx.iml" />
+    </modules>
+  </component>
+</project>

+ 4 - 0
.idea/php.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="PhpProjectSharedConfiguration" php_language_level="7" />
+</project>

+ 8 - 0
.idea/sphinx.iml

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

+ 678 - 0
.idea/workspace.xml

@@ -0,0 +1,678 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ChangeListManager">
+    <list default="true" id="d417c7e2-9370-4a4d-828f-09be06adcd69" 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="FileColors">
+    <fileColor scope="Non-Project Files (Material Default)" color="2E3C43" />
+    <fileColor scope="Non-Project Files (Material Darker)" color="323232" />
+    <fileColor scope="Non-Project Files (Material Lighter)" color="eae8e8" />
+    <fileColor scope="Non-Project Files (Material Palenight)" color="2f2e43" />
+  </component>
+  <component name="FileEditorManager">
+    <leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
+      <file leaf-file-name="Search.php" pinned="false" current-in-tab="true">
+        <entry file="file://$PROJECT_DIR$/App/Api/Controller/Search.php">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="450">
+              <caret line="27" column="29" lean-forward="true" selection-start-line="27" selection-start-column="29" selection-end-line="27" selection-end-column="29" />
+              <folding>
+                <element signature="e#130#172#0#PHP" expanded="true" />
+                <marker date="1512544195039" expanded="true" signature="1251:1300" ph="..." />
+              </folding>
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="sphinxapi.php" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/Library/sphinxapi.php">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="22310">
+              <caret line="970" column="33" lean-forward="false" selection-start-line="970" selection-start-column="33" selection-end-line="970" selection-end-column="33" />
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="Single.php" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/Frame/Package/Database/Single.php">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="1012">
+              <caret line="44" column="27" lean-forward="false" selection-start-line="44" selection-start-column="27" selection-end-line="44" selection-end-column="27" />
+              <folding>
+                <marker date="1512532015731" expanded="true" signature="9213:9265" ph="INSERT INTO `${$tableName}`... " />
+              </folding>
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="Init.php" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/Frame/Init.php">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="1587">
+              <caret line="69" column="0" lean-forward="false" selection-start-line="69" selection-start-column="0" selection-end-line="69" selection-end-column="0" />
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="Autoload.php" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/Autoload.php">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="621">
+              <caret line="27" column="0" lean-forward="false" selection-start-line="27" selection-start-column="0" selection-end-line="27" selection-end-column="0" />
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="Controller.php" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/Frame/Controller.php">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="897">
+              <caret line="39" column="0" lean-forward="false" selection-start-line="39" selection-start-column="0" selection-end-line="39" selection-end-column="0" />
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="Hash.php" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/App/Api/Package/Database/Hash.php">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="46">
+              <caret line="2" column="25" lean-forward="false" selection-start-line="2" selection-start-column="25" selection-end-line="2" selection-end-column="25" />
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="Core.php" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/Config/MySQL/Core.php">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="92">
+              <caret line="4" column="25" lean-forward="false" selection-start-line="4" selection-start-column="25" selection-end-line="4" selection-end-column="25" />
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="File.php" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/App/Api/Package/Database/File.php">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="46">
+              <caret line="2" column="25" lean-forward="false" selection-start-line="2" selection-start-column="25" selection-end-line="2" selection-end-column="25" />
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="search.php" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/search.php">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="322">
+              <caret line="14" column="0" lean-forward="false" selection-start-line="14" selection-start-column="0" selection-end-line="14" selection-end-column="33" />
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+    </leaf>
+  </component>
+  <component name="FindInProjectRecents">
+    <findStrings>
+      <find>MeiTu</find>
+      <find>Phplife</find>
+    </findStrings>
+    <replaceStrings>
+      <replace>PhpLife</replace>
+    </replaceStrings>
+  </component>
+  <component name="IdeDocumentHistory">
+    <option name="CHANGED_PATHS">
+      <list>
+        <option value="$PROJECT_DIR$/search.php" />
+        <option value="$PROJECT_DIR$/Frame/Controller.php" />
+        <option value="$PROJECT_DIR$/Frame/Config.php" />
+        <option value="$PROJECT_DIR$/App/Api/Controller.php" />
+        <option value="$PROJECT_DIR$/Frame/Init.php" />
+        <option value="$PROJECT_DIR$/../manage.makeup.meitu.com/public/api/index.php" />
+        <option value="$PROJECT_DIR$/public/api/index.php" />
+        <option value="$PROJECT_DIR$/Autoload.php" />
+        <option value="$PROJECT_DIR$/Frame/Dispatcher.php" />
+        <option value="$PROJECT_DIR$/Config/Sphinx/Core.php" />
+        <option value="$PROJECT_DIR$/env/product/sphinx/core.json" />
+        <option value="$PROJECT_DIR$/Library/Sphinx.php" />
+        <option value="$PROJECT_DIR$/env/product/mysql/core.json" />
+        <option value="$PROJECT_DIR$/Config/MySQL/Core.php" />
+        <option value="$PROJECT_DIR$/App/Api/Package/Database/File.php" />
+        <option value="$PROJECT_DIR$/App/Api/Package/Database/Hash.php" />
+        <option value="$PROJECT_DIR$/App/Api/Controller/Search.php" />
+      </list>
+    </option>
+  </component>
+  <component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
+  <component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
+  <component name="JsGulpfileManager">
+    <detection-done>true</detection-done>
+    <sorting>DEFINITION_ORDER</sorting>
+  </component>
+  <component name="PhpServers">
+    <servers>
+      <server host="makeup-api.x.97admin.com" id="62747657-d000-4d90-bcca-47dce85ec9d4" name="makeup-api.x.97admin.com" use_path_mappings="true">
+        <path_mappings>
+          <mapping local-root="$PROJECT_DIR$" remote-root="/www/sphinx" />
+          <mapping local-root="$PROJECT_DIR$/.idea" remote-root="/www/sphinx/.idea" />
+          <mapping local-root="$PROJECT_DIR$/App" remote-root="/www/sphinx/App" />
+          <mapping local-root="$PROJECT_DIR$/Config" remote-root="/www/sphinx/Config" />
+          <mapping local-root="$PROJECT_DIR$/Frame" remote-root="/www/sphinx/Frame" />
+          <mapping local-root="$PROJECT_DIR$/Library" remote-root="/www/sphinx/Library" />
+          <mapping local-root="$PROJECT_DIR$/env" remote-root="/www/sphinx/env" />
+          <mapping local-root="$PROJECT_DIR$/public" remote-root="/www/sphinx/public" />
+          <mapping local-root="$PROJECT_DIR$/public/api" remote-root="/www/sphinx/public/api" />
+        </path_mappings>
+      </server>
+      <server host="sphinx.x.97admin.com" id="e19e8944-7c7f-4770-a17f-35b900678144" name="sphinx" use_path_mappings="true">
+        <path_mappings>
+          <mapping local-root="$PROJECT_DIR$" remote-root="/www/sphinx" />
+        </path_mappings>
+      </server>
+    </servers>
+  </component>
+  <component name="PhpWorkspaceProjectConfiguration" backward_compatibility_performed="true" />
+  <component name="ProjectFrameBounds" fullScreen="true">
+    <option name="x" value="1280" />
+    <option name="y" value="-257" />
+    <option name="width" value="1280" />
+    <option name="height" value="729" />
+  </component>
+  <component name="ProjectInspectionProfilesVisibleTreeState">
+    <entry key="Project Default">
+      <profile-state>
+        <expanded-state>
+          <State>
+            <id />
+          </State>
+          <State>
+            <id>Bitwise operation issuesJavaScript</id>
+          </State>
+          <State>
+            <id>DOM issuesJavaScript</id>
+          </State>
+          <State>
+            <id>JavaScript</id>
+          </State>
+        </expanded-state>
+      </profile-state>
+    </entry>
+  </component>
+  <component name="ProjectView">
+    <navigator currentView="ProjectPane" proportions="" version="1">
+      <flattenPackages />
+      <showMembers />
+      <showModules />
+      <showLibraryContents />
+      <hideEmptyPackages />
+      <abbreviatePackageNames />
+      <autoscrollToSource />
+      <autoscrollFromSource />
+      <sortByType />
+      <manualOrder />
+      <foldersAlwaysOnTop value="true" />
+    </navigator>
+    <panes>
+      <pane id="Scratches" />
+      <pane id="ProjectPane">
+        <subPane>
+          <expand>
+            <path>
+              <item name="sphinx" type="b2602c69:ProjectViewProjectNode" />
+              <item name="sphinx" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+            <path>
+              <item name="sphinx" type="b2602c69:ProjectViewProjectNode" />
+              <item name="sphinx" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="App" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+            <path>
+              <item name="sphinx" type="b2602c69:ProjectViewProjectNode" />
+              <item name="sphinx" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="App" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="Api" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+            <path>
+              <item name="sphinx" type="b2602c69:ProjectViewProjectNode" />
+              <item name="sphinx" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="App" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="Api" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="Controller" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+            <path>
+              <item name="sphinx" type="b2602c69:ProjectViewProjectNode" />
+              <item name="sphinx" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="App" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="Api" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="Package" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+            <path>
+              <item name="sphinx" type="b2602c69:ProjectViewProjectNode" />
+              <item name="sphinx" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="App" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="Api" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="Package" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="Database" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+            <path>
+              <item name="sphinx" type="b2602c69:ProjectViewProjectNode" />
+              <item name="sphinx" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="Config" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+            <path>
+              <item name="sphinx" type="b2602c69:ProjectViewProjectNode" />
+              <item name="sphinx" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="Config" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="MySQL" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+            <path>
+              <item name="sphinx" type="b2602c69:ProjectViewProjectNode" />
+              <item name="sphinx" type="2a2b976b:PhpTreeStructureProvider$1" />
+              <item name="Library" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+          </expand>
+          <select />
+        </subPane>
+      </pane>
+      <pane id="Scope" />
+    </panes>
+  </component>
+  <component name="PropertiesComponent">
+    <property name="settings.editor.selected.configurable" value="settings.php.debug.servers" />
+    <property name="WebServerToolWindowFactoryState" value="false" />
+  </component>
+  <component name="RecentsManager">
+    <key name="CopyFile.RECENT_KEYS">
+      <recent name="$PROJECT_DIR$/Config" />
+      <recent name="$PROJECT_DIR$/App/Api/Package/Database" />
+      <recent name="$PROJECT_DIR$/App/Api/Package" />
+      <recent name="$PROJECT_DIR$/env/product" />
+      <recent name="$PROJECT_DIR$" />
+    </key>
+    <key name="MoveFile.RECENT_KEYS">
+      <recent name="$PROJECT_DIR$/Library" />
+      <recent name="$PROJECT_DIR$/App" />
+      <recent name="$PROJECT_DIR$" />
+    </key>
+  </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="ShelveChangesManager" show_recycled="false">
+    <option name="remove_strategy" value="false" />
+  </component>
+  <component name="SvnConfiguration">
+    <configuration>$USER_HOME$/.subversion</configuration>
+  </component>
+  <component name="TaskManager">
+    <task active="true" id="Default" summary="Default task">
+      <changelist id="d417c7e2-9370-4a4d-828f-09be06adcd69" name="Default" comment="" />
+      <created>1512530312019</created>
+      <option name="number" value="Default" />
+      <option name="presentableId" value="Default" />
+      <updated>1512530312019</updated>
+      <workItem from="1512530314537" duration="4944000" />
+      <workItem from="1512540437756" duration="3594000" />
+      <workItem from="1512544775809" duration="826000" />
+    </task>
+    <servers />
+  </component>
+  <component name="TimeTrackingManager">
+    <option name="totallyTimeSpent" value="9364000" />
+  </component>
+  <component name="ToolWindowManager">
+    <frame x="1280" y="-280" width="1920" height="1080" extended-state="0" />
+    <editor active="true" />
+    <layout>
+      <window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.1687966" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
+      <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
+      <window_info id="Docker" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
+      <window_info id="LuaJ" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
+      <window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="true" content_ui="tabs" />
+      <window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
+      <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
+      <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
+      <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+      <window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
+      <window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
+      <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.39939025" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
+      <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
+      <window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
+      <window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
+      <window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
+      <window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
+      <window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.3292683" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+      <window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+    </layout>
+  </component>
+  <component name="TypeScriptGeneratedFilesManager">
+    <option name="version" value="1" />
+  </component>
+  <component name="VcsContentAnnotationSettings">
+    <option name="myLimit" value="2678400000" />
+  </component>
+  <component name="XDebuggerManager">
+    <breakpoint-manager>
+      <breakpoints>
+        <line-breakpoint enabled="true" type="php">
+          <url>file://$PROJECT_DIR$/../manage.makeup.meitu.com/public/api/index.php</url>
+          <line>4</line>
+          <option name="timeStamp" value="1" />
+        </line-breakpoint>
+      </breakpoints>
+      <option name="time" value="9" />
+    </breakpoint-manager>
+    <watches-manager />
+  </component>
+  <component name="editorHistoryManager">
+    <entry file="file://$PROJECT_DIR$/public/api/index.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="92">
+          <caret line="4" column="39" lean-forward="false" selection-start-line="4" selection-start-column="39" selection-end-line="4" selection-end-column="39" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Frame/Dispatcher.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="1449">
+          <caret line="63" column="0" lean-forward="false" selection-start-line="63" selection-start-column="0" selection-end-line="63" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Frame/Init.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="2047">
+          <caret line="89" column="0" lean-forward="false" selection-start-line="89" selection-start-column="0" selection-end-line="89" selection-end-column="0" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Frame/Logger.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="2530">
+          <caret line="110" column="0" lean-forward="false" selection-start-line="110" selection-start-column="0" selection-end-line="110" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Autoload.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="621">
+          <caret line="27" column="0" lean-forward="false" selection-start-line="27" selection-start-column="0" selection-end-line="27" selection-end-column="0" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Frame/Controller.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="897">
+          <caret line="39" column="0" lean-forward="false" selection-start-line="39" selection-start-column="0" selection-end-line="39" selection-end-column="0" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/App/Api/Controller/Search.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="230">
+          <caret line="16" column="35" lean-forward="true" selection-start-line="16" selection-start-column="35" selection-end-line="16" selection-end-column="35" />
+          <folding>
+            <element signature="e#130#172#0#PHP" expanded="true" />
+            <marker date="1512544195039" expanded="true" signature="1251:1300" ph="..." />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Config/Sphinx/Core.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="506">
+          <caret line="22" column="0" lean-forward="false" selection-start-line="22" selection-start-column="0" selection-end-line="22" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/env/product/sphinx/core.json">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="46">
+          <caret line="2" column="16" lean-forward="true" selection-start-line="2" selection-start-column="16" selection-end-line="2" selection-end-column="16" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Library/Sphinx.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="1081">
+          <caret line="52" column="0" lean-forward="false" selection-start-line="52" selection-start-column="0" selection-end-line="52" selection-end-column="0" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Frame/Config.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="566">
+          <caret line="91" column="21" lean-forward="true" selection-start-line="91" selection-start-column="21" selection-end-line="91" selection-end-column="21" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/../manage.makeup.meitu.com/functions.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="311">
+          <caret line="54" column="0" lean-forward="false" selection-start-line="54" selection-start-column="0" selection-end-line="54" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/../manage.makeup.meitu.com/Autoload.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="334">
+          <caret line="27" column="0" lean-forward="false" selection-start-line="27" selection-start-column="0" selection-end-line="27" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/../manage.makeup.meitu.com/public/api/index.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="92">
+          <caret line="4" column="2" lean-forward="false" selection-start-line="4" selection-start-column="2" selection-end-line="4" selection-end-column="2" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/../manage.makeup.meitu.com/constants.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="253">
+          <caret line="11" column="0" lean-forward="false" selection-start-line="11" selection-start-column="0" selection-end-line="11" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/../manage.makeup.meitu.com/Frame/Init.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="253">
+          <caret line="11" column="0" lean-forward="false" selection-start-line="11" selection-start-column="0" selection-end-line="11" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Frame/Filter.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="414">
+          <caret line="18" column="0" lean-forward="false" selection-start-line="18" selection-start-column="0" selection-end-line="18" selection-end-column="0" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/App/Api/Controller.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="172">
+          <caret line="28" column="0" lean-forward="false" selection-start-line="28" selection-start-column="0" selection-end-line="28" selection-end-column="0" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/public/api/index.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="92">
+          <caret line="4" column="39" lean-forward="false" selection-start-line="4" selection-start-column="39" selection-end-line="4" selection-end-column="39" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Frame/Dispatcher.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="1449">
+          <caret line="63" column="0" lean-forward="false" selection-start-line="63" selection-start-column="0" selection-end-line="63" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Config/Sphinx/Core.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="506">
+          <caret line="22" column="0" lean-forward="false" selection-start-line="22" selection-start-column="0" selection-end-line="22" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Frame/Logger.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="2530">
+          <caret line="110" column="0" lean-forward="false" selection-start-line="110" selection-start-column="0" selection-end-line="110" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/env/product/sphinx/core.json">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="46">
+          <caret line="2" column="13" lean-forward="false" selection-start-line="2" selection-start-column="13" selection-end-line="2" selection-end-column="13" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Library/Sphinx.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="311">
+          <caret line="43" column="32" lean-forward="false" selection-start-line="43" selection-start-column="32" selection-end-line="43" selection-end-column="32" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/env/product/mysql/core.json">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="414">
+          <caret line="18" column="18" lean-forward="false" selection-start-line="18" selection-start-column="18" selection-end-line="18" selection-end-column="18" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/App/Api/Controller/Search.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="450">
+          <caret line="27" column="29" lean-forward="true" selection-start-line="27" selection-start-column="29" selection-end-line="27" selection-end-column="29" />
+          <folding>
+            <element signature="e#130#172#0#PHP" expanded="true" />
+            <marker date="1512544195039" expanded="true" signature="1251:1300" ph="..." />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Library/sphinxapi.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="22310">
+          <caret line="970" column="33" lean-forward="false" selection-start-line="970" selection-start-column="33" selection-end-line="970" selection-end-column="33" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Frame/Package/Database/Single.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="1012">
+          <caret line="44" column="27" lean-forward="false" selection-start-line="44" selection-start-column="27" selection-end-line="44" selection-end-column="27" />
+          <folding>
+            <marker date="1512532015731" expanded="true" signature="9213:9265" ph="INSERT INTO `${$tableName}`... " />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Frame/Init.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="1587">
+          <caret line="69" column="0" lean-forward="false" selection-start-line="69" selection-start-column="0" selection-end-line="69" selection-end-column="0" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Autoload.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="621">
+          <caret line="27" column="0" lean-forward="false" selection-start-line="27" selection-start-column="0" selection-end-line="27" selection-end-column="0" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Frame/Controller.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="897">
+          <caret line="39" column="0" lean-forward="false" selection-start-line="39" selection-start-column="0" selection-end-line="39" selection-end-column="0" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/App/Api/Package/Database/Hash.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="46">
+          <caret line="2" column="25" lean-forward="false" selection-start-line="2" selection-start-column="25" selection-end-line="2" selection-end-column="25" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/Config/MySQL/Core.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="92">
+          <caret line="4" column="25" lean-forward="false" selection-start-line="4" selection-start-column="25" selection-end-line="4" selection-end-column="25" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/App/Api/Package/Database/File.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="46">
+          <caret line="2" column="25" lean-forward="false" selection-start-line="2" selection-start-column="25" selection-end-line="2" selection-end-column="25" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/search.php">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="322">
+          <caret line="14" column="0" lean-forward="false" selection-start-line="14" selection-start-column="0" selection-end-line="14" selection-end-column="33" />
+          <folding />
+        </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>

+ 80 - 0
App/Api/Controller.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace PhpLife\App\Api;
+
+use PhpLife\App\Admin\Config\CountryList;
+use PhpLife\Config\McAlive;
+use PhpLife\Frame\Logger;
+use PhpLife\Frame\Request;
+use PhpLife\Library\Area;
+use PhpLife\Library\Des;
+use PhpLife\Library\Memcache;
+
+abstract class Controller extends \PhpLife\Frame\Controller
+{
+    protected $cookie;
+    public $cache_key;
+    protected $need_cache = false;
+
+    /**
+     * 验证登录信息
+     * 无需验证或者单独验证的类可以覆盖该方法
+     *
+     * @param bool $checkLogin
+     * @return bool
+     * @throws \Exception
+     */
+    protected function checkAuth($checkLogin = false)
+    {
+        return true;
+    }
+
+
+    /**
+     * 输出模板,自动查找模板路径
+     *
+     * @param
+     *            $layout
+     */
+    protected function render($layout = 'Common/Layout')
+    {
+        header("Content-type: text/html; charset=utf-8");
+        $this->getTemplate()->render(false, $layout);
+    }
+
+
+
+    /**
+     * 输出json信息
+     *
+     * @param int $code
+     * @param string $msg
+     * @param array $data
+     * @param string $callback
+     * @param bool $need_cache 是否需要进行全局缓存
+     */
+    protected function output($code = 0, $msg = '', $data = array(), $callback = "")
+    {
+        $output = array(
+            'code' => $code,
+            'msg' => $msg,
+            'data' => $data ?: array()
+        );
+        header('Content-type: application/json');
+        if ($callback) {
+            echo $response = $callback . "(" . json_encode($output) . ")";
+            if ($this->need_cache) {
+                Memcache::instance()->set($this->cache_key, $response, ALL_CACHE_TIME);
+            }
+            exit();
+        } else {
+            echo $response = json_encode($output);
+            if ($this->need_cache) {
+                Memcache::instance()->set($this->cache_key, $response, ALL_CACHE_TIME);
+            }
+            exit();
+        }
+        // 记录调试信息
+        die();
+    }
+}

+ 43 - 0
App/Api/Controller/Search.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: chendeben
+ * Date: 2017/12/6
+ * Time: 11:50
+ */
+namespace PhpLife\App\Api\Controller;
+
+use PhpLife\App\Api\Package\Database\File;
+use PhpLife\App\Api\Package\Database\Hash;
+use PhpLife\App\Api\Controller;
+use PhpLife\Library\Sphinx;
+
+class Search extends Controller
+{
+    public function  main(){
+        $keyword=$this->getRequest()->string("key");
+        $p=$this->getRequest()->intval("page",1);
+        $count=10;
+        $res=Sphinx::instance()->query($keyword, ($p-1)*$count, $count);
+        $ids=array_keys($res['matches']);
+        $result=Hash::select(array(),array('id'=>$ids));
+        foreach ($result as &$file) {
+            $temp=File::getOne(array('info_hash'=>$file['info_hash']));
+            if ($temp){
+                $temp=json_decode($temp['file_list'],true);
+                $new=array();
+                foreach ($temp as $key=>$value){
+                    if (strpos($value['path'], "_____")===false){
+                        $new[]=$value;
+                    }
+                }
+                $file['file_list']=$new;
+            }else{
+                $file['file_list']=array(array('path'=>$file['name'],'lengh'=>$file['length']));
+            }
+        }
+        echo "<pre>";
+        print_r($result);
+        echo "</pre>";
+    }
+}

+ 21 - 0
App/Api/Package/Database/File.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace PhpLife\App\Api\Package\Database;
+
+use PhpLife\Frame\Package\Database\Single;
+
+/**
+ * 磁力文件信息
+ */
+class File extends Single
+{
+
+    const CONFIG_CLASS = 'MySQL\\Core';
+
+    protected static $tableName = 'search_filelist';
+
+    protected static $fields = array();
+
+    protected static $primaryKey = 'id';
+
+}

+ 20 - 0
App/Api/Package/Database/Hash.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace PhpLife\App\Api\Package\Database;
+
+use PhpLife\Frame\Package\Database\Single;
+
+/**
+ * 磁力信息
+ */
+class Hash extends Single
+{
+
+    const CONFIG_CLASS = 'MySQL\\Core';
+
+    protected static $tableName = 'search_hash';
+
+    protected static $fields = array();
+
+    protected static $primaryKey = 'id';
+}

+ 29 - 0
Autoload.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace PhpLife;
+
+// 设置自动加载
+spl_autoload_register('\PhpLife\Autoload::main');
+
+class Autoload
+{
+    /**
+     * 自动加载
+     * @param $className
+     */
+    public static function main($className)
+    {
+        $file = trim($className, '\\');
+        $file = explode('\\', $file);
+        // 判断顶级命名空间
+        $path = __DIR__;
+        $topNameSpace = array_shift($file);
+        if ($topNameSpace != 'PhpLife') {
+            $path = $path . DIRECTORY_SEPARATOR . 'Vendor' . DIRECTORY_SEPARATOR . $topNameSpace;
+        }
+        $file = $path . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $file) . '.php';
+        if (is_file($file)) {
+            require_once($file);
+        }
+    }
+}

+ 15 - 0
Config/MySQL/Core.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace PhpLife\Config\MySQL;
+
+use PhpLife\Frame\Config;
+
+/**
+ * 默认数据库配置,不同的库使用不同的配置文件
+ */
+class Core extends Config
+{
+    protected static $dataFile = 'mysql/core.json';
+    protected static $data = array();
+
+}

+ 36 - 0
Config/Sphinx/Core.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace PhpLife\Config\Sphinx;
+use PhpLife\Frame\Config;
+
+/**
+ * Sphinx配置
+ */
+class Core extends Config
+{
+    protected static $dataFile = 'sphinx/core.json';
+    protected static $data = array();
+
+    /**
+     * 获取配置信息,
+     * 如果配置了$dataFile则读取后者中的数据
+     * @param null $section
+     * @param bool $is_pre
+     * @return array|null|string
+     */
+    public static function getData($section = null, $is_pre = FALSE)
+    {
+        $class = get_called_class();
+        $path = self::getPathByMode($class::$dataFile, $is_pre);
+        $data = self::readFile($path);
+        if (! is_null($section)) {
+            if (isset($data[$section])) {
+                return $data[$section];
+            } else {
+                return null;
+            }
+        }
+        return $data;
+    }
+
+}

+ 100 - 0
Frame/Config.php

@@ -0,0 +1,100 @@
+<?php
+
+namespace PhpLife\Frame;
+
+/**
+ * 配置文件
+ */
+class Config
+{
+    protected static $dataFile = ''; // 配置文件目录,绝对路径,json格式,优先使用覆盖$data
+    protected static $data = array();
+
+    /**
+     * 获取配置信息,
+     * 如果配置了$dataFile则读取后者中的数据
+     * @param null $section
+     * @return mixed
+     */
+    public static function getData($section = null,$is_pre=FALSE)
+    {
+        $class = get_called_class();
+        // 只读取一次文件
+        if ($class::$dataFile) {
+            $path = self::getPathByMode($class::$dataFile,$is_pre);
+            $class::$data = self::readFile($path);
+            $class::$dataFile = '';
+        }
+        $data = $class::$data;
+        if (!is_null($section)) {
+            if (isset($data[$section])) {
+                return $data[$section];
+            } else {
+                return null;
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * 根据后缀读取文件内容并且返回
+     * ini 直接解析
+     * json 读取后解析,如果解析失败返回空
+     * php 文件, include
+     * 其他文件 直接读取内容并且返回
+     *
+     * @param $path
+     * @return array | string;
+     */
+    public static function readFile($path)
+    {
+        $data = array();
+        if (!file_exists($path)) {
+            return $data;
+        }
+        $pathInfo = pathinfo($path);
+        $extension = strtolower($pathInfo['extension']);
+        if ($extension == 'ini') {
+            $data = parse_ini_file($path);
+        } elseif ($extension == 'json') {
+            $content = file_get_contents($path);
+            if (!$content) {
+                return $data;
+            }
+            $content = json_decode($content, true);
+            if (!$content) {
+                return $data;
+            }
+            $data = $content;
+            unset($content);
+        } elseif ($extension == 'php') {
+            $data = include($path);
+        } else {
+            $data = file_get_contents($path);
+        }
+        return $data;
+    }
+
+    /**
+     * 根据环境变量$mode获取文件路径
+     * @param $filename
+     * @return string
+     */
+    protected static function getPathByMode($filename,$is_pre=FALSE)
+    {
+        $envPath = dirname(__DIR__) . '/env';
+        $mode = Request::getMode();
+        switch ($mode) {
+            case Request::MODE_ONLINE:
+                $path = $envPath.'/product/';
+                break;
+        }
+        if ($is_pre){
+            $path.='pre/';
+        }
+        $filename = $path . ltrim($filename, '/');
+        return $filename;
+    }
+
+
+}

+ 159 - 0
Frame/Controller.php

@@ -0,0 +1,159 @@
+<?php
+
+namespace PhpLife\Frame;
+
+abstract class Controller
+{
+    protected $tpl; // 模板路径, 可选
+    protected $queryObj;
+
+    final public function __construct()
+    {
+        $this->queryObj = $this->getQuery();
+    }
+
+    /**
+     * 主逻辑
+     */
+    final public function run()
+    {
+        try {
+            $this->before();
+            $this->checkAuth();
+            $this->main();
+        } catch (\Exception $ex) {
+            $this->showError($ex->getCode(), $ex->getMessage());
+        }
+    }
+
+    /**
+     * 尽快输出结果,然后执行后续逻辑
+     * 本方法的的信息不会被输出
+     * 即使exit之后仍旧能被执行
+     */
+    final public function __destruct()
+    {
+        if (function_exists('fastcgi_finish_request')) {
+            fastcgi_finish_request();
+        }
+        $this->after();
+    }
+
+    /**
+     * 前置方法,处理参数
+     */
+    protected function before()
+    {
+
+    }
+
+    /**
+     * 执行验证
+     */
+    protected function checkAuth()
+    {
+
+    }
+
+    /**
+     * 执行逻辑
+     * @return mixed
+     */
+    protected abstract function main();
+
+    /**
+     * 后续方法
+     */
+    protected function after()
+    {
+
+    }
+
+    /**
+     * 显示错误信息
+     * @param int $code
+     * @param string $message
+     */
+    protected function showError($code = 0, $message = '')
+    {
+        echo "[$code] $message \n<br>";
+        exit;
+    }
+
+    /**
+     * @return Filter
+     */
+    protected function getRequest()
+    {
+        static $instance = null;
+        if (is_null($instance)) {
+            $instance = new Filter($_REQUEST);
+        }
+        return $instance;
+    }
+
+    /**
+     * @return Filter
+     */
+    protected function getQuery()
+    {
+        static $instance = null;
+        if (is_null($instance)) {
+            $instance = new Filter($_GET);
+        }
+        return $instance;
+    }
+
+    /**
+     * @return Filter
+     */
+    protected function getPost()
+    {
+        static $instance = null;
+        if (is_null($instance)) {
+            $instance = new Filter($_POST);
+        }
+        return $instance;
+    }
+
+    /**
+     * 获取模板, 固定模板, 与controller名称类似
+     * @return \PhpLife\Frame\Template
+     */
+    protected function getTemplate()
+    {
+        static $template;
+        if ($template) {
+            return $template;
+        }
+        $class = get_called_class();
+        $class = explode('\\', $class);
+        $path = dirname(__DIR__) . '/' . implode('/', array_slice($class, 1, 2)) . '/Template/';
+        if(!$this->tpl){
+            $this->tpl = implode('/', array_slice($class, 4));
+        }
+        $template = new Template();
+        $template->setPath($path)->setTpl($this->tpl);
+        $data = get_object_vars($this);
+        if ($data) {
+            $template->assign($data);
+        }
+        return $template;
+    }
+
+    /**
+     * 检查CSRF攻击
+     * @return bool
+     */
+    protected function checkCsrf()
+    {
+        $host = strtolower(trim($_SERVER['HTTP_HOST']));
+        $referer = parse_url($_SERVER['HTTP_REFERER']);
+        $referer = substr(strtolower(trim($referer['host'])), -(strlen($host)));
+        if (!empty($referer) AND $referer != $host AND $referer != ('www.' . $host)) {
+            return false;
+        }
+        return true;
+    }
+
+}

+ 132 - 0
Frame/Dispatcher.php

@@ -0,0 +1,132 @@
+<?php
+
+namespace PhpLife\Frame;
+
+class Dispatcher
+{
+    const CONFIG_PREFIX = '\\PhpLife\\Config';
+
+    const CONTROLLER_ERROR = 'Error';
+    const CONTROLLER_DEFAULT = 'Index';
+    const METHOD_DEFAULT = 'main';
+
+    /**
+     * 使用方法
+     * @param $customPrefix
+     * @param string $pathInfo
+     */
+    public static function run($customPrefix, $pathInfo = '')
+    {
+        // 获取前缀
+        $prefix = '\PhpLife\\App\\' . self::convertToClassName($customPrefix) . "\\Controller";
+        if (!$pathInfo) {
+            $pathInfo = self::getPathInfo();
+        }
+        $pathInfo = self::convertToClassName($pathInfo);
+        $pathInfo = explode('\\', $pathInfo);
+        if(count($pathInfo) > 1){
+            $method = array_pop($pathInfo);
+        }else{
+            $method = self::METHOD_DEFAULT;
+        }
+        $controller = $prefix . '\\' . implode('\\', $pathInfo);
+        // 加入一次出错重试的机会
+        for ($i = 0; $i < 2; $i++) {
+            try {
+                // 判断是否是公开方法
+                $reflection = new \ReflectionMethod($controller, $method);
+                if ($reflection->isPublic() == false) {
+                    throw new \Exception($controller . "::$method is not public");
+                }
+                $controller = new $controller();
+                $controller->$method();
+                break;
+            } catch (\Exception $ex) {
+                Logger::emerg($ex, 'dispatcher.run');
+                $controller = $prefix . '\\' . self::CONTROLLER_ERROR;
+                $method = self::METHOD_DEFAULT;
+            }
+        }
+    }
+
+    /**
+     * 分发请求
+     * @param string $customPrefix
+     * @param string $pathInfo
+     */
+    public static function dispatch($customPrefix, $pathInfo = '')
+    {
+        // 获取前缀
+        $prefix = '\PhpLife\\App\\' . self::convertToClassName($customPrefix) . "\\Controller";
+        if (!$pathInfo) {
+            $pathInfo = self::getPathInfo();
+        }
+        $pathInfo = self::convertToClassName($pathInfo);
+        try {
+            $controller = $prefix . '\\' . $pathInfo;
+            // 如果类不存在则使用默认的错误类
+            if (!class_exists($controller)) {
+                Logger::error($controller, 'dispatcher.controller_not_exist');
+                $controller = $prefix . '\\' . self::CONTROLLER_ERROR;
+                // 如果错误类也不存在,则抛出异常
+                if (!class_exists($controller)) {
+                    throw new \Exception($controller . ' not exist');
+                }
+            }
+            // 判断主方法
+            if (!method_exists($controller, 'run')) {
+                throw new \Exception($controller . ' has no method called "run"');
+            }
+            $controller = new $controller();
+            $controller->run();
+        } catch (\Exception $ex) {
+            Logger::emerg($ex, 'dispatcher.dispatch');
+        }
+    }
+
+    /**
+     * 获取pathInfo, 兼容Apache, Nginx
+     * 如果使用Apache的话可以直接使用 $_SERVER['PATH_INFO']的
+     * @return string
+     */
+    protected static function getPathInfo()
+    {
+        //去掉uri中的PHP_SELF 信息
+        $length = strlen($_SERVER['PHP_SELF']);
+        if (substr($_SERVER['REQUEST_URI'], 0, $length) == $_SERVER['PHP_SELF']) {
+            $request = substr($_SERVER['REQUEST_URI'], $length);
+        } else {
+            $request = $_SERVER['REQUEST_URI'];
+        }
+        // 去除 ?后面的部分
+        $request = parse_url($request);
+        $request = $request['path'];
+        $request = urldecode($request);
+        $request = trim($request, '/_ ');
+        // 去掉 '.' 后缀
+        if (strpos($request, '.') !== false) {
+            $request = explode('.', $request);
+            $request = array_shift($request);
+        }
+        if (empty($request)) {
+            $request = self::CONTROLLER_DEFAULT;
+        }
+        return $request;
+    }
+
+    /**
+     * 将字符串转成带命名空间的类名
+     * @param $string
+     * @return string
+     */
+    public static function convertToClassName($string)
+    {
+        $string = trim($string, '\\/_ ');
+        $string = strtolower($string);
+        $string = str_replace('/', '\\', $string);
+        $string = str_replace(' ', '\\', ucwords(str_replace('\\', ' ', $string)));
+        $string = str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
+        return $string;
+    }
+
+}

+ 20 - 0
Frame/Exception.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace PhpLife\Frame;
+
+/**
+ * 主要是在抛出异常的时候记录出错信息
+ * Class Exception
+ * @package PhpLife\Frame
+ */
+class Exception extends \Exception
+{
+    protected $filename = 'exception';
+
+    public function __construct($message = '', $code = 0, $previous = null)
+    {
+        $data = array('message' => $message, 'code' => $code, 'previous' => $previous);
+        Logger::info($data, $this->filename);
+        parent::__construct($message, $code, $previous);
+    }
+}

+ 13 - 0
Frame/Exception/Database.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace PhpLife\Frame\Exception;
+
+/**
+ * 数据库异常
+ * @package PhpLife\Frame\Exception
+ */
+class Database extends \PhpLife\Frame\Exception
+{
+    protected $filename = 'exception.database';
+
+}

+ 12 - 0
Frame/Exception/Validate.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace PhpLife\Frame\Exception;
+
+/**
+ * 输入信息验证出错
+ */
+class Validate extends \PhpLife\Frame\Exception
+{
+    protected $filename = 'exception.validate';
+
+}

+ 251 - 0
Frame/Filter.php

@@ -0,0 +1,251 @@
+<?php
+namespace PhpLife\Frame;
+
+use PhpLife\Frame\Helper\Validate;
+
+/**
+ * 参数过滤类
+ */
+class Filter
+{
+
+    protected $_data = array();
+
+    public function __construct(Array $data = array())
+    {
+        foreach ($data as $name => $value) {
+            $this->_data[$name] = $value;
+        }
+    }
+
+    /**
+     * 过滤字符串
+     *
+     * @param string $name
+     * @param string $default
+     * @return string
+     */
+    public function string($name, $default = null)
+    {
+        $result = null;
+        if (isset($this->_data[$name])) {
+            $result = $this->_data[$name];
+            $result = trim($result);
+            $result = (string)$result;
+        } elseif (!is_null($default)) {
+            $result = $default;
+        }
+        return $result;
+    }
+
+    /**
+     * 批量获取字符串
+     *
+     * @param string $name
+     * @param string $default
+     * @return string
+     */
+    public function getBatchString($arr, $isString = TRUE)
+    {
+        $result = null;
+        if (is_array($arr)) {
+            foreach ($arr as $v) {
+                if (isset($this->_data[$v])) {
+                    $result[$v] = $isString ? (string)trim($this->_data[$v]) : $this->_data[$v];
+                }
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * 获取手机区号
+     *
+     * @param string $name
+     * @param string $default
+     * @return string
+     */
+    public function mobileCode($name = 'mobile_code', $default = '+86')
+    {
+        $mobileCode = $this->string($name, $default);
+        $mobileCode = '+' . ltrim($mobileCode, '+');
+        return $mobileCode;
+    }
+
+    /**
+     * 过滤整型
+     *
+     * @param string $name
+     * @param int $default
+     * @return int
+     */
+    public function intval($name, $default = null)
+    {
+        $result = null;
+        if (isset($this->_data[$name])) {
+            $result = $this->_data[$name];
+            $result = trim($result);
+            $result = intval($result);
+        } elseif (!is_null($default)) {
+            $result = $default;
+        }
+        return $result;
+    }
+
+    /**
+     * 过滤浮点类型
+     *
+     * @param string $name
+     * @param float $default
+     * @return float
+     */
+    public function float($name, $default = null)
+    {
+        $result = null;
+        if (isset($this->_data[$name])) {
+            $result = $this->_data[$name];
+            $result = trim($result);
+            $result = floatval($result);
+        } elseif (!is_null($default)) {
+            $result = $default;
+        }
+        return $result;
+    }
+
+    /**
+     * 验证手机号
+     *
+     * @param string $message
+     * @param int $code
+     * @param string $name
+     * @return int|string
+     * @throws \Exception
+     */
+    public function mobile($message = '手机号格式错误', $code = 45050, $name = 'mobile')
+    {
+        $result = '';
+        if (isset($this->_data[$name])) {
+            $result = $this->_data[$name];
+            $result = trim($result);
+            $result = intval($result);
+        }
+        if (!$result || Validate::isMobile($result) == false) {
+            throw new \PhpLife\Frame\Exception\Validate($message, $code);
+        }
+        return $result;
+    }
+
+    /**
+     * 获取json数据格式
+     *
+     * @param
+     *            $name
+     * @param
+     *            $default
+     * @return array
+     */
+    public function json($name, $default = null)
+    {
+        $result = null;
+        if (isset($this->_data[$name])) {
+            $result = $this->_data[$name];
+            $result = trim($result);
+            $decoded = json_decode($result,true);
+            if ($decoded) {
+                $result = $decoded;
+            } else {
+                $result = array();
+            }
+        } elseif (!is_null($default)) {
+            $result = $default;
+        }
+        return $result;
+    }
+
+    /**
+     * 检查字段是否存在,可选检查是否为空
+     *
+     * @param string $name
+     * @param bool $checkEmpty
+     * @param string $message
+     * @param int $code
+     * @throws \Exception
+     * @return bool
+     */
+    public function exists($name, $checkEmpty = false, $message = '', $code = 0)
+    {
+        if ($checkEmpty) {
+            $result = isset($this->_data[$name]) && (is_string($this->_data[$name]) && strlen($this->_data[$name]) > 0);
+        } else {
+            $result = isset($this->_data[$name]);
+        }
+        if (!$result && $message) {
+            throw new \PhpLife\Frame\Exception\Validate($message, $code);
+        }
+        return $result;
+    }
+
+    /**
+     * 获取数组数据
+     *
+     * @param
+     *            $name
+     * @param array $default
+     * @return array|mixed
+     */
+    public function getArray($name, $default = array())
+    {
+        $result = array();
+        if (isset($this->_data[$name])) {
+            $result = $this->_data[$name];
+        } elseif (!is_null($default)) {
+            $result = $default;
+        }
+        return $result;
+    }
+
+    /**
+     * 参数验证
+     * @param $data -参数
+     * @param bool $required -是否必须
+     * @return array
+     * @throws Exception
+     */
+    public function params($data, $required = false)
+    {
+
+        $res = array();
+        foreach ($data as $name => $rule) {
+            if ($required) {
+                $this->required($name);
+            }
+            $res[$name] = $this->$rule($name);
+        }
+        return $res;
+    }
+
+    public function required($name)
+    {
+        if($this->_data[$name] || strlen($this->_data[$name]) > 0){
+            return $this->_data[$name];
+        }else{
+            throw new Exception('缺少必填参数:' . $name,400);
+        }
+    }
+
+    public function compare($name, $value, $echo = 'selected')
+    {
+        if (isset($this->_data[$name]) && $this->_data[$name] !== '' && $this->_data[$name] == $value) {
+            echo $echo;
+            return;
+        } else {
+            echo '';
+            return;
+        }
+    }
+
+    public function toArray()
+    {
+        return $this->_data;
+    }
+}

+ 748 - 0
Frame/Helper.php

@@ -0,0 +1,748 @@
+<?php
+
+namespace PhpLife\Frame;
+
+use PhpLife\App\Admin\Config\Proxy;
+use PhpLife\Library\Webp;
+
+class Helper
+{
+
+    /*
+    * 检测链接是否是SSL连接
+    * @return bool
+    */
+    public static function isSSL()
+    {
+        if (!isset($_SERVER['HTTPS']))
+            return false;
+        if ($_SERVER['HTTPS'] === 1) {  //Apache
+            return true;
+        } elseif ($_SERVER['HTTPS'] === 'on') { //IIS
+            return true;
+        } elseif ($_SERVER['SERVER_PORT'] == 443) { //其他
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 随机数生成器
+     */
+    public static function random()
+    {
+        return md5(microtime() . rand() . uniqid() . $_SERVER['REMOTE_ADDR']);
+    }
+
+    /**
+     * 简单验证email
+     * @param $email
+     * @return bool
+     */
+    public static function validateEmail($email)
+    {
+        $email = trim($email);
+        if (!preg_match("/([\w\-]+\@[\w\-]+\.[\w\-]+)/", $email)) {
+            return False;
+        }
+        return True;
+    }
+
+    /**
+     * 检查文件是否存在
+     * @todo 检查content-Type和文件大小
+     * @param $url
+     * @param string $extension
+     * @param bool $checkHeader 检查头信息
+     * @return bool
+     */
+    public static function validateFile($url, $extension = '', $checkHeader = false)
+    {
+        if (!$url) {
+            return false;
+        }
+        $urlInfo = parse_url($url);
+        if ($urlInfo['scheme'] !== 'http' && $urlInfo['scheme'] !== 'https') {
+            return false;
+        }
+        // 检查后缀
+        if ($extension) {
+            $pathInfo = pathinfo($url);
+            if ($pathInfo['extension'] != trim($extension, '.')) {
+                return false;
+            }
+        }
+        // 检查Http头信息
+        if ($checkHeader) {
+            $result = get_headers($url);
+            $httpCode = explode(' ', $result[0]);
+            $httpCode = $httpCode[1];
+            if ($httpCode != 200) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 对象转换
+     * @param object $target
+     * @param object $source
+     * @return object
+     */
+    public static function objectMerge($target, $source)
+    {
+        if (!is_object($target)) {
+            return null;
+        }
+        $result = clone $target;
+        $source = (array)$source;
+        foreach ($source as $k => $v) {
+            $result->{$k} = $v;
+        }
+        return $result;
+    }
+
+    /**
+     * 去除首尾全角及半角空格,多个空格合并为一个
+     * @param type $str
+     * @return string
+     */
+    public static function trimAndMerge($str)
+    {
+        $str = preg_replace('/( | )+/', ' ', $str);
+        return trim(preg_replace("/^ +| +$/ ", " ", $str));
+    }
+
+    /**
+     * 去除首尾空格,包含全角空格
+     */
+    public static function trim($str)
+    {
+        //$str = trim($str,"  \t\n\r\0\x0B");
+        $str = mb_ereg_replace('(^( | )+|( | )+$)', '', $str);
+        return $str;
+    }
+
+    /**
+     * 转换为友好时间
+     * @param int|string $timestamp
+     * @return string
+     */
+    public static function convertToFriendlyTime($timestamp)
+    {
+        if (!is_numeric($timestamp)) {
+            $timestamp = strtotime($timestamp);
+        }
+        $now = time();
+        $interval = abs($now - $timestamp);
+        $today = strtotime(date("Ymd"));
+        $tomorrow = strtotime(date("Ymd", $now + 86400));
+        $thisYear = strtotime(date("Y-01-01 00:00"));
+        if ($interval < 60) {
+            if ($interval < 10) {
+                $interval = 10;
+            }
+            $string = $interval . "秒前";
+        } else if ($interval < 3600) {
+            $string = floor($interval / 60) . '分钟前';
+        } else if ($today < $timestamp AND $timestamp < $tomorrow) {
+            $string = '今天' . date('H:i', $timestamp);
+        } else if ($thisYear < $timestamp) {
+            $string = date('n月j日', $timestamp);
+        } else {
+            $string = date('Y-m-d', $timestamp);
+        }
+        return $string;
+    }
+
+    /**
+     * 写入ini文件
+     * @param type $path
+     * @param type $data
+     * @return type
+     */
+    public static function writeIniFile($path, $data)
+    {
+        $content = null;
+        foreach ($data as $key => $item) {
+            if (is_array($item)) {
+                $content .= "\n[{$key}]\n";
+                foreach ($item as $k => $v) {
+                    if (is_numeric($v) || is_bool($v)) {
+                        $v = '"' . $v . '"';
+                    }
+                    $content .= "{$k} = {$v}\n";
+                }
+            } else {
+                if (is_numeric($item) || is_bool($item)) {
+                    $item = '"' . $item . '"';
+                }
+                $content .= "{$key} = {$item}\n";
+            }
+        }
+        return file_put_contents($path, $content);
+    }
+
+    public static function checkField($input, $fileds)
+    {
+        $input = (array)$input;
+        $output = array();
+        foreach ($input as $k => $v) {
+            if (in_array($k, $fileds)) {
+                $output[$k] = $v;
+            }
+        }
+        return $output;
+    }
+
+    /**
+     * 递归合并数组,兼容 数字类型
+     * @param array $target
+     * @param mixed $source
+     * @return array
+     * @author daniel@danielsmedegaardbuus.dk
+     * http://www.php.net/manual/zh/function.array-merge-recursive.php
+     */
+    public static function arrayMerge(&$target, &$source)
+    {
+        if (empty($target) OR !is_array($target)) {
+            $target = array();
+        }
+        if (empty($source) OR !is_array($source)) {
+            $source = array();
+        }
+        $merged = $target;
+        foreach ($source as $key => &$value) {
+            if (is_array($value) && isset($merged [$key]) && is_array($merged [$key])) {
+                $merged [$key] = self::arrayMerge($merged [$key], $value);
+            } else if (is_object($value) AND is_object($merged[$key])) {
+                $value = (array)$value;
+                foreach ($value as $k => $v) {
+                    $merged [$key]->{$k} = $v;
+                }
+            } else {
+                $merged [$key] = $value;
+            }
+        }
+        return $merged;
+    }
+
+    /**
+     * 链接添加a标签
+     * @param string $content
+     * @return string
+     */
+    public static function urlAddHref($string)
+    {
+        preg_match_all('@(https?://([-\w\.]+)+(:\d+)?(/([-\w/_\.]*(\?\S+)?)?)?)@', $string, $matches, PREG_SET_ORDER);
+        foreach ($matches as $matche) {
+            $url = $matche [0];
+        }
+        if ($url) {
+            $newUrl = '<a href = "' . $url . '">' . $url . '</a>';
+            $string = str_replace($url, $newUrl, $string);
+        }
+        return $string;
+    }
+
+    /**
+     * 计算字符串长度,汉字按两个字符
+     * @param string $str
+     * @return int
+     */
+    public static function strlenUtf8($str)
+    {
+        $length = strlen(preg_replace('/[u4e00-u9fa5 ]/', '', $str));
+
+        if ($length) {
+            return strlen($str) - $length + intval($length / 3) * 2;
+        } else {
+            return strlen($str);
+        }
+    }
+
+    public static function makeSemiangle($str)
+    {
+        if (!isset($str[0])) {
+            return null;
+        }
+        $arr = array(
+            '0' => '0', '1' => '1', '2' => '2', '3' => '3', '4' => '4',
+            '5' => '5', '6' => '6', '7' => '7', '8' => '8', '9' => '9',
+            'A' => 'A', 'B' => 'B', 'C' => 'C', 'D' => 'D', 'E' => 'E',
+            'F' => 'F', 'G' => 'G', 'H' => 'H', 'I' => 'I', 'J' => 'J',
+            'K' => 'K', 'L' => 'L', 'M' => 'M', 'N' => 'N', 'O' => 'O',
+            'P' => 'P', 'Q' => 'Q', 'R' => 'R', 'S' => 'S', 'T' => 'T',
+            'U' => 'U', 'V' => 'V', 'W' => 'W', 'X' => 'X', 'Y' => 'Y',
+            'Z' => 'Z', 'a' => 'a', 'b' => 'b', 'c' => 'c', 'd' => 'd',
+            'e' => 'e', 'f' => 'f', 'g' => 'g', 'h' => 'h', 'i' => 'i',
+            'j' => 'j', 'k' => 'k', 'l' => 'l', 'm' => 'm', 'n' => 'n',
+            'o' => 'o', 'p' => 'p', 'q' => 'q', 'r' => 'r', 's' => 's',
+            't' => 't', 'u' => 'u', 'v' => 'v', 'w' => 'w', 'x' => 'x',
+            'y' => 'y', 'z' => 'z',
+            '(' => '(', ')' => ')', '〔' => '[', '〕' => ']', '【' => '[',
+            '】' => ']', '〖' => '[', '〗' => ']', '“' => '[', '”' => ']',
+            '‘' => '[', '’' => ']', '{' => '{', '}' => '}', '《' => '<',
+            '》' => '>', '%' => '%', '+' => '+', '—' => '-', '-' => '-',
+            '~' => '-', ':' => ':', '。' => '.', '、' => ',', ',' => '.',
+            ';' => ',', '?' => '?', '!' => '!', '…' => '-', '‖' => '|',
+            '”' => '"', '’' => '`', '‘' => '`', '|' => '|', '〃' => '"',
+            ' ' => ' ', '@' => '@', '、' => '.',);
+        return strtr($str, $arr);
+    }
+
+    /**
+     * 获取子串,替换全角 汉字2字符,英文1字符,数字判断
+     */
+    public static function getUtf8SubStr($str, $len)
+    {
+        $str = self::makeSemiangle($str);
+        return self::getRawUtf8SubStr($str, $len);
+    }
+
+    /**
+     * 获取子串,不替换全角
+     */
+    public static function getRawUtf8SubStr($str, $len)
+    {
+        if ((strlen($str) + mb_strlen($str, 'UTF8')) / 2 <= $len) {
+            return $str;
+        } else {
+            $temp_len = 0;
+            $temp_str = '';
+            for ($i = 0; $i < mb_strlen($str, 'UTF-8'); $i++) {
+                if ($temp_len < $len) {
+                    $tmp = mb_substr($str, $i, 1, 'UTF-8');
+                    $temp_str .= $tmp;
+                    $temp_len += ((strlen($tmp) + mb_strlen($tmp, 'UTF-8')) / 2);
+                }
+            }
+            return $temp_str;
+        }
+    }
+
+    /**
+     * 从一个数组里面取出以指定key为中心的偏移, 九宫格
+     */
+    public static function getCenterFromList($list, $key, $length = 9)
+    {
+        $list = array_values($list);
+        $list = array_unique($list);
+        $ids = array_flip($list);
+        $current = $ids[$key];
+        $total = count($list);
+
+        $first = $current - $length > 0 ? $current - $length : 0;
+        $last = $current + $length < $total - 1 ? $current + $length : $total - 1;
+        $limit = $last - $first + 1;
+
+        return array_slice($list, $first, $limit);
+    }
+
+    public static function safeHtmlEncodeString($str)
+    {
+        $str = htmlspecialchars_decode($str);
+        return htmlspecialchars($str);
+    }
+
+    /**
+     * 删除数据中指定的值
+     * @param max $val
+     * @param array $array
+     */
+    public static function delArrayValue($val, $array)
+    {
+        $key = array_search($val, $array);
+        if ($key !== false) {
+            unset($array[$key]);
+        }
+        return array_values($array);
+    }
+
+    public static function strcut($str, $length, $etc = '...')
+    {
+        $result = '';
+        $str = html_entity_decode(trim(strip_tags($str)), ENT_QUOTES, 'UTF-8');
+        $strlen = strlen($str);
+
+        for ($i = 0; (($i < $strlen) && ($length > 0)); $i++) {
+            $number = strpos(str_pad(decbin(ord(substr($str, $i, 1))), 8, '0', STR_PAD_LEFT), '0');
+            if ($number) {
+                if ($length < 1.0) {
+                    break;
+                }
+                $result .= substr($str, $i, $number);
+                $length -= 1.0;
+                $i += $number - 1;
+            } else {
+                $result .= substr($str, $i, 1);
+                $length -= 0.5;
+            }
+        }
+
+        $result = htmlspecialchars($result, ENT_QUOTES, 'UTF-8');
+
+        if ($i < $strlen) {
+            $result .= $etc;
+        }
+        return $result;
+    }
+
+    /**
+     * 递归调整文件属性
+     */
+    public static function chmod($path, $filemode = 0755)
+    {
+        if (!is_dir($path)) {
+            return chmod($path, $filemode);
+        }
+        $dh = opendir($path);
+        while (($file = readdir($dh)) !== false) {
+            if ($file != '.' && $file != '..') {
+                $fullpath = $path . '/' . $file;
+                if (is_link($fullpath)) {
+                    return false;
+                } elseif (!is_dir($fullpath) && !chmod($fullpath, $filemode)) {
+                    return false;
+                } elseif (!self::chmod($fullpath, $filemode)) {
+                    return false;
+                }
+            }
+        }
+        closedir($dh);
+        return chmod($path, $filemode);
+    }
+
+    /**
+     * 获取服务器IP
+     * @param bool $toLong 是否返回整形
+     * @return string
+     */
+    public static function getServerIp($toLong = false)
+    {
+        if (isset($_SERVER['SERVER_ADDR'])) {
+            $ip = $_SERVER['SERVER_ADDR'];
+        } else {
+            $domain = gethostname();
+            $ip = gethostbyname($domain);
+        }
+        if ($toLong) {
+            $ip = ip2long($ip);
+        }
+        return $ip;
+    }
+
+    /**
+     * 获取客户端Ip
+     * @param bool $toLong 是否返回整形
+     * @return string|int
+     */
+    public static function getClientIp($toLong = false)
+    {
+        $ip = '';
+        if (isset($_SERVER["HTTP_X_FORWARDED_FOR"]) && $_SERVER["HTTP_X_FORWARDED_FOR"]) {
+            $ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
+        } elseif (isset($_SERVER["HTTP_CLIENT_IP"]) && $_SERVER["HTTP_CLIENT_IP"]) {
+            $ip = $_SERVER["HTTP_CLIENT_IP"];
+        } elseif (isset($_SERVER["REMOTE_ADDR"]) && $_SERVER["REMOTE_ADDR"]) {
+            $ip = $_SERVER["REMOTE_ADDR"];
+        }
+        $pos = strpos($ip, ',');
+        if ($pos) {
+            $ip = substr($ip, 0, $pos);
+        }
+        $ip = trim($ip);
+        if ($toLong) {
+            $ip = ip2long($ip);
+        }
+        return $ip;
+    }
+
+    /**
+     * 获取截取后的文字
+     * 英文汉字均算做一个长度
+     */
+    public static function getUtf8ShortCut($content, $length)
+    {
+        $shortCut = mb_substr($content, 0, $length, 'UTF-8');
+        $shortCut = strcmp($shortCut, $content) == 0 ? $shortCut : $shortCut . '...';
+        return $shortCut;
+    }
+
+    /**
+     * 检查是否有http
+     * @param type $str
+     * @return type
+     */
+    public static function httpLinkChecker($str)
+    {
+        $reg = '/^https?/i';
+        if (preg_match($reg, $str)) {
+            return $str;
+        } else {
+            return 'http://' . $str;
+        }
+    }
+
+    /**
+     * 获取进程运行时间
+     */
+    public static function getPidTime($pid)
+    {
+
+        $i_pinfo = file_get_contents('/proc/' . $pid . '/stat');
+        $args = explode(" ", $i_pinfo);
+        $start_time = $args[21];
+
+        $m_pinfo = file_get_contents('/proc/stat');
+        $args = explode("\n", $m_pinfo);
+        foreach ($args as $line) {
+            $v = trim($line);
+            if (strpos($line, "btime") !== false) {
+                $args_1 = explode(" ", $v);
+                $uptime = $args_1[1];
+                break;
+            }
+        }
+        if ($uptime == '' || $start_time == '') {
+            return 0;
+        }
+        return (int)microtime(true) - (int)($uptime + $start_time / 100);
+    }
+
+    /**
+     * 过滤标点符号
+     * @param type $content
+     * @return type
+     */
+    public static function strap($content)
+    {
+        $content = trim($content);
+        $content = strip_tags($content);
+        $content = preg_replace("/(&[a-zA-Z]{0,4};)/u", ' ', $content);
+        $content = preg_replace("/([^a-zA-Z0-9\x{4e00}-\x{9fa5}]+)/u", ' ', $content);
+        $content = trim($content);
+        return $content;
+    }
+
+    public static function html2text($str)
+    {
+        $str = preg_replace("/<style .*?<\/style>/is", "", $str);
+        $str = preg_replace("/<script .*?<\/script>/is", "", $str);
+        $str = preg_replace("/<br \s*\/?\/>/i", "\n", $str);
+        $str = preg_replace("/<\/?p>/i", "\n\n", $str);
+        $str = preg_replace("/<\/?td>/i", "\n", $str);
+        $str = preg_replace("/<\/?div>/i", "\n", $str);
+        $str = preg_replace("/<\/?blockquote>/i", "\n", $str);
+        $str = preg_replace("/<\/?li>/i", "\n", $str);
+        $str = preg_replace("/\&nbsp\;/i", " ", $str);
+        $str = preg_replace("/\&nbsp/i", " ", $str);
+        $str = preg_replace("/\&amp\;/i", "&", $str);
+        $str = preg_replace("/\&amp/i", "&", $str);
+        $str = preg_replace("/\&lt\;/i", "<", $str);
+        $str = preg_replace("/\&lt/i", "<", $str);
+        $str = preg_replace("/\&ldquo\;/i", '"', $str);
+        $str = preg_replace("/\&ldquo/i", '"', $str);
+        $str = preg_replace("/\&lsquo\;/i", "'", $str);
+        $str = preg_replace("/\&lsquo/i", "'", $str);
+        $str = preg_replace("/\&rsquo\;/i", "'", $str);
+        $str = preg_replace("/\&rsquo/i", "'", $str);
+        $str = preg_replace("/\&gt\;/i", ">", $str);
+        $str = preg_replace("/\&gt/i", ">", $str);
+        $str = preg_replace("/\&rdquo\;/i", '"', $str);
+        $str = preg_replace("/\&rdquo/i", '"', $str);
+        $str = strip_tags($str);
+        $str = preg_replace("/(\n\s*\n)+/is", "\n", $str);
+        return $str;
+    }
+
+    public static function convertStringToUtf8($string)
+    {
+        $string = urldecode($string);
+        $encoding = mb_detect_encoding($string, array('ASCII', 'UTF-8', 'GB2312', 'GBK', 'BIG5'));
+        $string = mb_convert_encoding($string, 'UTF-8', $encoding);
+        return $string;
+    }
+
+    /**
+     * utf8多字节编码文字折行
+     * @param $text
+     * @param int $length
+     * @return string
+     */
+    public static function wordwrapUtf8($text, $length = 75)
+    {
+        $result = array();
+        $len = mb_strlen($text, 'UTF-8');
+        $step = ceil($len / $length);
+        for ($i = 0; $i < $step; $i++) {
+            $result[] = mb_substr($text, $length * $i, $length, "UTF-8");
+        }
+        return implode("\n", $result);
+    }
+
+
+    /**
+     * 从 $argv 中解析合并参数, 兼容一下情况:
+     *      无前缀, 被解析成索引数组
+     *      单前缀(只能是单字母, 后面可以直接跟value, 也可以空格后面跟着value,二者不能混用), 被解析成关联数组
+     *      双前缀形式(可以是单字母也可以是多字母,后面必需跟着 '='), 被解析成关联数组
+     * 如果不确定, 请自行打印结果查看是否正确
+     * @example : php index.php first -s 'second' --third='third'
+     * @return array('first', 's' => 'second', 'third' => 'third')
+     */
+    public static function getArguments()
+    {
+        $arguments = $_SERVER['argv'];
+        for ($i = 0; $i < $_SERVER['argc']; $i++) {
+            if (!isset($arguments[$i][0])) {
+                continue;
+            }
+
+            //判断第一位是否是 '-'
+            if (substr($arguments[$i], 0, 1) != '-') {
+                continue;
+            }
+
+            //判断第二位是否是 '-'
+            if (substr($arguments[$i], 1, 1) != '-') {
+                if (strlen($arguments[$i]) == 2) {
+                    $key = substr($arguments[$i], 1);
+                    $value = $arguments[$i + 1];
+                    $arguments[$key] = trim($value);
+                    unset($arguments[$i]);
+                    unset($arguments[$i + 1]);
+                } elseif (strlen($arguments[$i]) > 2) {
+                    $key = substr($arguments[$i], 1, 1);
+                    $value = substr($arguments[$i], 2);
+                    $arguments[$key] = trim($value);
+                    unset($arguments[$i]);
+                } else {
+                    //do nothing;
+                }
+            } else {
+                $value = substr($arguments[$i], 2);
+                $value = explode('=', $value, 2);
+                $arguments[$value[0]] = trim($value[1]);
+                unset($arguments[$i]);
+            }
+        }
+        return $arguments;
+    }
+
+    /**
+     * 尝试解析字符串,如果解析失败则返回原值
+     * @param $string
+     * @return string | array
+     */
+    public static function jsonDecode($string)
+    {
+        if (!$string) {
+            return $string;
+        }
+        $result = json_decode($string, true);
+        if ($result) {
+            $string = $result;
+        }
+        return $string;
+    }
+
+    /**
+     * 获取数组分片, 从指定列表中获取指定元素之后的若干个切片
+     * @param $idList
+     * @param $lastId
+     * @param $count
+     * @return array
+     */
+    public static function getSlice($idList, $lastId = 0, $count = 20)
+    {
+        $result = array();
+        if (!$idList || ($lastId && !in_array($lastId, $idList))) {
+            return $result;
+        }
+        if ($lastId) {
+            $position = array_search($lastId, $idList) + 1;
+        } else {
+            $position = 0;
+        }
+        $result = array_slice($idList, $position, $count);
+        return $result;
+    }
+    /**
+     * 创建目录
+     * @param unknown $dir
+     * @param number $mode
+     */
+    public static function mkdir($dir, $mode = 0755)
+    {
+        if (!is_dir($dir)) {
+            $temp = explode('/', $dir);
+            $cur_dir = '';
+            for ($i = 0; $i < count($temp); $i++) {
+                $cur_dir .= $temp[$i] . '/';
+                if (!is_dir($cur_dir)) {
+                    @mkdir($cur_dir, 0777);
+                    @chmod($cur_dir, $mode);
+                }
+            }
+        }
+    }
+    /**
+     * 下载图片
+     *
+     * @param unknown $imgUrl
+     * @return string
+     */
+    public static function downImg($imgUrl,$suffix=NULL)
+    {
+        $suffix = $suffix?:strtolower(substr(strrchr($imgUrl, '.'), 1));
+        if (strlen($suffix)>5){
+            $suffix="jpg";
+        }
+        if ($suffix==""){
+            $suffix="jpg";
+        }
+        $new_name = "makeup/images/".date("Y-m")."/img_" . uniqid() . rand(1, 1000) . "." . $suffix;
+        $path = "/www/api.test.meitu.com/" . $new_name;
+        if (!is_dir("/www/api.test.meitu.com/makeup/images/".date("Y-m")."/")){
+            Helper::mkdir("/www/api.test.meitu.com/makeup/images/".date("Y-m")."/");
+        }
+        $file=self::getContent($imgUrl);
+        $i=0;
+        while ($file==""&&$i<3){
+            $file=self::getContent($imgUrl);
+            Logger::setLog(null,'下载图片失败。原图:'.$imgUrl,'down_img_error');
+            $i++;
+        }
+        if ($file==""){
+            return "";
+        }
+        file_put_contents($path, $file);
+        Webp::tranPic2Webp($path);//转换成webp图片
+        $push = "/www/api.mgr.meitu.com/cdn/push.sh";
+        push($push, $new_name);
+        push($push, $new_name.'.webp');
+        return "/".$new_name;
+    }
+    
+    public static function getContent($url){
+        $curlHandle = curl_init();
+        curl_setopt($curlHandle, CURLOPT_URL, $url);
+        curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, 0); // 让CURL支持HTTPS访问
+        curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 30);
+        if (strpos($url,"https")!==false){
+            curl_setopt($curlHandle, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
+            curl_setopt($curlHandle, CURLOPT_PROXY, Proxy::getData("host"));
+            curl_setopt($curlHandle, CURLOPT_PROXYPORT, Proxy::getData('port'));
+            curl_setopt($curlHandle, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
+            curl_setopt($curlHandle, CURLOPT_PROXYUSERPWD, Proxy::getData('user').":".Proxy::getData('password'));
+        }
+        curl_setopt($curlHandle, CURLOPT_TIMEOUT, 30);
+        $file = curl_exec($curlHandle);
+        curl_close($curlHandle);
+        return $file;
+    }
+
+}

+ 67 - 0
Frame/Helper/Date.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace PhpLife\Frame\Helper;
+
+/**
+ * 文件操作
+ */
+class Date extends \PhpLife\Frame\Helper
+{
+
+    /**
+     * 转换为友好时间
+     * @param int|string $timestamp
+     * @return string
+     */
+    public static function convert($timestamp)
+    {
+        if (!is_numeric($timestamp)) {
+            $timestamp = strtotime($timestamp);
+        }
+        $now = time();
+        $interval = abs($now - $timestamp);
+        $today = strtotime(date("Ymd"));
+        $tomorrow = strtotime(date("Ymd", $now + 86400));
+        $thisYear = strtotime(date("Y-01-01 00:00"));
+        if ($interval < 60) {
+            $string = "1分钟前";
+        } else if ($interval < 3600) {
+            $string = floor($interval / 60) . '分钟前';
+        } else if ($today < $timestamp AND $timestamp < $tomorrow) {
+            $string = '今天' . date('H:i', $timestamp);
+        } else if ($thisYear < $timestamp) {
+            $string = date('m-d H:i', $timestamp);
+        } else {
+            $string = date('y-m-d H:i', $timestamp);
+        }
+        return $string;
+    }
+
+    /**
+     * 获取相对时间
+     * @param $timestamp
+     * @param $customString ;
+     * @return string
+     */
+    public static function related($timestamp, $customString = '')
+    {
+        if (!is_numeric($timestamp)) {
+            $timestamp = strtotime($timestamp);
+        }
+        $interval = abs(time() - $timestamp);
+        if ($interval == 0) {
+            $interval = 1;
+        }
+        if ($interval < 3600) {
+            $string = ceil($interval / 60) . '分钟前';
+        } elseif ($interval < 86400) {
+            $string = ceil($interval / 3600) . '小时前';
+        } else {
+            $string = ceil($interval / 86400) . '天前';
+        }
+        $string = $string . $customString;
+        return $string;
+    }
+
+
+}

+ 39 - 0
Frame/Helper/File.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace PhpLife\Frame\Helper;
+
+/**
+ * 文件操作
+ */
+class File extends \PhpLife\Frame\Helper
+{
+
+    /**
+     * 写入ini文件
+     * @param string $path
+     * @param string|array $data
+     * @return int|bool
+     */
+    public static function writeIniFile($path, $data)
+    {
+        $content = null;
+        foreach ($data as $key => $item) {
+            if (is_array($item)) {
+                $content .= "\n[{$key}]\n";
+                foreach ($item as $k => $v) {
+                    if (is_numeric($v) || is_bool($v)) {
+                        $v = '"' . $v . '"';
+                    }
+                    $content .= "{$k} = {$v}\n";
+                }
+            } else {
+                if (is_numeric($item) || is_bool($item)) {
+                    $item = '"' . $item . '"';
+                }
+                $content .= "{$key} = {$item}\n";
+            }
+        }
+        return file_put_contents($path, $content);
+    }
+
+}

+ 92 - 0
Frame/Helper/Format.php

@@ -0,0 +1,92 @@
+<?php
+
+namespace PhpLife\Frame\Helper;
+
+/**
+ * 格式转换
+ */
+class Format extends \PhpLife\Frame\Helper
+{
+    /**
+     * @hack 转换数字类型到字符串
+     * @param $data
+     * @return mixed
+     */
+    public static function convertNumberToString($data)
+    {
+        if (is_array($data)) {
+            foreach ($data as $k => $v) {
+                if (is_array($v)) {
+                    $data[$k] = self::convertNumberToString($v);
+                } else if (is_numeric($v)) {
+                    $data[$k] = (string)$v;
+                }
+            }
+        } else if (is_numeric($data)) {
+            $data = (string)$data;
+        }
+        return $data;
+    }
+
+    /**
+     * Json数据格式化,拷贝自网上
+     * @param  Mixed $data 数据
+     * @param  String $indent 缩进字符,默认4个空格
+     * @return JSON
+     */
+    public static function jsonFormat($data, $indent = null)
+    {
+        // 对数组中每个元素递归进行url_encode操作,保护中文字符
+        array_walk_recursive($data, 'self::jsonFormatProtect');
+        // json encode
+        $data = json_encode($data);
+        // 将url_encode的内容进行url_decode
+        $data = urldecode($data);
+        // 缩进处理
+        $ret = '';
+        $pos = 0;
+        $length = strlen($data);
+        $indent = isset($indent) ? $indent : '    ';
+        $newline = "\n";
+        $prevChar = '';
+        $outOfQuotes = true;
+
+        for ($i = 0; $i <= $length; $i++) {
+            $char = substr($data, $i, 1);
+            if ($char == '"' && $prevChar != '\\') {
+                $outOfQuotes = !$outOfQuotes;
+            } elseif (($char == '}' || $char == ']') && $outOfQuotes) {
+                $ret .= $newline;
+                $pos--;
+                for ($j = 0; $j < $pos; $j++) {
+                    $ret .= $indent;
+                }
+            }
+            $ret .= $char;
+            if (($char == ',' || $char == '{' || $char == '[') && $outOfQuotes) {
+                $ret .= $newline;
+                if ($char == '{' || $char == '[') {
+                    $pos++;
+                }
+                for ($j = 0; $j < $pos; $j++) {
+                    $ret .= $indent;
+                }
+            }
+            $prevChar = $char;
+        }
+        return $ret;
+    }
+
+    /**
+     * 将数组元素进行
+     * @param String $val
+     */
+    protected static function jsonFormatProtect(&$val)
+    {
+        if ($val !== true && $val !== false && $val !== null) {
+            $val = urldecode($val);
+        }
+    }
+
+}
+

+ 269 - 0
Frame/Helper/Html.php

@@ -0,0 +1,269 @@
+<?php
+
+namespace PhpLife\Frame\Helper;
+
+/**
+ * 格式转换
+ */
+class Html extends \PhpLife\Frame\Helper
+{
+
+    /**
+     * @param null $msg
+     * @param null $url
+     * @param array $get
+     * @param int $wait
+     */
+    public static function location($msg = null, $url = null,$isParent=FALSE, $get = Array(), $wait = 1)
+    {
+        $url = self::getSiteUrl($url, $get);
+        $target=$isParent?".parent":"";
+        $str = "<script type='text/javascript'>function flash(){window".$target.".location.href='$url';}setTimeout('flash()','" . intval($wait) * 500 . "');</script>";
+        if (!is_string($msg)) {
+            $msg = $msg ? "操作成功!" : "操作失败!";
+        }
+        echo $str . $msg . "&nbsp;&nbsp;正在<a href='$url'>跳转中</a>.....";
+        die();
+    }
+
+    /**
+     * 取得当前页地址
+     * @return mixed
+     */
+    public static function self()
+    {
+        return $_SERVER['REQUEST_URI'];
+    }
+
+    /**
+     * 调整url自动生成规则
+     * @param string $action
+     * @param array $get
+     * @param bool $mergeGet
+     * @return string
+     */
+    public static function getSiteUrl($action = '', Array $get = array(), $mergeGet = false)
+    {
+        $url = '/' . trim($action, '/');
+        if ($mergeGet) {
+            $get = array_merge($_GET, $get);
+        }
+        if ($get) {
+            $get = urldecode(http_build_query($get));
+        }
+        if ($get) {
+            $url .= '?' . $get;
+        }
+        return $url;
+    }
+
+    /**
+     * 链接生成器
+     * @param $url
+     * @param array $get
+     * @param null $text
+     * @param null $attributes
+     * @return string
+     */
+    public static function anchor($url, Array $get = array(), $text = null, $attributes = null)
+    {
+        $url = '/' . trim($url, "/") . '/?' . http_build_query($get);
+        $text = strlen($text) <= 0 ? $url : $text;
+        $string = "<a href='$url' $attributes>$text</a>";
+        return $string;
+    }
+
+    /**
+     * 简易分页函数
+     * @param string $url 跳转地址
+     * @param int $totalPage 总页数
+     * @param int $current 当前页
+     * @param int $records 总记录数
+     * @param int $displayCount 总共显示的页数
+     * @return string
+     */
+    public static function pagination($url, $totalPage = 10, $current = 1, $records = 100, $displayCount = 10)
+    {
+        $current = ($current < $totalPage) ? $current : $totalPage;
+        $current = ($current >= 1) ? $current : 1;
+        $first = 1;
+        $half = ceil($displayCount / 2);
+        $end = $totalPage;
+
+        $prev = $current - 1;
+        $next = $current + 1;
+        foreach ($_GET as $key => $value) {
+            if($value === ''){
+                unset($_GET[$key]);
+            }
+        }
+        $linkSum = "<li><span class='sum'>总数:$records</span></li>";
+        $linkFirst = '<li>' . self::anchor($url, array_merge($_GET, array('page' => $first)), '&laquo;') . '</li>';
+        $linkPrev = '<li>' . self::anchor($url, array_merge($_GET, array('page' => $prev)), '&#8249;') . '</li>';
+        $linkNext = '<li>' . self::anchor($url, array_merge($_GET, array('page' => $next)), '&#8250;') . '</li>';
+        $linkEnd = '<li>' . self::anchor($url, array_merge($_GET, array('page' => $end)), '&raquo;') . '</li>';
+
+
+        if ($current <= $half) {
+            $linkFirst = '';
+            $last = $displayCount;
+            if ($last > $end) {
+                $last = $end;
+            }
+            if ($current == 1) {
+                $linkPrev = '';
+            }
+            if ($current == $end) {
+                $linkNext = '';
+            }
+        } elseif ($current > ($end - $half) AND $current <= $end) {
+            $first = $current - $half + 1;
+            $last = $end;
+            $linkEnd = '';
+            if ($current == $end) {
+                $linkNext = '';
+            }
+        } else {
+            $first = $current - $half + 1;
+            $last = $current + $half;
+        }
+
+        $string = null;
+        for ($i = $first; $i <= $last; $i++) {
+            if ($i == $current) {
+                $string .= '<li class="active">' . self::anchor($url, array_merge($_GET, array('page' => $i)), "$i", "class='active'") . '</li>';
+            } else {
+                $string .= '<li>' . self::anchor($url, array_merge($_GET, array('page' => $i)), "$i") . '</li>';
+            }
+        }
+
+        return $linkSum . $linkFirst . $linkPrev . $string . $linkNext . $linkEnd;
+    }
+
+    /**
+     * 下拉菜单生成器
+     * @param $list
+     * @param $key
+     * @param $text
+     * @param null $attributes
+     * @return null|string
+     */
+    public static function loopOutputSelect($list, $key = '', $text = '', $attributes = null)
+    {
+        if (!is_array($list) OR empty($list)) {
+            return null;
+        }
+        $string = null;
+        foreach ($list as $k => $v) {
+            $selected = null;
+            if ($k == $key) {
+                $selected = "selected='selected'";
+            }
+            $string .= "<option value='$k' $selected >$v</option>\n";
+        }
+        if (!empty($text)) {
+            $string = "<select name='$text' id='$text' $attributes>" . $string . "</select>";
+        }
+        return $string;
+    }
+
+    /**
+     * 单选按钮生成器
+     * @param $list
+     * @param $key
+     * @param $text
+     * @param null $attributes
+     * @return null|string
+     */
+    public static function loopOutputRadio($list, $key = '', $text = '', $attributes = null)
+    {
+        if (!is_array($list) OR empty($list)) {
+            return null;
+        }
+        $i = 0;
+        $string = null;
+        foreach ($list as $k => $v) {
+            $checked = null;
+            if ($k == $key) {
+                $checked = "checked='checked'";
+            }
+            $string .= "<input type='radio' name='$text' value='$k' id='$text-$i' $checked $attributes /><label for='$text-$i'>$v</label>\n";
+            $i++;
+        }
+        return $string;
+    }
+
+    /**
+     * 多选框生成器
+     * @param $list
+     * @param string $key
+     * @param string $text
+     * @param string $attributes
+     * @param string $separator
+     * @return bool|string
+     */
+    public static function loopOutputCheckbox($list, $key = "", $text = '', $attributes = "", $separator = "\n")
+    {
+        if (!is_array($list) || empty($list)) {
+            return false;
+        }
+        $string = '';
+
+        if (!is_array($key)) {
+            $key = array($key);
+        }
+        foreach ($list as $k => $v) {
+            $string .= "<input type='checkbox' name='$text" . "[]' value='$k' ";
+            $string .= in_array($k, $key) ? " checked ='checked'" : "";
+            $string .= $attributes;
+            $string .= " />$v" . $separator;
+        }
+        return $string;
+    }
+    
+    /**
+     * 获取强密码
+     * @param int $length
+     * @return string
+     */
+    public static function getStrongCode($length = 8)
+    {
+        $length = $length > 8 ? intval($length) : 8;
+        $arr = array();
+        $arr[] = self::getRandoms($length - 4);
+        $arr[] = strtoupper(self::getRandoms(1, 'char'));
+        $arr[] = strtolower(self::getRandoms(1, 'char'));
+        $arr[] = mt_rand(0, 9);
+        $special = '!@#$%^*()';
+        $arr[] = $special{mt_rand(0, 11)};
+        shuffle($arr);
+        return implode('', $arr);
+    }
+    /**
+     * 获取随机数
+     *
+     * @param int $length
+     * @param string $type (可选)
+     * @return string
+     */
+    public static function getRandoms($length = 4, $type = '')
+    {
+        $returnStr = '';
+        $pattern = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLOMNOPQRSTUVWXYZ';
+        if ($type == 'char') {
+            $start = 10;
+            $end = 61;
+        } elseif ($type == 'num') {
+            $start = 0;
+            $end = 9;
+        } else {
+            $start = 0;
+            $end = 61;
+        }
+        for ($i = 0; $i < $length; $i++) {
+            $returnStr .= $pattern{mt_rand($start, $end)}; // 生成php随机数
+        }
+        return $returnStr;
+    }
+
+}

+ 31 - 0
Frame/Helper/Location.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace PhpLife\Frame\Helper;
+
+/**
+ * 地理位置
+ */
+class Location extends \PhpLife\Frame\Helper
+{
+
+    /**
+     * 求两个已知经纬度之间的距离,单位为米
+     * @url http://www.phpernote.com/php-function/890.html
+     * @param lng1 ,lng2 经度
+     * @param lat1 ,lat2 纬度
+     * @return float 距离,单位米
+     **/
+    public static function getDistance($lng1, $lat1, $lng2, $lat2)
+    {
+        //将角度转为弧度, deg2rad()函数将角度转换为弧度
+        $radLat1 = deg2rad($lat1);
+        $radLat2 = deg2rad($lat2);
+        $radLng1 = deg2rad($lng1);
+        $radLng2 = deg2rad($lng2);
+        $a = $radLat1 - $radLat2;
+        $b = $radLng1 - $radLng2;
+        $s = 2 * asin(sqrt(pow(sin($a / 2), 2) + cos($radLat1) * cos($radLat2) * pow(sin($b / 2), 2))) * 6378.137 * 1000;
+        return $s;
+    }
+
+}

+ 11 - 0
Frame/Helper/Network.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace PhpLife\Frame\Helper;
+
+/**
+ * 网络操作
+ */
+class Network extends \PhpLife\Frame\Helper
+{
+
+}

+ 54 - 0
Frame/Helper/Validate.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace PhpLife\Frame\Helper;
+
+/**
+ * 校验类, 尚未严格验证
+ * @package PhpLife\Frame\Helper
+ */
+class Validate extends \PhpLife\Frame\Helper
+{
+
+    public static function isUrl($url)
+    {
+        $regex = '/((https?|ftp|news):\/\/)?([a-z]*([a-z0-9\-]*[\.。])+([a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel)|(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))(\/[a-z0-9_\-\.~]+)*(\/([a-z0-9_\-\.]*)(\?[a-z0-9+_\-\.%=&]*)?)?(#[a-z][a-z0-9_]*)?/i';
+        return preg_match($regex, $url);
+    }
+
+    public static function isEmail($email)
+    {
+        return preg_match("/^(\w+((-\w+)|(\w.\w+))*)\@(\w+((\.|-)\w+)*\.\w+$)/", $email);
+    }
+
+    public static function isMobile($phone)
+    {
+        return preg_match("/^1\d{10}$/", $phone);
+    }
+
+    public static function isCountryMobile($phone)
+    {
+        return preg_match("/^[1-9]{1}\d{3,14}$/", $phone);
+    }
+
+
+    public static function isDate($str)
+    {
+        if (!preg_match("/^\d{4}-\d{2}-\d{2}$/", $str)) {
+            return FALSE;
+        }
+        $stamp = strtotime($str);
+        if (!is_numeric($stamp)) {
+            return FALSE;
+        }
+        if (checkdate(date('m', $stamp), date('d', $stamp), date('Y', $stamp))) {
+            return TRUE;
+        }
+        return FALSE;
+    }
+
+    public static function isIntval($mixed)
+    {
+        return (preg_match('/^\d*$/', $mixed) == 1);
+    }
+
+}

+ 92 - 0
Frame/Init.php

@@ -0,0 +1,92 @@
+<?php
+
+namespace PhpLife\Frame;
+
+// 引入全局常量
+
+require_once dirname(__DIR__) . '/constants.php';
+// 引入全局函数
+require_once dirname(__DIR__) . '/functions.php';
+// 引入自动加载
+require_once dirname(__DIR__) . '/Autoload.php';
+
+class Init
+{
+    /**
+     * 环境变量设置
+     * @param $configFile
+     * @return bool
+     */
+    public static function setEnvironment($configFile = '')
+    {
+        // 设置错误显示
+        error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING & ~E_STRICT);
+        ini_set('display_errors', false);
+        // 无需浏览器缓存
+        header("Cache-Control: no-cache, must-revalidate");
+        header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
+        // 设置时区和编码
+        date_default_timezone_set('Asia/Shanghai');
+        setlocale(LC_ALL, 'en_US.utf-8');
+        header('Content-type:text/html;charset=utf-8');
+        // 设置错误捕获机制
+        set_error_handler('\PhpLife\Frame\Init::errorHandler');
+        set_exception_handler('\PhpLife\Frame\Init::exceptionHandler');
+
+        // 如果没有附加配置文件的话则可以结束配置了,反之,则要重新设置一些变量
+        if (!$configFile || !file_exists($configFile)) {
+            return true;
+        }
+        // 解析配置文件
+        $config = parse_ini_file($configFile);
+        if (!$config) {
+            return true;
+        }
+        // 调整错误显示
+        if (isset($config['display_errors'])) {
+            ini_set('display_errors', intval($config['display_errors']));
+        }
+        // 设置日志路径
+        if (isset($config['log_path'])) {
+            Logger::setLogPath($config['log_path']);
+        }
+        // 设置日志等级
+        if (isset($config['log_level'])) {
+            Logger::setLogLevel(intval($config['log_level']));
+        }
+        // 设置环境变量
+        if (isset($config['mode'])) {
+            Request::setMode($config['mode']);
+        }
+        return true;
+    }
+
+    /**
+     * 异常捕获
+     * @param $ex
+     */
+    public static function exceptionHandler($ex)
+    {
+        Logger::error($ex, 'uncaught_exception');
+    }
+
+    /**
+     * 错误捕获
+     * @param $errorCode
+     * @param $errorMessage
+     * @param $errorFile
+     * @param $errorLine
+     * @return bool
+     */
+    public static function errorHandler($errorCode, $errorMessage, $errorFile, $errorLine)
+    {
+        // 忽略notice错误
+        if (in_array($errorCode, array(E_STRICT))) {
+            return;
+        }
+        $trace = debug_backtrace();
+        $data = array('code' => $errorCode, 'message' => $errorMessage, 'file' => $errorFile, 'line' => $errorLine, 'trace' => $trace);
+        Logger::debug($data, 'uncaught_error', $errorCode);
+    }
+
+}

+ 58 - 0
Frame/Library.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace PhpLife\Frame;
+
+/**
+ * Library的父类, 基础类库
+ */
+class Library
+{
+
+    protected $configClass = null;
+    protected $configSection = null;
+
+    /**
+     * 构造函数
+     * @param null $configClass
+     * @param null $configSection
+     */
+    public function __construct($configClass = null, $configSection = null)
+    {
+        if ($configClass) {
+            // 判断是否是全路径
+            if(substr($configClass, 0, strlen(Dispatcher::CONFIG_PREFIX)) != Dispatcher::CONFIG_PREFIX){
+                $this->configClass = Dispatcher::CONFIG_PREFIX . '\\' . trim($configClass, '\\');
+            }else{
+                $this->configClass = $configClass;
+            }
+        }
+        // 设置配置文件内容片段
+        if (!is_null($configSection)) {
+            $this->configSection = $configSection;
+        }
+    }
+
+    /**
+     * 根据索引取出详细配置信息
+     */
+    protected function getConfig()
+    {
+        $class = $this->configClass;
+        if (class_exists($class) && method_exists($class, 'getData')) {
+            return $class::getData($this->configSection);
+        }
+        return array();
+    }
+
+    /**
+     * 获取一组标识
+     * @param $flag
+     * @return string
+     */
+    protected function getFlag($flag = false)
+    {
+        $flag = $this->configClass . ':' . $this->configSection . ':' . intval($flag);
+        return $flag;
+    }
+
+}

+ 83 - 0
Frame/Library/AntiSpam.php

@@ -0,0 +1,83 @@
+<?php
+
+namespace PhpLife\Frame\Library;
+
+/**
+ * IP频率限制,一定程度上反垃圾
+ */
+class AntiSpam
+{
+    const ANTI_SPAM_MINUTE = 'anti_spam:minute';
+    const ANTI_SPAM_HOUR = 'anti_spam:hour';
+    const ANTI_SPAM_DAY = 'anti_spam:year';
+    const ANTI_SPAM_LOCKED = 'anti_spam:locked';
+    // 频率设置
+    protected static $limits = array(
+        self::ANTI_SPAM_MINUTE => 500,
+        self::ANTI_SPAM_HOUR => 2000,
+        self::ANTI_SPAM_DAY => 10000,
+    );
+    // 正常过期
+    protected static $expire = array(
+        self::ANTI_SPAM_MINUTE => 60,
+        self::ANTI_SPAM_HOUR => 3600,
+        self::ANTI_SPAM_DAY => 86400,
+    );
+    // 封禁时间
+    protected static $forbiddenTime = array(
+        self::ANTI_SPAM_MINUTE => 60,
+        self::ANTI_SPAM_HOUR => 3600,
+        self::ANTI_SPAM_DAY => 86400,
+    );
+
+    public static function check()
+    {
+        // 检查是否已经被封杀
+        if (self::getConnection()->get(self::getKey(self::ANTI_SPAM_LOCKED))) {
+            return FALSE;
+        }
+        foreach (self::$limits as $key => $limit) {
+            $expire = self::$expire[$key];
+            $key = self::getKey($key);
+            $value = self::getConnection()->get($key);
+            if (!$value) {
+                // 值不存在就设置
+                self::getConnection()->set($key, 1, $expire);
+            } elseif ($value > $limit) {
+                // 封禁时间
+                $expire = self::$forbiddenTime[$key];
+                self::getConnection()->set(self::getKey(self::ANTI_SPAM_LOCKED), 1, $expire);
+                return FALSE;
+            } else {
+                // 正常情况下+1
+                self::getConnection()->increment($key, 1);
+            }
+        }
+        return TRUE;
+    }
+
+    protected static function getKey($key)
+    {
+        $ip = \PhpLife\Frame\Helper::getClientIp(TRUE);
+        return $key . ':' . $ip;
+    }
+
+    protected static function getConnection()
+    {
+        static $connection = null;
+        if ($connection) {
+            return $connection;
+        }
+        $cfg = \PhpLife\Config\Memcache\AntiSpam::getData();
+        $connection = new \Memcached;
+        $connection->setOption(\Memcached::OPT_COMPRESSION, TRUE);
+        $connection->setOption(\Memcached::OPT_DISTRIBUTION, TRUE);
+        $connection->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, TRUE);
+        $connection->setOption(\Memcached::OPT_NO_BLOCK, TRUE);
+        $connection->setOption(\Memcached::OPT_CONNECT_TIMEOUT, 50);
+        $connection->setOption(\Memcached::OPT_POLL_TIMEOUT, 50);
+        $connection->addServers($cfg);
+        return $connection;
+    }
+
+}

+ 161 - 0
Frame/Library/BaseConverter.php

@@ -0,0 +1,161 @@
+<?php
+
+namespace PhpLife\Frame\Library;
+
+/**
+ * BASE62 解析类,专门针对长10进制与62进制互转
+ */
+class BaseConverter
+{
+    private static $string = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+    private static $encodeBlockSize = 7;
+    private static $decodeBlockSize = 4;
+
+    /**
+     * 批量从10进制转换到62进制
+     *
+     * @param array $mids
+     * @return array 以10进制mid为key的62进制mid数组
+     */
+    public static function multi_from10to62(array $mids)
+    {
+        $ret = array();
+        foreach ($mids as $mid) {
+            $ret[$mid] = self::from10to62($mid);
+        }
+        return $ret;
+    }
+
+    /**
+     * 批量从62进制转换到10进制
+     *
+     * @param array $mids
+     * @param bool $compat
+     * @param bool $for_mid
+     * @return array 以62进制mid为key的10进制mid数组
+     */
+    public static function multi_from62to10(array $mids, $compat = false, $for_mid = true)
+    {
+        $ret = array();
+        foreach ($mids as $mid) {
+            $ret[$mid] = self::from62to10($mid, $compat, $for_mid);
+        }
+        return $ret;
+    }
+
+    /**
+     * 将mid从10进制转换成62进制字符串
+     *
+     * @param    string $mid
+     * @return    string
+     */
+    public static function from10to62($mid)
+    {
+        $str = "";
+        $midlen = strlen($mid);
+        $segments = ceil($midlen / self::$encodeBlockSize);
+        $start = $midlen;
+        for ($i = 1; $i < $segments; $i += 1) {
+            $start -= self::$encodeBlockSize;
+            $seg = substr($mid, $start, self::$encodeBlockSize);
+            $seg = self::encodeSegment($seg);
+            $str = str_pad($seg, self::$decodeBlockSize, '0', STR_PAD_LEFT) . $str;
+        }
+        $str = self::encodeSegment(substr($mid, 0, $start)) . $str;
+        return $str;
+    }
+
+    /**
+     * 将62进制字符串转成10进制mid
+     *
+     * $compat $for_mid 参数意图不确定,直接从原作者处考过来。使用时直接使用默认参数即可。
+     *
+     * @param    string $str
+     * @param    bool $compat
+     * @param    bool $for_mid
+     * @return    string
+     */
+    public static function from62to10($str, $compat = false, $for_mid = true)
+    {
+        $mid = "";
+        $strlen = strlen($str);
+        $segments = ceil($strlen / self::$decodeBlockSize);
+        $start = $strlen;
+        for ($i = 1; $i < $segments; $i += 1) {
+            $start -= self::$decodeBlockSize;
+            $seg = substr($str, $start, self::$decodeBlockSize);
+            $seg = self::decodeSegment($seg);
+            /* Note by Rodin:
+             * 由于 decodeBlockSize设置为4,而 encodeBlockSize 设置为 7
+             * 而 base 62里4位最大的数为 ZZZZ,对应到10进制为 14776336,比encodeBlockSize
+             * 多1位……所以,将10进制转成62进制且在不足位时左补0,不会有任何问题。反之,
+             * 在62转成10进制的时候就可能会出现溢出,这是个潜在的bug,不清楚为什么目前没有
+             * bug爆出且当初采用了这种不标准不严谨的62进制转换法……
+             * 目前常用的mid里存在类似: l4ETJ4DfL (9位),在每segment大约出现大于 GZZZ 的串以后
+             * 总位数就会不符合目前的预期……
+             * 例如:
+             * ZZZZZZZZZ转换成10进制会成为 611477633614776336,总位数已经达到了20位,而常见的7位
+             * mid的62进制数字通常只有16位
+             */
+            $mid = str_pad($seg, self::$encodeBlockSize, '0', STR_PAD_LEFT) . $mid;
+        }
+        $mid = self::decodeSegment(substr($str, 0, $start)) . $mid;
+
+        //判断v3、v4版本mid
+        if ($for_mid) {
+            $midlen = strlen($mid);
+            $first = substr($mid, 0, 1);
+            if ($midlen == 16 && ($first == '3' || $first == '4')) {
+                return $mid;
+            }
+            if ($midlen == 19 && $first == '5') {
+                return $mid;
+            }
+        }
+        //end
+        if ($compat && !in_array(substr($mid, 0, 3), array('109', '110', '201', '211', '221', '231', '241'))) {
+            $mid = self::decodeSegment(substr($str, 0, 4)) . self::decodeSegment(substr($str, 4));
+        }
+        if ($for_mid) {
+            if (substr($mid, 0, 1) == '1' && substr($mid, 7, 1) == '0') {
+                $mid = substr($mid, 0, 7) . substr($mid, 8);
+            }
+        }
+        return $mid;
+    }
+
+    /**
+     * 将10进制转换成62进制
+     *
+     * @param    string $str 10进制字符串
+     * @return    string
+     */
+    private static function encodeSegment($str)
+    {
+        $out = '';
+        while ($str > 0) {
+            $idx = $str % 62;
+            $out = substr(self::$string, $idx, 1) . $out;
+            $str = floor($str / 62);
+        }
+        return $out;
+    }
+
+    /**
+     * 将62进制转换成10进制
+     *
+     * @param    string $str 62进制字符串
+     * @return    string
+     */
+    private static function decodeSegment($str)
+    {
+        $out = 0;
+        $base = 1;
+        for ($t = strlen($str) - 1; $t >= 0; $t -= 1) {
+            $out = $out + $base * strpos(self::$string, substr($str, $t, 1));
+            $base *= 62;
+        }
+        return strval($out);
+    }
+
+}

+ 8 - 0
Frame/Library/Benchmark.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace PhpLife\Frame\Library;
+
+class Benchmark extends \PhpLife\Frame\Library
+{
+
+}

+ 19 - 0
Frame/Library/Curl.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace PhpLife\Frame\Library;
+
+class Curl extends \PhpLife\Frame\Library
+{
+
+
+    public static function get()
+    {
+
+    }
+
+    public static function post()
+    {
+
+    }
+
+}

+ 44 - 0
Frame/Library/Memcache.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace PhpLife\Frame\Library;
+
+/**
+ * 缓存设置,使用memcache缓存数据
+ */
+class Memcache extends \PhpLife\Frame\Library
+{
+
+    /**
+     * 获取连接池
+     * @return \Memcached
+     */
+    public function getConnection()
+    {
+        static $pool = array();
+        $connectionName = $this->getFlag(true);
+        if (!isset($pool[$connectionName]) || ($pool[$connectionName] == false)) {
+            $cfg = $this->getConfig();
+            $pool[$connectionName] = $this->connect($cfg);
+        }
+        return $pool[$connectionName];
+    }
+
+    /**
+     * 连接Memcache
+     * @param $cfg
+     * @return \Memcached
+     */
+    protected function connect($cfg)
+    {
+        $connection = new \Memcached;
+        $connection->setOption(\Memcached::OPT_COMPRESSION, true);
+        $connection->setOption(\Memcached::OPT_DISTRIBUTION, true);
+        $connection->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
+        $connection->setOption(\Memcached::OPT_NO_BLOCK, true);
+        $connection->setOption(\Memcached::OPT_CONNECT_TIMEOUT, 50);
+        $connection->setOption(\Memcached::OPT_POLL_TIMEOUT, 50);
+        $connection->addServers($cfg);
+        return $connection;
+    }
+
+}

+ 41 - 0
Frame/Library/Memcached.php

@@ -0,0 +1,41 @@
+<?php
+namespace PhpLife\Frame\Library;
+
+/**
+ * 缓存设置,使用memcache缓存数据
+ */
+class Memcached extends \PhpLife\Frame\Library
+{
+
+    /**
+     * 获取连接池
+     * 
+     * @return \Memcached
+     */
+    public function getConnection()
+    {
+        static $pool = array();
+        $connectionName = $this->getFlag(true);
+        if (! isset($pool[$connectionName]) || ($pool[$connectionName] == false)) {
+            $cfg = $this->getConfig();
+            $pool[$connectionName] = $this->connect($cfg);
+        }
+        return $pool[$connectionName];
+    }
+
+    /**
+     * 连接Memcache
+     * 
+     * @param
+     *            $cfg
+     * @return \Memcached
+     */
+    protected function connect($cfg)
+    {
+        $connection = new \Memcache();
+        foreach ($cfg as $val) {
+            $connection->addServer($val['host'], $val['port']);
+        }
+        return $connection;
+    }
+}

+ 252 - 0
Frame/Library/MySQL.php

@@ -0,0 +1,252 @@
+<?php
+namespace PhpLife\Frame\Library;
+
+use PhpLife\Frame\Logger;
+use PhpLife\Frame\Exception;
+
+/**
+ * 处理MySQL连接、查询和写入
+ *
+ * @package PhpLife\Frame\Library
+ */
+class MySQL extends \PhpLife\Frame\Library
+{
+
+    private static $throwException = false;
+    // 出错后是否抛出异常
+    private static $inTransaction = false;
+    // 是否在事务中
+    private static $newConnection = false;
+    // 是否使用新连接
+    const CONNECTION_TIMEOUT = 21600;
+    // 每个连接最多可以使用时间,超过这个时间之后创建新连接
+    protected $exception;
+
+    /**
+     * 执行查询操作
+     *
+     * @param string $sql            
+     * @param array $data            
+     * @param bool $isMaster            
+     * @param bool $fetchAll            
+     * @throws \Exception
+     * @return array|mixed
+     */
+    public function read($sql, $data, $isMaster = false, $fetchAll = true)
+    {
+        try {
+            $stmt = $this->getConnection($isMaster)->prepare($sql);
+            $stmt->execute($data);
+            $stmt->setFetchMode(\PDO::FETCH_ASSOC);
+            if ($fetchAll) {
+                $result = $stmt->fetchAll();
+            } else {
+                $result = $stmt->fetch();
+            }
+        } catch (\Exception $ex) {
+            $result = null;
+            $this->exception = $ex;
+            $message = array(
+                'info' => $ex->getCode() . '=>' . $ex->getMessage() . ';' . json_encode($ex->errorInfo),
+                'sql' => $sql,
+                'data' => $data
+            );
+            Logger::error($message, 'mysql.read.error');
+            // 如果是失去连接的错误,则进行重试
+            if (in_array($ex->errorInfo[1], array(
+                '2006',
+                '2013'
+            ))) {
+                self::$newConnection = true;
+                return $this->read($sql, $data, $isMaster, $fetchAll);
+            }
+            // 处理事务时抛出异常
+            if (self::$throwException) {
+                throw new \Exception($ex->getCode() . '=>' . $ex->getMessage(), '90203');
+            }
+        }
+        Logger::debug(array(
+            'sql' => $sql,
+            'data' => $data,
+            'result' => $result
+        ), 'mysql.read.debug');
+        return $result;
+    }
+
+    /**
+     * 执行写入操作
+     *
+     * @param string $sql            
+     * @param
+     *            $data
+     * @throws \Exception
+     * @return boolean
+     */
+    public function write($sql, $data)
+    {
+        try {
+            $stmt = $this->getConnection(true)->prepare($sql);
+            $stmt->execute($data);
+            $result = $stmt->rowCount();
+        } catch (\Exception $ex) {
+            $result = false;
+            $this->exception = $ex;
+            $message = array(
+                'info' => $ex->getCode() . '=>' . $ex->getMessage() . ';' . json_encode($ex->errorInfo),
+                'sql' => $sql,
+                'data' => $data
+            );
+            Logger::error($message, 'mysql.write.error');
+            // 如果是失去连接的错误,则进行重试
+            if (in_array($ex->errorInfo[1], array(
+                '2006',
+                '2013'
+            ))) {
+                self::$newConnection = true;
+                return $this->write($sql, $data);
+            }
+            // 处理事务时抛出异常
+            if (self::$throwException) {
+                throw new \Exception($ex->getCode() . '=>' . $ex->getMessage(), '90202');
+            }
+        }
+        Logger::debug(array(
+            'sql' => $sql,
+            'data' => $data,
+            'result' => $result
+        ), 'mysql.write.debug');
+        return $result;
+    }
+
+    /**
+     * 事务处理, $callback中如果出错必需抛出异常,否则会真正执行
+     * 开始时会设置为主动抛出异常,结束时会设置为关闭抛出异常
+     *
+     * @param
+     *            $callback
+     * @param
+     *            $throwException
+     * @return bool
+     */
+    public function transaction($callback, $throwException = false)
+    {
+        self::$inTransaction = true; // 处于事务中
+        self::$throwException = $throwException; // 执行SQL出错时候是否抛出异常
+        try {
+            $connection = $this->getConnection(true);
+            $connection->beginTransaction();
+            $callback();
+            $connection->commit();
+            $result = true;
+        } catch (\Exception $ex) {
+            $result = false;
+            $message = array(
+                'info' => $ex->getCode() . '=>' . $ex->getMessage() . ';' . json_encode($ex->errorInfo)
+            );
+            Logger::error($message, 'mysql.transaction.error');
+            // 如果是失去连接的错误,则进行重试
+            if (in_array($ex->errorInfo[1], array(
+                '2006',
+                '2013'
+            ))) {
+                self::$newConnection = true;
+                return $this->transaction($callback, $throwException);
+            }
+         
+            try {
+                $this->getConnection(true)->rollBack();
+               
+            } catch (\Exception $e) {
+                
+                // rollback failed
+            }
+            if($throwException){
+                throw new Exception($ex->getMessage(),$ex->getCode());
+            }
+        }
+        self::$throwException = false; // 关闭抛出异常
+        self::$inTransaction = false; // 事务结束
+        Logger::debug(array(
+            'result' => $result
+        ), 'mysql.transaction.debug');
+        return $result;
+    }
+
+    /**
+     * 获取自增ID
+     *
+     * @return string
+     */
+    public function getLastInsertId()
+    {
+        return $this->getConnection(true)->lastInsertId();
+    }
+
+    /**
+     * 获取异常
+     *
+     * @return mixed|\Exception
+     */
+    public function getLastException()
+    {
+        return $this->exception;
+    }
+
+    /**
+     * 获取连接池
+     * 如果超时时处于事务中,则不主动断开重连
+     *
+     * @param bool $isMaster            
+     * @return \PDO
+     */
+    protected function getConnection($isMaster = false)
+    {
+        static $pool = array();
+        $connectionName = $this->getFlag($isMaster);
+        $connectionTimeout = $connectionName . '_timeout';
+        if (self::$newConnection || ! isset($pool[$connectionName]) || $pool[$connectionName] == false || (self::$inTransaction == false && (time() - $pool[$connectionTimeout] > self::CONNECTION_TIMEOUT))) {
+            $cfg = $this->getConfig();
+            if ($isMaster) {
+                $cfg = $cfg['master'];
+            } else {
+                $cfg = $cfg['slave_list'][array_rand($cfg['slave_list'])];
+            }
+            $pool[$connectionName] = $this->connect($cfg);
+            $pool[$connectionTimeout] = time();
+            self::$newConnection = false;
+        }
+        return $pool[$connectionName];
+    }
+
+    /**
+     * 连接数据库
+     *
+     * @param
+     *            $cfg
+     * @return \PDO
+     * @throws \Exception
+     */
+    protected function connect($cfg)
+    {
+        $opts = array(
+            \PDO::ATTR_AUTOCOMMIT => true,
+            \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
+            \PDO::ATTR_TIMEOUT => $cfg['timeout'],
+            \PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES '" . $cfg['charset'] . "'",
+            \PDO::ATTR_PERSISTENT => $cfg['persistent']?true : false
+        ); // 启用长连接
+        
+        $dsn = 'mysql:dbname=' . $cfg['dbname'] . ';host=' . $cfg['host'] . ';port=' . $cfg['port'];
+        try {
+            $db = new \PDO($dsn, $cfg['username'], $cfg['password'], $opts);
+        } catch (\PDOException $ex) {
+            $this->exception = $ex;
+            $message = "MySQL connection failed [" . $dsn . ';' . $cfg['username'] . ';password]' . $ex->getCode() . '=>' . $ex->getMessage();
+            Logger::emerg($message, 'mysql.connect.error');
+            throw new \PDOException($message, 90201);
+        }
+        $message = "MySQL connect [" . $dsn . ';' . $cfg['username'] . ';password]';
+        Logger::debug($message, 'mysql.connect.debug');
+        return $db;
+    }
+}

+ 402 - 0
Frame/Library/MySQL/Query.php

@@ -0,0 +1,402 @@
+<?php
+
+namespace PhpLife\Frame\Library\MySQL;
+
+/**
+ * 查询生成器
+ */
+class Query
+{
+
+    const PREPARE_PREFIX = ':prepared_';
+
+    private $tableName;
+    private $operation = 'SELECT';
+    private $field = array();
+    private $primaryKey = '';
+    private $data = array();
+    private $where;
+    private $orderBy;
+    private $groupBy;
+    private $limit = 'LIMIT 2000';
+    //替换后的映射表
+    private $prepared = array();
+    private $sql = '';
+
+    /**
+     * 实例
+     * @return Query
+     */
+    public static function newInstance()
+    {
+        return new Query();
+    }
+
+    /**
+     * 获取select
+     * @return Query
+     */
+    public function select()
+    {
+        $this->operation = 'SELECT';
+        return $this;
+    }
+
+    /**
+     * 获取count
+     * @return Query
+     */
+    public function count()
+    {
+        $this->operation = 'COUNT';
+        return $this;
+    }
+
+    /**
+     * 插入条记录
+     * @return Query
+     */
+    public function insert()
+    {
+        $this->operation = 'INSERT';
+        return $this;
+    }
+
+    /**
+     * replace into
+     * @return Query
+     */
+    public function replace()
+    {
+        $this->operation = 'REPLACE';
+        return $this;
+    }
+
+    /**
+     * 更新
+     * @return Query
+     */
+    public function update()
+    {
+        $this->operation = 'UPDATE';
+        return $this;
+    }
+
+    /**
+     * 删除
+     * @return Query
+     */
+    public function delete()
+    {
+        $this->operation = 'DELETE';
+        return $this;
+    }
+
+    /**
+     * 设置表名
+     * @param string $tableName
+     * @return Query
+     */
+    public function setTableName($tableName)
+    {
+        if ($tableName) {
+            $this->tableName = $this->_prepareFieldName($tableName);
+        }
+        return $this;
+    }
+
+    /**
+     * 设置字段
+     * @param Array /String $field
+     * @return Query
+     */
+    public function setField($field)
+    {
+        if ($field) {
+            if (!is_array($field)) {
+                $field = explode(',', $field);
+            }
+            $this->field = $field;
+        }
+        return $this;
+    }
+
+    /**
+     * 设置主键名
+     * @param $primaryKey
+     * @return $this
+     */
+    public function setPrimaryKey($primaryKey)
+    {
+        if ($primaryKey) {
+            $this->primaryKey = trim($primaryKey);
+        }
+        return $this;
+    }
+
+    /**
+     * 设置where条件
+     * @param array $where
+     * @param string $whereString
+     * @return Query
+     */
+    public function setWhere(Array $where, $whereString = '')
+    {
+        if (empty($where) && empty($whereString)) {
+            return $this;
+        }
+        if ($whereString) {
+            foreach ($where as $key => $val) {
+                $preparedKeys = array();
+                if (is_array($val)) {
+                    foreach ($val as $row) {
+                        $preparedKeys[] = $this->_prepareData($row);
+                    }
+                } else {
+                    $preparedKeys[] = $this->_prepareData($val);
+                }
+                $key = ':' . ltrim($key, ':');
+                $whereString = str_replace($key, implode(',', $preparedKeys), $whereString);
+            }
+            $this->where = 'WHERE ' . $whereString;
+        } elseif ($where) {
+            $data = array();
+            foreach ($where as $key => $val) {
+                if (is_array($val)) {
+                    $preparedKeys = array();
+                    foreach ($val as $row) {
+                        $preparedKeys[] = $this->_prepareData($row);
+                    }
+                    $data[] = $this->_prepareFieldName($key) . ' IN (' . implode(',', $preparedKeys) . ')';
+                } else {
+                    $data[] = $this->_prepareFieldName($key) . " = " . $this->_prepareData($val);
+                }
+            }
+            $this->where = 'WHERE ' . implode(' AND ', $data);
+        }
+        return $this;
+    }
+
+    /**
+     * 设置排序
+     * @param Array $orderBy = array('id' => 1); value 是 true 表示倒序,反之是正序
+     * @return Query
+     */
+    public function setOrderBy(Array $orderBy)
+    {
+        if ($orderBy && is_array($orderBy)) {
+            $tmp = array();
+            foreach ($orderBy as $key => $val) {
+                if (is_numeric($key)) {
+                    continue;
+                }
+                $order = $val ? 'DESC' : 'ASC';
+                $tmp[] = $this->_prepareFieldName($key) . ' ' . $order;
+            }
+            $tmp = implode(',', $tmp);
+            $this->orderBy = "ORDER BY " . $tmp;
+        }
+        return $this;
+    }
+
+    /**
+     * 设置范围
+     * @param int $limit
+     * @param int $offset
+     * @return Query
+     */
+    public function setLimit($limit = 2000, $offset = 0)
+    {
+        if ($limit) {
+            $this->limit = 'LIMIT ' . intval($offset) . ',' . intval($limit);
+        } else {
+            $this->limit = '';
+        }
+        return $this;
+    }
+
+    /**
+     * 设置分组
+     * @param Array /String $groupBy
+     * @return Query
+     */
+    public function setGroupBy($groupBy)
+    {
+        if (empty($groupBy)) {
+            return $this;
+        }
+        if (is_array($groupBy)) {
+            $tmp = array();
+            foreach ($groupBy as $v) {
+                $tmp[] = $this->_prepareFieldName($v);
+            }
+            $this->groupBy = 'GROUP BY ' . implode(",", $tmp);
+        } else {
+            $this->groupBy = 'GROUP BY ' . $this->_prepareFieldName($groupBy);
+        }
+        return $this;
+    }
+
+    /**
+     * 设置数据
+     * @param Array $data
+     * @return Query
+     */
+    public function setData(Array $data)
+    {
+        if ($data) {
+            $this->data = $data;
+        }
+        return $this;
+    }
+
+    /**
+     * 获取相关SQL
+     * @param boolean $raw
+     * @return String $sql
+     */
+    public function getSQL($raw = false)
+    {
+        $sql = $this->_getSQL();
+        if ($raw) {
+            foreach ($this->prepared as $key => $value) {
+                $key = ':' . trim($key, ':');
+                if (is_array($value)) {
+                    foreach ($value as $k => $v) {
+                        $value[$k] = addslashes($v);
+                    }
+                    $value = implode(',', $value);
+                } else {
+                    $value = "'" . addslashes($value) . "'";
+                }
+                $sql = str_replace($key, $value, $sql);
+            }
+        }
+        return $sql;
+    }
+
+    /**
+     * 返回Prepare过的数据
+     * @return Array
+     */
+    public function getData()
+    {
+        $sql = $this->_getSQL();
+        $data = array();
+        foreach ($this->prepared as $key => $value) {
+            if (strpos($sql, $key) !== false) {
+                $key = ltrim($key, ':');
+                $data[$key] = $value;
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * 拼SQL
+     * @return string
+     */
+    private function _getSQL()
+    {
+        if ($this->sql) {
+            return $this->sql;
+        }
+        switch ($this->operation) {
+            case 'INSERT':
+                $sql = 'INSERT INTO  ' . $this->tableName . ' SET ' . $this->_getData();
+                break;
+            case 'UPDATE':
+                $sql = 'UPDATE ' . $this->tableName . ' SET ' . $this->_getData() . ' ' . $this->where;
+                break;
+            case 'REPLACE':
+                $sql = 'REPLACE INTO  ' . $this->tableName . ' SET ' . $this->_getData();
+                break;
+            case 'DELETE':
+                $sql = 'DELETE FROM ' . $this->tableName . ' ' . $this->where;
+                break;
+            case 'COUNT':
+                $sql = 'SELECT COUNT(*) AS `count`  FROM ' . $this->tableName . ' ' . $this->where . ' ' . $this->groupBy;
+                break;
+            case 'SELECT':
+            default:
+                $sql = 'SELECT ' . $this->_getField() . ' FROM ' . $this->tableName . ' ' . $this->where . ' ' . $this->orderBy . ' ' . $this->groupBy . ' ' . $this->limit;
+                break;
+        }
+        return $this->sql = $sql;
+    }
+
+    /**
+     * 获取字段名称
+     * @return String
+     */
+    private function _getField()
+    {
+        if (empty($this->field)) {
+            $field = '*';
+        } else {
+            $tmp = array();
+            foreach ($this->field as $v) {
+                if(strstr($v,'sum')){
+                    $tmp[] = $v;
+                }else{
+                    $tmp[] = $this->_prepareFieldName($v);
+                }
+            }
+            $field = implode(",", $tmp);
+        }
+        return $field;
+    }
+
+    /**
+     * 获取prepare后的数据
+     * @return String
+     */
+    private function _getData()
+    {
+        $data = array();
+        foreach ($this->data as $key => $value) {
+            //简单字段检查
+            if (($this->field AND !in_array($key, $this->field)) || is_null($value)) {
+                continue;
+            }
+            $data[$key] = $this->_prepareFieldName($key) . ' = ' . $this->_prepareData($value);
+        }
+        $data = implode(",", $data);
+        return $data;
+    }
+
+    /**
+     * 过滤字段名,表名
+     * @param String $value
+     * @return string
+     */
+    private function _prepareFieldName($value)
+    {
+        $value = strtr($value, array(' ' => '', '`' => ''));
+        $value = "`" . $value . "`";
+        return $value;
+    }
+
+    /**
+     * prepare替换
+     * @param String $value
+     * @return string
+     */
+    private function _prepareData($value)
+    {
+        $count = count($this->prepared);
+        $key = self::PREPARE_PREFIX . $count . '_';
+        $this->prepared[$key] = $value;
+        return $key;
+    }
+
+    /**
+     * 魔术输出方法
+     * @return String
+     */
+    public function __toString()
+    {
+        return $this->getSQL();
+    }
+
+}

+ 63 - 0
Frame/Library/Redis.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace PhpLife\Frame\Library;
+
+/**
+ * Redis连接
+ */
+class Redis extends \PhpLife\Frame\Library
+{
+    protected $exception;
+
+    public function getLastException()
+    {
+        return $this->exception;
+    }
+
+    /**
+     * 获取redis连接池
+     * @param bool $isMaster
+     * @return \Redis
+     */
+    public function getConnection($isMaster = false)
+    {
+        static $pool = array();
+        $cacheName = $this->getFlag($isMaster);
+        if (!isset($pool[$cacheName]) || $pool[$cacheName] == false) {
+            $cfg = $this->getConfig();
+            if ($isMaster) {
+                $cfg = $cfg['master'];
+            } else {
+                $cfg = $cfg['slave_list'][array_rand($cfg['slave_list'])];
+            }
+            $pool[$cacheName] = $this->connect($cfg);
+        }
+        return $pool[$cacheName];
+    }
+
+    /**
+     * 连接Redis
+     * @param $cfg
+     * @return \Redis
+     * @throws \Exception
+     */
+    protected function connect($cfg)
+    {
+        try {
+            $redis = new \Redis();
+            if (isset($cfg['pconnect']) && $cfg['pconnect'] == 1) {
+                $redis->pconnect($cfg['host'], $cfg['port'], $cfg['timeout']);
+            } else {
+                $redis->connect($cfg['host'], $cfg['port'], $cfg['timeout']);
+            }
+            if (isset($cfg['password']) && $cfg['password']) {
+                $redis->auth($cfg['password']);
+            }
+        } catch (\Exception $ex) {
+            $this->exception = $ex;
+            $message = "Redis connection failed : [" . $cfg['host'] . ';' . $cfg['port'] . ']';
+            throw new \Exception($message, 90301);
+        }
+        return $redis;
+    }
+}

+ 79 - 0
Frame/Library/Ticket.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace PhpLife\Frame\Library;
+
+/**
+ * 发号器类
+ */
+class Ticket extends \PhpLife\Frame\Library
+{
+    const TICKET_START_TIME = 1262275200; // 2010-1-1 00:00:00
+    const DEFAULT_SERVER_ID = 9; //使用微秒发出去的id编号
+    const TICKET_KEY_PREFIX = 'id_';
+
+    /**
+     * 获取发号
+     * @param $businessId
+     * @return mixed
+     * @throws \Exception
+     */
+    public static function getTicket($businessId = 0)
+    {
+//         for ($i = 0; $i < 10; $i++) {
+//             $result = self::getFromRemote($businessId);
+//             if ($result) {
+//                 return $result;
+//             }
+//         }
+//         \PhpLife\Frame\Logger::crit('ticket error', 'ticket');
+        $result = self::getTicketFromTimestamp();
+        return $result;
+    }
+
+    /**
+     * 从发号器获取号码
+     * @param $businessId
+     * @return mixed
+     */
+    protected static function getFromRemote($businessId = 0)
+    {
+        $configClass = 'Memcache\\Ticket';
+        $memcached = new Memcache($configClass);
+        $connection = $memcached->getConnection();
+        $key = self::TICKET_KEY_PREFIX . intval($businessId);
+        $result = $connection->get($key);
+        return $result;
+    }
+
+    /**
+     * 获取发号
+     * @return int
+     */
+    protected static function getTicketFromTimestamp()
+    {
+        $time = self::getTimestamp();
+        $time[0] = $time[0] - self::TICKET_START_TIME;
+        $time = implode('', $time);
+        $rand = sprintf('%03d', rand(0, 999));
+        $code = $time . $rand . self::DEFAULT_SERVER_ID;
+        return $code;
+    }
+
+    /**
+     * 获取时间戳信息,
+     * 返回值分别是秒,毫秒,微秒
+     * @return array
+     */
+    protected static function getTimestamp()
+    {
+        list($microTime, $seconds) = explode(" ", microtime());
+        $microTime = intval($microTime * 1000000);
+        $timeStamp = array(
+            0 => $seconds,
+            1 => substr($microTime, 0, 3),
+            2 => substr($microTime, 3, 3),
+        );
+        return $timeStamp;
+    }
+
+}

+ 414 - 0
Frame/Logger.php

@@ -0,0 +1,414 @@
+<?php
+namespace PhpLife\Frame;
+
+/**
+ * 简单日志记录
+ */
+class Logger
+{
+
+    private static $logPath = '/www/privdata/api.makeup.meitu.com/log/';
+
+    private static $logLevel = LOG_ERR;
+
+    private static $maps = array(
+        LOG_DEBUG => 'debug',
+        LOG_INFO => 'info',
+        LOG_NOTICE => 'notice',
+        LOG_WARNING => 'warning',
+        LOG_ERR => 'err',
+        LOG_CRIT => 'crit',
+        LOG_ALERT => 'alert',
+        LOG_EMERG => 'emerg'
+    );
+
+    /**
+     * 获取单例
+     *
+     * @return Logger
+     */
+    public static function getInstance()
+    {
+        static $instance = null;
+        if (is_null($instance)) {
+            $instance = new Logger();
+        }
+        return $instance;
+    }
+
+    /**
+     * 设置日志记录错误等级
+     *
+     * @param int $level
+     * @return Logger
+     */
+    public static function setLogLevel($level = LOG_DEBUG)
+    {
+        self::$logLevel = intval($level);
+        return self::getInstance();
+    }
+
+    /**
+     * 设置日志记录路径
+     *
+     * @param
+     *            $path
+     * @return Logger
+     */
+    public static function setLogPath($path)
+    {
+        $path = trim($path);
+        if (! empty($path) and is_writable($path)) {
+            self::$logPath = $path;
+        }
+        return self::getInstance();
+    }
+
+    public static function debug($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_DEBUG);
+    }
+
+    public static function info($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_INFO);
+    }
+
+    public static function notice($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_NOTICE);
+    }
+
+    public static function warning($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_WARNING);
+    }
+
+    public static function error($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_ERR);
+    }
+
+    public static function crit($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_CRIT);
+    }
+
+    public static function alert($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_ALERT);
+    }
+
+    public static function emerg($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_EMERG);
+    }
+
+    public static function log($message = '', $filename = null, $logType = LOG_DEBUG)
+    {
+        // 达不到错误记录级别的不与记录,主要是线上环境忽略debug信息
+        if ($logType > self::getLogLevel()) {
+            return false;
+        }
+        self::file($message, $filename, $logType);
+        return true;
+    }
+
+    /**
+     * 自定义记录日志
+     *
+     * @param
+     *            $message
+     * @param
+     *            $filename
+     * @return bool
+     */
+    public static function setLog($email = NULL, $message = "", $filename = "")
+    {
+        if (empty($message) || empty($filename))
+            return false;
+        $filename = self::getLogPath($filename);
+        $message = "[" . date('Y-m-d H:i:s') . "]" . "-[IP:" . self::getClientIp() . "]" . "-[http://" . self::getServerIp() . $_SERVER['REQUEST_URI'] . "]" . ($email ? "-[EMAIL:$email]" : "") . "-[操作:$message]" . "\n";
+
+        return error_log($message, 3, $filename);
+    }
+
+    /**
+     * 自定义记录日志
+     *
+     * @param
+     *            $message
+     * @param
+     *            $filename
+     * @return bool
+     */
+    public static function logApi($message = "", $filename = "")
+    {
+        if (empty($message) || empty($filename))
+            return false;
+        $filename = self::getLogPath($filename);
+        $message = "[" . date('Y-m-d H:i:s') . "]" . "-[IP:" . self::getClientIp() . "]" . "-[http://" . self::getServerIp() . $_SERVER['REQUEST_URI'] . "]" . '[post:' . http_build_query($_POST) . ']' . '[get:' . http_build_query($_GET) . ']' . "-[msg:$message]" . "\n";
+        return error_log($message, 3, $filename);
+    }
+
+    /**
+     * 记录大数据的日志
+     *
+     * @param
+     *            $message
+     * @param
+     *            $filename
+     * @return bool
+     */
+    public static function bigLog($data)
+    {
+        if (empty($data))
+            return false;
+        $filename = self::getBigdataLogPath();
+        $message = json_encode($data)."\r\n";
+        return error_log($message, 3, $filename);
+    }
+    /**
+     * 记录简单的日志内容
+     *
+     * @param
+     *            $message
+     * @param
+     *            $filename
+     * @return bool
+     */
+    public static function singleLog($data,$filename)
+    {
+        if (is_array($data)){
+            $msg=http_build_query($data);
+        }else {
+            $msg=$data;
+        }
+        $filename = self::getLogPath($filename);
+        $message = "[" . date('Y-m-d H:i:s') . "][".$_SERVER['REQUEST_URI'] . "]-[IP:" . self::getClientIp() . "][data:".$msg."]\r\n";
+        return error_log($message, 3, $filename);
+    }
+
+    /**
+     * 使用文件记录推送日志
+     *
+     * @param string $message
+     * @param string $filename
+     * @param int $logType
+     * @return bool
+     */
+    private static function file($message = '', $filename = null, $logType = LOG_INFO)
+    {
+        if (! $filename) {
+            $logType = intval($logType);
+            $filename = self::$maps[$logType];
+        }
+        $filename = self::getLogPath($filename);
+        $message = self::formatFields($message);
+        return error_log($message, 3, $filename);
+    }
+
+    /**
+     * 格式日志记录
+     *
+     * @param string $message
+     * @return string
+     */
+    private static function formatFields($message)
+    {
+        if ($message instanceof \Exception) {
+            $trace = $message->getTrace();
+            $trace = $trace[1];
+            $trace = ' ;trace => ' . json_encode($trace);
+            $message = $message->getCode() . ' => ' . $message->getMessage() . $trace;
+        } elseif ($message) {
+            $message = (is_string($message) || is_numeric($message)) ? $message : json_encode($message);
+        } else {
+            $message = ' - ';
+        }
+
+        $cli = '';
+        if (empty($_SERVER['argv'])) {
+            $cli = "curl 'http://" . self::getServerIp() . $_SERVER['REQUEST_URI'] . "'";
+            // 解析头信息
+            $headers = self::getHeaders();
+            if ($headers) {
+                foreach ($headers as $k => $v) {
+                    $cli .= " -H '$k:$v'";
+                }
+            }
+            if ($_POST) {
+                $cli .= " -d '" . http_build_query($_POST) . "'";
+            }
+            if ($_COOKIE) {
+                $cookie = array();
+                foreach ($_COOKIE as $k => $v) {
+                    $cookie[] = $k . '=' . $v;
+                }
+                $cookie = implode(';', $cookie);
+                $cli .= " -b '" . $cookie . "'";
+            }
+        } else {
+            $cli = 'php ' . $_SERVER['PWD'] . DIRECTORY_SEPARATOR . implode(" ", $_SERVER['argv']);
+        }
+        $data = array();
+        $data['time'] = date('Y-m-d H:i:s');
+        $data['message'] = trim($message);
+        $data['client_ip'] = self::getClientIp();
+        $data['cli'] = $cli;
+        $string = '[' . implode("]  [", $data) . ']' . "\n";
+        return $string;
+    }
+
+    /**
+     * 获取错误等级
+     *
+     * @return int
+     */
+    private static function getLogLevel()
+    {
+        return self::$logLevel;
+    }
+
+    /**
+     * 获取日志路径,如果不存在或者不可写则使用tmp路径
+     *
+     * @param
+     *            $filename
+     * @return string
+     */
+    private static function getLogPath($filename)
+    {
+        static $path = '';
+        if (! $path) {
+            if ((! file_exists(self::$logPath))) {
+                mkdir(self::$logPath, 0777, true);
+            }
+            $path = is_writable(self::$logPath) ? self::$logPath : sys_get_temp_dir();
+            $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . date('Ym') . DIRECTORY_SEPARATOR . date('d') . DIRECTORY_SEPARATOR;
+            if (! is_writable($path)) {
+                if (! file_exists($path)) {
+                    mkdir($path, 0777, true);
+                }
+            }
+        }
+        $filename = strtolower(strtr($filename, array(
+            ' ' => '',
+            DIRECTORY_SEPARATOR => ''
+        )));
+        $filename = $path . $filename . '.log';
+        return $filename;
+    }
+
+    /**
+     * 获取日志路径,如果不存在或者不可写则使用tmp路径
+     *
+     * @param
+     *            $filename
+     * @return string
+     */
+    private static function getBigdataLogPath()
+    {
+        $path = '/www/privdata/api.makeup.meitu.com/bigdata/'.date('Ymd') . DIRECTORY_SEPARATOR ;
+        if ((! file_exists($path))) {
+            mkdir($path, 0777, true);
+        }
+        $filename = date("H").'_'.self::getServerIp();
+        $filename = $path . $filename . '.log';
+        return $filename;
+    }
+
+    /**
+     * 获取HTTP_HOST
+     *
+     * @return mixed
+     */
+    private static function getHost()
+    {
+        static $host;
+        if (! $host) {
+            if (isset($_SERVER['HTTP_HOST']) && $_SERVER['HTTP_HOST']) {
+                $host = $_SERVER['HTTP_HOST'];
+            } else {
+                $host = 'script';
+            }
+        }
+        return $host;
+    }
+
+    /**
+     * 获取客户端Ip
+     *
+     * @return string|int
+     */
+    private static function getClientIp()
+    {
+        static $ip;
+        if ($ip) {
+            return $ip;
+        }
+        if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
+            $ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
+        } elseif (isset($_SERVER["HTTP_CLIENT_IP"])) {
+            $ip = $_SERVER["HTTP_CLIENT_IP"];
+        } elseif (isset($_SERVER["HTTP_CLIENTIP"])) {
+            $ip = $_SERVER["HTTP_CLIENTIP"];
+        } elseif (isset($_SERVER["HTTP_X_REAL_IP"])) {
+            $ip = $_SERVER["HTTP_X_REAL_IP"];
+        } elseif (isset($_SERVER["REMOTE_ADDR"])) {
+            $ip = $_SERVER["REMOTE_ADDR"];
+        } else {
+            $ip = '127.0.0.1';
+        }
+        $pos = strpos($ip, '|');
+        if ($pos) {
+            $ip = substr($ip, 0, $pos);
+        }
+        $ip = trim($ip);
+        return $ip;
+    }
+
+    /**
+     * 获取服务器IP
+     *
+     * @return string
+     */
+    private static function getServerIp()
+    {
+        static $ip;
+        if ($ip) {
+            return $ip;
+        }
+        if (isset($_SERVER['SERVER_ADDR'])) {
+            $ip = $_SERVER['SERVER_ADDR'];
+        } else {
+            $cmd = "/sbin/ifconfig|grep 'inet addr'|awk '{print $2}'|awk -F':' '{print $2}'|awk '$1 !~ /127.0.0.1/{print}'|tail -n 1";
+            $handle = popen($cmd, 'r');
+            $ip = trim(fread($handle, 1024));
+            pclose($handle);
+        }
+        return $ip;
+    }
+
+    /**
+     * 获取Header信息
+     *
+     * @return mixed
+     */
+    private static function getHeaders()
+    {
+        return array();
+        static $headers = array();
+        if ($headers) {
+            return $headers;
+        }
+        foreach ($_SERVER as $key => $value) {
+            if (substr($key, 0, 5) != 'HTTP_') {
+                continue;
+            }
+            $header = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))));
+            $headers[$header] = $value;
+        }
+        return $headers;
+    }
+}

+ 85 - 0
Frame/Model.php

@@ -0,0 +1,85 @@
+<?php
+
+namespace PhpLife\Frame;
+
+class Model
+{
+
+    /**
+     * 创建单例模式
+     * @return Model
+     */
+    public static function getInstance()
+    {
+        static $pool;
+        $class = get_called_class();
+        if (!isset($pool[$class])) {
+            $pool[$class] = new $class();
+        }
+        return $pool[$class];
+    }
+
+    /**
+     * 强大的批量获取方法,从数据库或者缓存中读取数据
+     * 1. 先读取缓存
+     * 2. 然后从数据库中读取缓存没有命中的
+     * 3. 将没有命中的写入缓存
+     * 4. 返回结果
+     * @param $idArray
+     * @param string $databaseClass 完整数据库类路径, 要求拥有getBatch方法
+     * @param string $cacheClass 完整缓存类路径, 要求拥有 getBatch, setBatch 方法
+     * @return bool
+     */
+    public static function getBatchFromDatabaseOrCache($idArray, $databaseClass = '', $cacheClass = '')
+    {
+        $cachedData = array();
+        // 从缓存中读取数据
+        if ($cacheClass && class_exists($cacheClass) && method_exists($cacheClass, 'getBatch')) {
+            $cachedData = $cacheClass::getBatch($idArray);
+        }
+        // 取出剩余的ID
+        $idLeft = array();
+        foreach ($idArray as $id) {
+            if (isset($cachedData[$id]) && $cachedData[$id]) {
+                continue;
+            }
+            $idLeft[] = $id;
+        }
+        // 从数据库取出内容
+        $databaseData = array();
+        if ($idLeft && $databaseClass && class_exists($databaseClass) && method_exists($databaseClass, 'getBatch')) {
+            $databaseData = $databaseClass::getBatch($idLeft);
+        }
+        // 设置从数据库读取的缓存
+        if ($databaseData && $cacheClass && class_exists($cacheClass) && method_exists($cacheClass, 'setBatch')) {
+            $cacheClass::setBatch($databaseData);
+        }
+        // 合并数据
+        $data = array();
+        foreach ($idArray as $id) {
+            if (isset($cachedData[$id]) && $cachedData[$id]) {
+                $data[$id] = $cachedData[$id];
+            } elseif (isset($databaseData[$id]) && $databaseData[$id]) {
+                $data[$id] = $databaseData[$id];
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * 检查状态位, 如果code是布尔, 则直接返回
+     * @param $stat
+     * @param $code
+     * @return int
+     */
+    protected static function checkStat($stat, $code)
+    {
+        if (is_bool($code)) {
+            return $code;
+        }
+        $code = intval($code);
+        $result = $stat & $code;
+        return $result;
+    }
+
+}

+ 59 - 0
Frame/Object.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace PhpLife\Frame;
+
+/**
+ * 数据结构父类
+ */
+class Object
+{
+
+    private $_data = array ();
+
+    public function __construct(Array $data = array ())
+    {
+        foreach ($data as $name => $value) {
+            $this->_data[$name] = $value;
+        }
+    }
+
+    public function __isset($name)
+    {
+        return isset($this->_data[$name]);
+    }
+
+    public function __set($name, $value)
+    {
+        if (isset($this->_data[$name])) {
+            $this->_data[$name] = $value;
+        }
+    }
+
+    public function __get($name)
+    {
+        if (isset($this->_data[$name])) {
+            return $this->_data[$name];
+        }
+    }
+
+    public function toArray()
+    {
+        return $this->_data;
+    }
+
+    public function __toString()
+    {
+        return json_encode($this->_data);
+    }
+
+    public function __sleep()
+    {
+        return $this->_data;
+    }
+
+    public function __wakeUp()
+    {
+        // ;
+    }
+
+}

+ 56 - 0
Frame/Package.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace PhpLife\Frame;
+
+/**
+ * 数据层父类
+ * Class Package
+ * @package PhpLife\Frame
+ */
+class Package
+{
+    const PREFIX = '';
+
+    /**
+     * 创建单例模式
+     * @return Package
+     */
+    public static function getInstance()
+    {
+        static $pool;
+        $class = get_called_class();
+        if (!isset($pool[$class])) {
+            $pool[$class] = new $class();
+        }
+        return $pool[$class];
+    }
+
+    /**
+     * 获取带前缀的key
+     * @param $id
+     * @return string
+     */
+    protected static function getKey($id = 0)
+    {
+        $class = get_called_class();
+        if (!$class::PREFIX) {
+            $prefix = substr(md5($class), 0, 16);
+        } else {
+            $prefix = $class::PREFIX;
+        }
+        $prefix = strtolower($prefix);
+        $key = trim($prefix, ':') . ':' . $id;
+        return $key;
+    }
+
+    /**
+     * 获取发号器ID
+     * @return mixed
+     * @throws \Exception
+     */
+    protected static function getTicket()
+    {
+        return \PhpLife\Frame\Library\Ticket::getTicket();
+    }
+
+}

+ 11 - 0
Frame/Package/Cache.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace PhpLife\Frame\Package;
+
+/**
+ * 缓存
+ */
+class Cache extends \PhpLife\Frame\Package
+{
+
+}

+ 152 - 0
Frame/Package/Cache/Memcache.php

@@ -0,0 +1,152 @@
+<?php
+
+namespace PhpLife\Frame\Package\Cache;
+
+use PhpLife\Frame\Request;
+
+/**
+ * 使用Memcache做缓存
+ */
+class Memcache extends \PhpLife\Frame\Package\Cache
+{
+    const CONFIG_CLASS = 'Memcache\\Core';
+    const PREFIX = '';
+    const EXPIRE = 259200;
+
+    /**
+     * 单个读取
+     * @param $id
+     * @return array
+     */
+    public static function get($id)
+    {
+        if(Request::isCacheEnabled() == false){
+            return array();
+        }
+        $key = self::getKey($id);
+        $result = self::getConnection()->get($key);
+        return $result;
+    }
+
+    /**
+     * 批量读取,如果没有数据,则不在返回的列表中
+     * @param array $idList
+     * @return array
+     */
+    public static function getBatch(Array $idList)
+    {
+        if(Request::isCacheEnabled() == false){
+            return array();
+        }
+        $keyList = array();
+        foreach ($idList as $id) {
+            $keyList[$id] = self::getKey($id);
+        }
+        $result = self::getConnection()->getMulti($keyList);
+        $data = array();
+        if ($result) {
+            $keyList = array_flip($keyList);
+            foreach ($result as $k => $v) {
+                $k = $keyList[$k];
+                $data[$k] = $v;
+            }
+        }
+        unset($result);
+        unset($idList);
+        unset($keyList);
+        return $data;
+    }
+
+    /**
+     * @param $id
+     * @param $data
+     * @param expire
+     * @return bool
+     */
+    public static function set($id, $data, $expire = null)
+    {
+        $key = self::getKey($id);
+        $class = get_called_class();
+        if(is_null($expire)){
+            $expire = $class::EXPIRE;
+        }
+        $expire = time() + intval($expire);
+        $result = self::getConnection()->set($key, $data, $expire);
+        return $result;
+    }
+
+    /**
+     * 批量设置
+     * @param array $data
+     * @param $expire
+     * @return bool
+     */
+    public static function setBatch($data, $expire = null)
+    {
+        $dataList = array();
+        foreach ($data as $k => $v) {
+            $k = self::getKey($k);
+            $dataList[$k] = $v;
+        }
+        $class = get_called_class();
+        if(is_null($expire)){
+            $expire = $class::EXPIRE;
+        }
+        $expire = time() + intval($expire);
+        $result = self::getConnection()->setMulti($dataList, $expire);
+        unset($data);
+        unset($dataList);
+        return $result;
+    }
+
+    /**
+     * 增加
+     * @param $id
+     * @param int $value
+     * @return int
+     */
+    public static function increase($id, $value = 1)
+    {
+        $key = self::getKey($id);
+        $value = abs($value);
+        return self::getConnection()->increment($key, $value);
+    }
+
+    /**
+     * 减少
+     * @param $id
+     * @param int $value
+     * @return int
+     */
+    public static function decrease($id, $value = 1)
+    {
+        $key = self::getKey($id);
+        $value = abs($value);
+        return self::getConnection()->decrement($key, $value);
+    }
+
+    /**
+     * 删除
+     * @param $id
+     * @return bool
+     */
+    public static function delete($id)
+    {
+        $key = self::getKey($id);
+        return self::getConnection()->delete($key);
+    }
+
+    /**
+     * 获取连接
+     * @return \Memcached
+     */
+    protected static function getConnection()
+    {
+        $class = get_called_class();
+        $configClass = $class::CONFIG_CLASS;
+        $memcached = new \PhpLife\Frame\Library\Memcache($configClass);
+        $connection = $memcached->getConnection();
+        return $connection;
+    }
+
+}

+ 179 - 0
Frame/Package/Cache/Redis.php

@@ -0,0 +1,179 @@
+<?php
+
+namespace PhpLife\Frame\Package\Cache;
+
+/**
+ * 使用Redis做缓存
+ */
+class Redis extends \PhpLife\Frame\Package\Cache
+{
+    const CONFIG_CLASS = 'Redis\\Core';
+    const PREFIX = '';
+    const EXPIRE = 0; // 3 天
+
+    /**
+     * 单个读取
+     * @param $id
+     * @return array
+     */
+    public static function get($id)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection()->get($key);
+        } catch (\Exception $ex) {
+            $result = null;
+        }
+        return $result;
+    }
+
+    /**
+     * 批量读取,如果没有数据,则对应的value为空
+     * @param array $idList
+     * @return array
+     */
+    public static function getBatch(Array $idList)
+    {
+        $keyList = array();
+        foreach ($idList as $id) {
+            $keyList[$id] = self::getKey($id);
+        }
+        try {
+            $result = self::getConnection()->mget($keyList);
+        } catch (\Exception $ex) {
+            $result = null;
+        }
+        $data = array();
+        if ($result) {
+            $i = 0;
+            foreach ($keyList as $k => $v) {
+                $data[$k] = $result[$i];
+                $i++;
+            }
+        }
+        unset($result);
+        unset($idList);
+        unset($keyList);
+        return $data;
+    }
+
+    /**
+     * @param $id
+     * @param int $data
+     * @return bool
+     */
+    public static function set($id, $data)
+    {
+        if (is_array($data)) {
+            $data = serialize($data);
+        }
+        $key = self::getKey($id);
+        $class = get_called_class();
+        try {
+            if ($class::EXPIRE) {
+                $result = self::getConnection(true)->setex($key, $class::EXPIRE, $data);
+            } else {
+                $result = self::getConnection(true)->set($key, $data);
+            }
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 批量设置
+     * @param array $data
+     * @return bool
+     */
+    public static function setBatch($data)
+    {
+        $dataList = array();
+        foreach ($data as $k => $v) {
+            $k = self::getKey($k);
+            $v = is_array($v) ? serialize($v) : $v;
+            $dataList[$k] = $v;
+        }
+        $class = get_called_class();
+        try {
+            $result = self::getConnection(true)->mset($dataList);
+            // 设置过期时间
+            if ($class::EXPIRE) {
+                foreach ($dataList as $k => $v) {
+                    self::getConnection()->expire($k, $class::EXPIRE);
+                }
+            }
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        unset($data);
+        unset($dataList);
+        return $result;
+    }
+
+    /**
+     * 加1
+     * @param $id
+     * @param int $value
+     * @return int
+     */
+    public static function increase($id, $value = 1)
+    {
+        $key = self::getKey($id);
+        $value = abs($value);
+        try {
+            $result = self::getConnection(true)->incr($key, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * @param $id
+     * @param int $value
+     * @return int
+     */
+    public static function decrease($id, $value = 1)
+    {
+        $key = self::getKey($id);
+        $value = abs($value);
+        try {
+            $result = self::getConnection(true)->decr($key, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 删除
+     * @param $id
+     * @return bool
+     */
+    public static function delete($id)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->delete($key);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取连接池
+     * @param bool $isMaster
+     * @return \Redis
+     */
+    protected static function getConnection($isMaster = false)
+    {
+        $class = get_called_class();
+        $configClass = $class::CONFIG_CLASS;
+        $redis = new \PhpLife\Frame\Library\Redis($configClass);
+        $connection = $redis->getConnection($isMaster);
+        return $connection;
+    }
+
+}

+ 11 - 0
Frame/Package/Counter.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace PhpLife\Frame\Package;
+
+/**
+ * 计数器类
+ */
+class Counter extends \PhpLife\Frame\Package
+{
+
+}

+ 148 - 0
Frame/Package/Counter/Hash.php

@@ -0,0 +1,148 @@
+<?php
+
+namespace PhpLife\Frame\Package\Counter;
+
+/**
+ * 计数器类,使用hash数据结构
+ * redis连不上server会抛出异常!!
+ */
+class Hash extends \PhpLife\Frame\Package\Counter
+{
+    const CONFIG_CLASS = 'Redis\\Counter';
+    const PREFIX = '';
+    const EXPIRE = 2592000; // 30 天
+
+    protected static $hashKeyList = array();
+
+    /**
+     * 批量获取一系列的id
+     * @param $idList
+     * @return array
+     */
+    public static function hBatchGetAll($idList)
+    {
+        $data = array();
+        foreach ($idList as $id) {
+            $data[$id] = self::hGetAll($id);
+        }
+        return $data;
+    }
+
+    /**
+     * 读取单个hash
+     * @param $id
+     * @return array
+     */
+    public static function hGetAll($id)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection()->hMGet($key, self::getHashKeyList());
+            foreach ($result as $k => $v) {
+                $result[$k] = intval($v);
+            }
+        } catch (\Exception $ex) {
+            $result = null;
+        }
+        return $result;
+    }
+
+    /**
+     * @param $id
+     * @param $hashKey
+     * @param int $value
+     * @return bool
+     */
+    public static function hSet($id, $hashKey, $value)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->hSet($key, $hashKey, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 加1
+     * @param $id
+     * @param $hashKey
+     * @param int $value 负数时减少
+     * @return int
+     */
+    public static function hIncrBy($id, $hashKey, $value = 1)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->hIncrBy($key, $hashKey, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 删除
+     * @param $id
+     * @return bool
+     */
+    public static function delete($id)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->delete($key);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 设置过期时间
+     * @param $id
+     * @param $expire
+     * @return mixed
+     */
+    public static function setExpire($id, $expire = null)
+    {
+        if (is_null($expire)) {
+            $class = get_called_class();
+            $expire = $class::EXPIRE;
+        } else {
+            $expire = intval($expire);
+        }
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->expire($key, $expire);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取hashKey列表
+     * @return mixed
+     */
+    protected static function getHashKeyList()
+    {
+        $class = get_called_class();
+        $hashKeyList = $class::$hashKeyList;
+        return $hashKeyList;
+    }
+
+    /**
+     * 获取连接池
+     * @param bool $isMaster
+     * @return \Redis
+     */
+    protected static function getConnection($isMaster = false)
+    {
+        $class = get_called_class();
+        $configClass = $class::CONFIG_CLASS;
+        $redis = new \PhpLife\Frame\Library\Redis($configClass);
+        $connection = $redis->getConnection($isMaster);
+        return $connection;
+    }
+}

+ 141 - 0
Frame/Package/Counter/Single.php

@@ -0,0 +1,141 @@
+<?php
+
+namespace PhpLife\Frame\Package\Counter;
+
+/**
+ * 计数器类
+ * redis连不上server会抛出异常!!
+ */
+class Single extends \PhpLife\Frame\Package\Counter
+{
+    const CONFIG_CLASS = 'Redis\\Counter';
+    const PREFIX = '';
+    const EXPIRE = 2592000; // 30 天
+
+    /**
+     * 单个读取
+     * @param $id
+     * @return array
+     */
+    public static function get($id)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection()->get($key);
+            $result = intval($result);
+        } catch (\Exception $ex) {
+            $result = null;
+        }
+        return $result;
+    }
+
+    /**
+     * 批量获取
+     * @param array $idList
+     * @return array|null
+     */
+    public static function mGet(Array $idList)
+    {
+        $keyList = array();
+        foreach ($idList as $id) {
+            $k = self::getKey($id);
+            $keyList[$k] = $id;
+        }
+        try {
+            $result = array();
+            $data = self::getConnection()->mget(array_keys($keyList));
+            $i = 0;
+            foreach ($keyList as $k => $v) {
+                $result[$v] = intval($data[$i]);
+                $i++;
+            }
+        } catch (\Exception $ex) {
+            $result = null;
+        }
+        return $result;
+    }
+
+    /**
+     * @param $id
+     * @param int $value
+     * @return bool
+     */
+    public static function set($id, $value)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->set($key, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 加1
+     * @param $id
+     * @param int $value
+     * @return int
+     */
+    public static function increase($id, $value = 1)
+    {
+        $key = self::getKey($id);
+        $value = abs($value);
+        try {
+            $result = self::getConnection(true)->incr($key, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * @param $id , 如果值小于0则设为0
+     * @param int $value
+     * @return int
+     */
+    public static function decrease($id, $value = 1)
+    {
+        $key = self::getKey($id);
+        $value = abs($value);
+        try {
+            $result = self::getConnection(true)->decr($key, $value);
+            if ($result < 0) {
+                $result = self::getConnection(true)->set($key, 0);
+            }
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 删除
+     * @param $id
+     * @return bool
+     */
+    public static function delete($id)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->delete($key);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取连接池
+     * @param bool $isMaster
+     * @return \Redis
+     */
+    protected static function getConnection($isMaster = false)
+    {
+        $class = get_called_class();
+        $configClass = $class::CONFIG_CLASS;
+        $redis = new \PhpLife\Frame\Library\Redis($configClass);
+        $connection = $redis->getConnection($isMaster);
+        return $connection;
+    }
+}

+ 13 - 0
Frame/Package/Database.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace PhpLife\Frame\Package;
+
+/**
+ * 数据库操作类
+ * 一种是单表模式,一种是分表模式
+ */
+class Database extends \PhpLife\Frame\Package
+{
+
+
+}

+ 275 - 0
Frame/Package/Database/Common.php

@@ -0,0 +1,275 @@
+<?php
+namespace PhpLife\Frame\Package\Database;
+
+/**
+ * 数据库操作类
+ * 单表模式
+ */
+class Common extends \PhpLife\Frame\Package\Database\Single
+{
+
+    /**
+     * 通过ID获取单条记录
+     * 
+     * @param string $id            
+     * @return array
+     */
+    public function getOne($id)
+    {
+        return self::getLine($id);
+    }
+
+    /**
+     * 通过字段条件获取单条记录
+     * 
+     * @param array $where
+     *            查询条件
+     * @param array $fields
+     *            要获取的字段信息
+     * @return array
+     */
+    public function getOneByField($where = array(), $fields = array())
+    {
+        $list = $this->select($fields, $where, '', 1);
+        return ! empty($list) ? $list[0] : null;
+    }
+
+    /**
+     * 通过字段条件获取排序后的限制记录
+     * 
+     * @param array $where
+     *            查询条件
+     * @param array $fields
+     *            要获取的字段信息
+     * @param int $limit
+     *            限制记录数
+     * @param int $offset
+     *            查询偏移量
+     * @param array $orderBy
+     *            排序 array("field1"=>1,"field2"=>0); 0升序1降序
+     * @return array
+     */
+    public function getList($where = array(), $fields = array(), $limit = null, $offset = null, $orderBy = array())
+    {
+        return $this->select($fields, $where, '', $limit, $offset, $orderBy);
+    }
+
+    /**
+     * 复杂语句的计数查询
+     * 
+     * @param array $where            
+     * @return int
+     */
+    public function countByMuti($where = array())
+    {
+        $arr = $this->buildWhere($where);
+        return $this->count($arr['where'], $arr['whereString']);
+    }
+
+    /**
+     * 复杂的查询语句
+     * 
+     * @param array $where   <br>   
+     *              1.'key'=>$value 等于的查询条件<br>
+     *              2.'key'=>array( '_logic'=>'比较运算符或like' , 'value'=>$value ) '<','<=','>','>=','!=','LIKE'的查询条件<br>
+     *              3.'key'=>array($value1,$value2,$value3,...) IN范围查询<br>
+     *              4.'key'=>array( array( '_logic'=>'比较运算符或like' , 'value'=>$value ) ,'key'=>$value ) 一个字段多个查询条件<br>      
+     * @param array $fields            
+     * @param int $limit            
+     * @param int $offset            
+     * @param array $orderBy            
+     * @return array
+     */
+    public function getListByMuti($where = array(), $fields = array(), $limit = null, $offset = null, $orderBy = array())
+    {
+        $arr = $this->buildWhere($where);
+        return $this->select($fields, $arr['where'], $arr['whereString'], $limit, $offset, $orderBy);
+    }
+    /**
+     * 复杂的查询语句
+     *
+     * @param array $where   <br>
+     *              1.'key'=>$value 等于的查询条件<br>
+     *              2.'key'=>array( '_logic'=>'比较运算符或like' , 'value'=>$value ) '<','<=','>','>=','!=','LIKE'的查询条件<br>
+     *              3.'key'=>array($value1,$value2,$value3,...) IN范围查询<br>
+     *              4.'key'=>array( array( '_logic'=>'比较运算符或like' , 'value'=>$value ) ,'key'=>$value ) 一个字段多个查询条件<br>
+     * @param array $fields
+     * @param int $limit
+     * @param int $offset
+     * @param array $orderBy
+     * @return array
+     */
+    public function getAllByMutiple($where = array(), $fields = array(), $orderBy = array(),$limit=null,$offset=null)
+    {
+        $arr = $this->buildWhere($where);
+        return $this->select($fields, $arr['where'], $arr['whereString'], $limit, $offset, $orderBy);
+    }
+    /**
+     * 更新(基于ID)
+     * @param int $id
+     * @param array $data
+     * @return int
+     */
+    public function update($id, $data)
+    {
+        return parent::update($data, array('id'=>$id));
+    }
+
+    /**
+     * 复杂的更新语句
+     * 
+     * @param array $set     
+     *            1.'key'=>$value 等于参数设置
+     *            2.'key'=>array( '_logic'=>'运算符','value'=>$value ) 含 '+', '-','*','/','%'运算符号的参数设置       
+     * @param array $where            
+     * @return bool|int
+     */
+    public function updateByMuti($set = array(), $where = array())
+    {
+        $wArr = $this->buildWhere($where);
+        $sArr = $this->buildSet($set);
+        $sql = " UPDATE `" . $this->tableName . "` SET " . $sArr['setString'] . ' WHERE ' . $wArr['whereString'];
+        $prepared = array_merge($wArr['where'], $sArr['set']);
+        return $this->write($sql, $prepared);
+    }
+
+    /**
+     * 将复杂的查询条件转换为PDO类可解读的参数绑定方式
+     * 
+     * @param array $map
+     *            条件
+     *            $map允许的键值格式(所有的条件是AND组合的):
+     *            1.'key'=>$value 等于的查询条件
+     *            2.'key'=>array( '_logic'=>'比较运算符或like' , 'value'=>$value ) '<','<=','>','>=','!=','LIKE'的查询条件
+     *            3.'key'=>array($value1,$value2,$value3,...) IN范围查询
+     *            4.'key'=>array( array( '_logic'=>'比较运算符或like' , 'value'=>$value ) ,'key'=>$value ) 一个字段多个查询条件
+     * @return array
+     */
+    public function buildWhere($map)
+    {
+        $fhArr = array(
+            '<',
+            '<=',
+            '>',
+            '>=',
+            '!=',
+            'LIKE'
+        );
+        $where = array();
+        $whereString = array();
+        foreach ($map as $k => $v) {
+            if (is_array($v)) {
+                if (! empty($v['_logic'])) {
+                    $v['_logic'] = strtoupper($v['_logic']);
+                    if (in_array($v['_logic'], $fhArr)) {
+                        $whereString[] = self::escapeKey($k) . ' ' . $v['_logic'] . ' ' . self::replaceKey($k, 'wh_') . '';
+                        $where[self::replaceKey($k, 'wh_')] = $v['value'];
+                    }
+                } else {
+                    $mixArr = array();
+                    foreach ($v as $ke => $va) {
+                        if (is_array($va)) {
+                            if (! empty($va['_logic'])) {
+                                $va['_logic'] = strtoupper($va['_logic']);
+                                if (in_array($va['_logic'], $fhArr)) {
+                                    $whereString[] = self::escapeKey($k) . ' ' . $va['_logic'] . ' ' . self::replaceKey($k . $ke, 'wh_') . '';
+                                    $where[self::replaceKey($k . $ke, 'wh_')] = $va['value'];
+                                }
+                            }
+                        } else {
+                            $mixArr[] = $va;
+                        }
+                    }
+                    if (! empty($mixArr)) {
+                        $string = self::escapeKey($k) . ' IN ( ';
+                        $first = '';
+                        foreach ($mixArr as $ke => $va) {
+                            $string .= $first . self::replaceKey($k . $ke, 'wh_');
+                            $where[self::replaceKey($k . $ke, 'wh_')] = $va;
+                            $first = ',';
+                        }
+                        $string .= ' ) ';
+                        $whereString[] = $string;
+                    }
+                }
+            } else {
+                $whereString[] = self::escapeKey($k) . ' = ' . self::replaceKey($k, 'wh_');
+                $where[self::replaceKey($k, 'wh_')] = $v;
+            }
+        }
+        return array(
+            'where' => $where,
+            'whereString' => implode(' AND ', $whereString)
+        );
+    }
+
+    /**
+     * 将复杂的更新条件转换为PDO类可解读的参数绑定方式
+     * 
+     * @param array $map
+     *            参数设置
+     *            $map设置参数的格式:
+     *            1.'key'=>$value 等于参数设置
+     *            2.'key'=>array( '_logic'=>'运算符','value'=>$value ) 含 '+', '-','*','/','%'运算符号的参数设置
+     * @return array()
+     */
+    public function buildSet($map)
+    {
+        if (! is_array($map) || empty($map)) {
+            return '';
+        }
+        $setString = array();
+        $set = array();
+        foreach ($map as $k => $v) {
+            if (is_array($v)) {
+                if (! empty($v['_logic'])) {
+                    if (in_array($v['_logic'], array(
+                        '+',
+                        '-',
+                        '*',
+                        '/',
+                        '%'
+                    ))) {
+                        $setString[] = self::escapeKey($k) . ' = ' . self::escapeKey($k) . ' ' . $v['_logic'] . ' ' . self::replaceKey($k, 'st_');
+                        $set[self::replaceKey($k, 'st_')] = $v['value'];
+                    }
+                }
+            } else {
+                $setString[] = self::escapeKey($k) . '=' . self::replaceKey($k, 'st_');
+                $set[self::replaceKey($k, 'st_')] = $v;
+            }
+        }
+        return array(
+            'set' => $set,
+            'setString' => implode(' , ', $setString)
+        );
+    }
+
+    /**
+     * 安全过滤key
+     * 
+     * @param string $key            
+     * @return string
+     */
+    protected function escapeKey($key)
+    {
+        $key = str_replace(array(
+            '`',
+            ' '
+        ), '', $key);
+        return ' `' . addslashes(stripslashes($key)) . '` ';
+    }
+
+    /**
+     * 参数绑定value
+     * 
+     * @param string $key            
+     * @return string
+     */
+    protected function replaceKey($key, $prefix = '')
+    {
+        return ':' . $prefix . $key;
+    }
+
+  
+}

+ 342 - 0
Frame/Package/Database/Sharding.php

@@ -0,0 +1,342 @@
+<?php
+
+namespace PhpLife\Frame\Package\Database;
+
+/**
+ * 数据库操作类
+ * 分表模式
+ */
+class Sharding extends \PhpLife\Frame\Package\Database
+{
+    protected $configClass = 'MySQL\\Core';
+    protected $configSection = null;
+    protected $tableName = '';
+    protected $fields = array();
+    protected $primaryKey = 'id';
+
+    /**
+     * 获取计数
+     * @param Array $whereParam
+     * @param string $whereString
+     * @return int
+     */
+    public function count($whereParam, $whereString = '')
+    {
+        $query = $this->getQuery()->setWhere($whereParam, $whereString)->count();
+        $result = $this->getConnection()->read($query->getSQL(), $query->getData());
+        if (!$result) {
+            return 0;
+        }
+        $result = current($result);
+        return intval($result['count']);
+    }
+
+    /**
+     * 获取列表
+     * @param array $fields
+     * @param array $whereParam
+     * @param string $whereString
+     * @param int $limit
+     * @param int $offset
+     * @param array $orderBy
+     * @return array
+     */
+    public function select(
+        $fields = array(),
+        $whereParam = array(),
+        $whereString = '',
+        $limit = 10,
+        $offset = 0,
+        $orderBy = array())
+    {
+        $query = $this->getQuery()
+            ->setField($fields)
+            ->setWhere($whereParam, $whereString)
+            ->setLimit($limit, $offset)
+            ->setOrderBy($orderBy)
+            ->select();
+        $result = $this->getConnection()->read($query->getSQL(), $query->getData());
+        if (!$result) {
+            return $result;
+        }
+        $data = array();
+        foreach ($result as $row) {
+            $data[$row[$this->primaryKey]] = $row;
+        }
+        unset($result);
+        return $data;
+    }
+
+    /**
+     * 获取列表数据,简单版
+     * @param array $whereParam
+     * @param int $limit
+     * @return array
+     */
+    public function getList($whereParam = array(), $limit = 10)
+    {
+        return $this->select(array(), $whereParam, '', $limit, 0, array());
+    }
+
+    /**
+     * 获取列表ID信息
+     * @param string $field
+     * @param array $whereParam
+     * @param $orderBy
+     * @param $limit
+     * @return array
+     */
+    public function getListId($field = '', $whereParam = array(), $orderBy = array(), $limit = 2000)
+    {
+        $query = self::getQuery()
+            ->setField($field)
+            ->setWhere($whereParam)
+            ->setLimit($limit)
+            ->setOrderBy($orderBy)
+            ->select();
+        $result = $this->getConnection()->read($query->getSQL(), $query->getData());
+        if (!$result) {
+            return $result;
+        }
+        $data = array();
+        foreach ($result as $row) {
+            $data[] = $row[$field];
+        }
+        unset($result);
+        return $data;
+    }
+
+    /**
+     * 获取单条记录
+     * @param array $whereParam
+     * @param string $whereString
+     * @param array $orderBy
+     * @return array;
+     */
+    public function getOne($whereParam = array(), $whereString = '', $orderBy = array())
+    {
+        $query = $this->getQuery()
+            ->setWhere($whereParam, $whereString)
+            ->setOrderBy($orderBy)
+            ->setLimit(1)
+            ->select();
+        $result = $this->getConnection()->read($query->getSQL(), $query->getData(), false, false);
+        return $result;
+    }
+
+    /**
+     * 根据主键获取单条记录
+     * @param int $id
+     * @return array
+     */
+    public function getLine($id)
+    {
+        $where = array($this->primaryKey => $id);
+        $query = $this->getQuery()->setWhere($where)->setLimit(1)->select();
+        $result = $this->getConnection()->read($query->getSQL(), $query->getData(), false, false);
+        return $result;
+    }
+
+    /**
+     * 批量根据主键查询
+     * @param $idList
+     * @return array|mixed
+     */
+    public function getBatch($idList)
+    {
+        $where = array($this->primaryKey => $idList);
+        $query = $this->getQuery()->setWhere($where)->select();
+        $result = $this->getConnection()->read($query->getSQL(), $query->getData());
+        if (!$result) {
+            return $result;
+        }
+        $data = array();
+        foreach ($result as $row) {
+            $data[$row[$this->primaryKey]] = $row;
+        }
+        unset($result);
+        return $data;
+    }
+
+    /**
+     * 创建记录
+     * @param $data
+     * @param bool 是否返回最后最后插入ID
+     * @return bool|int
+     */
+    public function insert($data, $returnLastInsertId = false)
+    {
+        $query = $this->getQuery()->setData($data)->insert();
+        $result = $this->getConnection()->write($query->getSQL(), $query->getData());
+        // 返回最后插入的id
+        if ($result && $returnLastInsertId) {
+            $result = $this->getConnection()->getLastInsertId();
+        }
+        return $result;
+    }
+
+    /**
+     * 批量插入
+     * $data = array(
+     *      array(
+     *          'id' => 10001,
+     *          'target_id' => 100,
+     *      ),
+     *      array(
+     *          'id' => 10002,
+     *          'target_id' => 100,
+     *      ),
+     * );
+     * @param $data
+     * @return bool|int
+     */
+    public function insertBatch($data)
+    {
+        $prefix = 'prepared_';
+        // 确定字段名
+        $fields = implode('`, `', array_keys(current($data)));
+        // 拼SQL和数据
+        $dataList = array();
+        $values = array();
+        $i = 0;
+        foreach ($data as $k => $v) {
+            foreach ($v as $x => $y) {
+                $key = $prefix . $i;
+                $dataList[$key] = $y;
+                $v[$x] = ':' . $key;
+                $i++;
+            }
+            $values[] = "(" . implode(", ", $v) . ")";
+        }
+        $values = implode(', ', $values);
+        $sql = "INSERT INTO `" . $this->tableName . "` (`$fields`) VALUES $values";
+        $result = $this->write($sql, $dataList);
+        return $result;
+    }
+
+    /**
+     * 更新记录
+     * @param $data
+     * @param $whereParam
+     * @param $whereString
+     * @return bool
+     */
+    public function update($data, $whereParam, $whereString = '')
+    {
+        $query = $this->getQuery()->setData($data)->setWhere($whereParam, $whereString)->update();
+        $result = $this->getConnection()->write($query->getSQL(), $query->getData());
+        return $result;
+    }
+
+    /**
+     * 替换输入
+     * @param $data
+     * @param $whereParam
+     * @param $whereString
+     * @return bool
+     */
+    public function replace($data, $whereParam, $whereString = '')
+    {
+        $query = $this->getQuery()->setData($data)->setWhere($whereParam, $whereString)->replace();
+        $result = $this->getConnection()->write($query->getSQL(), $query->getData());
+        return $result;
+    }
+
+    /**
+     * 删除逻辑
+     * @param $whereParam
+     * @param $whereString
+     * @return bool
+     */
+    public function delete($whereParam, $whereString = '')
+    {
+        $query = $this->getQuery()->setWhere($whereParam, $whereString)->delete();
+        $result = $this->getConnection()->write($query->getSQL(), $query->getData());
+        return $result;
+    }
+
+
+    /**
+     * 获取一个新的SQL生成器
+     * @return \PhpLife\Frame\Library\MySQL\Query
+     */
+    protected function getQuery()
+    {
+        return \PhpLife\Frame\Library\MySQL\Query::newInstance()
+            ->setTableName($this->tableName)
+            ->setField($this->fields)
+            ->setPrimaryKey($this->primaryKey);
+    }
+
+    /**
+     * 读取
+     * @param $sql
+     * @param $data
+     * @param bool $isMaster
+     * @return mixed|Array
+     */
+    protected function read($sql, $data, $isMaster = false)
+    {
+        $result = $this->getConnection()->read($sql, $data, $isMaster);
+        return $result;
+    }
+
+    /**
+     * 写入
+     * @param $sql
+     * @param $data
+     * @return bool|int
+     */
+    protected function write($sql, $data)
+    {
+        $result = $this->getConnection()->write($sql, $data);
+        return $result;
+    }
+
+    /**
+     * 获取连接池
+     * @return \PhpLife\Frame\Library\MySQL
+     */
+    protected function getConnection()
+    {
+        return new \PhpLife\Frame\Library\MySQL($this->configClass, $this->configSection);
+    }
+    /**
+     * 根据数值来确定分表-分库方法
+     * 1.
+     * 可以自定义分表-分库的模板前缀$tbl变量
+     * 2. 可以自定义截取长度
+     * 3. 一般可以根据用户UID来获取分表或者分库
+     *
+     * @param int $num
+     *            数值
+     * @param string $tbl
+     *            模板前缀
+     * @param int $default
+     *            默认截取长度
+     */
+    protected function num_identify($num, $tbl, $default = 1)
+    {
+        $num = (string) $num;
+        $len = strlen($num);
+        if ($len >= $default)
+            $str = substr($num, $len - $default, $default);
+        else
+            $str = str_pad($num, $default, '0', STR_PAD_LEFT);
+        return $tbl . '_' . $str;
+    }
+    /**
+     * 求余数的方式获取分表-分库方法
+     * 1. 求余方式余数比较少,适合小型的分表法
+     * 2. 可以自定义求余除数
+     * Dao中使用方法:$this->dao->db->fmod_identify($num, $tbl, $default = 7)
+     * @param int $num
+     * @param string $tbl
+     * @param int $default
+     * @return
+     */
+    public function fmod_identify($num, $tbl, $default = 7) {
+        return $tbl . '_' . fmod($num/$default);
+    }
+
+}

+ 421 - 0
Frame/Package/Database/Single.php

@@ -0,0 +1,421 @@
+<?php
+
+namespace PhpLife\Frame\Package\Database;
+use PhpLife\Frame\Filter;
+
+/**
+ * 数据库操作类
+ * 单表模式
+ */
+class Single extends \PhpLife\Frame\Package\Database
+{
+    const CONFIG_CLASS = 'MySQL\\Core';
+    protected static $prefix = 'ph_'; // 表前缀
+    protected static $tableName = '';
+    protected static $fields = array();
+    protected static $primaryKey = 'id';
+
+    /**
+     * 获取计数
+     * @param array $whereParam
+     * @param string $whereString
+     * @return int
+     */
+    public static function count($whereParam, $whereString = '')
+    {
+        $query = self::getQuery()->setWhere($whereParam, $whereString)->count();
+        $result = self::getConnection()->read($query->getSQL(), $query->getData());
+        if (!$result) {
+            return 0;
+        }
+        $result = current($result);
+        return intval($result['count']);
+    }
+
+    /**
+     * 获取列表,limit 不传取全部
+     * @param array $fields
+     * @param array $whereParam
+     * @param string $whereString
+     * @param int $limit 不传取全部
+     * @param int $offset
+     * @param array $orderBy
+     * @return array
+     */
+    public static function select(
+        $fields = array(),
+        $whereParam = array(),
+        $whereString = '',
+        $limit = null,
+        $offset = 0,
+        $orderBy = array())
+    {
+        $query = self::getQuery()
+            ->setField($fields)
+            ->setWhere($whereParam, $whereString)
+            ->setLimit($limit, $offset)
+            ->setOrderBy($orderBy)
+            ->select();
+        $result = self::getConnection()->read($query->getSQL(), $query->getData());
+        if (!$result) {
+            return $result;
+        }
+        $data = array();
+        $class = get_called_class();
+        foreach ($result as $row) {
+            $data[$row[$class::$primaryKey]] = $row;
+        }
+        unset($result);
+        return $data;
+    }
+
+    /**
+     * 获取列表数据,简单版
+     * @param array $whereParam
+     * @param int $limit
+     * @param $orderBy
+     * @return array
+     */
+    public static function getList($whereParam = array(), $limit = 10, $orderBy = array())
+    {
+        return self::select(array(), $whereParam, '', $limit, 0, $orderBy);
+    }
+
+    /**
+     * 获取列表ID信息
+     * @param string $field
+     * @param array $whereParam
+     * @param $orderBy
+     * @param $limit
+     * @return array
+     */
+    public static function getListId($field = '', $whereParam = array(), $orderBy = array(), $limit = 2000)
+    {
+        $query = self::getQuery()
+            ->setField($field)
+            ->setWhere($whereParam)
+            ->setLimit($limit)
+            ->setOrderBy($orderBy)
+            ->select();
+        $result = self::getConnection()->read($query->getSQL(), $query->getData());
+        if (!$result) {
+            return $result;
+        }
+        $data = array();
+        foreach ($result as $row) {
+            $data[] = $row[$field];
+        }
+        unset($result);
+        return $data;
+    }
+
+    /**
+     * 分页获取数据
+     * @param array $whereParam
+     * @param string $whereString
+     * @param int $page
+     * @param int $pageSize
+     * @param array $orderBy
+     * @param array $fields
+     * @return array
+     */
+    public static function getPageList($whereParam = array(), $whereString='', $page = 1, $pageSize = 20, $orderBy = array(), $fields = array()){
+        $page = $page ? $page : 1;
+        $offset = ($page - 1) * $pageSize;
+        //获取总条数
+        $total_count = self::count($whereParam,$whereString);
+        $pager = array(
+            'total_count' => $total_count,
+            'total_page' => 0,
+            'current_page' => $page,
+            'page_size' => $pageSize,
+            'data' => array()
+        );
+        if($total_count > 0){
+            list($url,) = explode('?',$_SERVER['REQUEST_URI']);
+            unset($_GET['page']);
+            //链接
+            $pager['url'] = $url;
+            //请求参数
+            $pager['filter'] =  $_GET ? http_build_query($_GET) : '';
+            //总页数
+            $pager['total_page'] = ceil($total_count / $pageSize);
+            //内容
+            $pager['data'] = self::select($fields,$whereParam,$whereString,$pageSize,$offset,$orderBy);
+        }
+        return $pager;
+    }
+
+    /**
+     * 获取单条记录
+     * @param array $whereParam
+     * @param string $whereString
+     * @param array $orderBy
+     * @return array;
+     */
+    public static function getOne($whereParam = array(), $whereString = '', $orderBy = array())
+    {
+        $query = self::getQuery()
+            ->setWhere($whereParam, $whereString)
+            ->setOrderBy($orderBy)
+            ->setLimit(1)
+            ->select();
+        $result = self::getConnection()->read($query->getSQL(), $query->getData(), false, false);
+        return $result;
+    }
+
+    /**
+     * 根据主键获取单条记录
+     * @param int $id
+     * @return array
+     */
+    public static function getLine($id)
+    {
+        if (!$id) {
+            return array();
+        }
+        $class = get_called_class();
+        $where = array($class::$primaryKey => $id);
+        $query = self::getQuery()->setWhere($where)->setLimit(1)->select();
+        $result = self::getConnection()->read($query->getSQL(), $query->getData(), false, false);
+        return $result;
+    }
+
+    /**
+     * 批量根据主键查询, 按照传入的ID进行排序
+     * @param $idList
+     * @return array|mixed
+     */
+    public static function getBatch($idList)
+    {
+        if (!$idList) {
+            return array();
+        }
+        $class = get_called_class();
+        $where = array($class::$primaryKey => $idList);
+        $query = self::getQuery()->setWhere($where)->select();
+        $result = self::getConnection()->read($query->getSQL(), $query->getData());
+        if (!$result) {
+            return $result;
+        }
+        $data = array();
+        foreach ($result as $row) {
+            $data[$row[$class::$primaryKey]] = $row;
+        }
+        // 按照传入的id顺序进行重新排序,如果id对应的value不存在则忽略
+        $result = array();
+        foreach ($idList as $id) {
+            if (isset($data[$id])) {
+                $result[$id] = $data[$id];
+            }
+        }
+        unset($data);
+        return $result;
+    }
+
+    /**
+     * 聚合获取列表数据
+     * @param $field
+     * @param $whereParam
+     * @param $whereString
+     * @param $groupBy
+     * @param $limit
+     * @return array|mixed
+     */
+    public static function getListByGroup($field,$whereParam,$whereString,$groupBy,$limit=null){
+        $query = self::getQuery()
+            ->setField($field)
+            ->setWhere($whereParam,$whereString)
+            ->setGroupBy($groupBy)
+            ->setLimit($limit)
+            ->select();
+        return self::getConnection()->read($query->getSQL(), $query->getData());
+    }
+
+    /**
+     * 联合获取列表数据
+     * @param $whereString
+     * @param $join
+     * @param $sort
+     * @param string $field
+     * @return array|mixed
+     */
+    public static function getListByJoin($whereString,$join,$sort='',$field=''){
+        $class = get_called_class();
+        $tableName = $class::$tableName;
+        $field = $field ? : $tableName.'.*';
+        $sql = 'select '.$field.' from '.$tableName.' left join '.$join[0].' on '.$tableName.'.'.$join[1].'='.$join[0].'.'.$join[2];
+        $whereString ? $sql .= ' where '.$whereString : null;
+        $sort ? $sql .= ' order by '.$sort : null;
+        $sql = str_replace('SELF',$tableName,$sql);//SELF代表主表
+        $sql = str_replace('JOIN',$join[0],$sql);//JOIN代表连接表
+        $result =  self::getConnection()->read($sql, array());
+        $data = array();
+        $class = get_called_class();
+        foreach ($result as $row) {
+            $data[$row[$class::$primaryKey]] = $row;
+        }
+        return $data;
+    }
+
+    /**
+     * 创建记录
+     * @param $data
+     * @param bool 是否返回最后最后插入ID
+     * @return bool|int
+     */
+    public static function insert($data, $returnLastInsertId = true)
+    {
+        $query = self::getQuery()->setData($data)->insert();
+        $result = self::getConnection()->write($query->getSQL(), $query->getData());
+        // 返回最后插入的id
+        if ($result && $returnLastInsertId) {
+            $result = self::getConnection()->getLastInsertId();
+        }
+        return $result;
+    }
+
+    /**
+     * 批量插入
+     * $data = array(
+     *      array(
+     *          'id' => 10001,
+     *          'target_id' => 100,
+     *      ),
+     *      array(
+     *          'id' => 10002,
+     *          'target_id' => 100,
+     *      ),
+     * );
+     * @param $data
+     * @return bool|int
+     */
+    public static function insertBatch($data)
+    {
+        $prefix = 'prepared_';
+        // 确定字段名
+        $fields = implode('`, `', array_keys(current($data)));
+        //print_r($data);
+        // 拼SQL和数据
+        $dataList = array();
+        $values = array();
+        $i = 0;
+        foreach ($data as $k => $v) {
+            foreach ($v as $x => $y) {
+                $key = $prefix . $i;
+                $dataList[$key] = $y;
+                $v[$x] = ':' . $key;
+                $i++;
+            }
+            $values[] = "(" . implode(", ", $v) . ")";
+        }
+        $values = implode(', ', $values);
+        $class = get_called_class();
+        $tableName = $class::$tableName;
+        //print_r($values);die();
+        $sql = "INSERT INTO `" . $tableName . "` (`$fields`) VALUES $values";
+        //print_r($sql);
+        $result = self::write($sql, $dataList);
+        return $result;
+    }
+
+    /**
+     * 更新记录
+     * @param $data
+     * @param $whereParam
+     * @param $whereString
+     * @return bool
+     */
+    public static function update($data, $whereParam, $whereString = '')
+    {
+        $query = self::getQuery()->setData($data)->setWhere($whereParam, $whereString)->update();
+        $result = self::getConnection()->write($query->getSQL(), $query->getData());
+        return $result;
+    }
+
+    /**
+     * 替换输入
+     * @param $data
+     * @param $whereParam
+     * @param $whereString
+     * @return bool
+     */
+    public static function replace($data, $whereParam, $whereString = '')
+    {
+        $query = self::getQuery()->setData($data)->setWhere($whereParam, $whereString)->replace();
+        $result = self::getConnection()->write($query->getSQL(), $query->getData());
+        return $result;
+    }
+
+    /**
+     * 删除逻辑
+     * @param $whereParam
+     * @param $whereString
+     * @return bool
+     */
+    public static function delete($whereParam, $whereString = '')
+    {
+        $query = self::getQuery()->setWhere($whereParam, $whereString)->delete();
+        $result = self::getConnection()->write($query->getSQL(), $query->getData());
+        return $result;
+    }
+
+
+    /**
+     * 获取一个新的SQL生成器
+     * @return \PhpLife\Frame\Library\MySQL\Query
+     */
+    protected static function getQuery()
+    {
+        $class = get_called_class();
+        return \PhpLife\Frame\Library\MySQL\Query::newInstance()
+            ->setTableName($class::$tableName)
+            ->setField($class::$fields)
+            ->setPrimaryKey($class::$primaryKey);
+    }
+
+    /**
+     * 从数据库读取数据,可以强制从主库读取
+     * @param string $sql
+     * @param array $data
+     * @param bool $isMaster
+     * @return mixed|Array
+     */
+    protected static function read($sql, $data, $isMaster = false)
+    {
+        $result = self::getConnection()->read($sql, $data, $isMaster);
+        return $result;
+    }
+
+    /**
+     * 写入
+     * @param $sql
+     * @param $data
+     * @return bool|int
+     */
+    protected static function write($sql, $data)
+    {
+        $result = self::getConnection()->write($sql, $data);
+        return $result;
+    }
+
+    /**
+     * 获取连接池
+     * @return \PhpLife\Frame\Library\MySQL
+     */
+    static function getConnection()
+    {
+        $class = get_called_class();
+        $configClass = $class::CONFIG_CLASS;
+        return new \PhpLife\Frame\Library\MySQL($configClass);
+    }
+
+    /**
+     * 获取表前缀
+     * @return string
+     */
+    public static function getPrefix(){
+        return isset(self::$prefix) ? self::$prefix : '';
+    }
+
+}

+ 62 - 0
Frame/Package/Queue.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace PhpLife\Frame\Package;
+
+/**
+ * 队列
+ * 使用redis实现
+ */
+class Queue extends \PhpLife\Frame\Package
+{
+    const CONFIG_CLASS = 'Redis\\Queue';
+    const PREFIX = '';
+
+    /**
+     * 获取队列长度
+     * @return int
+     */
+    public static function length()
+    {
+        $key = self::getKey();
+        $result = self::getConnection(true)->lLen($key);
+        return $result;
+    }
+
+    /**
+     * 插入数据
+     * @param $data
+     * @return int
+     */
+    public static function push($data)
+    {
+        $key = self::getKey();
+        $result = self::getConnection(true)->rPush($key, $data);
+        return $result;
+    }
+
+    /**
+     * 取出数据
+     * @return string
+     */
+    public static function pop()
+    {
+        $key = self::getKey();
+        $result = self::getConnection(true)->lPop($key);
+        return $result;
+    }
+
+    /**
+     * 获取连接池
+     * @param bool $isMaster
+     * @return \Redis
+     */
+    protected static function getConnection($isMaster = false)
+    {
+        $class = get_called_class();
+        $configClass = $class::CONFIG_CLASS;
+        $redis = new \PhpLife\Frame\Library\Redis($configClass);
+        $connection = $redis->getConnection($isMaster);
+        return $connection;
+    }
+
+}

+ 82 - 0
Frame/Package/RedisQueue.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace PhpLife\Frame\Package;
+
+/**
+ * @author cxm@meitu.com
+ * 队列
+ */
+class RedisQueue
+{
+
+    protected static $_instance = NULL;
+    protected $redisObj = NULL;
+    protected $key = NULL;
+
+    /**
+     * 
+     * @param unknown $key
+     * @param string $config
+     * @return self
+     */
+    public static function getInstance($key, $config = "Redis\\Queue")
+    {
+        if (empty($key)) {
+            return false;
+        }
+        if (!isset(self::$_instance[$key][$config]) || !(self::$_instance[$key][$config] instanceof self)) {
+            self::$_instance[$key][$config] = new self($key, $config);
+        }
+        return self::$_instance[$key][$config];
+    }
+
+    /**
+     * 获取队列长度
+     * @return int
+     */
+    public function length()
+    {
+        $result = $this->getConnection(true)->lLen($this->key);
+        return $result;
+    }
+
+    /**
+     * 插入数据
+     * @param $data
+     * @return int
+     */
+    public function push($data)
+    {
+        $result = $this->getConnection(true)->lPush($this->key, $data);
+        return $result;
+    }
+
+    /**
+     * 取出数据
+     * @return string
+     */
+    public function pop()
+    {
+        $result = $this->getConnection(true)->rPop($this->key);
+        return $result;
+    }
+
+    public function delete()
+    {
+        $result = $this->getConnection(true)->delete($this->key);
+        return $result;
+    }
+
+    protected function __construct($key, $config)
+    {
+        $this->key = $key;
+        $this->redisObj = new \PhpLife\Frame\Library\Redis($config);
+    }
+
+    protected function getConnection($isMaster)
+    {
+        $connection = $this->redisObj->getConnection($isMaster);
+        return $connection;
+    }
+
+}

+ 291 - 0
Frame/Package/Set.php

@@ -0,0 +1,291 @@
+<?php
+
+namespace PhpLife\Frame\Package;
+
+/**
+ * 使用 redis sorted sets
+ */
+class Set extends \PhpLife\Frame\Package
+{
+    const CONFIG_CLASS = 'Redis\\Core';
+    const PREFIX = '';
+    const EXPIRE = 0; // 3 天
+
+
+    /**
+     * 获取列表,从$start 到 $end,默认获取全部
+     * 从大到小获取!!
+     * @param $id
+     * @param $lastId
+     * @param $limit
+     * @return array
+     */
+    public static function get($id, $lastId, $limit = 20, $withscores = false)
+    {
+        $key = self::getKey($id);
+        try {
+            if ($lastId <= 0) {
+                $options = ($limit > 0) ? array('limit' => array(0, $limit)) : array();
+                if ($withscores) {
+                    $options['withscores'] = true;
+                }
+                $result = self::getConnection()->zRevRangeByScore($key, PHP_INT_MAX, 0, $options);
+            } else {
+                if ($withscores) {
+                    $options['withscores'] = true;
+                }
+                // 获取$lastId对应的score, 如果没有找到,则返回空
+                $score = self::getConnection()->zScore($key, $lastId);
+
+                if ($score === false) {
+                    return array();
+                }
+                if ($limit <= 0) {
+                    $result = self::getConnection()->zRevRangeByScore($key, $score, 0, $options);
+                    if ($result) {
+                        if ($lastId == current($result)) {
+                            array_shift($result);
+                        }
+                    }
+                } else {
+                    // 排除第一个元素, 多取出一个元素, 然后删除
+                    $limit = $limit + 1;
+                    $options = array('limit' => array(0, $limit));
+                    if ($withscores) {
+                        $options['withscores'] = true;
+                    }
+                    $result = self::getConnection()->zRevRangeByScore($key, $score, 0, $options);
+
+                    if ($result) {
+                        if ($lastId == current($result)) {
+                            array_shift($result);
+                        } elseif (count($result) >= $limit) {
+                            array_pop($result);
+                        }
+                    }
+                }
+            }
+        } catch (\Exception $ex) {
+            $result = array();
+        }
+        return $result;
+    }
+
+    public static function getsByIndex($id, $lastId, $limit = 20, $withscores = false)
+    {
+        $key = self::getKey($id);
+       
+        try {
+            if ($lastId <= 0) { 
+                $start = 0;
+                $stop = $limit-1; 
+                $result = self::getConnection()->zRevRange($key, $start, $stop, $withscores);
+            } else {
+                $last_index = self::getConnection()->zRevRank($key, $lastId);
+                if ($last_index) {
+                    $start = $last_index;
+                    $stop = ($start + $limit);
+                    $result = self::getConnection()->zRevRange($key, $start, $stop, $withscores);
+                    if ($result) {
+                        if ($lastId == current($result)) {
+                            array_shift($result);
+                        }
+                    }
+                } 
+            }
+        } catch (\Exception $ex) {
+            $result = array();
+        }
+        return $result;
+    }
+
+    public static function getByPage($id, $page, $limit = 20, $withscores = false)
+    {
+        $key = self::getKey($id);
+        try {
+            $options = array('limit' => array($page * $limit, $limit));
+            if ($withscores) {
+                $options['withscores'] = true;
+            }
+            $result = self::getConnection()->zRevRangeByScore($key, PHP_INT_MAX, 0, $options);
+
+        } catch (\Exception $ex) {
+            $result = array();
+        }
+        return $result;
+    }
+
+    /**
+     * 获取所有的ID列表
+     * @param $id
+     * @return array
+     */
+    public static function getAll($id, $withscores = true)
+    {
+        $key = self::getKey($id);
+        //89de3bf93d9670af:
+        $options = array();
+        if ($withscores) {
+            $options['withscores'] = true;
+        }
+        try {
+            $result = self::getConnection()->zRevRangeByScore($key, PHP_INT_MAX, 0, $options);
+        } catch (\Exception $ex) {
+            $result = array();
+        }
+        return $result;
+    }
+
+    /**
+     * 获取 $value 的索引值
+     */
+    public static function getIndex($id, $value)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->zRank($key, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 添加元素, 注意score相同时可能会有问题, 建议使用 addBatch()
+     * @param $id
+     * @param int $score
+     * @param int $value
+     * @return bool
+     */
+    public static function add($id, $score, $value)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->zAdd($key, $score, $value);
+            // 可选过期
+            $class = get_called_class();
+            if ($class::EXPIRE) {
+                self::getConnection(true)->expire($key, $class::EXPIRE);
+            }
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 批量添加, 重新计算权重
+     * @param $id
+     * @param array $data key为列表id,value为score,会重新排序,然后计算score
+     * @return bool|int
+     */
+    public static function addBatch($id, Array $data)
+    {
+        asort($data); // 升序排列
+        $i = 1;
+        foreach ($data as $k => $score) {
+            $score = $i * 10000;
+            self::add($id, $score, $k);
+            $i++;
+        }
+        return true;
+    }
+
+    /**
+     * 移除元素
+     * @param $id
+     * @param int $value
+     * @return int
+     */
+    public static function remove($id, $value)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->zDelete($key, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 更新集合内容, 先删除(不判断结果),然后插入
+     * @param $id
+     * @param $score
+     * @param $value
+     * @return bool|int
+     */
+    public static function update($id, $score, $value)
+    {
+        self::remove($id, $value);
+        $result = self::add($id, $score, $value);
+        return $result;
+    }
+
+    /**
+     * 移除指定范围内元素
+     * @param $id
+     * @param int $start
+     * @param int $stop
+     * @return int
+     */
+    public static function removeByLex($id, $start, $stop)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->zDeleteRangeByRank($key, $start, $stop);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取列表长度
+     * @param $id
+     * @param int $start
+     * @param int $end
+     * @return int
+     */
+    public static function count($id, $start = 0, $end = PHP_INT_MAX)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->zCount($key, $start, $end);
+        } catch (\Exception $ex) {
+            $result = 0;
+        }
+        return $result;
+    }
+
+    /**
+     * 删除
+     * @param $id
+     * @return bool
+     */
+    public static function delete($id)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->delete($key);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取连接池
+     * @param bool $isMaster
+     * @return \Redis
+     */
+    protected static function getConnection($isMaster = false)
+    {
+        $class = get_called_class();
+        $configClass = $class::CONFIG_CLASS;
+        $redis = new \PhpLife\Frame\Library\Redis($configClass);
+        $connection = $redis->getConnection($isMaster);
+        return $connection;
+    }
+
+}

+ 203 - 0
Frame/Request.php

@@ -0,0 +1,203 @@
+<?php
+
+namespace PhpLife\Frame;
+
+class Request
+{
+    const MODE_ONLINE = 0; // 正式环境
+    const MODE_BETA = 1; // Beta 环境
+    const MODE_PRE = 2; // Pre 预览环境
+    const MODE_DEVELOP = 3; // 开发环境
+    
+    const MODE_ONLINE_PRE=4;
+    const MODE_BETA_PRE=5;
+    const MODE_DEVLOP_PRE=6;
+
+    protected static $privateDataPath = ''; // 私有文件路径
+    protected static $mode = null;
+    protected static $exception;
+    protected static $currentUserId = null; // 当前登录UserID
+    protected static $sessionId = null;
+    protected static $isCacheEnabled = true; // 缓存是否有效
+
+    protected static $data = array(); // 运行期缓存
+
+    /**
+     * 获取一个随机的唯一的ID
+     * @return null|string
+     */
+    public static function getRequestId()
+    {
+        static $id = null;
+        if (is_null($id)) {
+            $id = md5(uniqid('', true) . microtime() . rand(0, 999999999));
+        }
+        return $id;
+    }
+
+    /**
+     * 设置运行期缓存,尽量不要设置大数据
+     * @param $key
+     * @param $value
+     */
+    public static function set($key, $value)
+    {
+        self::$data[$key] = $value;
+    }
+
+    /**
+     * 读取运行期缓存
+     * @param $key
+     * @return mixed
+     */
+    public static function get($key)
+    {
+        return self::$data[$key];
+    }
+
+    /**
+     * 抛出异常,并记录
+     * @param string $message
+     * @param int $code
+     * @throws \Exception
+     */
+    public static function throwException($message = '', $code = 0)
+    {
+        self::$exception = new \Exception($message, $code);
+        Logger::error(self::$exception, 'request.exception');
+        throw self::$exception;
+    }
+
+    /**
+     * 设置异常
+     * @param $exception
+     * @return bool
+     */
+    public static function setException($exception)
+    {
+        if ($exception instanceof \Exception) {
+            self::$exception = $exception;
+            Logger::error($exception, 'request.exception');
+        }
+        return true;
+    }
+
+    /**
+     * 获取异常
+     * @return mixed
+     */
+    public static function getException()
+    {
+        return self::$exception;
+    }
+
+    /**
+     * 设置环境变量
+     * @param int $value
+     */
+    public static function setMode($value = 0)
+    {
+        self::$mode = $value;
+    }
+
+    /**
+     * 获取环境变量,优先使用程序中设置的,其次使用环境变量
+     * @return int
+     */
+    public static function getMode()
+    {
+        return intval(self::$mode);
+    }
+
+    /**
+     * 检查环境变量
+     * @param $value
+     * @return bool
+     */
+    public static function checkMode($value)
+    {
+        if (self::getMode() == $value) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 设置当前登录用户ID
+     * @param $userId
+     */
+    public static function setCurrentUserId($userId)
+    {
+        if ($userId) {
+            self::$currentUserId = $userId;
+        }
+    }
+
+    /**
+     * 返回当前登录用户ID
+     * @return int|null
+     */
+    public static function getCurrentUserId()
+    {
+        return intval(self::$currentUserId);
+    }
+
+    /**
+     * 设置会话ID
+     * @param $sessionId
+     */
+    public static function setSessionId($sessionId)
+    {
+        if ($sessionId) {
+            self::$sessionId = $sessionId;
+        }
+    }
+
+    /**
+     * 获取会话ID
+     * @return int|null
+     */
+    public static function getSessionId()
+    {
+        return self::$sessionId;
+    }
+
+    /**
+     * 设置私有文件路径
+     * @param $path
+     */
+    public static function setPrivateDataPath($path)
+    {
+        self::$privateDataPath = rtrim($path, '/');
+    }
+
+    /**
+     * 获取私有文件路径
+     * @param string $filename
+     * @return string
+     */
+    public static function getPrivateDataPath($filename = '')
+    {
+        return self::$privateDataPath . '/' . rtrim($filename) . '/';
+    }
+
+    /**
+     * 判断是否开启缓存
+     * @return bool
+     */
+    public static function isCacheEnabled()
+    {
+        return self::$isCacheEnabled;
+    }
+
+    /**
+     * 是否启用缓存
+     * @param bool $value
+     */
+    public static function setCacheEnabled($value = true)
+    {
+        self::$isCacheEnabled = intval($value);
+    }
+
+}

+ 39 - 0
Frame/Router.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace PhpLife\Frame;
+
+class Router
+{
+    protected static $list = array();
+
+    /**
+     * 添加路由
+     * @param $key
+     * @param $value
+     */
+    public static function add($key, $value)
+    {
+        self::$list[$key] = $value;
+    }
+
+    /**
+     * 移除路由
+     * @param $key
+     */
+    public static function remove($key)
+    {
+        if (isset(self::$list[$key])) {
+            unset(self::$list[$key]);
+        }
+    }
+
+    /**
+     * 获取路由列表
+     * @return array
+     */
+    public static function getList()
+    {
+        return self::$list;
+    }
+
+}

+ 148 - 0
Frame/Session.php

@@ -0,0 +1,148 @@
+<?php
+
+namespace PhpLife\Frame;
+
+class Session
+{
+
+    protected static $expire = ADMIN_TOKEN_EXPIRED;
+
+    /**
+     * 设置超时时间, 初始化 cookie等
+     *
+     * @param int $expire
+     * @throws \Exception
+     */
+    public static function start($expire = 1800)
+    {
+        if (is_numeric($expire)) {
+            self::$expire = $expire;
+        }
+        self::getSessionId();
+    }
+
+    /**
+     * 设置会话信息
+     *
+     * @param string $key
+     * @param string $data
+     * @param null $user_id
+     * @return bool
+     */
+    public static function set($key, $data, $user_id = null)
+    {
+        $sessionId = self::getSessionId($user_id);
+        try {
+            $redis = self::getConnection(true);
+            $isExisted = $redis->exists($sessionId);
+            $result = $redis->hSet($sessionId, $key, $data);
+            // if (!$isExisted) {
+            $redis->expire($sessionId, self::$expire);
+            // }
+        } catch (\Exception $ex) {
+            Logger::debug($ex, 'session.set');
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 读取会话信息
+     *
+     * @param $key
+     * @param null $user_id
+     * @return mixed
+     */
+    public static function get($key, $user_id = null)
+    {
+
+        $sessionId = self::getSessionId($user_id);
+        try {
+            $redis = self::getConnection();
+            $result = $redis->hGet($sessionId, $key);
+            $redis->expire($sessionId, self::$expire);
+        } catch (\Exception $ex) {
+            Logger::debug($ex, 'session.get');
+            $result = null;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取所有的会话信息
+     *
+     * @param null $user_id
+     * @return array
+     */
+    public static function getAll($user_id = null)
+    {
+        $sessionId = self::getSessionId($user_id);
+        try {
+            $result = self::getConnection()->hGetAll($sessionId);
+        } catch (\Exception $ex) {
+            Logger::debug($ex, 'session.get_all');
+            $result = array();
+        }
+        return $result;
+    }
+
+    /**
+     * 删除会话
+     *
+     * @param null $user_id
+     * @return bool
+     */
+    public static function destroy($user_id = null)
+    {
+        $sessionId = self::getSessionId($user_id);
+        try {
+            $result = self::getConnection(true)->delete($sessionId);
+            $_COOKIE[$sessionId] = null;
+        } catch (\Exception $ex) {
+            Logger::debug($ex, 'session.destroy');
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取会话ID
+     *
+     * @param null $user_id
+     * @return string
+     * @throws \Exception
+     */
+    public static function getSessionId($user_id = null)
+    {
+        if ($user_id) {
+            $sessionId = md5($user_id);
+        } else {
+            $host = $_SERVER['HTTP_HOST'];
+            $key = 'sid:' . crc32($host);
+            if (!$_COOKIE[$key]) {
+                $sessionId = md5(microtime() . rand() . uniqid());
+                $result = setcookie($key, $sessionId, time() + self::$expire * 10, '/', $host, null, true);
+                if (!$result) {
+                    throw new \Exception('set cookie failed', 9000);
+                }
+            } else {
+                $sessionId = $_COOKIE[$key];
+            }
+        }
+        return $sessionId;
+    }
+
+    /**
+     *
+     * @param bool $isMaster
+     * @return \Redis
+     * @internal param $ $isMaster*            $isMaster
+     */
+    protected static function getConnection($isMaster = false)
+    {
+        $configClass = 'Redis\Session';
+        $redis = new \PhpLife\Frame\Library\Redis($configClass);
+        $connection = $redis->getConnection($isMaster);
+        return $connection;
+    }
+}

+ 234 - 0
Frame/Template.php

@@ -0,0 +1,234 @@
+<?php
+
+namespace PhpLife\Frame;
+
+use PhpLife\Config\Lang\Lang;
+/**
+ * 简易模板类
+ */
+class Template
+{
+    private $data = array();
+    private $path;
+    private $tpl = null;
+    private $suffix = '.phtml';
+
+
+    public function __isset($name)
+    {
+        return isset($this->data[$name]);
+    }
+
+    public function __set($name, $value = null)
+    {
+        $this->data[$name] = $value;
+    }
+
+    public function __get($name)
+    {
+        return isset($this->data[$name]) ? $this->data[$name] : null;
+    }
+
+    /**
+     * 设置模板路径
+     * @param $path
+     * @return $this
+     */
+    public function setPath($path)
+    {
+        if ($path) {
+            $this->path = rtrim($path, '/') . DIRECTORY_SEPARATOR;
+        }
+        return $this;
+    }
+
+    /**
+     * 设置模板后缀
+     * @param $suffix
+     * @return $this
+     */
+    public function setSuffix($suffix)
+    {
+        if ($suffix) {
+            $this->suffix = $suffix;
+        }
+        return $this;
+    }
+
+    /**
+     * 设置模板名称
+     * @param $tpl
+     * @return $this
+     */
+    public function setTpl($tpl)
+    {
+        if ($tpl) {
+            $this->tpl = $tpl;
+        }
+        return $this;
+    }
+
+    /**
+     * 变量赋值, 可以单独赋值,也可以批量赋值
+     * @param $name
+     * @param null $value
+     * @return $this
+     */
+    public function assign($name, $value = null)
+    {
+        if (is_array($name)) {
+            foreach ($name as $k => $v) {
+                $this->data[$k] = $v;
+            }
+        } else {
+            $this->data[$name] = $value;
+        }
+        return $this;
+    }
+
+    /**
+     * 获取全部数据
+     * @return array
+     */
+    public function getData()
+    {
+        return $this->data;
+    }
+
+    /**
+     * 载入其他模板
+     * @param $tpl
+     * @return null
+     */
+    public function load($tpl)
+    {
+        $tpl = $this->path . $tpl . $this->suffix;
+        if (is_readable($tpl)) {
+            include($tpl);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * 渲染页面
+     * @param bool $isReturn
+     * @param string $layout
+     * @return string
+     */
+    public function render($isReturn = false, $layout = '')
+    {
+        $filename = $this->path . $this->tpl . $this->suffix;
+        // 判断模板是否存在
+        if (!is_readable($filename)) {
+            Logger::error($filename, 'template.not_found');
+            return '';
+        }
+        // 判断layout是否存在
+        if ($layout && is_readable($this->path . $layout . $this->suffix)) {
+            $filename = $this->path . $layout . $this->suffix;
+        }
+        if ($isReturn == false) {
+            include($filename);
+        } else {
+            ob_start();
+            include($filename);
+            $content = ob_get_contents();
+            ob_end_clean();
+            return $content;
+        }
+    }
+
+    /**
+     * 输出ajax信息
+     * @param bool $result
+     * @param null $code
+     * @param null $msg
+     * @param null $data
+     */
+    public function output($result = true, $code = null, $msg = null, $data = null)
+    {
+        if ($result instanceof Exception) {
+            $code = $result->getCode();
+            $msg = $result->getMessage();
+            $data = $result->__toString();
+            $result = false;
+        }
+        if ($result AND !is_bool($result) AND is_null($data)) {
+            $data = $result;
+        }
+        $result = (bool)$result;
+        $code = (int)$code;
+        $msg = (!empty($msg)) ? $msg : '';
+        $data = (!empty($data)) ? $data : '';
+
+        $data = array(
+            'result' => $result,
+            'code' => $code,
+            'msg' => $msg,
+            'data' => $data,
+        );
+
+        header('Content-type: application/json');
+        echo json_encode($data);
+    }
+
+    /**
+     * 获取同域名下的其他子域名
+     * @param $domain
+     * @return string
+     */
+    protected function getDomainHost($domain)
+    {
+        $domainHost = $_SERVER['HTTP_HOST'];
+        $domainHost = $domainHost."/".$domain;
+        return $domainHost;
+    }
+    /**
+     * 获取多语言配置文件
+     * @param string $key key
+     * @param string $default 默认值
+     */
+    protected function getLang($key,$default="",$app="Admin",$lang=NULL){
+        return Lang::getData($key,$default,$app,$lang);
+    }
+
+    protected function getPager($pager){
+//        if($pager['total_page'] == 0 || $pager['total_page'] <= $pager['page_size']){
+//            return '';
+//        }
+        $current_start = ($pager['current_page']-1)*$pager['page_size']+1;
+        $current_end = $pager['current_page']*$pager['page_size'] < $pager['total_count'] ? $pager['current_page']*$pager['page_size']:$pager['total_count'];
+        $html = '<div class="row">
+        <div class="col-sm-5">
+        <div class="dataTables_info">当前显示 '.$current_start.' 到 '.$current_end.' 条,共 '.$pager['total_count'].' 条记录</div>
+        </div><div class="col-sm-7">
+        <div class="text-right">
+        <ul class="pagination" style="margin:0">';
+        $start = 1;
+        $skip = 0;
+        $end = $pager['total_page'];
+        if($pager['current_page'] - 3 > $start){
+            $start = $pager['current_page'] - 3;
+            $html .= '<li class="paginate_button"><a href="'.$pager['url'].'?page=1&'.$pager['filter'].'">首页</a></li>';
+        }else{
+            $skip = 4 - $pager['current_page'];
+        }
+        if($pager['current_page'] + 3 < $end){
+            $end = $pager['current_page'] + 3 + $skip;
+        }
+        for($i = $start; $i <= $end; $i++){
+            if($i == $pager['current_page']){
+                $html .= '<li class="paginate_button active"><a href="javascript:;">'.$i.'</a></li>';
+            }else{
+                $html .= '<li class="paginate_button"><a href="'.$pager['url'].'?page='.$i.'&'.$pager['filter'].'">'.$i.'</a></li>';
+            }
+        }
+        if($end < $pager['total_page']){
+            $html .= '<li class="paginate_button"><a href="'.$pager['url'].'?page='.$pager['total_page'].'&'.$pager['filter'].'">末页</a></li>';
+        }
+        $html .= '</ul></div>';
+        return $html;
+    }
+
+}

+ 47 - 0
Library/Sphinx.php

@@ -0,0 +1,47 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: chendeben
+ * Date: 2017/12/6
+ * Time: 11:55
+ */
+
+namespace PhpLife\Library;
+
+use PhpLife\Config\Sphinx\Core;
+
+require_once 'sphinxapi.php';
+
+class Sphinx
+{
+    private $server;
+
+    public static $instance;
+
+    public function __construct($is_pre = FALSE)
+    {
+        $config = Core::getData();
+        $host = $config['host'];
+        $port = $config['port'];
+        $this->server=new \SphinxClient();
+        $this->server->SetServer($host,$port);
+    }
+
+    public static function instance($is_pre = false)
+    {
+        $key = $is_pre ? 'pre' : 'formal';
+        if (!isset(self::$instance[$key])) {
+            $instance = new self($is_pre);
+            self::$instance[$key] = $instance;
+        } else {
+            $instance = self::$instance[$key];
+        }
+        return $instance;
+    }
+
+    public function query($keyword,$offset,$count,$max=1000){
+        $this->server->SetLimits ( $offset, $count, $max, 0 );
+        $res=$this->server->Query($keyword);
+        return$res;
+    }
+}

+ 1840 - 0
Library/sphinxapi.php

@@ -0,0 +1,1840 @@
+<?php
+
+//
+// $Id$
+//
+
+//
+// Copyright (c) 2001-2015, Andrew Aksyonoff
+// Copyright (c) 2008-2015, Sphinx Technologies Inc
+// All rights reserved
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU Library General Public License. You should
+// have received a copy of the LGPL license along with this program; if you
+// did not, you can find it at http://www.gnu.org/
+//
+// WARNING!!!
+//
+// As of 2015, we strongly recommend to use either SphinxQL or REST APIs
+// rather than the native SphinxAPI.
+//
+// While both the native SphinxAPI protocol and the existing APIs will
+// continue to exist, and perhaps should not even break (too much), exposing
+// all the new features via multiple different native API implementations
+// is too much of a support complication for us.
+//
+// That said, you're welcome to overtake the maintenance of any given
+// official API, and remove this warning ;)
+//
+
+/////////////////////////////////////////////////////////////////////////////
+// PHP version of Sphinx searchd client (PHP API)
+/////////////////////////////////////////////////////////////////////////////
+
+/// known searchd commands
+define ( "SEARCHD_COMMAND_SEARCH",		0 );
+define ( "SEARCHD_COMMAND_EXCERPT",		1 );
+define ( "SEARCHD_COMMAND_UPDATE",		2 );
+define ( "SEARCHD_COMMAND_KEYWORDS",	3 );
+define ( "SEARCHD_COMMAND_PERSIST",		4 );
+define ( "SEARCHD_COMMAND_STATUS",		5 );
+define ( "SEARCHD_COMMAND_FLUSHATTRS",	7 );
+
+/// current client-side command implementation versions
+define ( "VER_COMMAND_SEARCH",		0x11E );
+define ( "VER_COMMAND_EXCERPT",		0x104 );
+define ( "VER_COMMAND_UPDATE",		0x103 );
+define ( "VER_COMMAND_KEYWORDS",	0x100 );
+define ( "VER_COMMAND_STATUS",		0x101 );
+define ( "VER_COMMAND_QUERY",		0x100 );
+define ( "VER_COMMAND_FLUSHATTRS",	0x100 );
+
+/// known searchd status codes
+define ( "SEARCHD_OK",				0 );
+define ( "SEARCHD_ERROR",			1 );
+define ( "SEARCHD_RETRY",			2 );
+define ( "SEARCHD_WARNING",			3 );
+
+/// known match modes
+define ( "SPH_MATCH_ALL",			0 );
+define ( "SPH_MATCH_ANY",			1 );
+define ( "SPH_MATCH_PHRASE",		2 );
+define ( "SPH_MATCH_BOOLEAN",		3 );
+define ( "SPH_MATCH_EXTENDED",		4 );
+define ( "SPH_MATCH_FULLSCAN",		5 );
+define ( "SPH_MATCH_EXTENDED2",		6 );	// extended engine V2 (TEMPORARY, WILL BE REMOVED)
+
+/// known ranking modes (ext2 only)
+define ( "SPH_RANK_PROXIMITY_BM25",	0 );	///< default mode, phrase proximity major factor and BM25 minor one
+define ( "SPH_RANK_BM25",			1 );	///< statistical mode, BM25 ranking only (faster but worse quality)
+define ( "SPH_RANK_NONE",			2 );	///< no ranking, all matches get a weight of 1
+define ( "SPH_RANK_WORDCOUNT",		3 );	///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
+define ( "SPH_RANK_PROXIMITY",		4 );
+define ( "SPH_RANK_MATCHANY",		5 );
+define ( "SPH_RANK_FIELDMASK",		6 );
+define ( "SPH_RANK_SPH04",			7 );
+define ( "SPH_RANK_EXPR",			8 );
+define ( "SPH_RANK_TOTAL",			9 );
+
+/// known sort modes
+define ( "SPH_SORT_RELEVANCE",		0 );
+define ( "SPH_SORT_ATTR_DESC",		1 );
+define ( "SPH_SORT_ATTR_ASC",		2 );
+define ( "SPH_SORT_TIME_SEGMENTS", 	3 );
+define ( "SPH_SORT_EXTENDED", 		4 );
+define ( "SPH_SORT_EXPR", 			5 );
+
+/// known filter types
+define ( "SPH_FILTER_VALUES",		0 );
+define ( "SPH_FILTER_RANGE",		1 );
+define ( "SPH_FILTER_FLOATRANGE",	2 );
+define ( "SPH_FILTER_STRING",	3 );
+
+/// known attribute types
+define ( "SPH_ATTR_INTEGER",		1 );
+define ( "SPH_ATTR_TIMESTAMP",		2 );
+define ( "SPH_ATTR_ORDINAL",		3 );
+define ( "SPH_ATTR_BOOL",			4 );
+define ( "SPH_ATTR_FLOAT",			5 );
+define ( "SPH_ATTR_BIGINT",			6 );
+define ( "SPH_ATTR_STRING",			7 );
+define ( "SPH_ATTR_FACTORS",			1001 );
+define ( "SPH_ATTR_MULTI",			0x40000001 );
+define ( "SPH_ATTR_MULTI64",			0x40000002 );
+
+/// known grouping functions
+define ( "SPH_GROUPBY_DAY",			0 );
+define ( "SPH_GROUPBY_WEEK",		1 );
+define ( "SPH_GROUPBY_MONTH",		2 );
+define ( "SPH_GROUPBY_YEAR",		3 );
+define ( "SPH_GROUPBY_ATTR",		4 );
+define ( "SPH_GROUPBY_ATTRPAIR",	5 );
+
+// important properties of PHP's integers:
+//  - always signed (one bit short of PHP_INT_SIZE)
+//  - conversion from string to int is saturated
+//  - float is double
+//  - div converts arguments to floats
+//  - mod converts arguments to ints
+
+// the packing code below works as follows:
+//  - when we got an int, just pack it
+//    if performance is a problem, this is the branch users should aim for
+//
+//  - otherwise, we got a number in string form
+//    this might be due to different reasons, but we assume that this is
+//    because it didn't fit into PHP int
+//
+//  - factor the string into high and low ints for packing
+//    - if we have bcmath, then it is used
+//    - if we don't, we have to do it manually (this is the fun part)
+//
+//    - x64 branch does factoring using ints
+//    - x32 (ab)uses floats, since we can't fit unsigned 32-bit number into an int
+//
+// unpacking routines are pretty much the same.
+//  - return ints if we can
+//  - otherwise format number into a string
+
+/// pack 64-bit signed
+function sphPackI64 ( $v )
+{
+	assert ( is_numeric($v) );
+	
+	// x64
+	if ( PHP_INT_SIZE>=8 )
+	{
+		$v = (int)$v;
+		return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
+	}
+
+	// x32, int
+	if ( is_int($v) )
+		return pack ( "NN", $v < 0 ? -1 : 0, $v );
+
+	// x32, bcmath	
+	if ( function_exists("bcmul") )
+	{
+		if ( bccomp ( $v, 0 ) == -1 )
+			$v = bcadd ( "18446744073709551616", $v );
+		$h = bcdiv ( $v, "4294967296", 0 );
+		$l = bcmod ( $v, "4294967296" );
+		return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
+	}
+
+	// x32, no-bcmath
+	$p = max(0, strlen($v) - 13);
+	$lo = abs((float)substr($v, $p));
+	$hi = abs((float)substr($v, 0, $p));
+
+	$m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912
+	$q = floor($m/4294967296.0);
+	$l = $m - ($q*4294967296.0);
+	$h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328
+
+	if ( $v<0 )
+	{
+		if ( $l==0 )
+			$h = 4294967296.0 - $h;
+		else
+		{
+			$h = 4294967295.0 - $h;
+			$l = 4294967296.0 - $l;
+		}
+	}
+	return pack ( "NN", $h, $l );
+}
+
+/// pack 64-bit unsigned
+function sphPackU64 ( $v )
+{
+	assert ( is_numeric($v) );
+	
+	// x64
+	if ( PHP_INT_SIZE>=8 )
+	{
+		assert ( $v>=0 );
+		
+		// x64, int
+		if ( is_int($v) )
+			return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
+						  
+		// x64, bcmath
+		if ( function_exists("bcmul") )
+		{
+			$h = bcdiv ( $v, 4294967296, 0 );
+			$l = bcmod ( $v, 4294967296 );
+			return pack ( "NN", $h, $l );
+		}
+		
+		// x64, no-bcmath
+		$p = max ( 0, strlen($v) - 13 );
+		$lo = (int)substr ( $v, $p );
+		$hi = (int)substr ( $v, 0, $p );
+	
+		$m = $lo + $hi*1316134912;
+		$l = $m % 4294967296;
+		$h = $hi*2328 + (int)($m/4294967296);
+
+		return pack ( "NN", $h, $l );
+	}
+
+	// x32, int
+	if ( is_int($v) )
+		return pack ( "NN", 0, $v );
+	
+	// x32, bcmath
+	if ( function_exists("bcmul") )
+	{
+		$h = bcdiv ( $v, "4294967296", 0 );
+		$l = bcmod ( $v, "4294967296" );
+		return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
+	}
+
+	// x32, no-bcmath
+	$p = max(0, strlen($v) - 13);
+	$lo = (float)substr($v, $p);
+	$hi = (float)substr($v, 0, $p);
+	
+	$m = $lo + $hi*1316134912.0;
+	$q = floor($m / 4294967296.0);
+	$l = $m - ($q * 4294967296.0);
+	$h = $hi*2328.0 + $q;
+
+	return pack ( "NN", $h, $l );
+}
+
+// unpack 64-bit unsigned
+function sphUnpackU64 ( $v )
+{
+	list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
+
+	if ( PHP_INT_SIZE>=8 )
+	{
+		if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
+		if ( $lo<0 ) $lo += (1<<32);
+
+		// x64, int
+		if ( $hi<=2147483647 )
+			return ($hi<<32) + $lo;
+
+		// x64, bcmath
+		if ( function_exists("bcmul") )
+			return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
+
+		// x64, no-bcmath
+		$C = 100000;
+		$h = ((int)($hi / $C) << 32) + (int)($lo / $C);
+		$l = (($hi % $C) << 32) + ($lo % $C);
+		if ( $l>$C )
+		{
+			$h += (int)($l / $C);
+			$l  = $l % $C;
+		}
+
+		if ( $h==0 )
+			return $l;
+		return sprintf ( "%d%05d", $h, $l );
+	}
+
+	// x32, int
+	if ( $hi==0 )
+	{
+		if ( $lo>0 )
+			return $lo;
+		return sprintf ( "%u", $lo );
+	}
+
+	$hi = sprintf ( "%u", $hi );
+	$lo = sprintf ( "%u", $lo );
+
+	// x32, bcmath
+	if ( function_exists("bcmul") )
+		return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
+	
+	// x32, no-bcmath
+	$hi = (float)$hi;
+	$lo = (float)$lo;
+	
+	$q = floor($hi/10000000.0);
+	$r = $hi - $q*10000000.0;
+	$m = $lo + $r*4967296.0;
+	$mq = floor($m/10000000.0);
+	$l = $m - $mq*10000000.0;
+	$h = $q*4294967296.0 + $r*429.0 + $mq;
+
+	$h = sprintf ( "%.0f", $h );
+	$l = sprintf ( "%07.0f", $l );
+	if ( $h=="0" )
+		return sprintf( "%.0f", (float)$l );
+	return $h . $l;
+}
+
+// unpack 64-bit signed
+function sphUnpackI64 ( $v )
+{
+	list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
+
+	// x64
+	if ( PHP_INT_SIZE>=8 )
+	{
+		if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
+		if ( $lo<0 ) $lo += (1<<32);
+
+		return ($hi<<32) + $lo;
+	}
+
+	// x32, int
+	if ( $hi==0 )
+	{
+		if ( $lo>0 )
+			return $lo;
+		return sprintf ( "%u", $lo );
+	}
+	// x32, int
+	elseif ( $hi==-1 )
+	{
+		if ( $lo<0 )
+			return $lo;
+		return sprintf ( "%.0f", $lo - 4294967296.0 );
+	}
+	
+	$neg = "";
+	$c = 0;
+	if ( $hi<0 )
+	{
+		$hi = ~$hi;
+		$lo = ~$lo;
+		$c = 1;
+		$neg = "-";
+	}	
+
+	$hi = sprintf ( "%u", $hi );
+	$lo = sprintf ( "%u", $lo );
+
+	// x32, bcmath
+	if ( function_exists("bcmul") )
+		return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c );
+
+	// x32, no-bcmath
+	$hi = (float)$hi;
+	$lo = (float)$lo;
+	
+	$q = floor($hi/10000000.0);
+	$r = $hi - $q*10000000.0;
+	$m = $lo + $r*4967296.0;
+	$mq = floor($m/10000000.0);
+	$l = $m - $mq*10000000.0 + $c;
+	$h = $q*4294967296.0 + $r*429.0 + $mq;
+	if ( $l==10000000 )
+	{
+		$l = 0;
+		$h += 1;
+	}
+
+	$h = sprintf ( "%.0f", $h );
+	$l = sprintf ( "%07.0f", $l );
+	if ( $h=="0" )
+		return $neg . sprintf( "%.0f", (float)$l );
+	return $neg . $h . $l;
+}
+
+
+function sphFixUint ( $value )
+{
+	if ( PHP_INT_SIZE>=8 )
+	{
+		// x64 route, workaround broken unpack() in 5.2.2+
+		if ( $value<0 ) $value += (1<<32);
+		return $value;
+	}
+	else
+	{
+		// x32 route, workaround php signed/unsigned braindamage
+		return sprintf ( "%u", $value );
+	}
+}
+
+function sphSetBit ( $flag, $bit, $on )
+{
+	if ( $on )
+	{
+		$flag += ( 1<<$bit );
+	} else
+	{
+		$reset = 255 ^ ( 1<<$bit );
+		$flag = $flag & $reset;
+	}
+	return $flag;
+}
+
+
+/// sphinx searchd client class
+class SphinxClient
+{
+	var $_host;			///< searchd host (default is "localhost")
+	var $_port;			///< searchd port (default is 9312)
+	var $_offset;		///< how many records to seek from result-set start (default is 0)
+	var $_limit;		///< how many records to return from result-set starting at offset (default is 20)
+	var $_mode;			///< query matching mode (default is SPH_MATCH_EXTENDED2)
+	var $_weights;		///< per-field weights (default is 1 for all fields)
+	var $_sort;			///< match sorting mode (default is SPH_SORT_RELEVANCE)
+	var $_sortby;		///< attribute to sort by (defualt is "")
+	var $_min_id;		///< min ID to match (default is 0, which means no limit)
+	var $_max_id;		///< max ID to match (default is 0, which means no limit)
+	var $_filters;		///< search filters
+	var $_groupby;		///< group-by attribute name
+	var $_groupfunc;	///< group-by function (to pre-process group-by attribute value with)
+	var $_groupsort;	///< group-by sorting clause (to sort groups in result set with)
+	var $_groupdistinct;///< group-by count-distinct attribute
+	var $_maxmatches;	///< max matches to retrieve
+	var $_cutoff;		///< cutoff to stop searching at (default is 0)
+	var $_retrycount;	///< distributed retries count
+	var $_retrydelay;	///< distributed retries delay
+	var $_anchor;		///< geographical anchor point
+	var $_indexweights;	///< per-index weights
+	var $_ranker;		///< ranking mode (default is SPH_RANK_PROXIMITY_BM25)
+	var $_rankexpr;		///< ranking mode expression (for SPH_RANK_EXPR)
+	var $_maxquerytime;	///< max query time, milliseconds (default is 0, do not limit)
+	var $_fieldweights;	///< per-field-name weights
+	var $_overrides;	///< per-query attribute values overrides
+	var $_select;		///< select-list (attributes or expressions, with optional aliases)
+	var $_query_flags; ///< per-query various flags
+	var $_predictedtime; ///< per-query max_predicted_time
+	var $_outerorderby; ///< outer match sort by
+	var $_outeroffset; ///< outer offset
+	var $_outerlimit; ///< outer limit
+	var $_hasouter;
+
+	var $_error;		///< last error message
+	var $_warning;		///< last warning message
+	var $_connerror;		///< connection error vs remote error flag
+
+	var $_reqs;			///< requests array for multi-query
+	var $_mbenc;		///< stored mbstring encoding
+	var $_arrayresult;	///< whether $result["matches"] should be a hash or an array
+	var $_timeout;		///< connect timeout
+
+	/////////////////////////////////////////////////////////////////////////////
+	// common stuff
+	/////////////////////////////////////////////////////////////////////////////
+
+	/// create a new client object and fill defaults
+	function SphinxClient ()
+	{
+		// per-client-object settings
+		$this->_host		= "localhost";
+		$this->_port		= 9312;
+		$this->_path		= false;
+		$this->_socket		= false;
+
+		// per-query settings
+		$this->_offset		= 0;
+		$this->_limit		= 20;
+		$this->_mode		= SPH_MATCH_EXTENDED2;
+		$this->_weights		= array ();
+		$this->_sort		= SPH_SORT_RELEVANCE;
+		$this->_sortby		= "";
+		$this->_min_id		= 0;
+		$this->_max_id		= 0;
+		$this->_filters		= array ();
+		$this->_groupby		= "";
+		$this->_groupfunc	= SPH_GROUPBY_DAY;
+		$this->_groupsort	= "@group desc";
+		$this->_groupdistinct= "";
+		$this->_maxmatches	= 1000;
+		$this->_cutoff		= 0;
+		$this->_retrycount	= 0;
+		$this->_retrydelay	= 0;
+		$this->_anchor		= array ();
+		$this->_indexweights= array ();
+		$this->_ranker		= SPH_RANK_PROXIMITY_BM25;
+		$this->_rankexpr	= "";
+		$this->_maxquerytime= 0;
+		$this->_fieldweights= array();
+		$this->_overrides 	= array();
+		$this->_select		= "*";
+		$this->_query_flags = sphSetBit ( 0, 6, true ); // default idf=tfidf_normalized
+		$this->_predictedtime = 0;
+		$this->_outerorderby = "";
+		$this->_outeroffset = 0;
+		$this->_outerlimit = 0;
+		$this->_hasouter = false;
+
+		$this->_error		= ""; // per-reply fields (for single-query case)
+		$this->_warning		= "";
+		$this->_connerror	= false;
+
+		$this->_reqs		= array ();	// requests storage (for multi-query case)
+		$this->_mbenc		= "";
+		$this->_arrayresult	= false;
+		$this->_timeout		= 0;
+	}
+
+	function __destruct()
+	{
+		if ( $this->_socket !== false )
+			fclose ( $this->_socket );
+	}
+
+	/// get last error message (string)
+	function GetLastError ()
+	{
+		return $this->_error;
+	}
+
+	/// get last warning message (string)
+	function GetLastWarning ()
+	{
+		return $this->_warning;
+	}
+
+	/// get last error flag (to tell network connection errors from searchd errors or broken responses)
+	function IsConnectError()
+	{
+		return $this->_connerror;
+	}
+
+	/// set searchd host name (string) and port (integer)
+	function SetServer ( $host, $port = 0 )
+	{
+		assert ( is_string($host) );
+		if ( $host[0] == '/')
+		{
+			$this->_path = 'unix://' . $host;
+			return;
+		}
+		if ( substr ( $host, 0, 7 )=="unix://" )
+		{
+			$this->_path = $host;
+			return;
+		}
+				
+		$this->_host = $host;
+		$port = intval($port);
+		assert ( 0<=$port && $port<65536 );
+		$this->_port = ( $port==0 ) ? 9312 : $port;
+		$this->_path = '';
+	}
+
+	/// set server connection timeout (0 to remove)
+	function SetConnectTimeout ( $timeout )
+	{
+		assert ( is_numeric($timeout) );
+		$this->_timeout = $timeout;
+	}
+
+
+	function _Send ( $handle, $data, $length )
+	{
+		if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length )
+		{
+			$this->_error = 'connection unexpectedly closed (timed out?)';
+			$this->_connerror = true;
+			return false;
+		}
+		return true;
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+
+	/// enter mbstring workaround mode
+	function _MBPush ()
+	{
+		$this->_mbenc = "";
+		if ( ini_get ( "mbstring.func_overload" ) & 2 )
+		{
+			$this->_mbenc = mb_internal_encoding();
+			mb_internal_encoding ( "latin1" );
+		}
+    }
+
+	/// leave mbstring workaround mode
+	function _MBPop ()
+	{
+		if ( $this->_mbenc )
+			mb_internal_encoding ( $this->_mbenc );
+	}
+
+	/// connect to searchd server
+	function _Connect ()
+	{
+		if ( $this->_socket!==false )
+		{
+			// we are in persistent connection mode, so we have a socket
+			// however, need to check whether it's still alive
+			if ( !@feof ( $this->_socket ) )
+				return $this->_socket;
+
+			// force reopen
+			$this->_socket = false;
+		}
+
+		$errno = 0;
+		$errstr = "";
+		$this->_connerror = false;
+
+		if ( $this->_path )
+		{
+			$host = $this->_path;
+			$port = 0;
+		}
+		else
+		{
+			$host = $this->_host;
+			$port = $this->_port;
+		}
+
+		if ( $this->_timeout<=0 )
+			$fp = @fsockopen ( $host, $port, $errno, $errstr );
+		else
+			$fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout );
+		
+		if ( !$fp )
+		{
+			if ( $this->_path )
+				$location = $this->_path;
+			else
+				$location = "{$this->_host}:{$this->_port}";
+			
+			$errstr = trim ( $errstr );
+			$this->_error = "connection to $location failed (errno=$errno, msg=$errstr)";
+			$this->_connerror = true;
+			return false;
+		}
+
+		// send my version
+		// this is a subtle part. we must do it before (!) reading back from searchd.
+		// because otherwise under some conditions (reported on FreeBSD for instance)
+		// TCP stack could throttle write-write-read pattern because of Nagle.
+		if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) )
+		{
+			fclose ( $fp );
+			$this->_error = "failed to send client protocol version";
+			return false;
+		}
+
+		// check version
+		list(,$v) = unpack ( "N*", fread ( $fp, 4 ) );
+		$v = (int)$v;
+		if ( $v<1 )
+		{
+			fclose ( $fp );
+			$this->_error = "expected searchd protocol version 1+, got version '$v'";
+			return false;
+		}
+
+		return $fp;
+	}
+
+	/// get and check response packet from searchd server
+	function _GetResponse ( $fp, $client_ver )
+	{
+		$response = "";
+		$len = 0;
+
+		$header = fread ( $fp, 8 );
+		if ( strlen($header)==8 )
+		{
+			list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) );
+			$left = $len;
+			while ( $left>0 && !feof($fp) )
+			{
+				$chunk = fread ( $fp, min ( 8192, $left ) );
+				if ( $chunk )
+				{
+					$response .= $chunk;
+					$left -= strlen($chunk);
+				}
+			}
+		}
+		if ( $this->_socket === false )
+			fclose ( $fp );
+
+		// check response
+		$read = strlen ( $response );
+		if ( !$response || $read!=$len )
+		{
+			$this->_error = $len
+				? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"
+				: "received zero-sized searchd response";
+			return false;
+		}
+
+		// check status
+		if ( $status==SEARCHD_WARNING )
+		{
+			list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) );
+			$this->_warning = substr ( $response, 4, $wlen );
+			return substr ( $response, 4+$wlen );
+		}
+		if ( $status==SEARCHD_ERROR )
+		{
+			$this->_error = "searchd error: " . substr ( $response, 4 );
+			return false;
+		}
+		if ( $status==SEARCHD_RETRY )
+		{
+			$this->_error = "temporary searchd error: " . substr ( $response, 4 );
+			return false;
+		}
+		if ( $status!=SEARCHD_OK )
+		{
+			$this->_error = "unknown status code '$status'";
+			return false;
+		}
+
+		// check version
+		if ( $ver<$client_ver )
+		{
+			$this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work",
+				$ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff );
+		}
+
+		return $response;
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+	// searching
+	/////////////////////////////////////////////////////////////////////////////
+
+	/// set offset and count into result set,
+	/// and optionally set max-matches and cutoff limits
+	function SetLimits ( $offset, $limit, $max=0, $cutoff=0 )
+	{
+		assert ( is_int($offset) );
+		assert ( is_int($limit) );
+		assert ( $offset>=0 );
+		assert ( $limit>0 );
+		assert ( $max>=0 );
+		$this->_offset = $offset;
+		$this->_limit = $limit;
+		if ( $max>0 )
+			$this->_maxmatches = $max;
+		if ( $cutoff>0 )
+			$this->_cutoff = $cutoff;
+	}
+
+	/// set maximum query time, in milliseconds, per-index
+	/// integer, 0 means "do not limit"
+	function SetMaxQueryTime ( $max )
+	{
+		assert ( is_int($max) );
+		assert ( $max>=0 );
+		$this->_maxquerytime = $max;
+	}
+
+	/// set matching mode
+	function SetMatchMode ( $mode )
+	{
+		trigger_error ( 'DEPRECATED: Do not call this method or, even better, use SphinxQL instead of an API', E_USER_DEPRECATED );
+		assert ( $mode==SPH_MATCH_ALL
+			|| $mode==SPH_MATCH_ANY
+			|| $mode==SPH_MATCH_PHRASE
+			|| $mode==SPH_MATCH_BOOLEAN
+			|| $mode==SPH_MATCH_EXTENDED
+			|| $mode==SPH_MATCH_FULLSCAN
+			|| $mode==SPH_MATCH_EXTENDED2 );
+		$this->_mode = $mode;
+	}
+
+	/// set ranking mode
+	function SetRankingMode ( $ranker, $rankexpr="" )
+	{
+		assert ( $ranker===0 || $ranker>=1 && $ranker<SPH_RANK_TOTAL );
+		assert ( is_string($rankexpr) );
+		$this->_ranker = $ranker;
+		$this->_rankexpr = $rankexpr;
+	}
+
+	/// set matches sorting mode
+	function SetSortMode ( $mode, $sortby="" )
+	{
+		assert (
+			$mode==SPH_SORT_RELEVANCE ||
+			$mode==SPH_SORT_ATTR_DESC ||
+			$mode==SPH_SORT_ATTR_ASC ||
+			$mode==SPH_SORT_TIME_SEGMENTS ||
+			$mode==SPH_SORT_EXTENDED ||
+			$mode==SPH_SORT_EXPR );
+		assert ( is_string($sortby) );
+		assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 );
+
+		$this->_sort = $mode;
+		$this->_sortby = $sortby;
+	}
+
+	/// bind per-field weights by order
+	/// DEPRECATED; use SetFieldWeights() instead
+	function SetWeights ( $weights )
+	{
+		die("This method is now deprecated; please use SetFieldWeights instead");
+	}
+
+	/// bind per-field weights by name
+	function SetFieldWeights ( $weights )
+	{
+		assert ( is_array($weights) );
+		foreach ( $weights as $name=>$weight )
+		{
+			assert ( is_string($name) );
+			assert ( is_int($weight) );
+		}
+		$this->_fieldweights = $weights;
+	}
+
+	/// bind per-index weights by name
+	function SetIndexWeights ( $weights )
+	{
+		assert ( is_array($weights) );
+		foreach ( $weights as $index=>$weight )
+		{
+			assert ( is_string($index) );
+			assert ( is_int($weight) );
+		}
+		$this->_indexweights = $weights;
+	}
+
+	/// set IDs range to match
+	/// only match records if document ID is beetwen $min and $max (inclusive)
+	function SetIDRange ( $min, $max )
+	{
+		assert ( is_numeric($min) );
+		assert ( is_numeric($max) );
+		assert ( $min<=$max );
+		$this->_min_id = $min;
+		$this->_max_id = $max;
+	}
+
+	/// set values set filter
+	/// only match records where $attribute value is in given set
+	function SetFilter ( $attribute, $values, $exclude=false )
+	{
+		assert ( is_string($attribute) );
+		assert ( is_array($values) );
+		assert ( count($values) );
+
+		if ( is_array($values) && count($values) )
+		{
+			foreach ( $values as $value )
+				assert ( is_numeric($value) );
+
+			$this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values );
+		}
+	}
+	
+	/// set string filter
+	/// only match records where $attribute value is equal
+	function SetFilterString ( $attribute, $value, $exclude=false )
+	{
+		assert ( is_string($attribute) );
+		assert ( is_string($value) );
+		$this->_filters[] = array ( "type"=>SPH_FILTER_STRING, "attr"=>$attribute, "exclude"=>$exclude, "value"=>$value );
+	}	
+
+	/// set range filter
+	/// only match records if $attribute value is beetwen $min and $max (inclusive)
+	function SetFilterRange ( $attribute, $min, $max, $exclude=false )
+	{
+		assert ( is_string($attribute) );
+		assert ( is_numeric($min) );
+		assert ( is_numeric($max) );
+		assert ( $min<=$max );
+
+		$this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
+	}
+
+	/// set float range filter
+	/// only match records if $attribute value is beetwen $min and $max (inclusive)
+	function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false )
+	{
+		assert ( is_string($attribute) );
+		assert ( is_float($min) );
+		assert ( is_float($max) );
+		assert ( $min<=$max );
+
+		$this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
+	}
+
+	/// setup anchor point for geosphere distance calculations
+	/// required to use @geodist in filters and sorting
+	/// latitude and longitude must be in radians
+	function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long )
+	{
+		assert ( is_string($attrlat) );
+		assert ( is_string($attrlong) );
+		assert ( is_float($lat) );
+		assert ( is_float($long) );
+
+		$this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long );
+	}
+
+	/// set grouping attribute and function
+	function SetGroupBy ( $attribute, $func, $groupsort="@group desc" )
+	{
+		assert ( is_string($attribute) );
+		assert ( is_string($groupsort) );
+		assert ( $func==SPH_GROUPBY_DAY
+			|| $func==SPH_GROUPBY_WEEK
+			|| $func==SPH_GROUPBY_MONTH
+			|| $func==SPH_GROUPBY_YEAR
+			|| $func==SPH_GROUPBY_ATTR
+			|| $func==SPH_GROUPBY_ATTRPAIR );
+
+		$this->_groupby = $attribute;
+		$this->_groupfunc = $func;
+		$this->_groupsort = $groupsort;
+	}
+
+	/// set count-distinct attribute for group-by queries
+	function SetGroupDistinct ( $attribute )
+	{
+		assert ( is_string($attribute) );
+		$this->_groupdistinct = $attribute;
+	}
+
+	/// set distributed retries count and delay
+	function SetRetries ( $count, $delay=0 )
+	{
+		assert ( is_int($count) && $count>=0 );
+		assert ( is_int($delay) && $delay>=0 );
+		$this->_retrycount = $count;
+		$this->_retrydelay = $delay;
+	}
+
+	/// set result set format (hash or array; hash by default)
+	/// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
+	function SetArrayResult ( $arrayresult )
+	{
+		assert ( is_bool($arrayresult) );
+		$this->_arrayresult = $arrayresult;
+	}
+
+	/// set attribute values override
+	/// there can be only one override per attribute
+	/// $values must be a hash that maps document IDs to attribute values
+	function SetOverride ( $attrname, $attrtype, $values )
+	{
+		trigger_error('DEPRECATED: Do not call this method. Use SphinxQL REMAP() function instead.', E_USER_DEPRECATED);
+		assert ( is_string ( $attrname ) );
+		assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) );
+		assert ( is_array ( $values ) );
+
+		$this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values );
+	}
+
+	/// set select-list (attributes or expressions), SQL-like syntax
+	function SetSelect ( $select )
+	{
+		assert ( is_string ( $select ) );
+		$this->_select = $select;
+	}
+	
+	function SetQueryFlag ( $flag_name, $flag_value )
+	{
+		$known_names = array ( "reverse_scan", "sort_method", "max_predicted_time", "boolean_simplify", "idf", "global_idf" );
+		$flags = array (
+		"reverse_scan" => array ( 0, 1 ),
+		"sort_method" => array ( "pq", "kbuffer" ),
+		"max_predicted_time" => array ( 0 ),
+		"boolean_simplify" => array ( true, false ),
+		"idf" => array ("normalized", "plain", "tfidf_normalized", "tfidf_unnormalized" ),
+		"global_idf" => array ( true, false ),
+		);
+		
+		assert ( isset ( $flag_name, $known_names ) );
+		assert ( in_array( $flag_value, $flags[$flag_name], true ) || ( $flag_name=="max_predicted_time" && is_int ( $flag_value ) && $flag_value>=0 ) );
+		
+		if ( $flag_name=="reverse_scan" )	$this->_query_flags = sphSetBit ( $this->_query_flags, 0, $flag_value==1 );
+		if ( $flag_name=="sort_method" )	$this->_query_flags = sphSetBit ( $this->_query_flags, 1, $flag_value=="kbuffer" );
+		if ( $flag_name=="max_predicted_time" )
+		{
+			$this->_query_flags = sphSetBit ( $this->_query_flags, 2, $flag_value>0 );
+			$this->_predictedtime = (int)$flag_value;
+		}
+		if ( $flag_name=="boolean_simplify" )	$this->_query_flags = sphSetBit ( $this->_query_flags, 3, $flag_value );
+		if ( $flag_name=="idf" && ( $flag_value=="normalized" || $flag_value=="plain" ) )	$this->_query_flags = sphSetBit ( $this->_query_flags, 4, $flag_value=="plain" );
+		if ( $flag_name=="global_idf" )	$this->_query_flags = sphSetBit ( $this->_query_flags, 5, $flag_value );
+		if ( $flag_name=="idf" && ( $flag_value=="tfidf_normalized" || $flag_value=="tfidf_unnormalized" ) )	$this->_query_flags = sphSetBit ( $this->_query_flags, 6, $flag_value=="tfidf_normalized" );
+	}
+	
+	/// set outer order by parameters
+	function SetOuterSelect ( $orderby, $offset, $limit )
+	{
+		assert ( is_string($orderby) );
+		assert ( is_int($offset) );
+		assert ( is_int($limit) );
+		assert ( $offset>=0 );
+		assert ( $limit>0 );
+
+		$this->_outerorderby = $orderby;
+		$this->_outeroffset = $offset;
+		$this->_outerlimit = $limit;
+		$this->_hasouter = true;
+	}
+
+	
+	//////////////////////////////////////////////////////////////////////////////
+
+	/// clear all filters (for multi-queries)
+	function ResetFilters ()
+	{
+		$this->_filters = array();
+		$this->_anchor = array();
+	}
+
+	/// clear groupby settings (for multi-queries)
+	function ResetGroupBy ()
+	{
+		$this->_groupby		= "";
+		$this->_groupfunc	= SPH_GROUPBY_DAY;
+		$this->_groupsort	= "@group desc";
+		$this->_groupdistinct= "";
+	}
+
+	/// clear all attribute value overrides (for multi-queries)
+	function ResetOverrides ()
+    {
+    	$this->_overrides = array ();
+    }
+	
+	function ResetQueryFlag ()
+	{
+		$this->_query_flags = sphSetBit ( 0, 6, true ); // default idf=tfidf_normalized
+		$this->_predictedtime = 0;
+	}
+
+	function ResetOuterSelect ()
+	{
+		$this->_outerorderby = '';
+		$this->_outeroffset = 0;
+		$this->_outerlimit = 0;
+		$this->_hasouter = false;
+	}
+
+	//////////////////////////////////////////////////////////////////////////////
+
+	/// connect to searchd server, run given search query through given indexes,
+	/// and return the search results
+	function Query ( $query, $index="*", $comment="" )
+	{
+		assert ( empty($this->_reqs) );
+
+		$this->AddQuery ( $query, $index, $comment );
+		$results = $this->RunQueries ();
+		$this->_reqs = array (); // just in case it failed too early
+
+		if ( !is_array($results) )
+			return false; // probably network error; error message should be already filled
+
+		$this->_error = $results[0]["error"];
+		$this->_warning = $results[0]["warning"];
+		if ( $results[0]["status"]==SEARCHD_ERROR )
+			return false;
+		else
+			return $results[0];
+	}
+
+	/// helper to pack floats in network byte order
+	function _PackFloat ( $f )
+	{
+		$t1 = pack ( "f", $f ); // machine order
+		list(,$t2) = unpack ( "L*", $t1 ); // int in machine order
+		return pack ( "N", $t2 );
+	}
+
+	/// add query to multi-query batch
+	/// returns index into results array from RunQueries() call
+	function AddQuery ( $query, $index="*", $comment="" )
+	{
+		// mbstring workaround
+		$this->_MBPush ();
+
+		// build request
+		$req = pack ( "NNNNN", $this->_query_flags, $this->_offset, $this->_limit, $this->_mode, $this->_ranker );
+		if ( $this->_ranker==SPH_RANK_EXPR )
+			$req .= pack ( "N", strlen($this->_rankexpr) ) . $this->_rankexpr;
+		$req .= pack ( "N", $this->_sort ); // (deprecated) sort mode
+		$req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby;
+		$req .= pack ( "N", strlen($query) ) . $query; // query itself
+		$req .= pack ( "N", count($this->_weights) ); // weights
+		foreach ( $this->_weights as $weight )
+			$req .= pack ( "N", (int)$weight );
+		$req .= pack ( "N", strlen($index) ) . $index; // indexes
+		$req .= pack ( "N", 1 ); // id64 range marker
+		$req .= sphPackU64 ( $this->_min_id ) . sphPackU64 ( $this->_max_id ); // id64 range
+
+		// filters
+		$req .= pack ( "N", count($this->_filters) );
+		foreach ( $this->_filters as $filter )
+		{
+			$req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"];
+			$req .= pack ( "N", $filter["type"] );
+			switch ( $filter["type"] )
+			{
+				case SPH_FILTER_VALUES:
+					$req .= pack ( "N", count($filter["values"]) );
+					foreach ( $filter["values"] as $value )
+						$req .= sphPackI64 ( $value );
+					break;
+
+				case SPH_FILTER_RANGE:
+					$req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $filter["max"] );
+					break;
+
+				case SPH_FILTER_FLOATRANGE:
+					$req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] );
+					break;
+					
+				case SPH_FILTER_STRING:
+					$req .= pack ( "N", strlen($filter["value"]) ) . $filter["value"];
+					break;
+
+				default:
+					assert ( 0 && "internal error: unhandled filter type" );
+			}
+			$req .= pack ( "N", $filter["exclude"] );
+		}
+
+		// group-by clause, max-matches count, group-sort clause, cutoff count
+		$req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby;
+		$req .= pack ( "N", $this->_maxmatches );
+		$req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort;
+		$req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay );
+		$req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct;
+
+		// anchor point
+		if ( empty($this->_anchor) )
+		{
+			$req .= pack ( "N", 0 );
+		} else
+		{
+			$a =& $this->_anchor;
+			$req .= pack ( "N", 1 );
+			$req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"];
+			$req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"];
+			$req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] );
+		}
+
+		// per-index weights
+		$req .= pack ( "N", count($this->_indexweights) );
+		foreach ( $this->_indexweights as $idx=>$weight )
+			$req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight );
+
+		// max query time
+		$req .= pack ( "N", $this->_maxquerytime );
+
+		// per-field weights
+		$req .= pack ( "N", count($this->_fieldweights) );
+		foreach ( $this->_fieldweights as $field=>$weight )
+			$req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight );
+
+		// comment
+		$req .= pack ( "N", strlen($comment) ) . $comment;
+
+		// attribute overrides
+		$req .= pack ( "N", count($this->_overrides) );
+		foreach ( $this->_overrides as $key => $entry )
+		{
+			$req .= pack ( "N", strlen($entry["attr"]) ) . $entry["attr"];
+			$req .= pack ( "NN", $entry["type"], count($entry["values"]) );
+			foreach ( $entry["values"] as $id=>$val )
+			{
+				assert ( is_numeric($id) );
+				assert ( is_numeric($val) );
+
+				$req .= sphPackU64 ( $id );
+				switch ( $entry["type"] )
+				{
+					case SPH_ATTR_FLOAT:	$req .= $this->_PackFloat ( $val ); break;
+					case SPH_ATTR_BIGINT:	$req .= sphPackI64 ( $val ); break;
+					default:				$req .= pack ( "N", $val ); break;
+				}
+			}
+		}
+
+		// select-list
+		$req .= pack ( "N", strlen($this->_select) ) . $this->_select;
+		
+		// max_predicted_time
+		if ( $this->_predictedtime>0 )
+			$req .= pack ( "N", (int)$this->_predictedtime );
+			
+		$req .= pack ( "N", strlen($this->_outerorderby) ) . $this->_outerorderby;
+		$req .= pack ( "NN", $this->_outeroffset, $this->_outerlimit );
+		if ( $this->_hasouter )
+			$req .= pack ( "N", 1 );
+		else
+			$req .= pack ( "N", 0 );
+
+		// mbstring workaround
+		$this->_MBPop ();
+
+		// store request to requests array
+		$this->_reqs[] = $req;
+		return count($this->_reqs)-1;
+	}
+
+	/// connect to searchd, run queries batch, and return an array of result sets
+	function RunQueries ()
+	{
+		if ( empty($this->_reqs) )
+		{
+			$this->_error = "no queries defined, issue AddQuery() first";
+			return false;
+		}
+
+		// mbstring workaround
+		$this->_MBPush ();
+
+		if (!( $fp = $this->_Connect() ))
+		{
+			$this->_MBPop ();
+			return false;
+		}
+
+		// send query, get response
+		$nreqs = count($this->_reqs);
+		$req = join ( "", $this->_reqs );
+		$len = 8+strlen($req);
+		$req = pack ( "nnNNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs ) . $req; // add header
+
+		if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
+			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ) )
+		{
+			$this->_MBPop ();
+			return false;
+		}
+
+		// query sent ok; we can reset reqs now
+		$this->_reqs = array ();
+
+		// parse and return response
+		return $this->_ParseSearchResponse ( $response, $nreqs );
+	}
+
+	/// parse and return search query (or queries) response
+	function _ParseSearchResponse ( $response, $nreqs )
+	{
+		$p = 0; // current position
+		$max = strlen($response); // max position for checks, to protect against broken responses
+
+		$results = array ();
+		for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ )
+		{
+			$results[] = array();
+			$result =& $results[$ires];
+
+			$result["error"] = "";
+			$result["warning"] = "";
+
+			// extract status
+			list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+			$result["status"] = $status;
+			if ( $status!=SEARCHD_OK )
+			{
+				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+				$message = substr ( $response, $p, $len ); $p += $len;
+
+				if ( $status==SEARCHD_WARNING )
+				{
+					$result["warning"] = $message;
+				} else
+				{
+					$result["error"] = $message;
+					continue;
+				}
+			}
+
+			// read schema
+			$fields = array ();
+			$attrs = array ();
+
+			list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+			while ( $nfields-->0 && $p<$max )
+			{
+				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+				$fields[] = substr ( $response, $p, $len ); $p += $len;
+			}
+			$result["fields"] = $fields;
+
+			list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+			while ( $nattrs-->0 && $p<$max  )
+			{
+				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+				$attr = substr ( $response, $p, $len ); $p += $len;
+				list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+				$attrs[$attr] = $type;
+			}
+			$result["attrs"] = $attrs;
+
+			// read match count
+			list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+			list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+
+			// read matches
+			$idx = -1;
+			while ( $count-->0 && $p<$max )
+			{
+				// index into result array
+				$idx++;
+
+				// parse document id and weight
+				if ( $id64 )
+				{
+					$doc = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8;
+					list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+				}
+				else
+				{
+					list ( $doc, $weight ) = array_values ( unpack ( "N*N*",
+						substr ( $response, $p, 8 ) ) );
+					$p += 8;
+					$doc = sphFixUint($doc);
+				}
+				$weight = sprintf ( "%u", $weight );
+
+				// create match entry
+				if ( $this->_arrayresult )
+					$result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight );
+				else
+					$result["matches"][$doc]["weight"] = $weight;
+
+				// parse and create attributes
+				$attrvals = array ();
+				foreach ( $attrs as $attr=>$type )
+				{
+					// handle 64bit ints
+					if ( $type==SPH_ATTR_BIGINT )
+					{
+						$attrvals[$attr] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8;
+						continue;
+					}
+
+					// handle floats
+					if ( $type==SPH_ATTR_FLOAT )
+					{
+						list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+						list(,$fval) = unpack ( "f*", pack ( "L", $uval ) ); 
+						$attrvals[$attr] = $fval;
+						continue;
+					}
+
+					// handle everything else as unsigned ints
+					list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+					if ( $type==SPH_ATTR_MULTI )
+					{
+						$attrvals[$attr] = array ();
+						$nvalues = $val;
+						while ( $nvalues-->0 && $p<$max )
+						{
+							list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+							$attrvals[$attr][] = sphFixUint($val);
+						}
+					} else if ( $type==SPH_ATTR_MULTI64 )
+					{
+						$attrvals[$attr] = array ();
+						$nvalues = $val;
+						while ( $nvalues>0 && $p<$max )
+						{
+							$attrvals[$attr][] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8;
+							$nvalues -= 2;
+						}
+					} else if ( $type==SPH_ATTR_STRING )
+					{
+						$attrvals[$attr] = substr ( $response, $p, $val );
+						$p += $val;						
+					} else if ( $type==SPH_ATTR_FACTORS )
+					{
+						$attrvals[$attr] = substr ( $response, $p, $val-4 );
+						$p += $val-4;						
+					} else
+					{
+						$attrvals[$attr] = sphFixUint($val);
+					}
+				}
+
+				if ( $this->_arrayresult )
+					$result["matches"][$idx]["attrs"] = $attrvals;
+				else
+					$result["matches"][$doc]["attrs"] = $attrvals;
+			}
+
+			list ( $total, $total_found, $msecs, $words ) =
+				array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) );
+			$result["total"] = sprintf ( "%u", $total );
+			$result["total_found"] = sprintf ( "%u", $total_found );
+			$result["time"] = sprintf ( "%.3f", $msecs/1000 );
+			$p += 16;
+
+			while ( $words-->0 && $p<$max )
+			{
+				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+				$word = substr ( $response, $p, $len ); $p += $len;
+				list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
+				$result["words"][$word] = array (
+					"docs"=>sprintf ( "%u", $docs ),
+					"hits"=>sprintf ( "%u", $hits ) );
+			}
+		}
+
+		$this->_MBPop ();
+		return $results;
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+	// excerpts generation
+	/////////////////////////////////////////////////////////////////////////////
+
+	/// connect to searchd server, and generate exceprts (snippets)
+	/// of given documents for given query. returns false on failure,
+	/// an array of snippets on success
+	function BuildExcerpts ( $docs, $index, $words, $opts=array() )
+	{
+		assert ( is_array($docs) );
+		assert ( is_string($index) );
+		assert ( is_string($words) );
+		assert ( is_array($opts) );
+
+		$this->_MBPush ();
+
+		if (!( $fp = $this->_Connect() ))
+		{
+			$this->_MBPop();
+			return false;
+		}
+
+		/////////////////
+		// fixup options
+		/////////////////
+
+		if ( !isset($opts["before_match"]) )		$opts["before_match"] = "<b>";
+		if ( !isset($opts["after_match"]) )			$opts["after_match"] = "</b>";
+		if ( !isset($opts["chunk_separator"]) )		$opts["chunk_separator"] = " ... ";
+		if ( !isset($opts["limit"]) )				$opts["limit"] = 256;
+		if ( !isset($opts["limit_passages"]) )		$opts["limit_passages"] = 0;
+		if ( !isset($opts["limit_words"]) )			$opts["limit_words"] = 0;
+		if ( !isset($opts["around"]) )				$opts["around"] = 5;
+		if ( !isset($opts["exact_phrase"]) )		$opts["exact_phrase"] = false;
+		if ( !isset($opts["single_passage"]) )		$opts["single_passage"] = false;
+		if ( !isset($opts["use_boundaries"]) )		$opts["use_boundaries"] = false;
+		if ( !isset($opts["weight_order"]) )		$opts["weight_order"] = false;
+		if ( !isset($opts["query_mode"]) )			$opts["query_mode"] = false;
+		if ( !isset($opts["force_all_words"]) )		$opts["force_all_words"] = false;
+		if ( !isset($opts["start_passage_id"]) )	$opts["start_passage_id"] = 1;
+		if ( !isset($opts["load_files"]) )			$opts["load_files"] = false;
+		if ( !isset($opts["html_strip_mode"]) )		$opts["html_strip_mode"] = "index";
+		if ( !isset($opts["allow_empty"]) )			$opts["allow_empty"] = false;
+		if ( !isset($opts["passage_boundary"]) )	$opts["passage_boundary"] = "none";
+		if ( !isset($opts["emit_zones"]) )			$opts["emit_zones"] = false;
+		if ( !isset($opts["load_files_scattered"]) )		$opts["load_files_scattered"] = false;
+		
+
+		/////////////////
+		// build request
+		/////////////////
+
+		// v.1.2 req
+		$flags = 1; // remove spaces
+		if ( $opts["exact_phrase"] )	$flags |= 2;
+		if ( $opts["single_passage"] )	$flags |= 4;
+		if ( $opts["use_boundaries"] )	$flags |= 8;
+		if ( $opts["weight_order"] )	$flags |= 16;
+		if ( $opts["query_mode"] )		$flags |= 32;
+		if ( $opts["force_all_words"] )	$flags |= 64;
+		if ( $opts["load_files"] )		$flags |= 128;
+		if ( $opts["allow_empty"] )		$flags |= 256;
+		if ( $opts["emit_zones"] )		$flags |= 512;
+		if ( $opts["load_files_scattered"] )	$flags |= 1024;
+		$req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags
+		$req .= pack ( "N", strlen($index) ) . $index; // req index
+		$req .= pack ( "N", strlen($words) ) . $words; // req words
+
+		// options
+		$req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"];
+		$req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"];
+		$req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"];
+		$req .= pack ( "NN", (int)$opts["limit"], (int)$opts["around"] );
+		$req .= pack ( "NNN", (int)$opts["limit_passages"], (int)$opts["limit_words"], (int)$opts["start_passage_id"] ); // v.1.2
+		$req .= pack ( "N", strlen($opts["html_strip_mode"]) ) . $opts["html_strip_mode"];
+		$req .= pack ( "N", strlen($opts["passage_boundary"]) ) . $opts["passage_boundary"];
+
+		// documents
+		$req .= pack ( "N", count($docs) );
+		foreach ( $docs as $doc )
+		{
+			assert ( is_string($doc) );
+			$req .= pack ( "N", strlen($doc) ) . $doc;
+		}
+
+		////////////////////////////
+		// send query, get response
+		////////////////////////////
+
+		$len = strlen($req);
+		$req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header
+		if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
+			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ) )
+		{
+			$this->_MBPop ();
+			return false;
+		}
+
+		//////////////////
+		// parse response
+		//////////////////
+
+		$pos = 0;
+		$res = array ();
+		$rlen = strlen($response);
+		for ( $i=0; $i<count($docs); $i++ )
+		{
+			list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );
+			$pos += 4;
+
+			if ( $pos+$len > $rlen )
+			{
+				$this->_error = "incomplete reply";
+				$this->_MBPop ();
+				return false;
+			}
+			$res[] = $len ? substr ( $response, $pos, $len ) : "";
+			$pos += $len;
+		}
+
+		$this->_MBPop ();
+		return $res;
+	}
+
+
+	/////////////////////////////////////////////////////////////////////////////
+	// keyword generation
+	/////////////////////////////////////////////////////////////////////////////
+
+	/// connect to searchd server, and generate keyword list for a given query
+	/// returns false on failure,
+	/// an array of words on success
+	function BuildKeywords ( $query, $index, $hits )
+	{
+		assert ( is_string($query) );
+		assert ( is_string($index) );
+		assert ( is_bool($hits) );
+
+		$this->_MBPush ();
+
+		if (!( $fp = $this->_Connect() ))
+		{
+			$this->_MBPop();
+			return false;
+		}
+
+		/////////////////
+		// build request
+		/////////////////
+
+		// v.1.0 req
+		$req  = pack ( "N", strlen($query) ) . $query; // req query
+		$req .= pack ( "N", strlen($index) ) . $index; // req index
+		$req .= pack ( "N", (int)$hits );
+
+		////////////////////////////
+		// send query, get response
+		////////////////////////////
+
+		$len = strlen($req);
+		$req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header
+		if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
+			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) ) )
+		{
+			$this->_MBPop ();
+			return false;
+		}
+
+		//////////////////
+		// parse response
+		//////////////////
+
+		$pos = 0;
+		$res = array ();
+		$rlen = strlen($response);
+		list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) );
+		$pos += 4;
+		for ( $i=0; $i<$nwords; $i++ )
+		{
+			list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );	$pos += 4;
+			$tokenized = $len ? substr ( $response, $pos, $len ) : "";
+			$pos += $len;
+
+			list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );	$pos += 4;
+			$normalized = $len ? substr ( $response, $pos, $len ) : "";
+			$pos += $len;
+
+			$res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized );
+
+			if ( $hits )
+			{
+				list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) );
+				$pos += 8;
+				$res [$i]["docs"] = $ndocs;
+				$res [$i]["hits"] = $nhits;
+			}
+
+			if ( $pos > $rlen )
+			{
+				$this->_error = "incomplete reply";
+				$this->_MBPop ();
+				return false;
+			}
+		}
+
+		$this->_MBPop ();
+		return $res;
+	}
+
+	function EscapeString ( $string )
+	{
+		$from = array ( '\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=', '<' );
+		$to   = array ( '\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=', '\<' );
+
+		return str_replace ( $from, $to, $string );
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+	// attribute updates
+	/////////////////////////////////////////////////////////////////////////////
+
+	/// batch update given attributes in given rows in given indexes
+	/// returns amount of updated documents (0 or more) on success, or -1 on failure
+	function UpdateAttributes ( $index, $attrs, $values, $mva=false, $ignorenonexistent=false )
+	{
+		// verify everything
+		assert ( is_string($index) );
+		assert ( is_bool($mva) );
+		assert ( is_bool($ignorenonexistent) );
+
+		assert ( is_array($attrs) );
+		foreach ( $attrs as $attr )
+			assert ( is_string($attr) );
+
+		assert ( is_array($values) );
+		foreach ( $values as $id=>$entry )
+		{
+			assert ( is_numeric($id) );
+			assert ( is_array($entry) );
+			assert ( count($entry)==count($attrs) );
+			foreach ( $entry as $v )
+			{
+				if ( $mva )
+				{
+					assert ( is_array($v) );
+					foreach ( $v as $vv )
+						assert ( is_int($vv) );
+				} else
+					assert ( is_int($v) );
+			}
+		}
+
+		// build request
+		$this->_MBPush ();
+		$req = pack ( "N", strlen($index) ) . $index;
+
+		$req .= pack ( "N", count($attrs) );
+		$req .= pack ( "N", $ignorenonexistent ? 1 : 0 );
+		foreach ( $attrs as $attr )
+		{
+			$req .= pack ( "N", strlen($attr) ) . $attr;
+			$req .= pack ( "N", $mva ? 1 : 0 );
+		}
+
+		$req .= pack ( "N", count($values) );
+		foreach ( $values as $id=>$entry )
+		{
+			$req .= sphPackU64 ( $id );
+			foreach ( $entry as $v )
+			{
+				$req .= pack ( "N", $mva ? count($v) : $v );
+				if ( $mva )
+					foreach ( $v as $vv )
+						$req .= pack ( "N", $vv );
+			}
+		}
+
+		// connect, send query, get response
+		if (!( $fp = $this->_Connect() ))
+		{
+			$this->_MBPop ();
+			return -1;
+		}
+
+		$len = strlen($req);
+		$req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header
+		if ( !$this->_Send ( $fp, $req, $len+8 ) )
+		{
+			$this->_MBPop ();
+			return -1;
+		}
+
+		if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) ))
+		{
+			$this->_MBPop ();
+			return -1;
+		}
+
+		// parse response
+		list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) );
+		$this->_MBPop ();
+		return $updated;
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+	// persistent connections
+	/////////////////////////////////////////////////////////////////////////////
+
+	function Open()
+	{
+		if ( $this->_socket !== false )
+		{
+			$this->_error = 'already connected';
+			return false;
+		}
+		if ( !$fp = $this->_Connect() )
+			return false;
+
+		// command, command version = 0, body length = 4, body = 1
+		$req = pack ( "nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1 );
+		if ( !$this->_Send ( $fp, $req, 12 ) )
+			return false;
+
+		$this->_socket = $fp;
+		return true;
+	}
+
+	function Close()
+	{
+		if ( $this->_socket === false )
+		{
+			$this->_error = 'not connected';
+			return false;
+		}
+
+		fclose ( $this->_socket );
+		$this->_socket = false;
+		
+		return true;
+	}
+
+	//////////////////////////////////////////////////////////////////////////
+	// status
+	//////////////////////////////////////////////////////////////////////////
+
+	function Status ($session=false)
+	{
+        assert ( is_bool($session) );
+
+		$this->_MBPush ();
+		if (!( $fp = $this->_Connect() ))
+		{
+			$this->_MBPop();
+			return false;
+		}
+
+		$req = pack ( "nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, $session?0:1 ); // len=4, body=1
+		if ( !( $this->_Send ( $fp, $req, 12 ) ) ||
+			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_STATUS ) ) )
+		{
+			$this->_MBPop ();
+			return false;
+		}
+
+		$res = substr ( $response, 4 ); // just ignore length, error handling, etc
+		$p = 0;
+		list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
+
+		$res = array();
+		for ( $i=0; $i<$rows; $i++ )
+			for ( $j=0; $j<$cols; $j++ )
+		{
+			list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
+			$res[$i][] = substr ( $response, $p, $len ); $p += $len;
+		}
+
+		$this->_MBPop ();
+		return $res;
+	}
+
+	//////////////////////////////////////////////////////////////////////////
+	// flush
+	//////////////////////////////////////////////////////////////////////////
+
+	function FlushAttributes ()
+	{
+		$this->_MBPush ();
+		if (!( $fp = $this->_Connect() ))
+		{
+			$this->_MBPop();
+			return -1;
+		}
+
+		$req = pack ( "nnN", SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0 ); // len=0
+		if ( !( $this->_Send ( $fp, $req, 8 ) ) ||
+			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_FLUSHATTRS ) ) )
+		{
+			$this->_MBPop ();
+			return -1;
+		}
+
+		$tag = -1;
+		if ( strlen($response)==4 )
+			list(,$tag) = unpack ( "N*", $response );
+		else
+			$this->_error = "unexpected response length";
+
+		$this->_MBPop ();
+		return $tag;
+	}
+}
+
+//
+// $Id$
+//

+ 22 - 0
constants.php

@@ -0,0 +1,22 @@
+<?php
+
+// 全局常量
+define('FRAME_START_TIME', microtime(true));
+define('APP_ID', 3);
+define('MTID_AKEY', '300200');
+define('MTID_SKEY', 'p8s8yuoY');
+//admin_token加密参数
+define('ADMIN_TOKEN_ENCRYPT_KEY','manage_makeup_meitu_com');
+//todo:admin_token有效期暂时改为6个小时,上线改为30分钟
+define('ADMIN_TOKEN_EXPIRED',1800*12);
+if (get_magic_quotes_gpc()) {
+    function stripslashes_deep($value)
+    {
+        $value = is_array($value) ? array_map('stripslashes_deep', $value) : stripslashes($value);
+        return $value;
+    }
+    $_POST = array_map('stripslashes_deep', $_POST);
+    $_GET = array_map('stripslashes_deep', $_GET);
+    $_COOKIE = array_map('stripslashes_deep', $_COOKIE);
+    $_REQUEST = array_map('stripslashes_deep', $_REQUEST);
+}

+ 22 - 0
env/product/mysql/core.json

@@ -0,0 +1,22 @@
+{
+  "master": {
+    "host": "s5.97admin.com",
+    "port": "3306",
+    "username": "ssbc_test",
+    "password": "",
+    "dbname": "ssbc",
+    "charset": "utf8mb4",
+    "timeout": 3
+  },
+  "slave_list": [
+    {
+      "host": "s5.97admin.com",
+      "port": "3306",
+      "username": "ssbc_test",
+      "password": "",
+      "dbname": "ssbc",
+      "charset": "utf8mb4",
+      "timeout": 3
+    }
+  ]
+}

+ 4 - 0
env/product/sphinx/core.json

@@ -0,0 +1,4 @@
+{
+  "host": "s5.97admin.com",
+  "port": "9312"
+}

+ 58 - 0
functions.php

@@ -0,0 +1,58 @@
+<?php
+
+// 全局函数
+
+// 捕获所有请求信息,包含fatal error
+// register_shutdown_function('shutdownHandler');
+function shutdownHandler()
+{
+    \PhpLife\Frame\Logger::emerg('', 'access');
+}
+
+/**
+ * 获取当前语言环境
+ *
+ * @return string
+ */
+function lang()
+{
+    static $global_lang;
+    if ($global_lang !== null) {
+        return $global_lang;
+    }
+    $global_lang = ! empty($_REQUEST['global_lang']) ? $_REQUEST['global_lang'] : $_COOKIE['global_lang'];
+    if (empty($global_lang) && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+        $global_lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
+        $global_lang=$global_lang=='zh'?$global_lang:"en";
+        setcookie("global_lang", $global_lang, time() + 3600 * 24 * 30, '/');
+    }
+    return $global_lang;
+}
+
+/**
+ * 获取当前平台值
+ *
+ * @return string
+ */
+function getplatform()
+{
+    $platform = ! empty($_REQUEST['platform']) ? intval($_REQUEST['platform']) : intval($_COOKIE['user_platform']);
+    $platform = $platform ?  : 1;
+    if (! $_COOKIE['user_platform'])
+        setcookie("user_platform", $platform, time() + 3600 * 24 * 30, '/');
+    return $platform;
+}
+
+/**
+ * 脚本推送
+ *
+ * @param string $shpath
+ *            执行的脚本路径
+ * @param string $parrams
+ *            附加的参数
+ * @return string
+ */
+function push($shpath, $parrams = "")
+{
+    return shell_exec("/bin/sh " . $shpath . " " . $parrams);
+}

+ 7 - 0
public/api/index.php

@@ -0,0 +1,7 @@
+<?php
+require_once dirname(dirname(__DIR__)) . '/Frame/Init.php';
+$host = (isset($_SERVER['HTTP_HOST']) && $_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'manage.makeup.meitu.com';
+// 设置环境变量
+\PhpLife\Frame\Init::setEnvironment('/www/privdata/'.$host.'/site.ini');
+// 分发请求
+\PhpLife\Frame\Dispatcher::dispatch('Api');

+ 15 - 0
search.php

@@ -0,0 +1,15 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: chendeben
+ * Date: 2017/12/6
+ * Time: 11:18
+ */
+require 'sphinxapi.php';
+$s=new SphinxClient();
+$s->SetServer("s5.97admin.com",9312);
+$s->SetLimits ( 0, 30, 1000, 0 );      //设置结果集偏移量  SetLimits (便宜量,匹配项数目,查询的结果集数默认1000,阀值达到后停止)
+//$s->SetFilter ( $attribute, $values, $exclude=false );     //设置属性过滤
+//$s->SetGroupBy ( $attribute, $func, $groupsort="@group desc" );    //设置分组的属性
+$res = $s->query('母女');
+$ids=array_keys($res['matches']);