Compare commits
254 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60f222b011 | |||
| 7a8b86ed12 | |||
| b940afa6b0 | |||
| 8afd00ff86 | |||
| 8992fd6d3a | |||
| 524a892480 | |||
| c420e23edb | |||
| 7419e03d67 | |||
| 9425544d1c | |||
| a8d78832df | |||
| adfb2236dc | |||
| 6034598bb4 | |||
| 6fe44cb1cb | |||
| 39d67d1775 | |||
| a8f9f12bb8 | |||
| 2c1abb1d11 | |||
| 21e0219568 | |||
| 952c3a1243 | |||
| 33cd7ebd6b | |||
| be02b3418d | |||
| f390617167 | |||
| f7410200e3 | |||
| 9cfdfbac78 | |||
| b015e78fa0 | |||
| bb9092c3e5 | |||
| cc1bad8d3f | |||
| 8f7380d796 | |||
| f37b9aee70 | |||
| 1ab9027c93 | |||
| 96d341aaab | |||
| 1f25f511cc | |||
| 07c7a628d1 | |||
| d7a59845eb | |||
| 98619083a1 | |||
| c8a9038efe | |||
| b638d8bd65 | |||
| 77753a123f | |||
| 2c9d475a45 | |||
| 3540209b26 | |||
| 797681d76c | |||
| 0ec83f9a95 | |||
| ec798bd624 | |||
| 6b57201e59 | |||
| 4e35dfe6ed | |||
| 970bdb0157 | |||
| d656faf915 | |||
| 2ecc70288e | |||
| ce95cdc021 | |||
| 22130c7afe | |||
| d85c65faef | |||
| c9f33eefec | |||
| 1826e76a34 | |||
| b9a6b7af22 | |||
| 95bfea4feb | |||
| e4bb8ac94a | |||
| d7a49bf56d | |||
| 3d1a1670cc | |||
| 7de6a79611 | |||
| 83198b39cb | |||
| db0724f09b | |||
| c1b013c926 | |||
| 40bc84ea3d | |||
| e977a97013 | |||
| 9330f7f5ea | |||
| d20bcc162c | |||
| b48ad793da | |||
| 3754734130 | |||
| 918b88332b | |||
| 9ba182d65c | |||
| 8cf54ce25b | |||
| 22d00739ff | |||
| 3b2a317d2b | |||
| afa9b173ba | |||
| 53f24c033c | |||
| 1ffcaaef8e | |||
| 62ba00270a | |||
| fe31335cf5 | |||
| b709b58fef | |||
| 76b220aa5d | |||
| dbf8f91696 | |||
| 335e8730da | |||
| cbed318832 | |||
| 31a6568cbe | |||
| 2aa86361e1 | |||
| 2a21dad90a | |||
| bae9f60012 | |||
| 79d1189803 | |||
| c3d0c8921b | |||
| 1da98a058b | |||
| 152386c5a0 | |||
| 5e2845d3a1 | |||
| bab49c9a66 | |||
| 1d3bfcaee5 | |||
| a37a33321f | |||
| 48042ecce4 | |||
| 5d8227d8bb | |||
| 38d3d77c59 | |||
| fec195b3a1 | |||
| 5945ed64c6 | |||
| ce515d991a | |||
| cb04dc6c34 | |||
| 17905c2d21 | |||
| b7c243823a | |||
| ebea14c74f | |||
| d6ef790010 | |||
| 5c64580378 | |||
| e49164fa81 | |||
| 8265e24add | |||
| 283985df52 | |||
| 95adca04bd | |||
| 2e65138222 | |||
| 87039524a7 | |||
| 66cd21c35f | |||
| 7e64131931 | |||
| e30d40cfbb | |||
| 751ed0003d | |||
| 6d9f89a1d4 | |||
| bc329be03b | |||
| 4597a0a50d | |||
| 3dda86d170 | |||
| d847da7591 | |||
| e1eca90835 | |||
| 9283b8c284 | |||
| d8d9afa4d2 | |||
| 4d3829c345 | |||
| a23037a437 | |||
| 181295e54e | |||
| 8cc2499e21 | |||
| a2c49452b4 | |||
| ebf0df5ca8 | |||
| 73bf691ccc | |||
| b2e9701826 | |||
| 789b0a14d1 | |||
| 3ee64fcd8c | |||
| 527503b0d9 | |||
| a95b2fccd7 | |||
| 7e2abb43e3 | |||
| 48fd753a96 | |||
| 136232dd57 | |||
| 008f19355c | |||
| 1fc0a5bbbc | |||
| ce099b8fc8 | |||
| 9c00ac6ba1 | |||
| a5c002bbce | |||
| cf50a699a6 | |||
| 662003c0ca | |||
| f24de2562a | |||
| b84bd7e16f | |||
| 2fb6abf53a | |||
| 2eab29f428 | |||
| 84ee0d259f | |||
| eccab42fc8 | |||
| 1465ba36d3 | |||
| 8380b097b2 | |||
| 6b8f901163 | |||
| bb16f0a75b | |||
| 84dbc5d71e | |||
| 166eeee64c | |||
| 7a649fd652 | |||
| 94ae568526 | |||
| 9ec597f26c | |||
| 388bc5006d | |||
| 0e33c2ad9d | |||
| af726fbcfb | |||
| 1e82a5964d | |||
| d62ba8b0cd | |||
| 320ebe25ec | |||
| 68257d1742 | |||
| 80d167cb1b | |||
| cd911e8fa4 | |||
| faa6fcb00c | |||
| 836c1f461f | |||
| 97b2f54383 | |||
| 9d5f8438c5 | |||
| 3f7963e7cf | |||
| 269df85d27 | |||
| d28c6863e4 | |||
| bba4990ccc | |||
| c57afd6a5d | |||
| 25f355c8d0 | |||
| b1b0673e69 | |||
| 72c086d726 | |||
| 3810ba6c0b | |||
| 5c775b4539 | |||
| deb3e54cc3 | |||
| d1a8f6c668 | |||
| 2291ba7402 | |||
| 20390091f2 | |||
| 123d819b0f | |||
| 30e275a27d | |||
| 1a53f587e8 | |||
| 9c3ba81135 | |||
| 62cf625fe9 | |||
| e1f4876621 | |||
| b480659a22 | |||
| 09b1d37ae3 | |||
| 47b28e7b57 | |||
| 9cd7ad34b3 | |||
| d736a74198 | |||
| ada4a926c0 | |||
| 4ad401bafc | |||
| 05c8159601 | |||
| efb6cb980e | |||
| 00ad6487b6 | |||
| 9aa836a72c | |||
| 9ea6082d04 | |||
| 9e595ab6e8 | |||
| 8b6dd5fdad | |||
| 5c8df885a2 | |||
| cc4d91cdbb | |||
| 32167d4721 | |||
| 1077f09755 | |||
| 642bf97272 | |||
| 4038598470 | |||
| 79453d4d2a | |||
| 6aa2b74840 | |||
| a003612810 | |||
| d6c11a5d93 | |||
| fe085ce889 | |||
| 99933e02f4 | |||
| 9af232840c | |||
| 465ea889a2 | |||
| c46fdd8985 | |||
| 9fd0a5fd61 | |||
| 95489fcf49 | |||
| 0e2932ca89 | |||
| 764d88894e | |||
| 339302956a | |||
| 1101937028 | |||
| fc315064e7 | |||
| 740059075a | |||
| 770f0a94c9 | |||
| df07afb744 | |||
| 9bc930750d | |||
| 77ded19742 | |||
| 2632517fab | |||
| c8e926446e | |||
| 253f836571 | |||
| babf6595ee | |||
| 91dc207a78 | |||
| d334856c6c | |||
| 51b1eb3149 | |||
| 91d9a069ac | |||
| 2a4bf676c9 | |||
| 0723555b2d | |||
| a519249dfc | |||
| aa7ab49df9 | |||
| 5132e67921 | |||
| 3eb9b6193a | |||
| 09412ce519 | |||
| f22e059172 | |||
| 153a94374f | |||
| 749ee84ca3 | |||
| bb2311148e |
@@ -1 +1,4 @@
|
||||
/.idea/*
|
||||
.idea
|
||||
/example/config/app.json
|
||||
/example/tpt/demo/
|
||||
|
||||
Generated
-10
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/hotime.iml" filepath="$PROJECT_DIR$/.idea/hotime.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-163
@@ -1,163 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="b2aca021-ff30-4cbf-8dc9-8cdd4f4c39dc" name="Default Changelist" comment="" />
|
||||
<option name="EXCLUDED_CONVERTED_TO_IGNORED" 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="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="Go File" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="GOROOT" path="D:/app/go" />
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectId" id="1TPrQqMMb4T7xS6v0afE7E8yl1y" />
|
||||
<component name="PropertiesComponent">
|
||||
<property name="DefaultGoTemplateProperty" value="Go File" />
|
||||
<property name="GO_FMT" value="true" />
|
||||
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||
<property name="aspect.path.notification.shown" value="true" />
|
||||
<property name="go.import.settings.migrated" value="true" />
|
||||
<property name="go.sdk.automatically.set" value="true" />
|
||||
<property name="go.tried.to.enable.integration.vgo.integrator" value="true" />
|
||||
</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="D:\gopath\src\code.hoteas.com\hoteas\hotime\tools" />
|
||||
</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="RunManager">
|
||||
<configuration name="go build code.hoteas.com/hoteas/hotime/example" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
|
||||
<module name="hotime" />
|
||||
<working_directory value="$PROJECT_DIR$/" />
|
||||
<kind value="PACKAGE" />
|
||||
<filePath value="$PROJECT_DIR$/example/main.go" />
|
||||
<package value="code.hoteas.com/hoteas/hotime/example" />
|
||||
<directory value="$PROJECT_DIR$/" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Go Build.go build code.hoteas.com/hoteas/hotime/example" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="SvnConfiguration">
|
||||
<configuration />
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="b2aca021-ff30-4cbf-8dc9-8cdd4f4c39dc" name="Default Changelist" comment="" />
|
||||
<created>1573372557346</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1573372557346</updated>
|
||||
<workItem from="1573372558521" duration="18000" />
|
||||
<workItem from="1573372583551" duration="5522000" />
|
||||
<workItem from="1582172338195" duration="5196000" />
|
||||
<workItem from="1582181939594" duration="501000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="清理">
|
||||
<created>1573372845218</created>
|
||||
<option name="number" value="00001" />
|
||||
<option name="presentableId" value="LOCAL-00001" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1573372845218</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00002" summary="清理">
|
||||
<created>1573372887175</created>
|
||||
<option name="number" value="00002" />
|
||||
<option name="presentableId" value="LOCAL-00002" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1573372887175</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00003" summary="自动设置数据库">
|
||||
<created>1573375369161</created>
|
||||
<option name="number" value="00003" />
|
||||
<option name="presentableId" value="LOCAL-00003" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1573375369161</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00004" summary="自动设置数据库">
|
||||
<created>1573375745411</created>
|
||||
<option name="number" value="00004" />
|
||||
<option name="presentableId" value="LOCAL-00004" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1573375745411</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00005" summary="自动设置数据库">
|
||||
<created>1573375887561</created>
|
||||
<option name="number" value="00005" />
|
||||
<option name="presentableId" value="LOCAL-00005" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1573375887561</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00006" summary="数据库缓存">
|
||||
<created>1573380045254</created>
|
||||
<option name="number" value="00006" />
|
||||
<option name="presentableId" value="LOCAL-00006" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1573380045254</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00007" summary="增加跨域配置,以及增加配置说明">
|
||||
<created>1582179656809</created>
|
||||
<option name="number" value="00007" />
|
||||
<option name="presentableId" value="LOCAL-00007" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1582179656809</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00008" summary="增加访问ip打印等功能">
|
||||
<created>1582182399676</created>
|
||||
<option name="number" value="00008" />
|
||||
<option name="presentableId" value="LOCAL-00008" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1582182399676</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="9" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="1" />
|
||||
</component>
|
||||
<component name="Vcs.Log.Tabs.Properties">
|
||||
<option name="TAB_STATES">
|
||||
<map>
|
||||
<entry key="MAIN">
|
||||
<value>
|
||||
<State>
|
||||
<option name="COLUMN_ORDER" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<MESSAGE value="清理" />
|
||||
<MESSAGE value="自动设置数据库" />
|
||||
<MESSAGE value="数据库缓存" />
|
||||
<MESSAGE value="增加跨域配置,以及增加配置说明" />
|
||||
<MESSAGE value="增加访问ip打印等功能" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="增加访问ip打印等功能" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,208 @@
|
||||
Apache License
|
||||
|
||||
Version 2.0, January 2004
|
||||
|
||||
http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION,
|
||||
AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution
|
||||
as defined by Sections 1 through 9 of that document.
|
||||
|
||||
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of that definition, "control" means (i) the power, direct
|
||||
or indirect, to cause the direction or management of such entity, whether
|
||||
by contract or otherwise, or (ii) ownership of fifty percent (50%) or more
|
||||
of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions
|
||||
granted by that License.
|
||||
|
||||
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation
|
||||
or translation of a Source form, including but not limited to compiled object
|
||||
code, generated documentation, and conversions to other media types.
|
||||
|
||||
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form,
|
||||
made available under the License, as indicated by a copyright notice that
|
||||
is included in or attached to the work (an example is provided in the Appendix
|
||||
below).
|
||||
|
||||
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form,
|
||||
that is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of that License, Derivative
|
||||
Works shall not include works that remain separable from, or merely link (or
|
||||
bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative
|
||||
Works thereof, that is intentionally submitted to Licensor for inclusion in
|
||||
the Work by the copyright owner or by an individual or Legal Entity authorized
|
||||
to submit on behalf of the copyright owner. For the purposes of that definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication
|
||||
sent to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor
|
||||
for the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently incorporated
|
||||
within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of that
|
||||
License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable copyright license to reproduce, prepare
|
||||
Derivative Works of, publicly display, publicly perform, sublicense, and distribute
|
||||
the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of that License,
|
||||
each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in that section) patent
|
||||
license to make, have made, use, offer to sell, sell, import, and otherwise
|
||||
transfer the Work, where such license applies only to those patent claims
|
||||
licensable by such Contributor that are necessarily infringed by their Contribution(s)
|
||||
alone or by combination of their Contribution(s) with the Work to which such
|
||||
Contribution(s) was submitted. If You institute patent litigation against
|
||||
any entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that the Work or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses granted to You
|
||||
under that License for that Work shall terminate as of the date such litigation
|
||||
is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or
|
||||
Derivative Works thereof in any medium, with or without modifications, and
|
||||
in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or Derivative Works a copy
|
||||
of that License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices stating that
|
||||
You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source
|
||||
form of the Work, excluding those notices that do not pertain to any part
|
||||
of the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its distribution,
|
||||
then any Derivative Works that You distribute must include a readable copy
|
||||
of the attribution notices contained within such NOTICE file, excluding those
|
||||
notices that do not pertain to any part of the Derivative Works, in at least
|
||||
one of the following places: within a NOTICE text file distributed as part
|
||||
of the Derivative Works; within the Source form or documentation, if provided
|
||||
along with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works
|
||||
that You distribute, alongside or as an addendum to the NOTICE text from the
|
||||
Work, provided that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction,
|
||||
or distribution of Your modifications, or for any such Derivative Works as
|
||||
a whole, provided Your use, reproduction, and distribution of the Work otherwise
|
||||
complies with the conditions stated in that License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any
|
||||
Contribution intentionally submitted for inclusion in the Work by You to the
|
||||
Licensor shall be under the terms and conditions of that License, without
|
||||
any additional terms or conditions. Notwithstanding the above, nothing herein
|
||||
shall supersede or modify the terms of any separate license agreement you
|
||||
may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. that License does not grant permission to use the trade names,
|
||||
trademarks, service marks, or product names of the Licensor, except as required
|
||||
for reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to
|
||||
in writing, Licensor provides the Work (and each Contributor provides its
|
||||
Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied, including, without limitation, any warranties
|
||||
or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness
|
||||
of using or redistributing the Work and assume any risks associated with Your
|
||||
exercise of permissions under that License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether
|
||||
in tort (including negligence), contract, or otherwise, unless required by
|
||||
applicable law (such as deliberate and grossly negligent acts) or agreed to
|
||||
in writing, shall any Contributor be liable to You for damages, including
|
||||
any direct, indirect, special, incidental, or consequential damages of any
|
||||
character arising as a result of that License or out of the use or inability
|
||||
to use the Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all other commercial
|
||||
damages or losses), even if such Contributor has been advised of the possibility
|
||||
of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work
|
||||
or Derivative Works thereof, You may choose to offer, and charge a fee for,
|
||||
acceptance of support, warranty, indemnity, or other liability obligations
|
||||
and/or rights consistent with that License. However, in accepting such obligations,
|
||||
You may act only on Your own behalf and on Your sole responsibility, not on
|
||||
behalf of any other Contributor, and only if You agree to indemnify, defend,
|
||||
and hold each Contributor harmless for any liability incurred by, or claims
|
||||
asserted against, such Contributor by reason of your accepting any such warranty
|
||||
or additional liability. END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own identifying
|
||||
information. (Don't include the brackets!) The text should be enclosed in
|
||||
the appropriate comment syntax for the file format. We also recommend that
|
||||
a file or class name and description of purpose be included on the same "printed
|
||||
page" as the copyright notice for easier identification within third-party
|
||||
archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
you may not use that file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,6 @@
|
||||
# hotime
|
||||
golang web服务框架
|
||||
支持数据库db:mysql、sqlite3
|
||||
支持缓存cache:redis,memory,数据库
|
||||
自带工具类,上下文,以及session等功能
|
||||
|
||||
+484
-191
@@ -1,13 +1,15 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
. "code.hoteas.com/golang/hotime/cache"
|
||||
"code.hoteas.com/golang/hotime/code"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
. "code.hoteas.com/golang/hotime/db"
|
||||
. "code.hoteas.com/golang/hotime/log"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -18,58 +20,79 @@ import (
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
MakeCodeRouter map[string]*code.MakeCode
|
||||
MethodRouter
|
||||
Router
|
||||
contextBase
|
||||
Error
|
||||
Log *logrus.Logger
|
||||
WebConnectLog *logrus.Logger
|
||||
Port string //端口号
|
||||
TLSPort string //ssl访问端口号
|
||||
connectListener []func(this *Context) bool //所有的访问监听,true按原计划继续使用,false表示有监听器处理
|
||||
connectDbFunc func(err ...*Error) *sql.DB
|
||||
connectListener []func(that *Context) bool //所有的访问监听,true按原计划继续使用,false表示有监听器处理
|
||||
connectDbFunc func(err ...*Error) (master, slave *sql.DB)
|
||||
configPath string
|
||||
Config Map
|
||||
Db HoTimeDB
|
||||
Server *http.Server
|
||||
CacheIns
|
||||
sessionLong CacheIns
|
||||
sessionShort CacheIns
|
||||
*HoTimeCache
|
||||
*http.Server
|
||||
http.Handler
|
||||
}
|
||||
|
||||
func (this *Application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
this.handler(w, req)
|
||||
func (that *Application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
that.handler(w, req)
|
||||
}
|
||||
|
||||
//启动实例
|
||||
func (this *Application) Run(router Router) {
|
||||
// Run 启动实例
|
||||
func (that *Application) Run(router Router) {
|
||||
//如果没有设置配置自动生成配置
|
||||
if this.configPath == "" || len(this.Config) == 0 {
|
||||
this.SetConfig()
|
||||
if that.configPath == "" || len(that.Config) == 0 {
|
||||
that.SetConfig()
|
||||
}
|
||||
|
||||
//防止手动设置缓存误伤
|
||||
if this.CacheIns == nil {
|
||||
this.SetCache(CacheIns(&CacheMemory{}))
|
||||
if that.HoTimeCache == nil {
|
||||
that.SetCache()
|
||||
}
|
||||
|
||||
//防止手动设置session误伤
|
||||
if this.sessionShort == nil && this.sessionLong == nil {
|
||||
if this.connectDbFunc == nil {
|
||||
this.SetSession(CacheIns(&CacheMemory{}), nil)
|
||||
} else {
|
||||
this.SetSession(CacheIns(&CacheMemory{}), CacheIns(&CacheDb{Db: &this.Db, Time: this.Config.GetInt64("cacheLongTime")}))
|
||||
//if that.sessionShort == nil && that.sessionLong == nil {
|
||||
// if that.connectDbFunc == nil {
|
||||
// that.SetSession(CacheIns(&CacheMemory{}), nil)
|
||||
// } else {
|
||||
// that.SetSession(CacheIns(&CacheMemory{}), CacheIns(&CacheDb{Db: &that.Db, Time: that.Config.GetInt64("cacheLongTime")}))
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
//that.Router = router
|
||||
if that.Router == nil {
|
||||
that.Router = Router{}
|
||||
}
|
||||
for k, v := range router {
|
||||
if that.Router[k] == nil {
|
||||
that.Router[k] = v
|
||||
}
|
||||
//直达接口层复用
|
||||
for k1, v1 := range v {
|
||||
if that.Router[k][k1] == nil {
|
||||
that.Router[k][k1] = v1
|
||||
}
|
||||
|
||||
for k2, v2 := range v1 {
|
||||
|
||||
that.Router[k][k1][k2] = v2
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.Router = router
|
||||
//重新设置MethodRouter//直达路由
|
||||
this.MethodRouter = MethodRouter{}
|
||||
that.MethodRouter = MethodRouter{}
|
||||
modeRouterStrict := true
|
||||
if this.Config.Get("modeRouterStrict").(bool) == false {
|
||||
if that.Config.GetBool("modeRouterStrict") == false {
|
||||
modeRouterStrict = false
|
||||
}
|
||||
if router != nil {
|
||||
for pk, pv := range router {
|
||||
if that.Router != nil {
|
||||
for pk, pv := range that.Router {
|
||||
if !modeRouterStrict {
|
||||
pk = strings.ToLower(pk)
|
||||
}
|
||||
@@ -83,7 +106,7 @@ func (this *Application) Run(router Router) {
|
||||
if !modeRouterStrict {
|
||||
mk = strings.ToLower(mk)
|
||||
}
|
||||
this.MethodRouter["/"+pk+"/"+ck+"/"+mk] = mv
|
||||
that.MethodRouter["/"+pk+"/"+ck+"/"+mk] = mv
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,185 +114,177 @@ func (this *Application) Run(router Router) {
|
||||
}
|
||||
}
|
||||
|
||||
//this.Port = port
|
||||
this.Port = this.Config.GetString("port")
|
||||
this.TLSPort = this.Config.GetString("tlsPort")
|
||||
//that.Port = port
|
||||
that.Port = that.Config.GetString("port")
|
||||
that.TLSPort = that.Config.GetString("tlsPort")
|
||||
|
||||
if this.connectDbFunc != nil && (this.Db.DB == nil || this.Db.DB.Ping() != nil) {
|
||||
this.Db.SetConnect(this.connectDbFunc)
|
||||
}
|
||||
|
||||
if this.CacheIns == nil {
|
||||
this.CacheIns = CacheIns(&CacheMemory{Map: Map{}, Time: this.Config.GetInt64("cacheShortTime")})
|
||||
if that.connectDbFunc != nil && (that.Db.DB == nil || that.Db.DB.Ping() != nil) {
|
||||
that.Db.SetConnect(that.connectDbFunc)
|
||||
}
|
||||
|
||||
//异常处理
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
this.SetError(errors.New(fmt.Sprint(err)), LOG_FMT)
|
||||
//log.Println(err)
|
||||
this.Run(router)
|
||||
//that.SetError(errors.New(fmt.Sprint(err)), LOG_FMT)
|
||||
that.Log.Warn(err)
|
||||
|
||||
that.Run(router)
|
||||
}
|
||||
}()
|
||||
|
||||
this.Server = &http.Server{}
|
||||
that.Server = &http.Server{}
|
||||
if !IsRun {
|
||||
IsRun = true
|
||||
}
|
||||
|
||||
ch := make(chan int)
|
||||
if ObjToCeilInt(this.Port) != 0 {
|
||||
if ObjToCeilInt(that.Port) != 0 {
|
||||
go func() {
|
||||
|
||||
App[this.Port] = this
|
||||
this.Server.Handler = this
|
||||
App[that.Port] = that
|
||||
that.Server.Handler = that
|
||||
//启动服务
|
||||
this.Server.Addr = ":" + this.Port
|
||||
err := this.Server.ListenAndServe()
|
||||
log.Println(err)
|
||||
that.Server.Addr = ":" + that.Port
|
||||
err := that.Server.ListenAndServe()
|
||||
that.Log.Error(err)
|
||||
ch <- 1
|
||||
|
||||
}()
|
||||
} else if ObjToCeilInt(this.TLSPort) != 0 {
|
||||
}
|
||||
|
||||
if ObjToCeilInt(that.TLSPort) != 0 {
|
||||
go func() {
|
||||
|
||||
App[this.TLSPort] = this
|
||||
this.Server.Handler = this
|
||||
App[that.TLSPort] = that
|
||||
that.Server.Handler = that
|
||||
//启动服务
|
||||
this.Server.Addr = ":" + this.TLSPort
|
||||
err := this.Server.ListenAndServeTLS(this.Config.GetString("tlsCert"), this.Config.GetString("tlsKey"))
|
||||
log.Println(err)
|
||||
that.Server.Addr = ":" + that.TLSPort
|
||||
err := that.Server.ListenAndServeTLS(that.Config.GetString("tlsCert"), that.Config.GetString("tlsKey"))
|
||||
that.Log.Error(err)
|
||||
ch <- 2
|
||||
|
||||
}()
|
||||
} else {
|
||||
log.Println("没有端口启用")
|
||||
}
|
||||
if ObjToCeilInt(that.Port) == 0 && ObjToCeilInt(that.TLSPort) == 0 {
|
||||
that.Log.Error("没有端口启用")
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
value := <-ch
|
||||
|
||||
log.Println("启动服务失败 : ", value)
|
||||
that.Log.Error("启动服务失败 : " + ObjToStr(value))
|
||||
}
|
||||
|
||||
//启动实例
|
||||
func (this *Application) SetConnectDB(connect func(err ...*Error) *sql.DB) {
|
||||
// SetConnectDB 启动实例
|
||||
func (that *Application) SetConnectDB(connect func(err ...*Error) (master, slave *sql.DB)) {
|
||||
|
||||
//this.Db.DBCached=false
|
||||
//if this.Config.GetCeilInt("dbCached")!=0{
|
||||
// this.Db.DBCached=true
|
||||
//}
|
||||
that.connectDbFunc = connect
|
||||
|
||||
this.connectDbFunc = connect
|
||||
this.Db.SetConnect(this.connectDbFunc)
|
||||
|
||||
this.Db.DBCached = false
|
||||
if this.Config.GetCeilInt("dbCached") != 0 {
|
||||
this.Db.DBCached = true
|
||||
}
|
||||
|
||||
this.Db.Type = this.Config.GetString("dbType")
|
||||
that.Db.SetConnect(that.connectDbFunc, &that.Error)
|
||||
|
||||
}
|
||||
|
||||
//设置配置文件路径全路径或者相对路径
|
||||
func (this *Application) SetSession(short CacheIns, Long CacheIns) {
|
||||
this.sessionLong = Long
|
||||
this.sessionShort = short
|
||||
|
||||
}
|
||||
|
||||
//默认配置缓存和session实现
|
||||
func (this *Application) SetDefault(connect func(err ...*Error) *sql.DB) {
|
||||
this.SetConfig()
|
||||
// SetDefault 默认配置缓存和session实现
|
||||
func (that *Application) SetDefault(connect func(err ...*Error) (*sql.DB, *sql.DB)) {
|
||||
that.SetConfig()
|
||||
|
||||
if connect != nil {
|
||||
this.connectDbFunc = connect
|
||||
this.Db.SetConnect(this.connectDbFunc)
|
||||
that.connectDbFunc = connect
|
||||
that.Db.SetConnect(that.connectDbFunc)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//设置配置文件路径全路径或者相对路径
|
||||
func (this *Application) SetCache(cache CacheIns) {
|
||||
|
||||
this.CacheIns = cache
|
||||
|
||||
// SetCache 设置配置文件路径全路径或者相对路径
|
||||
func (that *Application) SetCache() {
|
||||
cacheIns := HoTimeCache{}
|
||||
cacheIns.Init(that.Config.GetMap("cache"), HoTimeDBInterface(&that.Db), &that.Error)
|
||||
that.HoTimeCache = &cacheIns
|
||||
//mode生产模式开启的时候才开启数据库缓存,防止调试出问题
|
||||
if that.Config.GetInt("mode") == 0 {
|
||||
that.Db.HoTimeCache = &cacheIns
|
||||
}
|
||||
}
|
||||
|
||||
//设置配置文件路径全路径或者相对路径
|
||||
func (this *Application) SetConfig(configPath ...string) {
|
||||
// SetConfig 设置配置文件路径全路径或者相对路径
|
||||
func (that *Application) SetConfig(configPath ...string) {
|
||||
|
||||
that.Log = GetLog("", true)
|
||||
that.Error = Error{Logger: that.Log}
|
||||
if len(configPath) != 0 {
|
||||
this.configPath = configPath[0]
|
||||
that.configPath = configPath[0]
|
||||
}
|
||||
|
||||
if this.configPath == "" {
|
||||
this.configPath = "config/config.json"
|
||||
if that.configPath == "" {
|
||||
that.configPath = "config/config.json"
|
||||
}
|
||||
//加载配置文件
|
||||
btes, err := ioutil.ReadFile(this.configPath)
|
||||
this.Config = DeepCopyMap(Config).(Map)
|
||||
btes, err := ioutil.ReadFile(that.configPath)
|
||||
that.Config = DeepCopyMap(Config).(Map)
|
||||
if err == nil {
|
||||
|
||||
cmap := Map{}
|
||||
//文件是否损坏
|
||||
cmap.JsonToMap(string(btes), &this.Error)
|
||||
cmap.JsonToMap(string(btes), &that.Error)
|
||||
|
||||
for k, v := range cmap {
|
||||
this.Config.Put(k, v) //程序配置
|
||||
Config.Put(k, v) //系统配置
|
||||
that.Config[k] = v //程序配置
|
||||
Config[k] = v //系统配置
|
||||
}
|
||||
} else {
|
||||
log.Println("配置文件不存在,或者配置出错,使用缺省默认配置")
|
||||
that.Log.Error("配置文件不存在,或者配置出错,使用缺省默认配置")
|
||||
|
||||
}
|
||||
|
||||
if that.Error.GetError() != nil {
|
||||
fmt.Println(that.Error.GetError().Error())
|
||||
}
|
||||
|
||||
//文件如果损坏则不写入配置防止配置文件数据丢失
|
||||
if this.Error.GetError() == nil {
|
||||
var configByte bytes.Buffer
|
||||
if that.Error.GetError() == nil {
|
||||
//var configByte bytes.Buffer
|
||||
|
||||
err = json.Indent(&configByte, []byte(this.Config.ToJsonString()), "", "\t")
|
||||
//判断配置文件是否序列有变化有则修改配置,五则不变
|
||||
//判断配置文件是否序列有变化,有则修改配置,无则不变
|
||||
//fmt.Println(len(btes))
|
||||
if len(btes) != 0 && configByte.String() == string(btes) {
|
||||
configStr := that.Config.ToJsonString()
|
||||
if len(btes) != 0 && configStr == string(btes) {
|
||||
return
|
||||
}
|
||||
//写入配置说明
|
||||
var configNoteByte bytes.Buffer
|
||||
json.Indent(&configNoteByte, []byte(ConfigNote.ToJsonString()), "", "\t")
|
||||
ioutil.WriteFile(filepath.Dir(this.configPath)+"/confignote.json", configNoteByte.Bytes(), os.ModeAppend)
|
||||
//写入配置
|
||||
err = ioutil.WriteFile(this.configPath, configByte.Bytes(), os.ModeAppend)
|
||||
//var configNoteByte bytes.Buffer
|
||||
configNoteStr := ConfigNote.ToJsonString()
|
||||
//_ = json.Indent(&configNoteByte, []byte(ConfigNote.ToJsonString()), "", "\t")
|
||||
|
||||
_ = os.MkdirAll(filepath.Dir(that.configPath), os.ModeDir)
|
||||
err = ioutil.WriteFile(that.configPath, []byte(configStr), os.ModePerm)
|
||||
if err != nil {
|
||||
os.MkdirAll(filepath.Dir(this.configPath), os.ModeDir)
|
||||
os.Create(this.configPath)
|
||||
err = ioutil.WriteFile(this.configPath, configByte.Bytes(), os.ModeAppend)
|
||||
if err != nil {
|
||||
this.Error.SetError(err)
|
||||
}
|
||||
//写入配置说明
|
||||
os.Create(filepath.Dir(this.configPath) + "/confignote.json")
|
||||
ioutil.WriteFile(filepath.Dir(this.configPath)+"/confignote.json", configNoteByte.Bytes(), os.ModeAppend)
|
||||
|
||||
that.Error.SetError(err)
|
||||
}
|
||||
_ = ioutil.WriteFile(filepath.Dir(that.configPath)+"/configNote.json", []byte(configNoteStr), os.ModePerm)
|
||||
|
||||
}
|
||||
|
||||
that.Log = GetLog(that.Config.GetString("logFile"), true)
|
||||
that.Error = Error{Logger: that.Log}
|
||||
if that.Config.Get("webConnectLogShow") == nil || that.Config.GetBool("webConnectLogShow") {
|
||||
that.WebConnectLog = GetLog(that.Config.GetString("webConnectLogFile"), false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//连接判断,返回true继续传输至控制层,false则停止传输
|
||||
func (this *Application) SetConnectListener(lis func(this *Context) bool) {
|
||||
this.connectListener = append(this.connectListener, lis)
|
||||
// SetConnectListener 连接判断,返回false继续传输至控制层,true则停止传输
|
||||
func (that *Application) SetConnectListener(lis func(that *Context) (isFinished bool)) {
|
||||
that.connectListener = append(that.connectListener, lis)
|
||||
}
|
||||
|
||||
//网络错误
|
||||
//func (this *Application) session(w http.ResponseWriter, req *http.Request) {
|
||||
//func (that *Application) session(w http.ResponseWriter, req *http.Request) {
|
||||
//
|
||||
//}
|
||||
|
||||
//序列化链接
|
||||
func (this *Application) urlSer(url string) (string, []string) {
|
||||
func (that *Application) urlSer(url string) (string, []string) {
|
||||
q := strings.Index(url, "?")
|
||||
if q == -1 {
|
||||
q = len(url)
|
||||
@@ -290,9 +305,10 @@ func (this *Application) urlSer(url string) (string, []string) {
|
||||
|
||||
//访问
|
||||
|
||||
func (this *Application) handler(w http.ResponseWriter, req *http.Request) {
|
||||
func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
|
||||
nowUnixTime := time.Now()
|
||||
|
||||
_, s := this.urlSer(req.RequestURI)
|
||||
_, s := that.urlSer(req.RequestURI)
|
||||
//获取cookie
|
||||
// 如果cookie存在直接将sessionId赋值为cookie.Value
|
||||
// 如果cookie不存在就查找传入的参数中是否有token
|
||||
@@ -300,20 +316,24 @@ func (this *Application) handler(w http.ResponseWriter, req *http.Request) {
|
||||
// 如果token存在就判断token是否在Session中有保存
|
||||
// 如果有取出token并复制给cookie
|
||||
// 没有保存就生成随机的session
|
||||
cookie, err := req.Cookie(this.Config.GetString("sessionName"))
|
||||
cookie, err := req.Cookie(that.Config.GetString("sessionName"))
|
||||
sessionId := Md5(strconv.Itoa(Rand(10)))
|
||||
token := req.FormValue("token")
|
||||
//isFirst:=false
|
||||
if err != nil || (len(token) == 32 && cookie.Value != token) {
|
||||
if len(token) == 32 {
|
||||
sessionId = token
|
||||
}
|
||||
//else{
|
||||
// isFirst=true;
|
||||
//}
|
||||
http.SetCookie(w, &http.Cookie{Name: this.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
|
||||
} else {
|
||||
needSetCookie := ""
|
||||
token := req.Header.Get("Authorization")
|
||||
if len(token) != 32 {
|
||||
|
||||
token = req.FormValue("token")
|
||||
}
|
||||
//没有cookie或者cookie不等于token
|
||||
//有token优先token
|
||||
if len(token) == 32 {
|
||||
sessionId = token
|
||||
//没有token,则查阅session
|
||||
} else if err == nil && cookie.Value != "" {
|
||||
sessionId = cookie.Value
|
||||
//session也没有则判断是否创建cookie
|
||||
} else {
|
||||
needSetCookie = sessionId
|
||||
}
|
||||
|
||||
unescapeUrl, err := url.QueryUnescape(req.RequestURI)
|
||||
@@ -321,71 +341,71 @@ func (this *Application) handler(w http.ResponseWriter, req *http.Request) {
|
||||
unescapeUrl = req.RequestURI
|
||||
}
|
||||
//访问实例
|
||||
context := Context{SessionIns: SessionIns{SessionId: sessionId,
|
||||
LongCache: this.sessionLong,
|
||||
ShortCache: this.sessionShort,
|
||||
},
|
||||
CacheIns: this.CacheIns,
|
||||
Resp: w, Req: req, Application: this, RouterString: s, Config: this.Config, Db: &this.Db, HandlerStr: unescapeUrl}
|
||||
context := Context{SessionIns: SessionIns{SessionId: sessionId, HoTimeCache: that.HoTimeCache},
|
||||
Resp: w, Req: req, Application: that, RouterString: s, Config: that.Config, Db: &that.Db,
|
||||
HandlerStr: unescapeUrl}
|
||||
//header默认设置
|
||||
header := w.Header()
|
||||
header.Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
//url去掉参数并序列化
|
||||
context.HandlerStr, context.RouterString = this.urlSer(context.HandlerStr)
|
||||
context.HandlerStr, context.RouterString = that.urlSer(context.HandlerStr)
|
||||
|
||||
//跨域设置
|
||||
this.crossDomain(&context)
|
||||
//是否展示日志
|
||||
if this.Config.GetInt("connectLogShow") != 0 {
|
||||
log.Println(context.HandlerStr + time.Now().Format(" 2006-01-02 15:04 ") + Substr(context.Req.RemoteAddr, 0, strings.Index(context.Req.RemoteAddr, ":")))
|
||||
}
|
||||
that.crossDomain(&context, needSetCookie)
|
||||
|
||||
defer func() {
|
||||
//是否展示日志
|
||||
if that.WebConnectLog != nil {
|
||||
ipStr := Substr(context.Req.RemoteAddr, 0, strings.Index(context.Req.RemoteAddr, ":"))
|
||||
//负载均衡优化
|
||||
if ipStr == "127.0.0.1" {
|
||||
if req.Header.Get("X-Forwarded-For") != "" {
|
||||
ipStr = req.Header.Get("X-Forwarded-For")
|
||||
} else if req.Header.Get("X-Real-IP") != "" {
|
||||
ipStr = req.Header.Get("X-Real-IP")
|
||||
}
|
||||
}
|
||||
|
||||
that.WebConnectLog.Infoln(ipStr, context.Req.Method,
|
||||
"time cost:", ObjToFloat64(time.Now().UnixNano()-nowUnixTime.UnixNano())/1000000.00, "ms",
|
||||
"data length:", ObjToFloat64(context.DataSize)/1000.00, "KB", context.HandlerStr)
|
||||
}
|
||||
}()
|
||||
|
||||
//访问拦截true继续false暂停
|
||||
connectListenerLen := len(this.connectListener)
|
||||
if connectListenerLen != 0 {
|
||||
for i := 0; i < connectListenerLen; i++ {
|
||||
connectListenerLen := len(that.connectListener)
|
||||
|
||||
if !this.connectListener[i](&context) {
|
||||
for i := connectListenerLen - 1; i >= 0; i-- {
|
||||
|
||||
context.View()
|
||||
return
|
||||
}
|
||||
if that.connectListener[i](&context) {
|
||||
|
||||
context.View()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//接口服务
|
||||
//if len(s) == 3 {
|
||||
// //如果满足规则则路由到对应控制器去
|
||||
// if this.Router[s[0]] != nil && this.Router[s[0]][s[1]] != nil && this.Router[s[0]][s[1]][s[2]] != nil {
|
||||
// //控制层
|
||||
// this.Router[s[0]][s[1]][s[2]](&context)
|
||||
// //header.Set("Content-Type", "text/html; charset=utf-8")
|
||||
// context.View()
|
||||
// return
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//验证接口严格模式
|
||||
modeRouterStrict := this.Config.Get("modeRouterStrict").(bool)
|
||||
modeRouterStrict := that.Config.GetBool("modeRouterStrict")
|
||||
tempHandlerStr := context.HandlerStr
|
||||
if !modeRouterStrict {
|
||||
tempHandlerStr = strings.ToLower(tempHandlerStr)
|
||||
}
|
||||
|
||||
//执行接口
|
||||
if this.MethodRouter[tempHandlerStr] != nil {
|
||||
this.MethodRouter[tempHandlerStr](&context)
|
||||
if that.MethodRouter[tempHandlerStr] != nil {
|
||||
that.MethodRouter[tempHandlerStr](&context)
|
||||
context.View()
|
||||
return
|
||||
}
|
||||
|
||||
//url赋值
|
||||
path := this.Config.GetString("tpt") + tempHandlerStr
|
||||
path := that.Config.GetString("tpt") + tempHandlerStr
|
||||
|
||||
//判断是否为默认
|
||||
if path[len(path)-1] == '/' {
|
||||
defFile := this.Config.GetSlice("defFile")
|
||||
defFile := that.Config.GetSlice("defFile")
|
||||
|
||||
for i := 0; i < len(defFile); i++ {
|
||||
temp := path + defFile.GetString(i)
|
||||
@@ -409,12 +429,20 @@ func (this *Application) handler(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
//设置header
|
||||
delete(header, "Content-Type")
|
||||
if this.Config.GetInt("debug") != 1 {
|
||||
if that.Config.GetInt("mode") == 0 {
|
||||
header.Set("Cache-Control", "public")
|
||||
} else {
|
||||
header.Set("Cache-Control", "no-cache")
|
||||
}
|
||||
|
||||
if strings.Index(path, ".m3u8") != -1 {
|
||||
header.Add("Content-Type", "audio/mpegurl")
|
||||
t := strings.LastIndex(path, ".")
|
||||
if t != -1 {
|
||||
tt := path[t:]
|
||||
|
||||
if MimeMaps[tt] != "" {
|
||||
|
||||
header.Add("Content-Type", MimeMaps[tt])
|
||||
}
|
||||
}
|
||||
|
||||
//w.Write(data)
|
||||
@@ -422,32 +450,66 @@ func (this *Application) handler(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
func (this *Application) crossDomain(context *Context) {
|
||||
func (that *Application) crossDomain(context *Context, sessionId string) {
|
||||
|
||||
//没有跨域设置
|
||||
if context.Config.GetString("crossDomain") == "" {
|
||||
if sessionId != "" {
|
||||
http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
header := context.Resp.Header()
|
||||
header.Set("Access-Control-Allow-Origin", "*")
|
||||
header.Set("Access-Control-Allow-Methods", "*")
|
||||
header.Set("Access-Control-Allow-Credentials", "true")
|
||||
header.Set("Access-Control-Expose-Headers", "*")
|
||||
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token")
|
||||
|
||||
if context.Config.GetString("crossDomain") != "*" {
|
||||
header.Set("Access-Control-Allow-Origin", this.Config.GetString("crossDomain"))
|
||||
//不跨域,则不设置
|
||||
remoteHost := context.Req.Host
|
||||
if context.Config.GetString("port") == "80" || context.Config.GetString("port") == "443" {
|
||||
remoteHost = remoteHost + ":" + context.Config.GetString("port")
|
||||
}
|
||||
if context.Config.GetString("crossDomain") != "auto" {
|
||||
//不跨域,则不设置
|
||||
if strings.Contains(context.Config.GetString("crossDomain"), remoteHost) {
|
||||
|
||||
if sessionId != "" {
|
||||
http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
|
||||
}
|
||||
return
|
||||
}
|
||||
header.Set("Access-Control-Allow-Origin", that.Config.GetString("crossDomain"))
|
||||
// 后端设置,2592000单位秒,这里是30天
|
||||
header.Set("Access-Control-Max-Age", "2592000")
|
||||
|
||||
//header.Set("Access-Control-Allow-Origin", "*")
|
||||
header.Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE")
|
||||
header.Set("Access-Control-Allow-Credentials", "true")
|
||||
header.Set("Access-Control-Expose-Headers", "*")
|
||||
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token")
|
||||
|
||||
if sessionId != "" {
|
||||
//跨域允许需要设置cookie的允许跨域https才有效果
|
||||
context.Resp.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; Path=/; SameSite=None; Secure")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
origin := context.Req.Header.Get("Origin")
|
||||
if origin != "" {
|
||||
header.Set("Access-Control-Allow-Origin", origin)
|
||||
|
||||
refer := context.Req.Header.Get("Referer")
|
||||
if (origin != "" && strings.Contains(origin, remoteHost)) || strings.Contains(refer, remoteHost) {
|
||||
|
||||
if sessionId != "" {
|
||||
http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
refer := context.Req.Header.Get("Referer")
|
||||
if refer != "" {
|
||||
if origin != "" {
|
||||
header.Set("Access-Control-Allow-Origin", origin)
|
||||
//return
|
||||
} else if refer != "" {
|
||||
tempInt := 0
|
||||
lastInt := strings.IndexFunc(refer, func(r rune) bool {
|
||||
if r == '/' && tempInt > 8 {
|
||||
@@ -462,5 +524,236 @@ func (this *Application) crossDomain(context *Context) {
|
||||
}
|
||||
refer = Substr(refer, 0, lastInt)
|
||||
header.Set("Access-Control-Allow-Origin", refer)
|
||||
//header.Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
}
|
||||
|
||||
header.Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE")
|
||||
header.Set("Access-Control-Allow-Credentials", "true")
|
||||
header.Set("Access-Control-Expose-Headers", "*")
|
||||
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token")
|
||||
|
||||
if sessionId != "" {
|
||||
//跨域允许需要设置cookie的允许跨域https才有效果
|
||||
context.Resp.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; Path=/; SameSite=None; Secure")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Init 初始化application
|
||||
func Init(config string) *Application {
|
||||
appIns := Application{}
|
||||
//手动模式,
|
||||
appIns.SetConfig(config)
|
||||
|
||||
SetDB(&appIns)
|
||||
appIns.SetCache()
|
||||
codeConfig := appIns.Config.GetSlice("codeConfig")
|
||||
|
||||
if codeConfig != nil {
|
||||
|
||||
for k, _ := range codeConfig {
|
||||
codeMake := codeConfig.GetMap(k)
|
||||
if codeMake == nil {
|
||||
continue
|
||||
}
|
||||
//codeMake["table"] = k
|
||||
if appIns.MakeCodeRouter == nil {
|
||||
appIns.MakeCodeRouter = map[string]*code.MakeCode{}
|
||||
}
|
||||
|
||||
if codeMake.GetString("name") == "" {
|
||||
codeMake["name"] = codeMake.GetString("table")
|
||||
}
|
||||
|
||||
appIns.MakeCodeRouter[codeMake.GetString("name")] = &code.MakeCode{Error: appIns.Error}
|
||||
appIns.MakeCodeRouter[codeMake.GetString("name")].Db2JSON(&appIns.Db, codeMake)
|
||||
|
||||
//接入动态代码层
|
||||
if appIns.Router == nil {
|
||||
appIns.Router = Router{}
|
||||
}
|
||||
|
||||
//appIns.Router[codeMake.GetString("name")] = TptProject
|
||||
appIns.Router[codeMake.GetString("name")] = Proj{}
|
||||
for k2, _ := range TptProject {
|
||||
appIns.Router[codeMake.GetString("name")][k2] = Ctr{}
|
||||
for k3, v3 := range TptProject[k2] {
|
||||
appIns.Router[codeMake.GetString("name")][k2][k3] = v3
|
||||
}
|
||||
}
|
||||
|
||||
for k1, _ := range appIns.MakeCodeRouter[codeMake.GetString("name")].TableColumns {
|
||||
appIns.Router[codeMake.GetString("name")][k1] = appIns.Router[codeMake.GetString("name")]["hotimeCommon"]
|
||||
}
|
||||
|
||||
go func() {
|
||||
setMakeCodeLintener(codeMake.GetString("name"), &appIns)
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return &appIns
|
||||
}
|
||||
|
||||
// SetDB 智能数据库设置
|
||||
func SetDB(appIns *Application) {
|
||||
db := appIns.Config.GetMap("db")
|
||||
dbSqlite := db.GetMap("sqlite")
|
||||
dbMysql := db.GetMap("mysql")
|
||||
if db != nil && dbSqlite != nil {
|
||||
SetSqliteDB(appIns, dbSqlite)
|
||||
}
|
||||
if db != nil && dbMysql != nil {
|
||||
SetMysqlDB(appIns, dbMysql)
|
||||
}
|
||||
}
|
||||
func SetMysqlDB(appIns *Application, config Map) {
|
||||
|
||||
appIns.Db.Type = "mysql"
|
||||
appIns.Db.DBName = config.GetString("name")
|
||||
appIns.Db.Prefix = config.GetString("prefix")
|
||||
appIns.Db.Log = appIns.Log
|
||||
appIns.Db.Mode = appIns.Config.GetCeilInt("mode")
|
||||
appIns.SetConnectDB(func(err ...*Error) (master, slave *sql.DB) {
|
||||
//master数据库配置
|
||||
query := config.GetString("user") + ":" + config.GetString("password") +
|
||||
"@tcp(" + config.GetString("host") + ":" + config.GetString("port") + ")/" + config.GetString("name") + "?charset=utf8"
|
||||
DB, e := sql.Open("mysql", query)
|
||||
if e != nil && len(err) != 0 {
|
||||
err[0].SetError(e)
|
||||
}
|
||||
master = DB
|
||||
//slave数据库配置
|
||||
configSlave := config.GetMap("slave")
|
||||
if configSlave != nil {
|
||||
query := configSlave.GetString("user") + ":" + configSlave.GetString("password") +
|
||||
"@tcp(" + config.GetString("host") + ":" + configSlave.GetString("port") + ")/" + configSlave.GetString("name") + "?charset=utf8"
|
||||
DB1, e := sql.Open("mysql", query)
|
||||
if e != nil && len(err) != 0 {
|
||||
err[0].SetError(e)
|
||||
}
|
||||
slave = DB1
|
||||
}
|
||||
|
||||
return master, slave
|
||||
//return DB
|
||||
})
|
||||
}
|
||||
func SetSqliteDB(appIns *Application, config Map) {
|
||||
|
||||
appIns.Db.Type = "sqlite"
|
||||
appIns.Db.Prefix = config.GetString("prefix")
|
||||
appIns.Db.Mode = appIns.Config.GetCeilInt("mode")
|
||||
appIns.Db.Log = appIns.Log
|
||||
appIns.SetConnectDB(func(err ...*Error) (master, slave *sql.DB) {
|
||||
db, e := sql.Open("sqlite3", config.GetString("path"))
|
||||
if e != nil && len(err) != 0 {
|
||||
err[0].SetError(e)
|
||||
}
|
||||
master = db
|
||||
|
||||
return master, slave
|
||||
})
|
||||
}
|
||||
|
||||
func setMakeCodeLintener(name string, appIns *Application) {
|
||||
appIns.SetConnectListener(func(context *Context) (isFinished bool) {
|
||||
|
||||
codeIns := appIns.MakeCodeRouter[name]
|
||||
|
||||
if len(context.RouterString) < 2 || appIns.MakeCodeRouter[context.RouterString[0]] == nil {
|
||||
return isFinished
|
||||
}
|
||||
|
||||
if len(context.RouterString) > 1 && context.RouterString[0] == name {
|
||||
if context.RouterString[1] == "hotime" && context.RouterString[2] == "login" {
|
||||
return isFinished
|
||||
}
|
||||
if context.RouterString[1] == "hotime" && context.RouterString[2] == "logout" {
|
||||
return isFinished
|
||||
}
|
||||
|
||||
if context.RouterString[1] == "hotime" && context.RouterString[2] == "config" {
|
||||
return isFinished
|
||||
}
|
||||
|
||||
if context.Session(codeIns.FileConfig.GetString("table")+"_id").Data == nil {
|
||||
context.Display(2, "你还没有登录")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if len(context.RouterString) < 2 || len(context.RouterString) > 3 ||
|
||||
!(context.Router[context.RouterString[0]] != nil &&
|
||||
context.Router[context.RouterString[0]][context.RouterString[1]] != nil) {
|
||||
return isFinished
|
||||
}
|
||||
//排除无效操作
|
||||
if len(context.RouterString) == 2 &&
|
||||
context.Req.Method != "GET" &&
|
||||
context.Req.Method != "POST" {
|
||||
return isFinished
|
||||
}
|
||||
//排除已有接口的无效操作
|
||||
if len(context.RouterString) == 3 && context.Router[context.RouterString[0]] != nil && context.Router[context.RouterString[0]][context.RouterString[1]] != nil && context.Router[context.RouterString[0]][context.RouterString[1]][context.RouterString[2]] != nil {
|
||||
return isFinished
|
||||
}
|
||||
|
||||
//列表检索
|
||||
if len(context.RouterString) == 2 &&
|
||||
context.Req.Method == "GET" {
|
||||
if context.Router[context.RouterString[0]][context.RouterString[1]]["search"] == nil {
|
||||
return isFinished
|
||||
}
|
||||
context.Router[context.RouterString[0]][context.RouterString[1]]["search"](context)
|
||||
}
|
||||
//新建
|
||||
if len(context.RouterString) == 2 &&
|
||||
context.Req.Method == "POST" {
|
||||
if context.Router[context.RouterString[0]][context.RouterString[1]]["add"] == nil {
|
||||
return true
|
||||
}
|
||||
context.Router[context.RouterString[0]][context.RouterString[1]]["add"](context)
|
||||
}
|
||||
if len(context.RouterString) == 3 &&
|
||||
context.Req.Method == "POST" {
|
||||
return isFinished
|
||||
}
|
||||
//查询单条
|
||||
if len(context.RouterString) == 3 &&
|
||||
context.Req.Method == "GET" {
|
||||
|
||||
if context.Router[context.RouterString[0]][context.RouterString[1]]["info"] == nil {
|
||||
return isFinished
|
||||
}
|
||||
|
||||
context.Router[context.RouterString[0]][context.RouterString[1]]["info"](context)
|
||||
}
|
||||
//更新
|
||||
if len(context.RouterString) == 3 &&
|
||||
context.Req.Method == "PUT" {
|
||||
|
||||
if context.Router[context.RouterString[0]][context.RouterString[1]]["update"] == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
context.Router[context.RouterString[0]][context.RouterString[1]]["update"](context)
|
||||
}
|
||||
//移除
|
||||
if len(context.RouterString) == 3 &&
|
||||
context.Req.Method == "DELETE" {
|
||||
|
||||
if context.Router[context.RouterString[0]][context.RouterString[1]]["remove"] == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
context.Router[context.RouterString[0]][context.RouterString[1]]["remove"](context)
|
||||
}
|
||||
|
||||
context.View()
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
Vendored
+277
@@ -0,0 +1,277 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// HoTimeCache 可配置memory,db,redis,默认启用memory,默认优先级为memory>redis>db,memory与数据库缓存设置项一致,
|
||||
//缓存数据填充会自动反方向反哺,加入memory缓存过期将自动从redis更新,但memory永远不会更新redis,如果是集群建议不要开启memory,配置即启用
|
||||
type HoTimeCache struct {
|
||||
*Error
|
||||
dbCache *CacheDb
|
||||
redisCache *CacheRedis
|
||||
memoryCache *CacheMemory
|
||||
Config Map
|
||||
}
|
||||
|
||||
func (that *HoTimeCache) Session(key string, data ...interface{}) *Obj {
|
||||
var reData *Obj
|
||||
if len(data) == 0 {
|
||||
//内存缓存有
|
||||
if that.memoryCache != nil && that.memoryCache.SessionSet {
|
||||
reData = that.memoryCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
return reData
|
||||
}
|
||||
}
|
||||
|
||||
//redis缓存有
|
||||
if that.redisCache != nil && that.redisCache.SessionSet {
|
||||
reData = that.redisCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
if that.memoryCache != nil && that.memoryCache.SessionSet {
|
||||
that.memoryCache.Cache(key, reData.Data)
|
||||
}
|
||||
return reData
|
||||
}
|
||||
}
|
||||
|
||||
//db缓存有
|
||||
if that.dbCache != nil && that.dbCache.SessionSet {
|
||||
reData = that.dbCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
if that.memoryCache != nil && that.memoryCache.SessionSet {
|
||||
that.memoryCache.Cache(key, reData.Data)
|
||||
}
|
||||
if that.redisCache != nil && that.redisCache.SessionSet {
|
||||
that.redisCache.Cache(key, reData.Data)
|
||||
}
|
||||
|
||||
return reData
|
||||
}
|
||||
}
|
||||
return reData
|
||||
}
|
||||
|
||||
//设置缓存
|
||||
//设置内存缓存
|
||||
if that.memoryCache != nil && that.memoryCache.SessionSet {
|
||||
reData = that.memoryCache.Cache(key, data...)
|
||||
}
|
||||
//redis缓存有
|
||||
if that.redisCache != nil && that.redisCache.SessionSet {
|
||||
reData = that.redisCache.Cache(key, data...)
|
||||
}
|
||||
//redis缓存有
|
||||
if that.dbCache != nil && that.dbCache.SessionSet {
|
||||
reData = that.dbCache.Cache(key, data...)
|
||||
}
|
||||
return reData
|
||||
|
||||
}
|
||||
func (that *HoTimeCache) Db(key string, data ...interface{}) *Obj {
|
||||
var reData *Obj
|
||||
if len(data) == 0 {
|
||||
//内存缓存有
|
||||
if that.memoryCache != nil && that.memoryCache.DbSet {
|
||||
reData = that.memoryCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
return reData
|
||||
}
|
||||
}
|
||||
|
||||
//redis缓存有
|
||||
if that.redisCache != nil && that.redisCache.DbSet {
|
||||
reData = that.redisCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
if that.memoryCache != nil && that.memoryCache.DbSet {
|
||||
that.memoryCache.Cache(key, reData.Data)
|
||||
}
|
||||
return reData
|
||||
}
|
||||
}
|
||||
|
||||
//redis缓存有
|
||||
if that.dbCache != nil && that.dbCache.DbSet {
|
||||
reData = that.dbCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
if that.memoryCache != nil && that.memoryCache.DbSet {
|
||||
that.memoryCache.Cache(key, reData.Data)
|
||||
}
|
||||
|
||||
if that.redisCache != nil && that.redisCache.DbSet {
|
||||
that.redisCache.Cache(key, reData.Data)
|
||||
}
|
||||
|
||||
return reData
|
||||
}
|
||||
}
|
||||
return reData
|
||||
}
|
||||
|
||||
//设置缓存
|
||||
//设置内存缓存
|
||||
if that.memoryCache != nil && that.memoryCache.DbSet {
|
||||
reData = that.memoryCache.Cache(key, data...)
|
||||
}
|
||||
//redis缓存有
|
||||
if that.redisCache != nil && that.redisCache.DbSet {
|
||||
reData = that.redisCache.Cache(key, data...)
|
||||
}
|
||||
//redis缓存有
|
||||
if that.dbCache != nil && that.dbCache.DbSet {
|
||||
reData = that.dbCache.Cache(key, data...)
|
||||
}
|
||||
return reData
|
||||
}
|
||||
|
||||
func (that *HoTimeCache) Cache(key string, data ...interface{}) *Obj {
|
||||
var reData *Obj
|
||||
if len(data) == 0 {
|
||||
//内存缓存有
|
||||
if that.memoryCache != nil {
|
||||
reData = that.memoryCache.Cache(key, data...)
|
||||
if reData != nil {
|
||||
return reData
|
||||
}
|
||||
}
|
||||
|
||||
//redis缓存有
|
||||
if that.redisCache != nil {
|
||||
reData = that.redisCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
|
||||
if that.memoryCache != nil {
|
||||
that.memoryCache.Cache(key, reData.Data)
|
||||
}
|
||||
return reData
|
||||
}
|
||||
}
|
||||
|
||||
//redis缓存有
|
||||
if that.dbCache != nil {
|
||||
reData = that.dbCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
if that.memoryCache != nil {
|
||||
that.memoryCache.Cache(key, reData.Data)
|
||||
}
|
||||
if that.redisCache != nil {
|
||||
that.redisCache.Cache(key, reData.Data)
|
||||
}
|
||||
return reData
|
||||
}
|
||||
}
|
||||
return reData
|
||||
}
|
||||
|
||||
//设置缓存
|
||||
//设置内存缓存
|
||||
if that.memoryCache != nil {
|
||||
reData = that.memoryCache.Cache(key, data...)
|
||||
}
|
||||
//redis缓存有
|
||||
if that.redisCache != nil {
|
||||
reData = that.redisCache.Cache(key, data...)
|
||||
}
|
||||
//redis缓存有
|
||||
if that.dbCache != nil {
|
||||
reData = that.dbCache.Cache(key, data...)
|
||||
}
|
||||
return reData
|
||||
}
|
||||
|
||||
func (that *HoTimeCache) Init(config Map, hotimeDb HoTimeDBInterface, err ...*Error) {
|
||||
//防止空数据问题
|
||||
if config == nil {
|
||||
config = Map{}
|
||||
}
|
||||
if err[0] != nil {
|
||||
that.Error = err[0]
|
||||
}
|
||||
that.Config = config
|
||||
//memory配置初始化
|
||||
memory := that.Config.GetMap("memory")
|
||||
if memory == nil {
|
||||
memory = Map{
|
||||
"db": true,
|
||||
"session": true,
|
||||
"sort": 0,
|
||||
"timeout": 60 * 60 * 2,
|
||||
}
|
||||
|
||||
}
|
||||
if memory.Get("db") == nil {
|
||||
memory["db"] = true
|
||||
}
|
||||
|
||||
if memory.Get("session") == nil {
|
||||
memory["session"] = true
|
||||
}
|
||||
|
||||
if memory.Get("timeout") == nil {
|
||||
memory["timeout"] = 60 * 60 * 2
|
||||
}
|
||||
that.Config["memory"] = memory
|
||||
that.memoryCache = &CacheMemory{TimeOut: memory.GetCeilInt64("timeout"),
|
||||
DbSet: memory.GetBool("db"), SessionSet: memory.GetBool("session")}
|
||||
if err[0] != nil {
|
||||
that.memoryCache.SetError(err[0])
|
||||
}
|
||||
|
||||
//db配置初始化
|
||||
redis := that.Config.GetMap("redis")
|
||||
if redis != nil {
|
||||
if redis.GetString("host") == "" || redis.GetString("port") == "" {
|
||||
if err[0] != nil {
|
||||
err[0].SetError(errors.New("请检查redis配置host和port配置"))
|
||||
}
|
||||
return
|
||||
}
|
||||
if redis.Get("db") == nil {
|
||||
redis["db"] = true
|
||||
}
|
||||
|
||||
if redis.Get("session") == nil {
|
||||
redis["session"] = true
|
||||
}
|
||||
|
||||
if redis.Get("timeout") == nil {
|
||||
redis["timeout"] = 60 * 60 * 24 * 15
|
||||
}
|
||||
that.Config["redis"] = redis
|
||||
that.redisCache = &CacheRedis{TimeOut: redis.GetCeilInt64("timeout"),
|
||||
DbSet: redis.GetBool("db"), SessionSet: redis.GetBool("session"), Host: redis.GetString("host"),
|
||||
Pwd: redis.GetString("password"), Port: redis.GetCeilInt64("port")}
|
||||
|
||||
if err[0] != nil {
|
||||
that.redisCache.SetError(err[0])
|
||||
}
|
||||
}
|
||||
|
||||
//db配置初始化
|
||||
db := that.Config.GetMap("db")
|
||||
if db != nil {
|
||||
|
||||
if db.Get("db") == nil {
|
||||
db["db"] = false
|
||||
}
|
||||
|
||||
if db.Get("session") == nil {
|
||||
db["session"] = true
|
||||
}
|
||||
if db.Get("timeout") == nil {
|
||||
db["timeout"] = 60 * 60 * 24 * 30
|
||||
}
|
||||
that.Config["db"] = db
|
||||
|
||||
that.dbCache = &CacheDb{TimeOut: db.GetCeilInt64("timeout"),
|
||||
DbSet: db.GetBool("db"), SessionSet: db.GetBool("session"),
|
||||
Db: hotimeDb}
|
||||
|
||||
if err[0] != nil {
|
||||
that.dbCache.SetError(err[0])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+173
@@ -0,0 +1,173 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HoTimeDBInterface interface {
|
||||
GetPrefix() string
|
||||
Query(query string, args ...interface{}) []Map
|
||||
Exec(query string, args ...interface{}) (sql.Result, *Error)
|
||||
Get(table string, qu ...interface{}) Map
|
||||
Select(table string, qu ...interface{}) []Map
|
||||
Delete(table string, data map[string]interface{}) int64
|
||||
Update(table string, data Map, where Map) int64
|
||||
Insert(table string, data map[string]interface{}) int64
|
||||
GetType() string
|
||||
}
|
||||
|
||||
type CacheDb struct {
|
||||
TimeOut int64
|
||||
DbSet bool
|
||||
SessionSet bool
|
||||
Db HoTimeDBInterface
|
||||
*Error
|
||||
ContextBase
|
||||
isInit bool
|
||||
}
|
||||
|
||||
func (that *CacheDb) GetError() *Error {
|
||||
|
||||
return that.Error
|
||||
|
||||
}
|
||||
|
||||
func (that *CacheDb) SetError(err *Error) {
|
||||
that.Error = err
|
||||
}
|
||||
|
||||
func (that *CacheDb) initDbTable() {
|
||||
if that.isInit {
|
||||
return
|
||||
}
|
||||
if that.Db.GetType() == "mysql" {
|
||||
|
||||
dbNames := that.Db.Query("SELECT DATABASE()")
|
||||
|
||||
if len(dbNames) == 0 {
|
||||
return
|
||||
}
|
||||
dbName := dbNames[0].GetString("DATABASE()")
|
||||
res := that.Db.Query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='" + dbName + "' AND TABLE_NAME='" + that.Db.GetPrefix() + "cached'")
|
||||
if len(res) != 0 {
|
||||
that.isInit = true
|
||||
return
|
||||
}
|
||||
|
||||
_, e := that.Db.Exec("CREATE TABLE `" + that.Db.GetPrefix() + "cached` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `key` varchar(60) DEFAULT NULL, `value` varchar(2000) DEFAULT NULL, `time` bigint(20) DEFAULT NULL, `endtime` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=198740 DEFAULT CHARSET=utf8")
|
||||
if e.GetError() == nil {
|
||||
that.isInit = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if that.Db.GetType() == "sqlite" {
|
||||
res := that.Db.Query(`select * from sqlite_master where type = 'table' and name = '` + that.Db.GetPrefix() + `cached'`)
|
||||
|
||||
if len(res) != 0 {
|
||||
that.isInit = true
|
||||
return
|
||||
}
|
||||
_, e := that.Db.Exec(`CREATE TABLE "` + that.Db.GetPrefix() + `cached" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"key" TEXT(60),
|
||||
"value" TEXT(2000),
|
||||
"time" integer,
|
||||
"endtime" integer
|
||||
);`)
|
||||
if e.GetError() == nil {
|
||||
that.isInit = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//获取Cache键只能为string类型
|
||||
func (that *CacheDb) get(key string) interface{} {
|
||||
|
||||
cached := that.Db.Get("cached", "*", Map{"key": key})
|
||||
|
||||
if cached == nil {
|
||||
return nil
|
||||
}
|
||||
//data:=cacheMap[key];
|
||||
if cached.GetInt64("endtime") <= time.Now().Unix() {
|
||||
|
||||
that.Db.Delete("cached", Map{"id": cached.GetString("id")})
|
||||
return nil
|
||||
}
|
||||
|
||||
data := Map{}
|
||||
data.JsonToMap(cached.GetString("value"))
|
||||
|
||||
return data.Get("data")
|
||||
}
|
||||
|
||||
//key value ,时间为时间戳
|
||||
func (that *CacheDb) set(key string, value interface{}, tim int64) {
|
||||
|
||||
bte, _ := json.Marshal(Map{"data": value})
|
||||
|
||||
num := that.Db.Update("cached", Map{"value": string(bte), "time": time.Now().UnixNano(), "endtime": tim}, Map{"key": key})
|
||||
if num == int64(0) {
|
||||
that.Db.Insert("cached", Map{"value": string(bte), "time": time.Now().UnixNano(), "endtime": tim, "key": key})
|
||||
}
|
||||
|
||||
//随机执行删除命令
|
||||
if Rand(1000) > 950 {
|
||||
that.Db.Delete("cached", Map{"endtime[<]": time.Now().Unix()})
|
||||
}
|
||||
}
|
||||
|
||||
func (that *CacheDb) delete(key string) {
|
||||
|
||||
del := strings.Index(key, "*")
|
||||
//如果通配删除
|
||||
if del != -1 {
|
||||
key = Substr(key, 0, del)
|
||||
that.Db.Delete("cached", Map{"key": key + "%"})
|
||||
|
||||
} else {
|
||||
that.Db.Delete("cached", Map{"key": key})
|
||||
}
|
||||
}
|
||||
|
||||
func (that *CacheDb) Cache(key string, data ...interface{}) *Obj {
|
||||
|
||||
that.initDbTable()
|
||||
|
||||
if len(data) == 0 {
|
||||
return &Obj{Data: that.get(key)}
|
||||
}
|
||||
tim := time.Now().Unix()
|
||||
|
||||
if len(data) == 1 && data[0] == nil {
|
||||
that.delete(key)
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
|
||||
if len(data) == 1 {
|
||||
if that.TimeOut == 0 {
|
||||
//that.Time = Config.GetInt64("cacheLongTime")
|
||||
}
|
||||
tim += that.TimeOut
|
||||
}
|
||||
if len(data) == 2 {
|
||||
that.SetError(nil)
|
||||
tempt := ObjToInt64(data[1], that.Error)
|
||||
|
||||
if tempt > tim {
|
||||
tim = tempt
|
||||
} else if that.GetError() == nil {
|
||||
|
||||
tim = tim + tempt
|
||||
}
|
||||
}
|
||||
that.set(key, data[0], tim)
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
Vendored
+158
@@ -0,0 +1,158 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CacheMemory struct {
|
||||
TimeOut int64
|
||||
DbSet bool
|
||||
SessionSet bool
|
||||
Map
|
||||
*Error
|
||||
ContextBase
|
||||
mutex *sync.RWMutex
|
||||
}
|
||||
|
||||
func (that *CacheMemory) GetError() *Error {
|
||||
|
||||
return that.Error
|
||||
|
||||
}
|
||||
|
||||
func (that *CacheMemory) SetError(err *Error) {
|
||||
that.Error = err
|
||||
}
|
||||
|
||||
//获取Cache键只能为string类型
|
||||
func (that *CacheMemory) get(key string) interface{} {
|
||||
that.Error.SetError(nil)
|
||||
if that.Map == nil {
|
||||
that.Map = Map{}
|
||||
}
|
||||
|
||||
if that.Map[key] == nil {
|
||||
return nil
|
||||
}
|
||||
data := that.Map.Get(key, that.Error).(cacheData)
|
||||
if that.Error.GetError() != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if data.time < time.Now().Unix() {
|
||||
delete(that.Map, key)
|
||||
return nil
|
||||
}
|
||||
return data.data
|
||||
}
|
||||
|
||||
func (that *CacheMemory) refreshMap() {
|
||||
|
||||
go func() {
|
||||
that.mutex.Lock()
|
||||
defer that.mutex.Unlock()
|
||||
for key, v := range that.Map {
|
||||
data := v.(cacheData)
|
||||
if data.time <= time.Now().Unix() {
|
||||
delete(that.Map, key)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
//key value ,时间为时间戳
|
||||
func (that *CacheMemory) set(key string, value interface{}, time int64) {
|
||||
that.Error.SetError(nil)
|
||||
var data cacheData
|
||||
|
||||
if that.Map == nil {
|
||||
that.Map = Map{}
|
||||
}
|
||||
|
||||
dd := that.Map[key]
|
||||
|
||||
if dd == nil {
|
||||
data = cacheData{}
|
||||
} else {
|
||||
data = dd.(cacheData)
|
||||
}
|
||||
|
||||
data.time = time
|
||||
data.data = value
|
||||
|
||||
that.Map.Put(key, data)
|
||||
}
|
||||
|
||||
func (that *CacheMemory) delete(key string) {
|
||||
del := strings.Index(key, "*")
|
||||
//如果通配删除
|
||||
if del != -1 {
|
||||
key = Substr(key, 0, del)
|
||||
for k, _ := range that.Map {
|
||||
if strings.Index(k, key) != -1 {
|
||||
delete(that.Map, k)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
delete(that.Map, key)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (that *CacheMemory) Cache(key string, data ...interface{}) *Obj {
|
||||
|
||||
x := RandX(1, 100000)
|
||||
if x > 99950 {
|
||||
that.refreshMap()
|
||||
}
|
||||
if that.mutex == nil {
|
||||
that.mutex = &sync.RWMutex{}
|
||||
}
|
||||
|
||||
reData := &Obj{Data: nil}
|
||||
|
||||
if len(data) == 0 {
|
||||
that.mutex.RLock()
|
||||
reData.Data = that.get(key)
|
||||
that.mutex.RUnlock()
|
||||
return reData
|
||||
}
|
||||
tim := time.Now().Unix()
|
||||
|
||||
if len(data) == 1 && data[0] == nil {
|
||||
that.mutex.Lock()
|
||||
that.delete(key)
|
||||
that.mutex.Unlock()
|
||||
return reData
|
||||
}
|
||||
|
||||
if len(data) == 1 {
|
||||
|
||||
tim = tim + that.TimeOut
|
||||
|
||||
}
|
||||
if len(data) == 2 {
|
||||
that.Error.SetError(nil)
|
||||
tempt := ObjToInt64(data[1], that.Error)
|
||||
|
||||
if tempt > tim {
|
||||
|
||||
tim = tempt
|
||||
} else if that.Error.GetError() == nil {
|
||||
|
||||
tim = tim + tempt
|
||||
}
|
||||
}
|
||||
that.mutex.Lock()
|
||||
that.set(key, data[0], tim)
|
||||
that.mutex.Unlock()
|
||||
return reData
|
||||
|
||||
}
|
||||
Vendored
+71
-54
@@ -1,67 +1,84 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
. "go.hoteas.com/hotime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CacheRedis struct {
|
||||
Host string
|
||||
Pwd string
|
||||
Time int64
|
||||
conn redis.Conn
|
||||
tag int64
|
||||
Error
|
||||
TimeOut int64
|
||||
DbSet bool
|
||||
SessionSet bool
|
||||
Host string
|
||||
Pwd string
|
||||
Port int64
|
||||
conn redis.Conn
|
||||
tag int64
|
||||
ContextBase
|
||||
*Error
|
||||
}
|
||||
|
||||
func (that *CacheRedis) GetError() *Error {
|
||||
|
||||
return that.Error
|
||||
|
||||
}
|
||||
|
||||
func (that *CacheRedis) SetError(err *Error) {
|
||||
that.Error = err
|
||||
}
|
||||
|
||||
//唯一标志
|
||||
func (this *CacheRedis) GetTag() int64 {
|
||||
func (that *CacheRedis) GetTag() int64 {
|
||||
|
||||
if this.tag == int64(0) {
|
||||
this.tag = time.Now().UnixNano()
|
||||
if that.tag == int64(0) {
|
||||
that.tag = time.Now().UnixNano()
|
||||
}
|
||||
return this.tag
|
||||
return that.tag
|
||||
}
|
||||
|
||||
func (this *CacheRedis) reCon() bool {
|
||||
func (that *CacheRedis) reCon() bool {
|
||||
var err error
|
||||
this.conn, err = redis.Dial("tcp", this.Host)
|
||||
that.conn, err = redis.Dial("tcp", that.Host+":"+ObjToStr(that.Port))
|
||||
if err != nil {
|
||||
this.conn = nil
|
||||
this.Error.SetError(err)
|
||||
that.conn = nil
|
||||
that.Error.SetError(err)
|
||||
return false
|
||||
}
|
||||
|
||||
_, err = this.conn.Do("AUTH", this.Pwd)
|
||||
if err != nil {
|
||||
this.conn = nil
|
||||
this.Error.SetError(err)
|
||||
return false
|
||||
if that.Pwd != "" {
|
||||
_, err = that.conn.Do("AUTH", that.Pwd)
|
||||
if err != nil {
|
||||
that.conn = nil
|
||||
that.Error.SetError(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
func (this *CacheRedis) del(key string) {
|
||||
func (that *CacheRedis) del(key string) {
|
||||
del := strings.Index(key, "*")
|
||||
if del != -1 {
|
||||
val, err := redis.Strings(this.conn.Do("KEYS", key))
|
||||
val, err := redis.Strings(that.conn.Do("KEYS", key))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
this.conn.Send("MULTI")
|
||||
that.conn.Send("MULTI")
|
||||
for i, _ := range val {
|
||||
this.conn.Send("DEL", val[i])
|
||||
that.conn.Send("DEL", val[i])
|
||||
}
|
||||
this.conn.Do("EXEC")
|
||||
that.conn.Do("EXEC")
|
||||
} else {
|
||||
_, err := this.conn.Do("DEL", key)
|
||||
_, err := that.conn.Do("DEL", key)
|
||||
if err != nil {
|
||||
this.Error.SetError(err)
|
||||
_, err = this.conn.Do("PING")
|
||||
that.Error.SetError(err)
|
||||
_, err = that.conn.Do("PING")
|
||||
if err != nil {
|
||||
if this.reCon() {
|
||||
_, err = this.conn.Do("DEL", key)
|
||||
if that.reCon() {
|
||||
_, err = that.conn.Do("DEL", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,32 +86,32 @@ func (this *CacheRedis) del(key string) {
|
||||
}
|
||||
|
||||
//key value ,时间为时间戳
|
||||
func (this *CacheRedis) set(key string, value string, time int64) {
|
||||
_, err := this.conn.Do("SET", key, value, "EX", ObjToStr(time))
|
||||
func (that *CacheRedis) set(key string, value string, time int64) {
|
||||
_, err := that.conn.Do("SET", key, value, "EX", ObjToStr(time))
|
||||
if err != nil {
|
||||
|
||||
this.Error.SetError(err)
|
||||
_, err = this.conn.Do("PING")
|
||||
that.Error.SetError(err)
|
||||
_, err = that.conn.Do("PING")
|
||||
if err != nil {
|
||||
if this.reCon() {
|
||||
_, err = this.conn.Do("SET", key, value, "EX", ObjToStr(time))
|
||||
if that.reCon() {
|
||||
_, err = that.conn.Do("SET", key, value, "EX", ObjToStr(time))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *CacheRedis) get(key string) *Obj {
|
||||
func (that *CacheRedis) get(key string) *Obj {
|
||||
reData := &Obj{}
|
||||
var err error
|
||||
reData.Data, err = redis.String(this.conn.Do("GET", key))
|
||||
reData.Data, err = redis.String(that.conn.Do("GET", key))
|
||||
if err != nil {
|
||||
reData.Data = nil
|
||||
if !strings.Contains(err.Error(), "nil returned") {
|
||||
this.Error.SetError(err)
|
||||
_, err = this.conn.Do("PING")
|
||||
that.Error.SetError(err)
|
||||
_, err = that.conn.Do("PING")
|
||||
if err != nil {
|
||||
if this.reCon() {
|
||||
reData.Data, err = redis.String(this.conn.Do("GET", key))
|
||||
if that.reCon() {
|
||||
reData.Data, err = redis.String(that.conn.Do("GET", key))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,10 +121,10 @@ func (this *CacheRedis) get(key string) *Obj {
|
||||
return reData
|
||||
}
|
||||
|
||||
func (this *CacheRedis) Cache(key string, data ...interface{}) *Obj {
|
||||
func (that *CacheRedis) Cache(key string, data ...interface{}) *Obj {
|
||||
reData := &Obj{}
|
||||
if this.conn == nil {
|
||||
re := this.reCon()
|
||||
if that.conn == nil {
|
||||
re := that.reCon()
|
||||
if !re {
|
||||
return reData
|
||||
}
|
||||
@@ -115,38 +132,38 @@ func (this *CacheRedis) Cache(key string, data ...interface{}) *Obj {
|
||||
//查询缓存
|
||||
if len(data) == 0 {
|
||||
|
||||
reData = this.get(key)
|
||||
reData = that.get(key)
|
||||
return reData
|
||||
|
||||
}
|
||||
tim := int64(0)
|
||||
//删除缓存
|
||||
if len(data) == 1 && data[0] == nil {
|
||||
this.del(key)
|
||||
that.del(key)
|
||||
return reData
|
||||
}
|
||||
//添加缓存
|
||||
if len(data) == 1 {
|
||||
|
||||
if this.Time == 0 {
|
||||
this.Time = Config.GetInt64("cacheShortTime")
|
||||
if that.TimeOut == 0 {
|
||||
//that.Time = Config.GetInt64("cacheShortTime")
|
||||
}
|
||||
|
||||
tim += this.Time
|
||||
tim += that.TimeOut
|
||||
}
|
||||
if len(data) == 2 {
|
||||
this.Error.SetError(nil)
|
||||
tempt := ObjToInt64(data[1], &this.Error)
|
||||
that.Error.SetError(nil)
|
||||
tempt := ObjToInt64(data[1], that.Error)
|
||||
if tempt > tim {
|
||||
|
||||
tim = tempt
|
||||
} else if this.GetError() == nil {
|
||||
} else if that.GetError() == nil {
|
||||
|
||||
tim = tim + tempt
|
||||
}
|
||||
}
|
||||
|
||||
this.set(key, ObjToStr(data[0]), tim)
|
||||
that.set(key, ObjToStr(data[0]), tim)
|
||||
|
||||
return reData
|
||||
|
||||
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
type CacheIns interface {
|
||||
//set(key string, value interface{}, time int64)
|
||||
//get(key string) interface{}
|
||||
//delete(key string)
|
||||
GetError() *Error
|
||||
SetError(err *Error)
|
||||
Cache(key string, data ...interface{}) *Obj
|
||||
}
|
||||
|
||||
//单条缓存数据
|
||||
type cacheData struct {
|
||||
time int64
|
||||
data interface{}
|
||||
}
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CacheDb struct {
|
||||
Time int64
|
||||
Db *HoTimeDB
|
||||
contextBase
|
||||
isInit bool
|
||||
}
|
||||
|
||||
func (this *CacheDb) initDbTable() {
|
||||
if this.isInit {
|
||||
return
|
||||
}
|
||||
if this.Db.Type == "mysql" {
|
||||
|
||||
dbNames := this.Db.Query("SELECT DATABASE()")
|
||||
|
||||
if len(dbNames) == 0 {
|
||||
return
|
||||
}
|
||||
dbName := dbNames[0].GetString("DATABASE()")
|
||||
res := this.Db.Query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='" + dbName + "' AND TABLE_NAME='cached'")
|
||||
if len(res) != 0 {
|
||||
this.isInit = true
|
||||
return
|
||||
}
|
||||
|
||||
_, e := this.Db.Exec("CREATE TABLE `cached` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `ckey` varchar(60) DEFAULT NULL, `cvalue` varchar(2000) DEFAULT NULL, `time` bigint(20) DEFAULT NULL, `endtime` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=198740 DEFAULT CHARSET=utf8")
|
||||
if e.GetError() == nil {
|
||||
this.isInit = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if this.Db.Type == "sqlite" {
|
||||
res := this.Db.Query(`select * from sqlite_master where type = 'table' and name = 'cached'`)
|
||||
|
||||
if len(res) != 0 {
|
||||
this.isInit = true
|
||||
return
|
||||
}
|
||||
_, e := this.Db.Exec(`CREATE TABLE "cached" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"ckey" TEXT(60),
|
||||
"cvalue" TEXT(2000),
|
||||
"time" integer,
|
||||
"endtime" integer
|
||||
);`)
|
||||
if e.GetError() == nil {
|
||||
this.isInit = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//获取Cache键只能为string类型
|
||||
func (this *CacheDb) get(key string) interface{} {
|
||||
|
||||
cached := this.Db.Get("cached", "*", Map{"ckey": key})
|
||||
|
||||
if cached == nil {
|
||||
return nil
|
||||
}
|
||||
//data:=cacheMap[key];
|
||||
if cached.GetInt64("endtime") <= time.Now().Unix() {
|
||||
|
||||
this.Db.Delete("cached", Map{"id": cached.GetString("id")})
|
||||
return nil
|
||||
}
|
||||
|
||||
data := Map{}
|
||||
data.JsonToMap(cached.GetString("cvalue"))
|
||||
|
||||
return data.Get("data")
|
||||
}
|
||||
|
||||
//key value ,时间为时间戳
|
||||
func (this *CacheDb) set(key string, value interface{}, tim int64) {
|
||||
|
||||
bte, _ := json.Marshal(Map{"data": value})
|
||||
|
||||
num := this.Db.Update("cached", Map{"cvalue": string(bte), "time": time.Now().UnixNano(), "endtime": tim}, Map{"ckey": key})
|
||||
if num == int64(0) {
|
||||
this.Db.Insert("cached", Map{"cvalue": string(bte), "time": time.Now().UnixNano(), "endtime": tim, "ckey": key})
|
||||
}
|
||||
|
||||
//随机执行删除命令
|
||||
if Rand(1000) > 950 {
|
||||
this.Db.Delete("cached", Map{"endtime[<]": time.Now().Unix()})
|
||||
}
|
||||
}
|
||||
|
||||
func (this *CacheDb) delete(key string) {
|
||||
|
||||
del := strings.Index(key, "*")
|
||||
//如果通配删除
|
||||
if del != -1 {
|
||||
key = Substr(key, 0, del)
|
||||
this.Db.Delete("cached", Map{"ckey": key + "%"})
|
||||
|
||||
} else {
|
||||
this.Db.Delete("cached", Map{"ckey": key})
|
||||
}
|
||||
}
|
||||
|
||||
func (this *CacheDb) Cache(key string, data ...interface{}) *Obj {
|
||||
|
||||
this.initDbTable()
|
||||
|
||||
if len(data) == 0 {
|
||||
return &Obj{Data: this.get(key)}
|
||||
}
|
||||
tim := time.Now().Unix()
|
||||
|
||||
if len(data) == 1 && data[0] == nil {
|
||||
this.delete(key)
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
|
||||
if len(data) == 1 {
|
||||
if this.Time == 0 {
|
||||
this.Time = Config.GetInt64("cacheLongTime")
|
||||
}
|
||||
tim += this.Time
|
||||
}
|
||||
if len(data) == 2 {
|
||||
this.SetError(nil)
|
||||
tempt := ObjToInt64(data[1], &this.Error)
|
||||
|
||||
if tempt > tim {
|
||||
tim = tempt
|
||||
} else if this.GetError() == nil {
|
||||
|
||||
tim = tim + tempt
|
||||
}
|
||||
}
|
||||
this.set(key, data[0], tim)
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CacheMemory struct {
|
||||
Time int64
|
||||
Map
|
||||
contextBase
|
||||
mutex *sync.RWMutex
|
||||
}
|
||||
|
||||
//获取Cache键只能为string类型
|
||||
func (this *CacheMemory) get(key string) interface{} {
|
||||
this.Error.SetError(nil)
|
||||
if this.Map == nil {
|
||||
this.Map = Map{}
|
||||
}
|
||||
|
||||
if this.Map[key] == nil {
|
||||
return nil
|
||||
}
|
||||
data := this.Map.Get(key, &this.Error).(cacheData)
|
||||
if this.GetError() != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if data.time < time.Now().Unix() {
|
||||
delete(this.Map, key)
|
||||
return nil
|
||||
}
|
||||
return data.data
|
||||
}
|
||||
|
||||
func (this *CacheMemory) refreshMap() {
|
||||
|
||||
go func() {
|
||||
this.mutex.Lock()
|
||||
defer this.mutex.Unlock()
|
||||
for key, v := range this.Map {
|
||||
data := v.(cacheData)
|
||||
if data.time <= time.Now().Unix() {
|
||||
delete(this.Map, key)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
//key value ,时间为时间戳
|
||||
func (this *CacheMemory) set(key string, value interface{}, time int64) {
|
||||
this.Error.SetError(nil)
|
||||
var data cacheData
|
||||
|
||||
if this.Map == nil {
|
||||
this.Map = Map{}
|
||||
}
|
||||
|
||||
dd := this.Map[key]
|
||||
|
||||
if dd == nil {
|
||||
data = cacheData{}
|
||||
} else {
|
||||
data = dd.(cacheData)
|
||||
}
|
||||
|
||||
data.time = time
|
||||
data.data = value
|
||||
|
||||
this.Map.Put(key, data)
|
||||
}
|
||||
|
||||
func (this *CacheMemory) delete(key string) {
|
||||
del := strings.Index(key, "*")
|
||||
//如果通配删除
|
||||
if del != -1 {
|
||||
key = Substr(key, 0, del)
|
||||
for k, _ := range this.Map {
|
||||
if strings.Index(k, key) != -1 {
|
||||
delete(this.Map, key)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
delete(this.Map, key)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (this *CacheMemory) Cache(key string, data ...interface{}) *Obj {
|
||||
|
||||
x := RandX(1, 100000)
|
||||
if x > 99950 {
|
||||
this.refreshMap()
|
||||
}
|
||||
if this.mutex == nil {
|
||||
this.mutex = &sync.RWMutex{}
|
||||
}
|
||||
|
||||
reData := &Obj{Data: nil}
|
||||
|
||||
if len(data) == 0 {
|
||||
this.mutex.RLock()
|
||||
reData.Data = this.get(key)
|
||||
this.mutex.RUnlock()
|
||||
return reData
|
||||
}
|
||||
tim := time.Now().Unix()
|
||||
|
||||
if len(data) == 1 && data[0] == nil {
|
||||
this.mutex.Lock()
|
||||
this.delete(key)
|
||||
this.mutex.Unlock()
|
||||
return reData
|
||||
}
|
||||
|
||||
if len(data) == 1 {
|
||||
if this.Time == 0 {
|
||||
this.Time = Config.GetInt64("cacheShortTime")
|
||||
}
|
||||
|
||||
tim = tim + this.Time
|
||||
|
||||
}
|
||||
if len(data) == 2 {
|
||||
this.Error.SetError(nil)
|
||||
tempt := ObjToInt64(data[1], &this.Error)
|
||||
|
||||
if tempt > tim {
|
||||
|
||||
tim = tempt
|
||||
} else if this.GetError() == nil {
|
||||
|
||||
tim = tim + tempt
|
||||
}
|
||||
}
|
||||
this.mutex.Lock()
|
||||
this.set(key, data[0], tim)
|
||||
this.mutex.Unlock()
|
||||
return reData
|
||||
|
||||
}
|
||||
@@ -0,0 +1,906 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"fmt"
|
||||
"github.com/360EntSecGroup-Skylar/excelize"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Project 管理端项目
|
||||
var TptProject = Proj{
|
||||
//"user": UserCtr,
|
||||
"hotimeCommon": Ctr{
|
||||
"info": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
fileConfig := that.MakeCodeRouter[hotimeName].FileConfig
|
||||
tableName := that.RouterString[1]
|
||||
|
||||
data := that.Db.Get(fileConfig.GetString("table"), "*", Map{"id": that.Session(fileConfig.GetString("table") + "_id").ToCeilInt()})
|
||||
str, inData := that.MakeCodeRouter[hotimeName].Info(tableName, data, that.Db)
|
||||
where := Map{"id": that.RouterString[2]}
|
||||
|
||||
if len(inData) == 1 {
|
||||
inData["id"] = where["id"]
|
||||
where = Map{"AND": inData}
|
||||
} else if len(inData) > 1 {
|
||||
where["OR"] = inData
|
||||
where = Map{"AND": where}
|
||||
}
|
||||
|
||||
re := that.Db.Get(tableName, str, where)
|
||||
|
||||
if re == nil {
|
||||
that.Display(4, "找不到对应信息")
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range re {
|
||||
column := that.MakeCodeRouter[hotimeName].TableColumns[tableName][k]
|
||||
if column == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if (column["list"] == nil || column.GetBool("list")) && column.GetString("link") != "" {
|
||||
seStr := "id," + column.GetString("value")
|
||||
if that.MakeCodeRouter[hotimeName].TableColumns[column.GetString("link")]["phone"] != nil {
|
||||
seStr = seStr + ",phone"
|
||||
}
|
||||
|
||||
link := strings.Replace(column.GetString("name"), "_id", "", -1)
|
||||
if link == "parent" {
|
||||
link = tableName
|
||||
}
|
||||
re[link] = that.Db.Get(column.GetString("link"), seStr, Map{"id": v})
|
||||
}
|
||||
}
|
||||
|
||||
//如果有table字段则代为link
|
||||
if re["table"] != nil && re["table_id"] != nil {
|
||||
|
||||
column := that.MakeCodeRouter[hotimeName].TableColumns[tableName][re.GetString("table")]
|
||||
v := re.GetCeilInt64("table_id")
|
||||
|
||||
seStr := "id," + column.GetString("value")
|
||||
|
||||
if that.MakeCodeRouter[hotimeName].TableColumns[column.GetString("link")]["phone"] != nil {
|
||||
seStr = seStr + ",phone"
|
||||
}
|
||||
|
||||
link := strings.Replace(column.GetString("name"), "_id", "", -1)
|
||||
if link == "parent" {
|
||||
link = tableName
|
||||
}
|
||||
re[link] = that.Db.Get(column.GetString("link"), seStr, Map{"id": v})
|
||||
|
||||
}
|
||||
|
||||
that.Display(0, re)
|
||||
},
|
||||
"add": func(that *Context) {
|
||||
tableName := that.RouterString[1]
|
||||
that.Log = Map{"table": tableName, "type": 1}
|
||||
|
||||
hotimeName := that.RouterString[0]
|
||||
fileConfig := that.MakeCodeRouter[hotimeName].FileConfig
|
||||
data := that.Db.Get(fileConfig.GetString("table"), "*", Map{"id": that.Session(fileConfig.GetString("table") + "_id").ToCeilInt()})
|
||||
inData := that.MakeCodeRouter[hotimeName].Add(tableName, data, that.Req)
|
||||
if inData == nil {
|
||||
that.Display(3, "请求参数不足")
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range that.MakeCodeRouter[hotimeName].TableColumns[fileConfig.GetString("table")] {
|
||||
if v.GetString("link") != "" {
|
||||
|
||||
if v.GetString("link") == tableName && that.MakeCodeRouter[hotimeName].TableColumns[v.GetString("link")]["parent_id"] != nil {
|
||||
if inData["parent_id"] == nil && data[v.GetString("name")] != nil {
|
||||
inData["parent_id"] = data.GetCeilInt64(v.GetString("name"))
|
||||
pre := that.Db.Get(v.GetString("link"), "parent_ids", Map{"id": data.GetCeilInt64(v.GetString("name"))})
|
||||
inData["parent_ids"] = pre.GetString("parent_ids")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if v.GetString("link") == tableName && that.MakeCodeRouter[hotimeName].TableColumns[v.GetString("link")]["auth"] != nil {
|
||||
linkAuthMap := that.Db.Get(v.GetString("link"), "auth", Map{"id": data.GetCeilInt(v.GetString("name"))})
|
||||
linkAuth := linkAuthMap.GetMap("auth")
|
||||
|
||||
if linkAuth == nil {
|
||||
btes, err := ioutil.ReadFile(fileConfig.GetString("config"))
|
||||
if err != nil {
|
||||
that.Display(4, "找不到配置文件")
|
||||
return
|
||||
}
|
||||
linkAuth = Map{}
|
||||
conf := ObjToMap(string(btes))
|
||||
menus := conf.GetSlice("menus")
|
||||
|
||||
for k1, _ := range menus {
|
||||
v1 := menus.GetMap(k1)
|
||||
name := v1.GetString("name")
|
||||
if name == "" {
|
||||
name = v1.GetString("table")
|
||||
}
|
||||
if v1["auth"] != nil {
|
||||
linkAuth[name] = v1["auth"]
|
||||
|
||||
}
|
||||
if v1["auth"] == nil {
|
||||
table := that.MakeCodeRouter[hotimeName].TableConfig.GetMap(name)
|
||||
if table != nil {
|
||||
linkAuth[name] = that.MakeCodeRouter[hotimeName].TableConfig.GetMap(name)["auth"]
|
||||
} else {
|
||||
linkAuth[name] = Slice{"show"}
|
||||
}
|
||||
}
|
||||
|
||||
menusChild := v1.GetSlice("menus")
|
||||
for k2, _ := range menusChild {
|
||||
v2 := menusChild.GetMap(k2)
|
||||
name := v2.GetString("name")
|
||||
if name == "" {
|
||||
name = v2.GetString("table")
|
||||
}
|
||||
if v2["auth"] != nil {
|
||||
linkAuth[name] = v2["auth"]
|
||||
continue
|
||||
}
|
||||
if v2["auth"] == nil {
|
||||
table := that.MakeCodeRouter[hotimeName].TableConfig.GetMap(name)
|
||||
if table != nil {
|
||||
linkAuth[name] = that.MakeCodeRouter[hotimeName].TableConfig.GetMap(name)["auth"]
|
||||
} else {
|
||||
linkAuth[name] = Slice{"show"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toDB := Map{}
|
||||
newAuth := inData.GetMap("auth")
|
||||
if newAuth == nil {
|
||||
inData["auth"] = linkAuth.ToJsonString()
|
||||
continue
|
||||
}
|
||||
|
||||
for k1, _ := range newAuth {
|
||||
v1 := newAuth.GetSlice(k1)
|
||||
if linkAuth.GetString(k1) == "" {
|
||||
continue
|
||||
}
|
||||
toDbli := Slice{}
|
||||
for k2, _ := range v1 {
|
||||
v2 := v1.GetString(k2)
|
||||
|
||||
if !strings.Contains(linkAuth.GetString(k1), `"`+v2+`"`) {
|
||||
continue
|
||||
}
|
||||
toDbli = append(toDbli, v2)
|
||||
}
|
||||
toDB[k1] = toDbli
|
||||
}
|
||||
|
||||
inData["auth"] = toDB.ToJsonString()
|
||||
//break
|
||||
}
|
||||
}
|
||||
|
||||
re := that.Db.Insert(tableName, inData)
|
||||
|
||||
if re == 0 {
|
||||
that.Display(4, "无法插入对应数据")
|
||||
return
|
||||
}
|
||||
//索引管理,便于检索以及权限
|
||||
if inData.Get("parent_id") != nil && inData.GetString("parent_ids") != "" {
|
||||
index := that.Db.Get(tableName, "`parent_ids`", Map{"id": inData.Get("parent_id")})
|
||||
inData["parent_ids"] = index.GetString("parent_ids") + ObjToStr(re) + ","
|
||||
that.Db.Update(tableName, Map{"parent_ids": inData["parent_ids"]}, Map{"id": re})
|
||||
} else if inData.GetString("parent_ids") != "" {
|
||||
inData["parent_ids"] = "," + ObjToStr(re) + ","
|
||||
that.Db.Update(tableName, Map{"parent_ids": inData["parent_ids"]}, Map{"id": re})
|
||||
}
|
||||
that.Log["table_id"] = re
|
||||
that.Display(0, re)
|
||||
},
|
||||
"update": func(that *Context) {
|
||||
tableName := that.RouterString[1]
|
||||
that.Log = Map{"table": tableName, "type": 2, "table_id": that.RouterString[2]}
|
||||
|
||||
hotimeName := that.RouterString[0]
|
||||
fileConfig := that.MakeCodeRouter[hotimeName].FileConfig
|
||||
|
||||
inData := that.MakeCodeRouter[hotimeName].Edit(tableName, that.Req)
|
||||
|
||||
if inData == nil {
|
||||
that.Display(3, "没有找到要更新的数据")
|
||||
return
|
||||
}
|
||||
|
||||
data := that.Db.Get(fileConfig.GetString("table"), "*", Map{"id": that.Session(fileConfig.GetString("table") + "_id").ToCeilInt()})
|
||||
|
||||
//树状结构不允许修改自身的属性,修改别人的可以
|
||||
btes, err := ioutil.ReadFile(fileConfig.GetString("config"))
|
||||
if err != nil {
|
||||
that.Display(4, "找不到权限配置文件")
|
||||
return
|
||||
}
|
||||
|
||||
conf := ObjToMap(string(btes))
|
||||
|
||||
stop := conf.GetSlice("stop")
|
||||
for k, _ := range stop {
|
||||
v := stop.GetString(k)
|
||||
//不能改对应行数据
|
||||
if tableName == v && data.GetCeilInt64(v+"_id") == ObjToCeilInt64(that.RouterString[2]) {
|
||||
that.Display(4, "你没有权限修改当前数据")
|
||||
return
|
||||
}
|
||||
//不能改自身对应的数据
|
||||
if tableName == fileConfig.GetString("table") && inData[v+"_id"] != nil {
|
||||
delete(inData, v+"_id")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if inData["auth"] != nil {
|
||||
menus := conf.GetSlice("menus")
|
||||
for _, v := range that.MakeCodeRouter[hotimeName].TableColumns[fileConfig.GetString("table")] {
|
||||
|
||||
if v.GetString("link") != "" && that.MakeCodeRouter[hotimeName].TableColumns[v.GetString("link")]["auth"] != nil {
|
||||
linkAuthMap := that.Db.Get(v.GetString("link"), "auth", Map{"id": data.GetCeilInt(v.GetString("name"))})
|
||||
linkAuth := linkAuthMap.GetMap("auth")
|
||||
|
||||
if linkAuth == nil {
|
||||
|
||||
linkAuth = Map{}
|
||||
for k1, _ := range menus {
|
||||
v1 := menus.GetMap(k1)
|
||||
name := v1.GetString("name")
|
||||
if name == "" {
|
||||
name = v1.GetString("table")
|
||||
}
|
||||
if v1["auth"] != nil {
|
||||
linkAuth[name] = v1["auth"]
|
||||
continue
|
||||
}
|
||||
if v1["auth"] == nil {
|
||||
|
||||
table := that.MakeCodeRouter[hotimeName].TableConfig.GetMap(name)
|
||||
if table != nil {
|
||||
linkAuth[name] = that.MakeCodeRouter[hotimeName].TableConfig.GetMap(name)["auth"]
|
||||
} else {
|
||||
linkAuth[name] = Slice{"show"}
|
||||
}
|
||||
}
|
||||
menusChild := v1.GetSlice("menus")
|
||||
for k2, _ := range menusChild {
|
||||
v2 := menusChild.GetMap(k2)
|
||||
name := v2.GetString("name")
|
||||
if name == "" {
|
||||
name = v2.GetString("table")
|
||||
}
|
||||
if v2["auth"] != nil {
|
||||
linkAuth[name] = v2["auth"]
|
||||
continue
|
||||
}
|
||||
if v2["auth"] == nil {
|
||||
table := that.MakeCodeRouter[hotimeName].TableConfig.GetMap(name)
|
||||
if table != nil {
|
||||
linkAuth[name] = that.MakeCodeRouter[hotimeName].TableConfig.GetMap(name)["auth"]
|
||||
} else {
|
||||
linkAuth[name] = Slice{"show"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toDB := Map{}
|
||||
newAuth := inData.GetMap("auth")
|
||||
if newAuth == nil || len(newAuth) == 0 {
|
||||
inData["auth"] = linkAuth.ToJsonString()
|
||||
break
|
||||
}
|
||||
//不可超出用户权限
|
||||
for k1, _ := range newAuth {
|
||||
v1 := newAuth.GetSlice(k1)
|
||||
if linkAuth.GetString(k1) == "" {
|
||||
continue
|
||||
}
|
||||
toDbli := Slice{}
|
||||
for k2, _ := range v1 {
|
||||
v2 := v1.GetString(k2)
|
||||
if !strings.Contains(linkAuth.GetString(k1), `"`+v2+`"`) {
|
||||
continue
|
||||
}
|
||||
toDbli = append(toDbli, v2)
|
||||
}
|
||||
toDB[k1] = toDbli
|
||||
}
|
||||
|
||||
inData["auth"] = toDB.ToJsonString()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//索引管理,便于检索以及权限
|
||||
if inData.GetString("parent_ids") != "" {
|
||||
if inData.Get("parent_id") != nil {
|
||||
Index := that.Db.Get(tableName, "`index`", Map{"id": that.RouterString[2]})
|
||||
parentIndex := that.Db.Get(tableName, "`index`", Map{"id": inData.Get("parent_id")})
|
||||
inData["parent_ids"] = parentIndex.GetString("parent_ids") + that.RouterString[2] + ","
|
||||
|
||||
childNodes := that.Db.Select(tableName, "id,`index``", Map{"index[~]": "," + that.RouterString[2] + ","})
|
||||
|
||||
for _, v := range childNodes {
|
||||
v["parent_ids"] = strings.Replace(v.GetString("parent_ids"), Index.GetString("parent_ids"), inData.GetString("parent_ids"), -1)
|
||||
that.Db.Update(tableName, Map{"parent_ids": v["parent_ids"]}, Map{"id": v.GetCeilInt("id")})
|
||||
}
|
||||
} else {
|
||||
delete(inData, "parent_ids")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
re := that.Db.Update(tableName, inData, Map{"id": that.RouterString[2]})
|
||||
|
||||
if re == 0 {
|
||||
that.Display(4, "更新数据失败")
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(0, re)
|
||||
},
|
||||
"remove": func(that *Context) {
|
||||
tableName := that.RouterString[1]
|
||||
that.Log = Map{"table": tableName, "type": 3, "table_id": that.RouterString[2]}
|
||||
hotimeName := that.RouterString[0]
|
||||
inData := that.MakeCodeRouter[hotimeName].Delete(tableName, that.Req)
|
||||
if inData == nil {
|
||||
that.Display(3, "请求参数不足")
|
||||
return
|
||||
}
|
||||
re := int64(0)
|
||||
//索引管理,便于检索以及权限
|
||||
if inData.Get("parent_id") != nil && inData.GetSlice("parent_ids") != nil {
|
||||
re = that.Db.Delete(tableName, Map{"index[~]": "," + that.RouterString[2] + ","})
|
||||
} else {
|
||||
re = that.Db.Delete(tableName, Map{"id": that.RouterString[2]})
|
||||
}
|
||||
|
||||
if re == 0 {
|
||||
that.Display(4, "删除数据失败")
|
||||
return
|
||||
}
|
||||
that.Display(0, "删除成功")
|
||||
},
|
||||
"search": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
fileConfig := that.MakeCodeRouter[hotimeName].FileConfig
|
||||
tableName := that.RouterString[1]
|
||||
data := that.Db.Get(fileConfig.GetString("table"), "*", Map{"id": that.Session(fileConfig.GetString("table") + "_id").ToCeilInt()})
|
||||
|
||||
columnStr, leftJoin, where := that.MakeCodeRouter[hotimeName].Search(tableName, data, that.Req, that.Db)
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
download := ObjToInt(that.Req.FormValue("download"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
var count int
|
||||
var reData []Map
|
||||
//是否下载
|
||||
if download == 0 {
|
||||
count = that.Db.Count(tableName, leftJoin, where)
|
||||
reData = that.Db.Page(page, pageSize).
|
||||
PageSelect(tableName, leftJoin, columnStr, where)
|
||||
}
|
||||
if download == 1 {
|
||||
reData = that.Db.Select(tableName, leftJoin, columnStr, where)
|
||||
}
|
||||
|
||||
for _, v := range reData {
|
||||
for k, _ := range v {
|
||||
//v1:=v.GetMap(k)
|
||||
//v.RangeSort(func(k string, v1 interface{}) (isEnd bool) {
|
||||
//如果有table字段则代为link
|
||||
if v["table"] != nil && v["table_id"] != nil {
|
||||
|
||||
id := v.GetCeilInt64("table_id")
|
||||
tableNameLabel := that.MakeCodeRouter[hotimeName].TableConfig.GetMap(v.GetString("table")).GetString("label")
|
||||
//v["table_table_name"] = strings.Replace(tableNameLabel, "管理", "", -1)
|
||||
v["table_table_name"] = tableNameLabel
|
||||
sname := "name"
|
||||
if that.MakeCodeRouter[hotimeName].TableColumns[v.GetString("table")][sname] == nil {
|
||||
sname = "title"
|
||||
if that.MakeCodeRouter[hotimeName].TableColumns[v.GetString("table")][sname] == nil {
|
||||
sname = "id"
|
||||
}
|
||||
}
|
||||
|
||||
parentC := that.Db.Get(v.GetString("table"), sname, Map{"id": id})
|
||||
v["table_table_id_name"] = ""
|
||||
if parentC != nil {
|
||||
v["table_table_id_name"] = parentC.GetString(sname)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
column := that.MakeCodeRouter[hotimeName].TableColumns[tableName][k]
|
||||
|
||||
if column == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if (column["list"] == nil || column["list"] == true) && column["name"] == "parent_id" && column.GetString("link") != "" {
|
||||
|
||||
parentC := that.Db.Get(column.GetString("link"), column.GetString("value"), Map{"id": v.GetCeilInt(k)})
|
||||
v[column.GetString("link")+"_"+column.GetString("name")+"_"+column.GetString("value")] = ""
|
||||
if parentC != nil {
|
||||
v[column.GetString("link")+"_"+column.GetString("name")+"_"+column.GetString("value")] = parentC.GetString(column.GetString("value"))
|
||||
}
|
||||
}
|
||||
|
||||
//return isEnd
|
||||
}
|
||||
}
|
||||
|
||||
if download == 1 {
|
||||
|
||||
tableNameLabel := that.MakeCodeRouter[hotimeName].TableConfig.GetMap(tableName).GetString("label")
|
||||
f := excelize.NewFile()
|
||||
// 创建一个工作表
|
||||
f.NewSheet(tableNameLabel)
|
||||
|
||||
f.DeleteSheet("Sheet1")
|
||||
columns := that.MakeCodeRouter[hotimeName].TableConfig.GetMap(tableName).GetSlice("columns")
|
||||
//单列
|
||||
n := 0
|
||||
for k, _ := range columns {
|
||||
v := columns.GetMap(k)
|
||||
if v["list"] != nil && v.GetBool("list") == false {
|
||||
continue
|
||||
}
|
||||
n++
|
||||
//第一个要加状态行
|
||||
//单行
|
||||
for k1, v1 := range reData {
|
||||
if k1 == 0 {
|
||||
f.SetCellValue(tableNameLabel, convertToTitle(n)+"1", v.GetString("label"))
|
||||
}
|
||||
|
||||
if v.GetString("link") != "" {
|
||||
f.SetCellValue(tableNameLabel, convertToTitle(n)+ObjToStr(k1+2), v1.GetString(v.GetString("link")+"_"+v.GetString("name")+"_"+v.GetString("value")))
|
||||
continue
|
||||
}
|
||||
|
||||
if v.GetString("name") == "table" {
|
||||
f.SetCellValue(tableNameLabel, convertToTitle(n)+ObjToStr(k1+2), v1.GetString("table_"+v.GetString("name")+"_name"))
|
||||
continue
|
||||
}
|
||||
|
||||
if v.GetString("name") == "table_id" {
|
||||
f.SetCellValue(tableNameLabel, convertToTitle(n)+ObjToStr(k1+2), v1.GetString("table_"+v.GetString("name")+"_name"))
|
||||
continue
|
||||
}
|
||||
|
||||
//select类型
|
||||
options := v.GetSlice("options")
|
||||
if len(options) != 0 {
|
||||
isEnd := false
|
||||
for ok, _ := range options {
|
||||
ov := options.GetMap(ok)
|
||||
if ov.GetString("value") == v1.GetString(v.GetString("name")) {
|
||||
f.SetCellValue(tableNameLabel, convertToTitle(n)+ObjToStr(k1+2), ov.GetString("name"))
|
||||
isEnd = true
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
if isEnd {
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
f.SetCellValue(tableNameLabel, convertToTitle(n)+ObjToStr(k1+2), v1.GetString(v.GetString("name")))
|
||||
}
|
||||
}
|
||||
filePath := that.Config.GetString("filePath")
|
||||
if filePath == "" {
|
||||
filePath = "/file/temp/"
|
||||
}
|
||||
|
||||
//path := time.Now().Format(filePath)
|
||||
e := os.MkdirAll(that.Config.GetString("tpt")+filePath, os.ModeDir)
|
||||
if e != nil {
|
||||
that.Display(3, e)
|
||||
return
|
||||
}
|
||||
filePath = filePath + tableName + time.Now().Format("2006-01-02-15-04-05") + ".xlsx"
|
||||
|
||||
// 根据指定路径保存文件
|
||||
if err := f.SaveAs(that.Config.GetString("tpt") + filePath); err != nil {
|
||||
fmt.Println(err)
|
||||
that.Display(4, "输出异常")
|
||||
return
|
||||
}
|
||||
f.Save()
|
||||
//that.Resp.Header().Set("Location", filePath)
|
||||
//that.Resp.WriteHeader(307) //关键在这里!
|
||||
that.Display(0, filePath)
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(0, Map{"count": count, "data": reData})
|
||||
},
|
||||
},
|
||||
"hotime": Ctr{
|
||||
"file": func(that *Context) {
|
||||
//文件上传接口
|
||||
hotimeName := that.RouterString[0]
|
||||
fileConfig := that.MakeCodeRouter[hotimeName].FileConfig
|
||||
if that.Session(fileConfig.GetString("table")+"_id").Data == nil {
|
||||
that.Display(2, "你还没有登录")
|
||||
return
|
||||
}
|
||||
//读取网络文件
|
||||
fi, fheader, err := that.Req.FormFile("file")
|
||||
if err != nil {
|
||||
that.Display(3, err)
|
||||
return
|
||||
|
||||
}
|
||||
filePath := that.Config.GetString("filePath")
|
||||
if filePath == "" {
|
||||
filePath = "/file/2006/01/02/"
|
||||
}
|
||||
|
||||
path := time.Now().Format(filePath)
|
||||
e := os.MkdirAll(that.Config.GetString("tpt")+path, os.ModeDir)
|
||||
if e != nil {
|
||||
that.Display(3, e)
|
||||
return
|
||||
}
|
||||
filePath = path + Md5(ObjToStr(RandX(100000, 9999999))) + fheader.Filename[strings.LastIndex(fheader.Filename, "."):]
|
||||
newFile, e := os.Create(that.Config.GetString("tpt") + filePath)
|
||||
|
||||
if e != nil {
|
||||
that.Display(3, e)
|
||||
return
|
||||
}
|
||||
|
||||
_, e = io.Copy(newFile, fi)
|
||||
|
||||
if e != nil {
|
||||
that.Display(3, e)
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(0, filePath)
|
||||
},
|
||||
"info": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
fileConfig := that.MakeCodeRouter[hotimeName].FileConfig
|
||||
tableName := fileConfig.GetString("table")
|
||||
|
||||
data := that.Db.Get(fileConfig.GetString("table"), "*", Map{"id": that.Session(fileConfig.GetString("table") + "_id").ToCeilInt()})
|
||||
str, inData := that.MakeCodeRouter[hotimeName].Info(tableName, data, that.Db)
|
||||
where := Map{"id": that.Session(fileConfig.GetString("table") + "_id").ToCeilInt()}
|
||||
|
||||
if len(inData) == 1 {
|
||||
inData["id"] = where["id"]
|
||||
where = Map{"AND": inData}
|
||||
} else if len(inData) > 1 {
|
||||
where["OR"] = inData
|
||||
where = Map{"AND": where}
|
||||
}
|
||||
|
||||
re := that.Db.Get(tableName, str, where)
|
||||
if re == nil {
|
||||
that.Display(4, "找不到对应信息")
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range re {
|
||||
column := that.MakeCodeRouter[hotimeName].TableColumns[tableName][k]
|
||||
if column == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if (column["list"] == nil || column.GetBool("list")) && column.GetString("link") != "" {
|
||||
seStr := "id," + column.GetString("value")
|
||||
if that.MakeCodeRouter[hotimeName].TableColumns[column.GetString("link")]["phone"] != nil {
|
||||
seStr = seStr + ",phone"
|
||||
}
|
||||
|
||||
link := strings.Replace(column.GetString("name"), "_id", "", -1)
|
||||
if link == "parent" {
|
||||
link = tableName
|
||||
}
|
||||
re[link] = that.Db.Get(column.GetString("link"), seStr, Map{"id": v})
|
||||
}
|
||||
}
|
||||
//如果有table字段则代为link
|
||||
if re["table"] != nil && re["table_id"] != nil {
|
||||
|
||||
column := that.MakeCodeRouter[hotimeName].TableColumns[tableName][re.GetString("table")]
|
||||
v := re.GetCeilInt64("table_id")
|
||||
|
||||
seStr := "id," + column.GetString("value")
|
||||
|
||||
if that.MakeCodeRouter[hotimeName].TableColumns[column.GetString("link")]["phone"] != nil {
|
||||
seStr = seStr + ",phone"
|
||||
}
|
||||
|
||||
link := strings.Replace(column.GetString("name"), "_id", "", -1)
|
||||
if link == "parent" {
|
||||
link = tableName
|
||||
}
|
||||
re[link] = that.Db.Get(column.GetString("link"), seStr, Map{"id": v})
|
||||
|
||||
}
|
||||
re["table"] = fileConfig.GetString("table")
|
||||
that.Display(0, re)
|
||||
},
|
||||
"login": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
fileConfig := that.MakeCodeRouter[hotimeName].FileConfig
|
||||
|
||||
name := that.Req.FormValue("name")
|
||||
password := that.Req.FormValue("password")
|
||||
if name == "" || password == "" {
|
||||
that.Display(3, "参数不足")
|
||||
return
|
||||
}
|
||||
|
||||
where := Map{"name": name}
|
||||
if that.MakeCodeRouter[hotimeName].TableColumns[fileConfig.GetString("table")]["phone"] != nil {
|
||||
where["phone"] = name
|
||||
where = Map{"OR": where, "password": Md5(password)}
|
||||
} else {
|
||||
where["password"] = Md5(password)
|
||||
}
|
||||
|
||||
if len(where) > 1 {
|
||||
where = Map{"AND": where}
|
||||
}
|
||||
|
||||
user := that.Db.Get(fileConfig.GetString("table"), "*", where)
|
||||
|
||||
if user == nil {
|
||||
that.Display(5, "登录失败")
|
||||
return
|
||||
}
|
||||
that.Session(fileConfig.GetString("table")+"_id", user.GetCeilInt("id"))
|
||||
that.Session(fileConfig.GetString("table")+"_name", name)
|
||||
delete(user, "password")
|
||||
user["table"] = fileConfig.GetString("table")
|
||||
user["token"] = that.SessionId
|
||||
that.Display(0, user)
|
||||
},
|
||||
|
||||
"logout": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
fileConfig := that.MakeCodeRouter[hotimeName].FileConfig
|
||||
that.Session(fileConfig.GetString("table")+"_id", nil)
|
||||
that.Session(fileConfig.GetString("table")+"_name", nil)
|
||||
that.Display(0, "退出登录成功")
|
||||
},
|
||||
"config": func(that *Context) {
|
||||
|
||||
hotimeName := that.RouterString[0]
|
||||
fileConfig := that.MakeCodeRouter[hotimeName].FileConfig
|
||||
btes, err := ioutil.ReadFile(fileConfig.GetString("config"))
|
||||
|
||||
if err != nil {
|
||||
that.Display(4, "找不到配置文件")
|
||||
return
|
||||
}
|
||||
if that.Session(fileConfig.GetString("table")+"_id").Data == nil {
|
||||
|
||||
conf := ObjToMap(string(btes))
|
||||
delete(conf, "menus")
|
||||
delete(conf, "tables")
|
||||
//没有登录只需要返回这些信息
|
||||
that.Display(0, conf)
|
||||
return
|
||||
}
|
||||
//可读写配置
|
||||
conf := ObjToMap(string(btes))
|
||||
menus := conf.GetSlice("menus")
|
||||
|
||||
user := that.Db.Get(fileConfig.GetString("table"), "*", Map{"id": that.Session(fileConfig.GetString("table") + "_id").Data})
|
||||
|
||||
if user == nil {
|
||||
that.Display(2, "暂未登录")
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range that.MakeCodeRouter[hotimeName].TableColumns[fileConfig.GetString("table")] {
|
||||
if v.GetString("link") != "" {
|
||||
linkHasAuth := that.MakeCodeRouter[hotimeName].TableColumns[v.GetString("link")]["auth"]
|
||||
if linkHasAuth == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
linkAuthMap := that.Db.Get(v.GetString("link"), "auth", Map{"id": user.GetCeilInt(v.GetString("name"))})
|
||||
linkAuth := linkAuthMap.GetMap("auth")
|
||||
//conf := ObjToMap(string(btes))
|
||||
//menus := conf.GetSlice("menus")
|
||||
if linkAuth == nil {
|
||||
linkAuth = Map{}
|
||||
}
|
||||
|
||||
for k1, _ := range menus {
|
||||
v1 := menus.GetMap(k1)
|
||||
name := v1.GetString("name")
|
||||
if name == "" {
|
||||
name = v1.GetString("table")
|
||||
}
|
||||
if v1["auth"] != nil {
|
||||
|
||||
if linkAuth[name] == nil {
|
||||
linkAuth[name] = v1["auth"]
|
||||
} else {
|
||||
newAuth := Slice{}
|
||||
for k2, _ := range linkAuth.GetSlice(name) {
|
||||
v2 := linkAuth.GetSlice(name).GetString(k2)
|
||||
if strings.Contains(v1.GetString("auth"), v2) {
|
||||
newAuth = append(newAuth, v2)
|
||||
}
|
||||
}
|
||||
linkAuth[name] = newAuth
|
||||
}
|
||||
}
|
||||
|
||||
menusChild := v1.GetSlice("menus")
|
||||
for k2, _ := range menusChild {
|
||||
v2 := menusChild.GetMap(k2)
|
||||
name := v2.GetString("name")
|
||||
if name == "" {
|
||||
name = v2.GetString("table")
|
||||
}
|
||||
if v2["auth"] != nil {
|
||||
|
||||
if linkAuth[name] == nil {
|
||||
linkAuth[name] = v2["auth"]
|
||||
|
||||
} else {
|
||||
|
||||
newAuth := Slice{}
|
||||
for k3, _ := range linkAuth.GetSlice(name) {
|
||||
v3 := linkAuth.GetSlice(name).GetString(k3)
|
||||
if strings.Contains(v2.GetString("auth"), v3) {
|
||||
newAuth = append(newAuth, v3)
|
||||
}
|
||||
}
|
||||
linkAuth[name] = newAuth
|
||||
continue
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k1, _ := range menus {
|
||||
v1 := menus.GetMap(k1)
|
||||
//保证个人权限可用
|
||||
if fileConfig.GetString("table") == v1.GetString("table") {
|
||||
v1["auth"] = Slice{"info", "edit"}
|
||||
}
|
||||
name := v1.GetString("name")
|
||||
if name == "" {
|
||||
name = v1.GetString("table")
|
||||
}
|
||||
|
||||
if linkAuth[name] != nil {
|
||||
v1["auth"] = linkAuth[name]
|
||||
}
|
||||
|
||||
v1menus := v1.GetSlice("menus")
|
||||
for k2, _ := range v1menus {
|
||||
v2 := v1menus.GetMap(k2)
|
||||
//保证个人权限可用
|
||||
if fileConfig.GetString("table") == v2.GetString("table") {
|
||||
v2["auth"] = Slice{"info", "edit"}
|
||||
|
||||
}
|
||||
name := v2.GetString("name")
|
||||
if name == "" {
|
||||
name = v2.GetString("table")
|
||||
}
|
||||
if linkAuth[name] != nil {
|
||||
v2["auth"] = linkAuth[name]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//不可读写数据
|
||||
config := DeepCopyMap(that.MakeCodeRouter[hotimeName].Config).(Map)
|
||||
|
||||
config["menus"] = menus
|
||||
newTables := Map{}
|
||||
newTables[fileConfig.GetString("table")] = config.GetMap("tables").GetMap(fileConfig.GetString("table"))
|
||||
for k1, _ := range menus {
|
||||
v1 := menus.GetMap(k1)
|
||||
if config.GetMap("tables").GetMap(v1.GetString("table")) != nil && len(v1.GetSlice("auth")) != 0 {
|
||||
config.GetMap("tables").GetMap(v1.GetString("table"))["auth"] = Slice{}
|
||||
newTables[v1.GetString("name")] = config.GetMap("tables").GetMap(v1.GetString("table"))
|
||||
}
|
||||
v1menus := v1.GetSlice("menus")
|
||||
for k2, _ := range v1.GetSlice("menus") {
|
||||
v2 := v1menus.GetMap(k2)
|
||||
if config.GetMap("tables").GetMap(v2.GetString("table")) != nil && len(v2.GetSlice("auth")) != 0 {
|
||||
|
||||
//有自定义配置文件
|
||||
if conf.GetMap("tables") != nil && conf.GetMap("tables").GetMap(v2.GetString("table")) != nil {
|
||||
|
||||
columns := config.GetMap("tables").GetMap(v2.GetString("table")).GetSlice("columns")
|
||||
columnsConf := conf.GetMap("tables").GetMap(v2.GetString("table")).GetSlice("columns")
|
||||
for k3, _ := range columns {
|
||||
v3 := columns.GetMap(k3)
|
||||
for k4, _ := range columnsConf {
|
||||
v4 := columnsConf.GetMap(k4)
|
||||
|
||||
if v3.GetString("name") == v4.GetString("name") {
|
||||
columns[k3] = columnsConf[k4]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
config.GetMap("tables").GetMap(v2.GetString("table"))["auth"] = Slice{}
|
||||
newTables[v2.GetString("table")] = config.GetMap("tables").GetMap(v2.GetString("table"))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conf["tables"] = newTables
|
||||
|
||||
that.Display(0, conf)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func convertToTitle(columnNumber int) string {
|
||||
var res []byte
|
||||
for columnNumber > 0 {
|
||||
a := columnNumber % 26
|
||||
if a == 0 {
|
||||
a = 26
|
||||
}
|
||||
res = append(res, 'A'+byte(a-1))
|
||||
columnNumber = (columnNumber - a) / 26
|
||||
}
|
||||
// 上面输出的res是反着的,前后交换
|
||||
for i, n := 0, len(res); i < n/2; i++ {
|
||||
res[i], res[n-1-i] = res[n-1-i], res[i]
|
||||
}
|
||||
return string(res)
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
package code
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var Config = Map{
|
||||
"name": "HoTimeDashBoard",
|
||||
//"id": "2f92h3herh23rh2y8",
|
||||
"label": "HoTime管理平台",
|
||||
"stop": Slice{"role", "org"}, //不更新的,同时不允许修改用户自身对应的表数据
|
||||
"labelConfig": Map{
|
||||
"show": "开启",
|
||||
"add": "添加",
|
||||
"delete": "删除",
|
||||
"edit": "编辑",
|
||||
"info": "查看详情",
|
||||
"download": "下载清单",
|
||||
},
|
||||
"menus": []Map{
|
||||
//{"label": "平台首页", "name": "HelloWorld", "icon": "el-icon-s-home"},
|
||||
//{"label": "测试表格", "table": "table", "icon": "el-icon-suitcase"},
|
||||
//{"label": "系统管理", "name": "setting", "icon": "el-icon-setting",
|
||||
// "menus": []Map{
|
||||
// {"label": "用户管理", "table": "user",
|
||||
// "default": {
|
||||
// "path": "info",
|
||||
// "id": "1"
|
||||
// },
|
||||
// "auth": ["show","edit","info","add","delete"],
|
||||
// },
|
||||
// {"label": "组织管理", "table": "organization"},
|
||||
// {"label": "地区管理", "table": "area"},
|
||||
// {"label": "角色管理", "table": "role"},
|
||||
// {"label": "日志管理", "table": "log"},
|
||||
// {"label": "系统设置", "table": "system", "default": "edit"},
|
||||
// },
|
||||
//},
|
||||
},
|
||||
}
|
||||
var DefaultMenuParentName = "sys"
|
||||
|
||||
var ColumnDataType = map[string]string{
|
||||
//sqlite专有类型
|
||||
"real": "number",
|
||||
//mysql数据类型宽泛类型
|
||||
"int": "number",
|
||||
"float": "number",
|
||||
"double": "number",
|
||||
"decimal": "number",
|
||||
"integer": "number", //sqlite3
|
||||
"char": "text",
|
||||
"text": "text",
|
||||
"blob": "text",
|
||||
"date": "time",
|
||||
"time": "time",
|
||||
"year": "time",
|
||||
"geometry": "gis", //不建议使用gis类型,建议使用其他代替
|
||||
}
|
||||
|
||||
type ColumnShow struct {
|
||||
Name string
|
||||
List bool
|
||||
Edit bool
|
||||
Info bool
|
||||
Must bool
|
||||
Type string //空字符串表示
|
||||
Strict bool //name严格匹配必须是这个词才行
|
||||
}
|
||||
|
||||
var ColumnNameType = []ColumnShow{
|
||||
//通用
|
||||
{"idcard", false, true, true, false, "", false},
|
||||
{"id", true, false, true, false, "", true},
|
||||
{"sn", true, false, true, false, "", false},
|
||||
{"parent_ids", false, false, false, false, "index", true},
|
||||
{"parent_id", true, true, true, false, "", true},
|
||||
{"amount", true, true, true, false, "money", true},
|
||||
{"info", false, true, true, false, "textArea", false},
|
||||
//"sn"{true,true,true,""},
|
||||
{"status", true, true, true, false, "select", false},
|
||||
{"state", true, true, true, false, "select", false},
|
||||
{"sex", true, true, true, false, "select", false},
|
||||
{"delete", false, false, false, false, "", false},
|
||||
|
||||
{"lat", false, true, true, false, "", false},
|
||||
{"lng", false, true, true, false, "", false},
|
||||
{"latitude", false, true, true, false, "", false},
|
||||
{"longitude", false, true, true, false, "", false},
|
||||
|
||||
{"index", false, false, false, false, "index", false},
|
||||
|
||||
{"password", false, true, false, false, "password", false},
|
||||
{"pwd", false, true, false, false, "password", false},
|
||||
|
||||
{"version", false, false, false, false, "", false},
|
||||
{"seq", false, true, true, false, "", false},
|
||||
{"sort", false, true, true, false, "", false},
|
||||
{"note", false, true, true, false, "", false},
|
||||
{"description", false, true, true, false, "", false},
|
||||
{"abstract", false, true, true, false, "", false},
|
||||
{"content", false, true, true, false, "textArea", false},
|
||||
{"address", true, true, true, false, "", false},
|
||||
{"full_name", false, true, true, false, "", false},
|
||||
{"create_time", false, false, true, false, "time", true},
|
||||
{"modify_time", true, false, true, false, "time", true},
|
||||
{"image", false, true, true, false, "image", false},
|
||||
{"img", false, true, true, false, "image", false},
|
||||
{"icon", false, true, true, false, "image", false},
|
||||
{"avatar", false, true, true, false, "image", false},
|
||||
{"file", false, true, true, false, "file", false},
|
||||
{"age", false, true, true, false, "", false},
|
||||
{"email", false, true, true, false, "", false},
|
||||
{"time", true, true, true, false, "time", false},
|
||||
{"level", false, false, true, false, "", false},
|
||||
{"rule", true, true, true, false, "form", false},
|
||||
{"auth", false, true, true, false, "auth", true},
|
||||
{"table", true, false, true, false, "table", false},
|
||||
{"table_id", true, false, true, false, "table_id", false},
|
||||
}
|
||||
+1146
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,236 @@
|
||||
package code
|
||||
|
||||
var InitTpt = `package {{name}}
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var ID = "{{id}}"
|
||||
|
||||
// Project 管理端项目
|
||||
var Project = Proj{
|
||||
//"user": UserCtr,
|
||||
{{tablesCtr}}
|
||||
"hotime":Ctr{
|
||||
"login": func(that *Context) {
|
||||
|
||||
name := that.Req.FormValue("name")
|
||||
password := that.Req.FormValue("password")
|
||||
if name == "" || password == "" {
|
||||
that.Display(3, "参数不足")
|
||||
return
|
||||
}
|
||||
user := that.Db.Get("admin", "*", Map{"AND": Map{"OR":Map{"name": name,"phone":name}, "password": Md5(password)}})
|
||||
if user == nil {
|
||||
that.Display(5, "登录失败")
|
||||
return
|
||||
}
|
||||
that.Session("admin_id", user.GetCeilInt("id"))
|
||||
that.Session("admin_name", name)
|
||||
that.Display(0, that.SessionId)
|
||||
},
|
||||
"logout": func(that *Context) {
|
||||
that.Session("admin_id", nil)
|
||||
that.Session("admin_name", nil)
|
||||
that.Display(0, "退出登录成功")
|
||||
},
|
||||
"info": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
||||
str, inData := that.MakeCodeRouter[hotimeName].Info("admin", data, that.Db)
|
||||
where := Map{"id": that.Session("admin_id").ToCeilInt()}
|
||||
if len(inData) ==1 {
|
||||
inData["id"] =where["id"]
|
||||
where = Map{"AND": inData}
|
||||
}else if len(inData) >1 {
|
||||
where["OR"]=inData
|
||||
where = Map{"AND": where}
|
||||
}
|
||||
re := that.Db.Get("admin", str, where)
|
||||
if re == nil {
|
||||
that.Display(4, "找不到对应信息")
|
||||
return
|
||||
}
|
||||
for k, v := range re {
|
||||
column := that.MakeCodeRouter[hotimeName].TableColumns["admin"][k]
|
||||
if column == nil {
|
||||
continue
|
||||
}
|
||||
if (column["list"] == nil || column.GetBool("list")) && column.GetString("link") != "" {
|
||||
re[column.GetString("link")] = that.Db.Get(column.GetString("link"),"id," +column.GetString("value"), Map{"id": v})
|
||||
}
|
||||
}
|
||||
|
||||
that.Display(0, re)
|
||||
},
|
||||
},
|
||||
}
|
||||
`
|
||||
var CtrTpt = `package {{name}}
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var {{table}}Ctr = Ctr{
|
||||
"info": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
||||
str, inData := that.MakeCodeRouter[hotimeName].Info(that.RouterString[1], data, that.Db)
|
||||
where := Map{"id": that.RouterString[2]}
|
||||
|
||||
if len(inData) ==1 {
|
||||
inData["id"] =where["id"]
|
||||
where = Map{"AND": inData}
|
||||
}else if len(inData) >1 {
|
||||
where["OR"]=inData
|
||||
where = Map{"AND": where}
|
||||
}
|
||||
|
||||
re := that.Db.Get(that.RouterString[1], str, where)
|
||||
|
||||
if re == nil {
|
||||
that.Display(4, "找不到对应信息")
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range re {
|
||||
column := that.MakeCodeRouter[hotimeName].TableColumns[that.RouterString[1]][k]
|
||||
if column == nil {
|
||||
continue
|
||||
}
|
||||
if (column["list"] == nil || column.GetBool("list")) && column.GetString("link") != "" {
|
||||
re[column.GetString("link")] = that.Db.Get(column.GetString("link"), "id,"+column.GetString("value"), Map{"id": v})
|
||||
}
|
||||
}
|
||||
|
||||
that.Display(0, re)
|
||||
},
|
||||
"add": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
inData := that.MakeCodeRouter[hotimeName].Add(that.RouterString[1], that.Req)
|
||||
if inData == nil {
|
||||
that.Display(3, "请求参数不足")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
re := that.Db.Insert(that.RouterString[1], inData)
|
||||
|
||||
if re == 0 {
|
||||
that.Display(4, "无法插入对应数据")
|
||||
return
|
||||
}
|
||||
//索引管理,便于检索以及权限
|
||||
if inData.Get("parent_id") != nil && inData.GetString("index") != "" {
|
||||
index := that.Db.Get(that.RouterString[1], "` + "`index`" + `", Map{"id": inData.Get("parent_id")})
|
||||
inData["index"] = index.GetString("index")+ObjToStr(re)+","
|
||||
that.Db.Update(that.RouterString[1],Map{"index":inData["index"]},Map{"id":re})
|
||||
}else if inData.GetString("index") != ""{
|
||||
inData["index"] = "," + ObjToStr(re) + ","
|
||||
that.Db.Update(that.RouterString[1], Map{"index": inData["index"]}, Map{"id": re})
|
||||
}
|
||||
|
||||
that.Display(0, re)
|
||||
},
|
||||
"update": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
inData := that.MakeCodeRouter[hotimeName].Edit(that.RouterString[1], that.Req)
|
||||
if inData == nil {
|
||||
that.Display(3, "没有找到要更新的数据")
|
||||
return
|
||||
}
|
||||
|
||||
//索引管理,便于检索以及权限
|
||||
if inData.Get("parent_id") != nil && inData.GetString("index") != "" {
|
||||
Index := that.Db.Get(that.RouterString[1], "` + "`index`" + `", Map{"id": that.RouterString[2]})
|
||||
parentIndex := that.Db.Get(that.RouterString[1], "` + "`index`" + `", Map{"id": inData.Get("parent_id")})
|
||||
inData["index"] = parentIndex.GetString("index") + that.RouterString[2] + ","
|
||||
|
||||
childNodes := that.Db.Select(that.RouterString[1], "id,` + "`index`" + `", Map{"index[~]": "," + that.RouterString[2] + ","})
|
||||
|
||||
for _, v := range childNodes {
|
||||
v["index"] = strings.Replace(v.GetString("index"), Index.GetString("index"), inData.GetString("index"), -1)
|
||||
that.Db.Update(that.RouterString[1], Map{"index": v["index"]}, Map{"id": v.GetCeilInt("id")})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
re := that.Db.Update(that.RouterString[1], inData, Map{"id": that.RouterString[2]})
|
||||
|
||||
if re == 0 {
|
||||
that.Display(4, "更新数据失败")
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(0, re)
|
||||
},
|
||||
"remove": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
inData := that.MakeCodeRouter[hotimeName].Delete(that.RouterString[1], that.Req)
|
||||
if inData == nil {
|
||||
that.Display(3, "请求参数不足")
|
||||
return
|
||||
}
|
||||
re :=int64(0)
|
||||
//索引管理,便于检索以及权限
|
||||
if inData.Get("parent_id") != nil && inData.GetSlice("index") != nil {
|
||||
re=that.Db.Delete(that.RouterString[1], Map{"index[~]": "," + that.RouterString[2] + ","})
|
||||
}else {
|
||||
re=that.Db.Delete(that.RouterString[1], Map{"id": that.RouterString[2]})
|
||||
}
|
||||
|
||||
if re == 0 {
|
||||
that.Display(4, "删除数据失败")
|
||||
return
|
||||
}
|
||||
that.Display(0, "删除成功")
|
||||
},
|
||||
|
||||
"search": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
||||
|
||||
columnStr, leftJoin, where := that.MakeCodeRouter[hotimeName].Search(that.RouterString[1], data, that.Req, that.Db)
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
count := that.Db.Count(that.RouterString[1], leftJoin, where)
|
||||
reData := that.Db.Page(page, pageSize).
|
||||
PageSelect(that.RouterString[1], leftJoin, columnStr, where)
|
||||
|
||||
for _, v := range reData {
|
||||
for k, _ := range v {
|
||||
column := that.MakeCodeRouter[hotimeName].TableColumns[that.RouterString[1]][k]
|
||||
if column == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if column["list"] != false && column["name"] == "parent_id" && column.GetString("link") != "" {
|
||||
parentC := that.Db.Get(column.GetString("link"), column.GetString("value"), Map{"id": v.GetCeilInt(k)})
|
||||
v[column.GetString("link")+"_"+column.GetString("name")+"_"+column.GetString("value")] = ""
|
||||
if parentC != nil {
|
||||
v[column.GetString("link")+"_"+column.GetString("name")+"_"+column.GetString("value")] = parentC.GetString(column.GetString("value"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
that.Display(0, Map{"count": count, "data": reData})
|
||||
},
|
||||
}
|
||||
`
|
||||
@@ -0,0 +1,18 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ContextBase struct {
|
||||
tag string
|
||||
}
|
||||
|
||||
//唯一标志
|
||||
func (that *ContextBase) GetTag() string {
|
||||
|
||||
if that.tag == "" {
|
||||
that.tag = ObjToStr(time.Now().Unix()) + ":" + ObjToStr(Random())
|
||||
}
|
||||
return that.tag
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Error 框架层处理错误
|
||||
type Error struct {
|
||||
Logger *logrus.Logger
|
||||
error
|
||||
}
|
||||
|
||||
func (that *Error) GetError() error {
|
||||
|
||||
return that.error
|
||||
|
||||
}
|
||||
|
||||
func (that *Error) SetError(err error) {
|
||||
that.error = err
|
||||
if that.Logger != nil && err != nil {
|
||||
//that.Logger=log.GetLog("",false)
|
||||
that.Logger.Warn(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package hotime
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
// return res
|
||||
//}
|
||||
|
||||
//字符串首字符大写
|
||||
// StrFirstToUpper 字符串首字符大写
|
||||
func StrFirstToUpper(str string) string {
|
||||
if len(str) == 0 {
|
||||
return str
|
||||
@@ -36,7 +36,43 @@ func StrFirstToUpper(str string) string {
|
||||
return strings.ToUpper(first) + other
|
||||
}
|
||||
|
||||
//字符串截取
|
||||
// StrLd 相似度计算 ld compares two strings and returns the levenshtein distance between them.
|
||||
func StrLd(s, t string, ignoreCase bool) int {
|
||||
if ignoreCase {
|
||||
s = strings.ToLower(s)
|
||||
t = strings.ToLower(t)
|
||||
}
|
||||
d := make([][]int, len(s)+1)
|
||||
for i := range d {
|
||||
d[i] = make([]int, len(t)+1)
|
||||
}
|
||||
for i := range d {
|
||||
d[i][0] = i
|
||||
}
|
||||
for j := range d[0] {
|
||||
d[0][j] = j
|
||||
}
|
||||
for j := 1; j <= len(t); j++ {
|
||||
for i := 1; i <= len(s); i++ {
|
||||
if s[i-1] == t[j-1] {
|
||||
d[i][j] = d[i-1][j-1]
|
||||
} else {
|
||||
min := d[i-1][j]
|
||||
if d[i][j-1] < min {
|
||||
min = d[i][j-1]
|
||||
}
|
||||
if d[i-1][j-1] < min {
|
||||
min = d[i-1][j-1]
|
||||
}
|
||||
d[i][j] = min + 1
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return d[len(s)][len(t)]
|
||||
}
|
||||
|
||||
// Substr 字符串截取
|
||||
func Substr(str string, start int, length int) string {
|
||||
rs := []rune(str)
|
||||
rl := len(rs)
|
||||
@@ -67,7 +103,7 @@ func Substr(str string, start int, length int) string {
|
||||
return string(rs[start:end])
|
||||
}
|
||||
|
||||
//获取最后出现字符串的下标
|
||||
// IndexLastStr 获取最后出现字符串的下标
|
||||
//return 找不到返回 -1
|
||||
func IndexLastStr(str, sep string) int {
|
||||
sepSlice := []rune(sep)
|
||||
@@ -98,7 +134,7 @@ func IndexLastStr(str, sep string) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
//md5
|
||||
// Md5 md5
|
||||
func Md5(req string) string {
|
||||
md5Ctx := md5.New()
|
||||
md5Ctx.Write([]byte(req))
|
||||
@@ -106,7 +142,7 @@ func Md5(req string) string {
|
||||
return hex.EncodeToString(cipherStr)
|
||||
}
|
||||
|
||||
//随机数
|
||||
// Rand 随机数
|
||||
func Rand(count int) int {
|
||||
res := Random()
|
||||
for i := 0; i < count; i++ {
|
||||
@@ -131,7 +167,7 @@ func Random() float64 {
|
||||
|
||||
}
|
||||
|
||||
//随机数范围
|
||||
// RandX 随机数范围
|
||||
func RandX(small int, max int) int {
|
||||
res := 0
|
||||
//随机对象
|
||||
@@ -183,7 +219,7 @@ func RandX(small int, max int) int {
|
||||
// GetDb()
|
||||
//}
|
||||
|
||||
//复制返回数组
|
||||
// DeepCopyMap 复制返回数组
|
||||
func DeepCopyMap(value interface{}) interface{} {
|
||||
if valueMap, ok := value.(Map); ok {
|
||||
newMap := make(Map)
|
||||
@@ -242,7 +278,7 @@ func DeepCopyMap(value interface{}) interface{} {
|
||||
// }
|
||||
//}
|
||||
|
||||
//浮点数四舍五入保留小数
|
||||
// Round 浮点数四舍五入保留小数
|
||||
func Round(f float64, n int) float64 {
|
||||
pow10_n := math.Pow10(n)
|
||||
return math.Trunc((f+0.5/pow10_n)*pow10_n) / pow10_n
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
//hotime的常用map
|
||||
type Map map[string]interface{}
|
||||
|
||||
//获取string
|
||||
func (that Map) GetString(key string, err ...*Error) string {
|
||||
|
||||
if len(err) != 0 {
|
||||
err[0].SetError(nil)
|
||||
}
|
||||
return ObjToStr((that)[key])
|
||||
|
||||
}
|
||||
|
||||
func (that *Map) Pointer() *Map {
|
||||
|
||||
return that
|
||||
}
|
||||
|
||||
//增加接口
|
||||
func (that Map) Put(key string, value interface{}) {
|
||||
//if that==nil{
|
||||
// that=Map{}
|
||||
//}
|
||||
that[key] = value
|
||||
}
|
||||
|
||||
//删除接口
|
||||
func (that Map) Delete(key string) {
|
||||
delete(that, key)
|
||||
|
||||
}
|
||||
|
||||
//获取Int
|
||||
func (that Map) GetInt(key string, err ...*Error) int {
|
||||
v := ObjToInt((that)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取Int
|
||||
func (that Map) GetInt64(key string, err ...*Error) int64 {
|
||||
v := ObjToInt64((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取向上取整Int64
|
||||
func (that Map) GetCeilInt64(key string, err ...*Error) int64 {
|
||||
v := ObjToCeilInt64((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取向上取整Int
|
||||
func (that Map) GetCeilInt(key string, err ...*Error) int {
|
||||
v := ObjToCeilInt((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取向上取整float64
|
||||
func (that Map) GetCeilFloat64(key string, err ...*Error) float64 {
|
||||
v := ObjToCeilFloat64((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取Float64
|
||||
func (that Map) GetFloat64(key string, err ...*Error) float64 {
|
||||
|
||||
v := ObjToFloat64((that)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (that Map) GetSlice(key string, err ...*Error) Slice {
|
||||
|
||||
//var v Slice
|
||||
v := ObjToSlice((that)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
func (that Map) GetBool(key string, err ...*Error) bool {
|
||||
|
||||
//var v Slice
|
||||
v := ObjToBool((that)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (that Map) GetTime(key string, err ...*Error) *time.Time {
|
||||
|
||||
v := ObjToTime((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (that Map) RangeSort(callback func(k string, v interface{}) (isEnd bool)) {
|
||||
testQu := []string{}
|
||||
//testQuData:= qu[0].(Map)
|
||||
for key, _ := range that {
|
||||
//fmt.Println(key, ":", value)
|
||||
testQu = append(testQu, key)
|
||||
}
|
||||
sort.Strings(testQu)
|
||||
for _, k := range testQu {
|
||||
re := callback(k, that[k])
|
||||
if re {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (that Map) GetMap(key string, err ...*Error) Map {
|
||||
//var data Slice
|
||||
|
||||
v := ObjToMap((that)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (that Map) Get(key string, err ...*Error) interface{} {
|
||||
|
||||
if v, ok := (that)[key]; ok {
|
||||
return v
|
||||
}
|
||||
e := errors.New("没有存储key及对应的数据")
|
||||
|
||||
if len(err) != 0 {
|
||||
err[0].SetError(e)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//请传递指针过来
|
||||
func (that Map) ToStruct(stct interface{}) {
|
||||
|
||||
data := reflect.ValueOf(stct).Elem()
|
||||
for k, v := range that {
|
||||
ks := StrFirstToUpper(k)
|
||||
dkey := data.FieldByName(ks)
|
||||
if !dkey.IsValid() {
|
||||
continue
|
||||
}
|
||||
switch dkey.Type().String() {
|
||||
case "int":
|
||||
dkey.SetInt(that.GetInt64(k))
|
||||
case "int64":
|
||||
dkey.Set(reflect.ValueOf(that.GetInt64(k)))
|
||||
case "float64":
|
||||
dkey.Set(reflect.ValueOf(that.GetFloat64(k)))
|
||||
case "string":
|
||||
dkey.Set(reflect.ValueOf(that.GetString(k)))
|
||||
case "interface{}":
|
||||
dkey.Set(reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (that Map) ToJsonString() string {
|
||||
return ObjToStr(that)
|
||||
|
||||
}
|
||||
|
||||
func (that Map) JsonToMap(jsonStr string, err ...*Error) {
|
||||
e := json.Unmarshal([]byte(jsonStr), &that)
|
||||
if e != nil && len(err) != 0 {
|
||||
err[0].SetError(e)
|
||||
}
|
||||
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package common
|
||||
|
||||
import "time"
|
||||
|
||||
//对象封装方便取用
|
||||
type Obj struct {
|
||||
Data interface{}
|
||||
Error
|
||||
ContextBase
|
||||
}
|
||||
|
||||
func (that *Obj) Put(data interface{}) {
|
||||
that.Data = data
|
||||
}
|
||||
|
||||
func (that *Obj) ToInt(err ...Error) int {
|
||||
if len(err) != 0 {
|
||||
that.Error = err[0]
|
||||
}
|
||||
return ObjToInt(that.Data, &that.Error)
|
||||
}
|
||||
|
||||
func (that *Obj) ToTime(err ...Error) *time.Time {
|
||||
if len(err) != 0 {
|
||||
that.Error = err[0]
|
||||
}
|
||||
return ObjToTime(that.Data, &that.Error)
|
||||
}
|
||||
|
||||
func (that *Obj) ToInt64(err ...Error) int64 {
|
||||
if len(err) != 0 {
|
||||
that.Error = err[0]
|
||||
}
|
||||
return ObjToInt64(that.Data, &that.Error)
|
||||
}
|
||||
|
||||
func (that *Obj) ToFloat64(err ...Error) float64 {
|
||||
if len(err) != 0 {
|
||||
that.Error = err[0]
|
||||
}
|
||||
return ObjToFloat64(that.Data, &that.Error)
|
||||
}
|
||||
|
||||
// ToCeilFloat64 获取向上取整float64
|
||||
func (that *Obj) ToCeilFloat64(err ...*Error) float64 {
|
||||
if len(err) != 0 {
|
||||
that.Error = *err[0]
|
||||
}
|
||||
v := ObjToCeilFloat64(that.Data, err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (that *Obj) ToStr() string {
|
||||
|
||||
return ObjToStr(that.Data)
|
||||
}
|
||||
|
||||
func (that *Obj) ToMap(err ...Error) Map {
|
||||
if len(err) != 0 {
|
||||
that.Error = err[0]
|
||||
}
|
||||
return ObjToMap(that.Data, &that.Error)
|
||||
}
|
||||
|
||||
func (that *Obj) ToSlice(err ...Error) Slice {
|
||||
if len(err) != 0 {
|
||||
that.Error = err[0]
|
||||
}
|
||||
return ObjToSlice(that.Data, &that.Error)
|
||||
}
|
||||
|
||||
func (that *Obj) ToMapArray(err ...Error) []Map {
|
||||
if len(err) != 0 {
|
||||
that.Error = err[0]
|
||||
}
|
||||
return ObjToMapArray(that.Data, &that.Error)
|
||||
}
|
||||
|
||||
func (that *Obj) ToObj() interface{} {
|
||||
|
||||
return that.Data
|
||||
}
|
||||
|
||||
//获取向上取整Int64
|
||||
func (that *Obj) ToCeilInt64(err ...*Error) int64 {
|
||||
if len(err) != 0 {
|
||||
that.Error = *err[0]
|
||||
}
|
||||
v := ObjToCeilInt64(that.Data, err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取向上取整Int
|
||||
func (that *Obj) ToCeilInt(err ...*Error) int {
|
||||
if len(err) != 0 {
|
||||
that.Error = *err[0]
|
||||
}
|
||||
v := ObjToCeilInt(that.Data, err...)
|
||||
return v
|
||||
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package hotime
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
//仅限于hotime.Slice
|
||||
@@ -96,6 +97,68 @@ func ObjToSlice(obj interface{}, e ...*Error) Slice {
|
||||
return v
|
||||
}
|
||||
|
||||
func ObjToTime(obj interface{}, e ...*Error) *time.Time {
|
||||
|
||||
tInt := ObjToInt64(obj)
|
||||
//字符串类型,只支持标准mysql datetime格式
|
||||
if tInt == 0 {
|
||||
tStr := ObjToStr(obj)
|
||||
|
||||
if len(tStr) > 18 {
|
||||
t, e := time.Parse("2006-01-02 15:04:05", tStr)
|
||||
if e == nil {
|
||||
return &t
|
||||
}
|
||||
} else if len(tStr) > 15 {
|
||||
t, e := time.Parse("2006-01-02 15:04", tStr)
|
||||
if e == nil {
|
||||
return &t
|
||||
}
|
||||
} else if len(tStr) > 12 {
|
||||
t, e := time.Parse("2006-01-02 15", tStr)
|
||||
if e == nil {
|
||||
return &t
|
||||
}
|
||||
} else if len(tStr) > 9 {
|
||||
t, e := time.Parse("2006-01-02", tStr)
|
||||
if e == nil {
|
||||
return &t
|
||||
}
|
||||
} else if len(tStr) > 6 {
|
||||
t, e := time.Parse("2006-01", tStr)
|
||||
if e == nil {
|
||||
return &t
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//纳秒级别
|
||||
if len(ObjToStr(tInt)) > 16 {
|
||||
t := time.Time{}.Add(time.Nanosecond * time.Duration(tInt))
|
||||
return &t
|
||||
//微秒级别
|
||||
} else if len(ObjToStr(tInt)) > 13 {
|
||||
t := time.Time{}.Add(time.Microsecond * time.Duration(tInt))
|
||||
return &t
|
||||
//毫秒级别
|
||||
} else if len(ObjToStr(tInt)) > 10 {
|
||||
t := time.Time{}.Add(time.Millisecond * time.Duration(tInt))
|
||||
return &t
|
||||
//秒级别
|
||||
} else if len(ObjToStr(tInt)) > 9 {
|
||||
t := time.Time{}.Add(time.Second * time.Duration(tInt))
|
||||
return &t
|
||||
} else if len(ObjToStr(tInt)) > 3 {
|
||||
t, e := time.Parse("2006", ObjToStr(tInt))
|
||||
if e == nil {
|
||||
return &t
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ObjToFloat64(obj interface{}, e ...*Error) float64 {
|
||||
var err error
|
||||
v := float64(0)
|
||||
@@ -135,6 +198,15 @@ func ObjToFloat64(obj interface{}, e ...*Error) float64 {
|
||||
err = errors.New("没有合适的转换对象!")
|
||||
}
|
||||
}
|
||||
|
||||
if math.IsNaN(v) {
|
||||
err = errors.New("float64 is NaN")
|
||||
v = 0
|
||||
}
|
||||
if math.IsInf(v, 0) {
|
||||
err = errors.New("float64 is Inf")
|
||||
v = 0
|
||||
}
|
||||
if len(e) != 0 {
|
||||
e[0].SetError(err)
|
||||
}
|
||||
@@ -212,6 +284,31 @@ func ObjToInt(obj interface{}, e ...*Error) int {
|
||||
return int(v)
|
||||
}
|
||||
|
||||
func ObjToBool(obj interface{}, e ...*Error) bool {
|
||||
var err error
|
||||
v := false
|
||||
|
||||
if obj == nil {
|
||||
|
||||
err = errors.New("没有合适的转换对象!")
|
||||
} else {
|
||||
switch obj.(type) {
|
||||
case bool:
|
||||
v = obj.(bool)
|
||||
default:
|
||||
toInt := ObjToInt(obj)
|
||||
if toInt != 0 {
|
||||
v = true
|
||||
}
|
||||
err = errors.New("没有合适的转换对象!")
|
||||
}
|
||||
}
|
||||
if len(e) != 0 {
|
||||
e[0].SetError(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func ObjToStr(obj interface{}) string {
|
||||
// fmt.Println(reflect.ValueOf(obj).Type().String() )
|
||||
str := ""
|
||||
@@ -232,7 +329,8 @@ func ObjToStr(obj interface{}) string {
|
||||
case float64:
|
||||
str = strconv.FormatFloat(obj.(float64), 'f', -1, 64)
|
||||
default:
|
||||
strbte, err := json.Marshal(obj)
|
||||
strbte, err := json.MarshalIndent(obj, "", "\t")
|
||||
|
||||
if err == nil {
|
||||
str = string(strbte)
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Slice []interface{}
|
||||
|
||||
// GetString 获取string
|
||||
func (that Slice) GetString(key int, err ...*Error) string {
|
||||
if len(err) != 0 {
|
||||
err[0].SetError(nil)
|
||||
}
|
||||
return ObjToStr((that)[key])
|
||||
}
|
||||
|
||||
func (that Slice) GetTime(key int, err ...*Error) *time.Time {
|
||||
|
||||
v := ObjToTime((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
// GetInt 获取Int
|
||||
func (that Slice) GetInt(key int, err ...*Error) int {
|
||||
v := ObjToInt((that)[key], err...)
|
||||
return v
|
||||
}
|
||||
|
||||
// GetInt64 获取Int
|
||||
func (that Slice) GetInt64(key int, err ...*Error) int64 {
|
||||
v := ObjToInt64((that)[key], err...)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// GetCeilInt64 获取向上取整Int64
|
||||
func (that Slice) GetCeilInt64(key int, err ...*Error) int64 {
|
||||
v := ObjToCeilInt64((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
// GetCeilInt 获取向上取整Int
|
||||
func (that Slice) GetCeilInt(key int, err ...*Error) int {
|
||||
v := ObjToCeilInt((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
// GetCeilFloat64 获取向上取整float64
|
||||
func (that Slice) GetCeilFloat64(key int, err ...*Error) float64 {
|
||||
v := ObjToCeilFloat64((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
// GetFloat64 获取Float64
|
||||
func (that Slice) GetFloat64(key int, err ...*Error) float64 {
|
||||
v := ObjToFloat64((that)[key], err...)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (that Slice) GetSlice(key int, err ...*Error) Slice {
|
||||
v := ObjToSlice((that)[key], err...)
|
||||
return v
|
||||
}
|
||||
|
||||
func (that Slice) GetBool(key int, err ...*Error) bool {
|
||||
|
||||
//var v Slice
|
||||
v := ObjToBool((that)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (that Slice) GetMap(key int, err ...*Error) Map {
|
||||
//var v Map
|
||||
v := ObjToMap((that)[key], err...)
|
||||
return v
|
||||
}
|
||||
|
||||
func (that Slice) Get(key int, err ...*Error) interface{} {
|
||||
|
||||
if key < len(that) {
|
||||
return that[key]
|
||||
}
|
||||
e := errors.New("没有存储key及对应的数据")
|
||||
if len(err) != 0 {
|
||||
err[0].SetError(e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (that Slice) Put(key int, value interface{}) {
|
||||
that[key] = value
|
||||
}
|
||||
|
||||
func (that Slice) ToJsonString() string {
|
||||
return ObjToStr(that)
|
||||
}
|
||||
@@ -1,10 +1,4 @@
|
||||
package hotime
|
||||
|
||||
const (
|
||||
LOG_FMT = 0
|
||||
LOG_NIL = 1
|
||||
LOG_FILE = 2
|
||||
)
|
||||
|
||||
//session储存头
|
||||
// HEAD_SESSION_ADD session储存头
|
||||
const HEAD_SESSION_ADD = "session#"
|
||||
|
||||
+45
-20
@@ -1,65 +1,90 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
. "code.hoteas.com/golang/hotime/db"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
contextBase
|
||||
*Application
|
||||
Resp http.ResponseWriter
|
||||
Req *http.Request
|
||||
Application *Application
|
||||
Log Map //日志有则创建
|
||||
RouterString []string
|
||||
Config Map
|
||||
Db *HoTimeDB
|
||||
RespData Map
|
||||
CacheIns
|
||||
RespFunc func()
|
||||
//CacheIns
|
||||
SessionIns
|
||||
DataSize int
|
||||
HandlerStr string //复写请求url
|
||||
}
|
||||
|
||||
//唯一标志
|
||||
func (this *Context) Mtd(router [3]string) Map {
|
||||
this.Application.Router[router[0]][router[1]][router[2]](this)
|
||||
d := this.RespData
|
||||
this.RespData = nil
|
||||
// Mtd 唯一标志
|
||||
func (that *Context) Mtd(router [3]string) Map {
|
||||
that.Application.Router[router[0]][router[1]][router[2]](that)
|
||||
d := that.RespData
|
||||
that.RespData = nil
|
||||
return d
|
||||
}
|
||||
|
||||
//打印
|
||||
func (this *Context) Display(statu int, data interface{}) {
|
||||
func (that *Context) Display(statu int, data interface{}) {
|
||||
|
||||
resp := Map{"statu": statu}
|
||||
resp := Map{"status": statu}
|
||||
if statu != 0 {
|
||||
temp := Map{}
|
||||
|
||||
tpe := this.Config.GetMap("error").GetString(ObjToStr(statu))
|
||||
tpe := that.Config.GetMap("error").GetString(ObjToStr(statu))
|
||||
if tpe == "" {
|
||||
this.SetError(errors.New("找不到对应的错误码"))
|
||||
//logFmt(errors.New("找不到对应的错误码"), 2, LOG_WARN)
|
||||
}
|
||||
|
||||
temp["type"] = tpe
|
||||
temp["msg"] = data
|
||||
resp["result"] = temp
|
||||
//兼容android等需要json转对象的服务
|
||||
resp["error"] = temp
|
||||
} else {
|
||||
resp["result"] = data
|
||||
}
|
||||
|
||||
this.RespData = resp
|
||||
that.RespData = resp
|
||||
|
||||
//this.Data=d;
|
||||
//that.Data=d;
|
||||
}
|
||||
|
||||
func (this *Context) View() {
|
||||
if this.RespData == nil {
|
||||
func (that *Context) View() {
|
||||
if that.RespFunc != nil {
|
||||
that.RespFunc()
|
||||
}
|
||||
if that.RespData == nil {
|
||||
return
|
||||
}
|
||||
d, err := json.Marshal(this.RespData)
|
||||
//创建日志
|
||||
if that.Log != nil {
|
||||
that.Log["time"] = time.Now().Format("2006-01-02 15:04")
|
||||
if that.Session("admin_id").Data != nil {
|
||||
that.Log["admin_id"] = that.Session("admin_id").ToCeilInt()
|
||||
}
|
||||
if that.Session("user_id").Data != nil {
|
||||
that.Log["user_id"] = that.Session("user_id").ToCeilInt()
|
||||
}
|
||||
that.Db.Insert("logs", that.Log)
|
||||
}
|
||||
|
||||
d, err := json.Marshal(that.RespData)
|
||||
if err != nil {
|
||||
that.Display(1, err.Error())
|
||||
that.View()
|
||||
return
|
||||
}
|
||||
this.RespData = nil
|
||||
this.Resp.Write(d)
|
||||
that.DataSize = len(d)
|
||||
that.RespData = nil
|
||||
that.Resp.Write(d)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package hotime
|
||||
|
||||
import "time"
|
||||
|
||||
type contextBase struct {
|
||||
Error
|
||||
tag int64
|
||||
}
|
||||
|
||||
//唯一标志
|
||||
func (this *contextBase) GetTag() int64 {
|
||||
|
||||
if this.tag == int64(0) {
|
||||
this.tag = time.Now().UnixNano()
|
||||
}
|
||||
return this.tag
|
||||
}
|
||||
@@ -1,935 +0,0 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type HoTimeDB struct {
|
||||
*sql.DB
|
||||
contextBase
|
||||
CacheIns
|
||||
Type string
|
||||
DBCached bool
|
||||
LastQuery string
|
||||
LastData []interface{}
|
||||
ConnectFunc func(err ...*Error) *sql.DB
|
||||
LastErr Error
|
||||
limit Slice
|
||||
Tx *sql.Tx //事务对象
|
||||
|
||||
}
|
||||
|
||||
//设置数据库配置连接
|
||||
func (this *HoTimeDB) SetConnect(connect func(err ...*Error) *sql.DB, err ...*Error) {
|
||||
this.ConnectFunc = connect
|
||||
this.InitDb()
|
||||
}
|
||||
|
||||
//事务,如果action返回true则执行成功;false则回滚
|
||||
func (this *HoTimeDB) Action(action func(db HoTimeDB) bool) bool {
|
||||
db := HoTimeDB{DB: this.DB, CacheIns: this.CacheIns, DBCached: this.DBCached}
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
this.LastErr.SetError(err)
|
||||
return false
|
||||
}
|
||||
|
||||
db.Tx = tx
|
||||
|
||||
result := action(db)
|
||||
|
||||
if !result {
|
||||
db.Tx.Rollback()
|
||||
return result
|
||||
}
|
||||
db.Tx.Commit()
|
||||
return result
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) InitDb(err ...*Error) Error {
|
||||
if len(err) != 0 {
|
||||
this.LastErr = *(err[0])
|
||||
}
|
||||
this.DB = this.ConnectFunc(&this.LastErr)
|
||||
if this.DB == nil {
|
||||
|
||||
return this.LastErr
|
||||
}
|
||||
e := this.DB.Ping()
|
||||
|
||||
this.LastErr.SetError(e)
|
||||
|
||||
return this.LastErr
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) Page(page, pageRow int) *HoTimeDB {
|
||||
page = (page - 1) * pageRow
|
||||
if page < 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
this.limit = Slice{page, pageRow}
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) PageSelect(table string, qu ...interface{}) []Map {
|
||||
|
||||
if len(qu) == 1 {
|
||||
qu = append(qu, Map{"LIMIT": this.limit})
|
||||
}
|
||||
if len(qu) == 2 {
|
||||
temp := qu[1].(Map)
|
||||
temp["LIMIT"] = this.limit
|
||||
qu[1] = temp
|
||||
}
|
||||
if len(qu) == 3 {
|
||||
temp := qu[2].(Map)
|
||||
temp["LIMIT"] = this.limit
|
||||
qu[2] = temp
|
||||
}
|
||||
//fmt.Println(qu)
|
||||
data := this.Select(table, qu...)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
//数据库数据解析
|
||||
func (this *HoTimeDB) Row(resl *sql.Rows) []Map {
|
||||
dest := make([]Map, 0)
|
||||
strs, _ := resl.Columns()
|
||||
|
||||
for i := 0; resl.Next(); i++ {
|
||||
lis := make(Map, 0)
|
||||
a := make([]interface{}, len(strs))
|
||||
|
||||
b := make([]interface{}, len(a))
|
||||
for j := 0; j < len(a); j++ {
|
||||
b[j] = &a[j]
|
||||
}
|
||||
resl.Scan(b...)
|
||||
for j := 0; j < len(a); j++ {
|
||||
if a[j] != nil && reflect.ValueOf(a[j]).Type().String() == "[]uint8" {
|
||||
lis[strs[j]] = string(a[j].([]byte))
|
||||
} else {
|
||||
lis[strs[j]] = a[j]
|
||||
}
|
||||
|
||||
}
|
||||
//防止int被误读为float64
|
||||
jlis, e := json.Marshal(lis)
|
||||
if e != nil {
|
||||
this.LastErr.SetError(e)
|
||||
} else {
|
||||
lis.JsonToMap(string(jlis), &this.LastErr)
|
||||
}
|
||||
|
||||
dest = append(dest, lis)
|
||||
|
||||
}
|
||||
|
||||
return dest
|
||||
}
|
||||
|
||||
//
|
||||
////code=0,1,2 0 backup all,1 backup data,2 backup ddl
|
||||
//func (this *HoTimeDB) Backup(path string, code int) {
|
||||
// var cmd *exec.Cmd
|
||||
// switch code {
|
||||
// case 0:cmd= exec.Command("mysqldump","-h"+ObjToStr(Config["dbHost"]), "-P"+ObjToStr(Config["dbPort"]),"-u"+ObjToStr(Config["dbUser"]), "-p"+ObjToStr(Config["dbPwd"]),ObjToStr(Config["dbName"]))
|
||||
// case 1:cmd= exec.Command("mysqldump","-h"+ObjToStr(Config["dbHost"]), "-P"+ObjToStr(Config["dbPort"]),"-u"+ObjToStr(Config["dbUser"]), "-p"+ObjToStr(Config["dbPwd"]), ObjToStr(Config["dbName"]))
|
||||
// case 2:cmd= exec.Command("mysqldump","--no-data","-h"+ObjToStr(Config["dbHost"]), "-P"+ObjToStr(Config["dbPort"]),"-u"+ObjToStr(Config["dbUser"]), "-p"+ObjToStr(Config["dbPwd"]),ObjToStr(Config["dbName"]))
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
// stdout, err := cmd.StdoutPipe()
|
||||
// if err != nil {
|
||||
// log.Println(err)
|
||||
// }
|
||||
//
|
||||
// if err := cmd.Start(); err != nil {
|
||||
// log.Println(err)
|
||||
// }
|
||||
//
|
||||
// bytes, err := ioutil.ReadAll(stdout)
|
||||
// if err != nil {
|
||||
// log.Println(err)
|
||||
// }
|
||||
// err = ioutil.WriteFile(path, bytes, 0644)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// return ;
|
||||
// //
|
||||
// //db := ``
|
||||
// //fmt.Println(db)
|
||||
// //
|
||||
// //tables := this.Query("show tables")
|
||||
// //lth := len(tables)
|
||||
// //if lth == 0 {
|
||||
// // return
|
||||
// //}
|
||||
// //for k, _ := range tables[0] {
|
||||
// // db = Substr(k, 10, len(k))
|
||||
// //}
|
||||
// //
|
||||
// //fd, _ := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||
// //fd.Write([]byte("/*datetime " + time.Now().Format("2006-01-02 15:04:05") + " */ \r\n"))
|
||||
// //fd.Close()
|
||||
// //
|
||||
// //for i := 0; i < lth; i++ {
|
||||
// // tt := tables[i]["Tables_in_"+db].(string)
|
||||
// // this.backupSave(path, tt, code)
|
||||
// // debug.FreeOSMemory()
|
||||
// //}
|
||||
//
|
||||
//}
|
||||
|
||||
func (this *HoTimeDB) backupSave(path string, tt string, code int) {
|
||||
fd, _ := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||
defer fd.Close()
|
||||
|
||||
str := "\r\n"
|
||||
if code == 0 || code == 2 {
|
||||
str += this.backupDdl(tt)
|
||||
}
|
||||
|
||||
if code == 0 || code == 1 {
|
||||
str += "insert into `" + tt + "`\r\n\r\n("
|
||||
str += this.backupCol(tt)
|
||||
}
|
||||
|
||||
fd.Write([]byte(str))
|
||||
}
|
||||
func (this *HoTimeDB) backupDdl(tt string) string {
|
||||
|
||||
data := this.Query("show create table " + tt)
|
||||
if len(data) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return ObjToStr(data[0]["Create Table"]) + ";\r\n\r\n"
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) backupCol(tt string) string {
|
||||
str := ""
|
||||
data := this.Select(tt, "*")
|
||||
|
||||
lthData := len(data)
|
||||
|
||||
if lthData == 0 {
|
||||
return str
|
||||
}
|
||||
lthCol := len(data[0])
|
||||
col := make([]string, lthCol)
|
||||
tempLthData := 0
|
||||
for k, _ := range data[0] {
|
||||
|
||||
if tempLthData == lthCol-1 {
|
||||
str += "`" + k + "`)"
|
||||
} else {
|
||||
str += "`" + k + "`,"
|
||||
}
|
||||
col[tempLthData] = k
|
||||
tempLthData++
|
||||
}
|
||||
|
||||
str += " values"
|
||||
|
||||
for j := 0; j < lthData; j++ {
|
||||
|
||||
for m := 0; m < lthCol; m++ {
|
||||
|
||||
if m == 0 {
|
||||
str += "("
|
||||
}
|
||||
|
||||
v := "NULL"
|
||||
if data[j][col[m]] != nil {
|
||||
v = "'" + strings.Replace(ObjToStr(data[j][col[m]]), "'", `\'`, -1) + "'"
|
||||
}
|
||||
|
||||
if m == lthCol-1 {
|
||||
str += v + ")"
|
||||
|
||||
} else {
|
||||
str += v + ","
|
||||
}
|
||||
}
|
||||
if j == lthData-1 {
|
||||
str += ";\r\n\r\n"
|
||||
} else {
|
||||
str += ",\r\n\r\n"
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) md5(query string, args ...interface{}) string {
|
||||
strByte, _ := json.Marshal(args)
|
||||
str := Md5(query + ":" + string(strByte))
|
||||
return str
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) Query(query string, args ...interface{}) []Map {
|
||||
|
||||
//fmt.Println(query)
|
||||
var err error
|
||||
var resl *sql.Rows
|
||||
|
||||
this.LastQuery = query
|
||||
this.LastData = args
|
||||
|
||||
if this.DB == nil {
|
||||
err = errors.New("没有初始化数据库")
|
||||
this.LastErr.SetError(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if this.Tx != nil {
|
||||
resl, err = this.Tx.Query(query, args...)
|
||||
} else {
|
||||
resl, err = this.DB.Query(query, args...)
|
||||
}
|
||||
|
||||
this.LastErr.SetError(err)
|
||||
if err != nil {
|
||||
if err = this.DB.Ping(); err != nil {
|
||||
this.LastErr.SetError(err)
|
||||
this.InitDb()
|
||||
if this.LastErr.GetError() != nil {
|
||||
return nil
|
||||
}
|
||||
return this.Query(query, args...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return this.Row(resl)
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) Exec(query string, args ...interface{}) (sql.Result, Error) {
|
||||
|
||||
this.LastQuery = query
|
||||
this.LastData = args
|
||||
var e error
|
||||
var resl sql.Result
|
||||
|
||||
if this.DB == nil {
|
||||
err := errors.New("没有初始化数据库")
|
||||
this.LastErr.SetError(err)
|
||||
return nil, this.LastErr
|
||||
}
|
||||
|
||||
if this.Tx != nil {
|
||||
resl, e = this.Tx.Exec(query, args...)
|
||||
} else {
|
||||
resl, e = this.DB.Exec(query, args...)
|
||||
}
|
||||
|
||||
this.LastErr.SetError(e)
|
||||
|
||||
//判断是否连接断开了
|
||||
if e != nil {
|
||||
|
||||
if e = this.DB.Ping(); e != nil {
|
||||
this.LastErr.SetError(e)
|
||||
this.InitDb()
|
||||
if this.LastErr.GetError() != nil {
|
||||
return resl, this.LastErr
|
||||
}
|
||||
return this.Exec(query, args...)
|
||||
}
|
||||
}
|
||||
|
||||
return resl, this.LastErr
|
||||
}
|
||||
|
||||
//func (this *HoTimeDB)copy(data []Map)[]Map{
|
||||
// if data==nil{
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// lth:=len(data)
|
||||
//
|
||||
// res:=make([]Map,lth)
|
||||
//
|
||||
//
|
||||
// for i:=0;i<lth;i++{
|
||||
//
|
||||
// res[i]=DeepCopy(data[i]).(Map)
|
||||
// }
|
||||
//
|
||||
// return res
|
||||
//
|
||||
//}
|
||||
func (this *HoTimeDB) Select(table string, qu ...interface{}) []Map {
|
||||
|
||||
query := "SELECT"
|
||||
where := Map{}
|
||||
qs := make([]interface{}, 0)
|
||||
intQs, intWhere := 0, 1
|
||||
join := false
|
||||
if len(qu) == 3 {
|
||||
intQs = 1
|
||||
intWhere = 2
|
||||
join = true
|
||||
|
||||
}
|
||||
|
||||
if len(qu) > 0 {
|
||||
if reflect.ValueOf(qu[intQs]).Type().String() == "string" {
|
||||
query += " " + qu[intQs].(string)
|
||||
} else {
|
||||
for i := 0; i < len(qu[intQs].(Slice)); i++ {
|
||||
k := qu[intQs].(Slice)[i].(string)
|
||||
if strings.Contains(k, " AS ") {
|
||||
|
||||
query += " " + k + " "
|
||||
|
||||
} else {
|
||||
query += " `" + k + "` "
|
||||
}
|
||||
|
||||
if i+1 != len(qu[intQs].(Slice)) {
|
||||
query = query + ", "
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
query += " *"
|
||||
}
|
||||
|
||||
query += " FROM " + table
|
||||
|
||||
if join {
|
||||
for k, v := range qu[0].(Map) {
|
||||
switch Substr(k, 0, 3) {
|
||||
case "[>]":
|
||||
query += " LEFT JOIN " + Substr(k, 3, len(k)-3) + " ON " + v.(string)
|
||||
case "[<]":
|
||||
query += " RIGHT JOIN " + Substr(k, 3, len(k)-3) + " ON " + v.(string)
|
||||
}
|
||||
switch Substr(k, 0, 4) {
|
||||
case "[<>]":
|
||||
query += " FULL JOIN " + Substr(k, 4, len(k)-4) + " ON " + v.(string)
|
||||
case "[><]":
|
||||
query += " INNER JOIN " + Substr(k, 4, len(k)-4) + " ON " + v.(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(qu) > 1 {
|
||||
where = qu[intWhere].(Map)
|
||||
}
|
||||
|
||||
temp, resWhere := this.where(where)
|
||||
|
||||
query += temp
|
||||
qs = append(qs, resWhere...)
|
||||
md5 := this.md5(query, qs...)
|
||||
|
||||
if this.DBCached && this.CacheIns != nil {
|
||||
//如果缓存有则从缓存取
|
||||
cacheData := this.Cache(table + ":" + md5)
|
||||
|
||||
if cacheData.Data != nil {
|
||||
return cacheData.ToMapArray()
|
||||
}
|
||||
}
|
||||
|
||||
//无缓存则数据库取
|
||||
res := this.Query(query, qs...)
|
||||
|
||||
if res == nil {
|
||||
res = []Map{}
|
||||
}
|
||||
|
||||
//缓存
|
||||
if this.DBCached && this.CacheIns != nil {
|
||||
|
||||
this.Cache(table+":"+md5, res)
|
||||
}
|
||||
|
||||
return res
|
||||
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) Get(table string, qu ...interface{}) Map {
|
||||
//fmt.Println(qu)
|
||||
if len(qu) == 1 {
|
||||
qu = append(qu, Map{"LIMIT": 1})
|
||||
}
|
||||
if len(qu) == 2 {
|
||||
temp := qu[1].(Map)
|
||||
temp["LIMIT"] = 1
|
||||
qu[1] = temp
|
||||
}
|
||||
if len(qu) == 3 {
|
||||
temp := qu[2].(Map)
|
||||
temp["LIMIT"] = 1
|
||||
qu[2] = temp
|
||||
}
|
||||
//fmt.Println(qu)
|
||||
data := this.Select(table, qu...)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return data[0]
|
||||
}
|
||||
|
||||
/**
|
||||
** 计数
|
||||
*/
|
||||
func (this *HoTimeDB) Count(table string, qu ...interface{}) int {
|
||||
req := []interface{}{}
|
||||
|
||||
if len(qu) == 2 {
|
||||
req = append(req, qu[0])
|
||||
req = append(req, "COUNT(*)")
|
||||
req = append(req, qu[1])
|
||||
} else {
|
||||
req = append(req, "COUNT(*)")
|
||||
req = append(req, qu...)
|
||||
}
|
||||
|
||||
//req=append(req,qu...)
|
||||
data := this.Select(table, req...)
|
||||
//fmt.Println(data)
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
//res,_:=StrToInt(data[0]["COUNT(*)"].(string))
|
||||
res := ObjToStr(data[0]["COUNT(*)"])
|
||||
count, _ := StrToInt(res)
|
||||
return count
|
||||
|
||||
}
|
||||
|
||||
var condition = []string{"AND", "OR"}
|
||||
var vcond = []string{"GROUP", "ORDER", "LIMIT"}
|
||||
|
||||
//where语句解析
|
||||
func (this *HoTimeDB) where(data Map) (string, []interface{}) {
|
||||
|
||||
where := ""
|
||||
|
||||
res := make([]interface{}, 0)
|
||||
//AND OR判断
|
||||
for k, v := range data {
|
||||
x := 0
|
||||
for i := 0; i < len(condition); i++ {
|
||||
|
||||
if condition[i] == k {
|
||||
|
||||
tw, ts := this.cond(k, v.(Map))
|
||||
|
||||
where += tw
|
||||
res = append(res, ts...)
|
||||
break
|
||||
}
|
||||
x++
|
||||
}
|
||||
|
||||
y := 0
|
||||
for j := 0; j < len(vcond); j++ {
|
||||
|
||||
if vcond[j] == k {
|
||||
break
|
||||
}
|
||||
y++
|
||||
}
|
||||
if x == len(condition) && y == len(vcond) {
|
||||
tv, vv := this.varCond(k, v)
|
||||
|
||||
where += tv
|
||||
res = append(res, vv...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(where) != 0 {
|
||||
where = " WHERE " + where
|
||||
}
|
||||
|
||||
//特殊字符
|
||||
for j := 0; j < len(vcond); j++ {
|
||||
for k, v := range data {
|
||||
if vcond[j] == k {
|
||||
if k == "ORDER" {
|
||||
where += " " + k + " BY "
|
||||
//fmt.Println(reflect.ValueOf(v).Type())
|
||||
|
||||
//break
|
||||
} else if k == "GROUP" {
|
||||
|
||||
where += " " + k + " BY "
|
||||
} else {
|
||||
|
||||
where += " " + k
|
||||
}
|
||||
|
||||
if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
|
||||
for i := 0; i < len(v.(Slice)); i++ {
|
||||
where += " " + ObjToStr(v.(Slice)[i])
|
||||
|
||||
if len(v.(Slice)) != i+1 {
|
||||
where += ","
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
//fmt.Println(v)
|
||||
where += " " + ObjToStr(v)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return where, res
|
||||
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) {
|
||||
where := ""
|
||||
res := make([]interface{}, 0)
|
||||
length := len(k)
|
||||
if length > 4 {
|
||||
def := false
|
||||
|
||||
switch Substr(k, length-3, 3) {
|
||||
case "[>]":
|
||||
k = strings.Replace(k, "[>]", "", -1)
|
||||
where += "`" + k + "`>? "
|
||||
res = append(res, v)
|
||||
case "[<]":
|
||||
k = strings.Replace(k, "[<]", "", -1)
|
||||
where += "`" + k + "`<? "
|
||||
res = append(res, v)
|
||||
case "[!]":
|
||||
k = strings.Replace(k, "[!]", "", -1)
|
||||
where, res = this.notIn(k, v, where, res)
|
||||
case "[#]":
|
||||
k = strings.Replace(k, "[#]", "", -1)
|
||||
where += " " + k + "=" + ObjToStr(v)
|
||||
case "[#!]":
|
||||
k = strings.Replace(k, "[#!]", "", -1)
|
||||
where += " " + k + "!=" + ObjToStr(v)
|
||||
case "[!#]":
|
||||
k = strings.Replace(k, "[!#]", "", -1)
|
||||
where += " " + k + "!=" + ObjToStr(v)
|
||||
case "[~]":
|
||||
k = strings.Replace(k, "[~]", "", -1)
|
||||
where += "`" + k + "` LIKE ? "
|
||||
v = "%" + ObjToStr(v) + "%"
|
||||
res = append(res, v)
|
||||
case "[!~]": //左边任意
|
||||
k = strings.Replace(k, "[~]", "", -1)
|
||||
where += "`" + k + "` LIKE ? "
|
||||
v = "%" + ObjToStr(v)
|
||||
res = append(res, v)
|
||||
case "[~!]": //右边任意
|
||||
k = strings.Replace(k, "[~]", "", -1)
|
||||
where += "`" + k + "` LIKE ? "
|
||||
v = ObjToStr(v) + "%"
|
||||
res = append(res, v)
|
||||
case "[~~]": //手动任意
|
||||
k = strings.Replace(k, "[~]", "", -1)
|
||||
where += "`" + k + "` LIKE ? "
|
||||
//v = ObjToStr(v)
|
||||
res = append(res, v)
|
||||
default:
|
||||
def = true
|
||||
|
||||
}
|
||||
if def {
|
||||
switch Substr(k, length-4, 4) {
|
||||
case "[>=]":
|
||||
k = strings.Replace(k, "[>=]", "", -1)
|
||||
where += "`" + k + "`>=? "
|
||||
res = append(res, v)
|
||||
case "[<=]":
|
||||
k = strings.Replace(k, "[<=]", "", -1)
|
||||
where += "`" + k + "`<=? "
|
||||
res = append(res, v)
|
||||
case "[><]":
|
||||
k = strings.Replace(k, "[><]", "", -1)
|
||||
where += "`" + k + "` NOT BETWEEN ? AND ? "
|
||||
res = append(res, v.(Slice)[0])
|
||||
res = append(res, v.(Slice)[1])
|
||||
case "[<>]":
|
||||
k = strings.Replace(k, "[<>]", "", -1)
|
||||
where += "`" + k + "` BETWEEN ? AND ? "
|
||||
res = append(res, v.(Slice)[0])
|
||||
res = append(res, v.(Slice)[1])
|
||||
default:
|
||||
if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
|
||||
where += "`" + k + "` IN ("
|
||||
res = append(res, (v.(Slice))...)
|
||||
if len(v.(Slice)) == 0 {
|
||||
where += ") "
|
||||
} else {
|
||||
for i := 0; i < len(v.(Slice)); i++ {
|
||||
if i+1 != len(v.(Slice)) {
|
||||
where += "?,"
|
||||
} else {
|
||||
where += "?) "
|
||||
}
|
||||
//res=append(res,(v.(Slice))[i])
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
where += "`" + k + "`=? "
|
||||
res = append(res, v)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} else if k == "[#]" {
|
||||
k = strings.Replace(k, "[#]", "", -1)
|
||||
where += " " + ObjToStr(v) + " "
|
||||
} else {
|
||||
//fmt.Println(reflect.ValueOf(v).Type().String())
|
||||
if v == nil {
|
||||
where += "`" + k + "` IS NULL"
|
||||
} else if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
|
||||
|
||||
//fmt.Println(v)
|
||||
where += "`" + k + "` IN ("
|
||||
res = append(res, (v.(Slice))...)
|
||||
for i := 0; i < len(v.(Slice)); i++ {
|
||||
if i+1 != len(v.(Slice)) {
|
||||
where += "?,"
|
||||
} else {
|
||||
where += "?) "
|
||||
}
|
||||
//res=append(res,(v.(Slice))[i])
|
||||
}
|
||||
} else if reflect.ValueOf(v).Type().String() == "[]interface {}" {
|
||||
where += "`" + k + "` IN ("
|
||||
res = append(res, (v.([]interface{}))...)
|
||||
for i := 0; i < len(v.([]interface{})); i++ {
|
||||
if i+1 != len(v.([]interface{})) {
|
||||
where += "?,"
|
||||
} else {
|
||||
where += "?) "
|
||||
}
|
||||
//res=append(res,(v.(Slice))[i])
|
||||
}
|
||||
} else {
|
||||
|
||||
where += "`" + k + "`=? "
|
||||
res = append(res, v)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// this.Db.Update("user",hotime.Map{"ustate":"1"},hotime.Map{"AND":hotime.Map{"OR":hotime.Map{"uid":4,"uname":"dasda"}},"ustate":1})
|
||||
func (this *HoTimeDB) notIn(k string, v interface{}, where string, res []interface{}) (string, []interface{}) {
|
||||
//where:=""
|
||||
//fmt.Println(reflect.ValueOf(v).Type().String())
|
||||
if v == nil {
|
||||
|
||||
where += "`" + k + "` IS NOT NULL "
|
||||
|
||||
} else if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
|
||||
where += "`" + k + "` NOT IN ("
|
||||
res = append(res, (v.(Slice))...)
|
||||
for i := 0; i < len(v.(Slice)); i++ {
|
||||
if i+1 != len(v.(Slice)) {
|
||||
where += "?,"
|
||||
} else {
|
||||
where += "?) "
|
||||
}
|
||||
//res=append(res,(v.(Slice))[i])
|
||||
}
|
||||
} else if reflect.ValueOf(v).Type().String() == "[]interface {}" {
|
||||
where += "`" + k + "` NOT IN ("
|
||||
res = append(res, (v.([]interface{}))...)
|
||||
for i := 0; i < len(v.([]interface{})); i++ {
|
||||
if i+1 != len(v.([]interface{})) {
|
||||
where += "?,"
|
||||
} else {
|
||||
where += "?) "
|
||||
}
|
||||
//res=append(res,(v.(Slice))[i])
|
||||
}
|
||||
} else {
|
||||
|
||||
where += "`" + k + "` !=? "
|
||||
res = append(res, v)
|
||||
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) cond(tag string, data Map) (string, []interface{}) {
|
||||
where := " "
|
||||
res := make([]interface{}, 0)
|
||||
lens := len(data)
|
||||
//fmt.Println(lens)
|
||||
for k, v := range data {
|
||||
x := 0
|
||||
for i := 0; i < len(condition); i++ {
|
||||
|
||||
if condition[i] == k {
|
||||
|
||||
tw, ts := this.cond(k, v.(Map))
|
||||
if lens--; lens <= 0 {
|
||||
//fmt.Println(lens)
|
||||
where += "(" + tw + ") "
|
||||
} else {
|
||||
where += "(" + tw + ") " + tag + " "
|
||||
}
|
||||
|
||||
res = append(res, ts...)
|
||||
break
|
||||
}
|
||||
x++
|
||||
}
|
||||
|
||||
if x == len(condition) {
|
||||
|
||||
tv, vv := this.varCond(k, v)
|
||||
|
||||
res = append(res, vv...)
|
||||
if lens--; lens <= 0 {
|
||||
where += tv + ""
|
||||
} else {
|
||||
where += tv + " " + tag + " "
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
//更新数据
|
||||
func (this *HoTimeDB) Update(table string, data Map, where Map) int64 {
|
||||
|
||||
query := "UPDATE " + table + " SET "
|
||||
//UPDATE Person SET Address = 'Zhongshan 23', City = 'Nanjing' WHERE LastName = 'Wilson'
|
||||
qs := make([]interface{}, 0)
|
||||
tp := len(data)
|
||||
|
||||
for k, v := range data {
|
||||
vstr := "?"
|
||||
if Substr(k, len(k)-3, 3) == "[#]" {
|
||||
k = strings.Replace(k, "[#]", "", -1)
|
||||
|
||||
vstr = ObjToStr(v)
|
||||
} else {
|
||||
qs = append(qs, v)
|
||||
}
|
||||
query += "`" + k + "`=" + vstr + ""
|
||||
if tp--; tp != 0 {
|
||||
query += ", "
|
||||
}
|
||||
}
|
||||
|
||||
temp, resWhere := this.where(where)
|
||||
//fmt.Println(resWhere)
|
||||
|
||||
query += temp
|
||||
qs = append(qs, resWhere...)
|
||||
|
||||
res, err := this.Exec(query, qs...)
|
||||
|
||||
rows := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
rows, _ = res.RowsAffected()
|
||||
}
|
||||
|
||||
//如果更新成功,则删除缓存
|
||||
if rows != 0 {
|
||||
if this.DBCached && this.CacheIns != nil {
|
||||
this.Cache(table+"*", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) Delete(table string, data map[string]interface{}) int64 {
|
||||
|
||||
query := "DELETE FROM " + table + " "
|
||||
|
||||
temp, resWhere := this.where(data)
|
||||
query += temp
|
||||
|
||||
res, err := this.Exec(query, resWhere...)
|
||||
rows := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
rows, _ = res.RowsAffected()
|
||||
}
|
||||
|
||||
//如果删除成功,删除对应缓存
|
||||
if rows != 0 {
|
||||
if this.DBCached && this.CacheIns != nil {
|
||||
this.Cache(table+"*", nil)
|
||||
}
|
||||
}
|
||||
//return 0
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
//插入新数据
|
||||
func (this *HoTimeDB) Insert(table string, data map[string]interface{}) int64 {
|
||||
|
||||
values := make([]interface{}, 0)
|
||||
queryString := " ("
|
||||
valueString := " ("
|
||||
|
||||
lens := len(data)
|
||||
tempLen := 0
|
||||
for k, v := range data {
|
||||
tempLen++
|
||||
values = append(values, v)
|
||||
if tempLen < lens {
|
||||
queryString += "`" + k + "`,"
|
||||
valueString += "?,"
|
||||
} else {
|
||||
queryString += "`" + k + "`) "
|
||||
valueString += "?);"
|
||||
}
|
||||
|
||||
}
|
||||
query := "INSERT INTO " + table + queryString + "VALUES" + valueString
|
||||
|
||||
res, err := this.Exec(query, values...)
|
||||
|
||||
id := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
id, this.LastErr.err = res.LastInsertId()
|
||||
|
||||
}
|
||||
|
||||
//如果插入成功,删除缓存
|
||||
if id != 0 {
|
||||
if this.DBCached && this.CacheIns != nil {
|
||||
this.Cache(table+"*", nil)
|
||||
}
|
||||
}
|
||||
|
||||
//fmt.Println(id)
|
||||
return id
|
||||
}
|
||||
+1369
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,142 @@
|
||||
package aliyun
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
//"fmt"
|
||||
)
|
||||
|
||||
type company struct {
|
||||
ApiCode string
|
||||
Url string
|
||||
}
|
||||
|
||||
var Company = company{}
|
||||
|
||||
func (that *company) Init(apiCode string) {
|
||||
//"06c6a07e89dd45c88de040ee1489eef7"
|
||||
that.ApiCode = apiCode
|
||||
that.Url = "http://api.81api.com"
|
||||
}
|
||||
|
||||
// GetCompanyOtherAll 获取企业基础信息
|
||||
func (that *company) GetCompanyOtherAll(name string) Map {
|
||||
|
||||
res := Map{}
|
||||
data, e := that.GetCompanyPatentsInfo(name) //获取专利信息
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
} else {
|
||||
res["PatentsInfo"] = data.GetMap("data")
|
||||
}
|
||||
data, e = that.GetCompanyOtherCopyrightsInfo(name) //获取其他专利
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
} else {
|
||||
res["OtherCopyrightsInfo"] = data.GetMap("data")
|
||||
}
|
||||
data, e = that.GetCompanyTrademarksInfo(name) //获取商标
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
} else {
|
||||
res["TrademarksInfo"] = data.GetMap("data")
|
||||
}
|
||||
data, e = that.GetCompanySoftwareCopyrightsInfo(name) //获取软著
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
} else {
|
||||
res["SoftwareCopyrightsInfo"] = data.GetMap("data")
|
||||
}
|
||||
data, e = that.GetCompanyProfileTags(name) //获取大数据标签
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
} else {
|
||||
res["ProfileTags"] = data.GetSlice("data")
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// GetCompanyBaseInfo 获取企业基础信息
|
||||
func (that *company) GetCompanyList(name string) (Map, error) {
|
||||
url := "/fuzzyQueryCompanyInfo/"
|
||||
|
||||
body, err := that.basePost(url, name)
|
||||
return ObjToMap(body), err
|
||||
}
|
||||
|
||||
// GetCompanyBaseInfo 获取企业基础信息
|
||||
func (that *company) GetCompanyBaseInfo(name string) (Map, error) {
|
||||
url := "/getCompanyBaseInfo/"
|
||||
|
||||
body, err := that.basePost(url, name)
|
||||
return ObjToMap(body), err
|
||||
}
|
||||
|
||||
// GetCompanyPatentsInfo 获取专利信息
|
||||
func (that *company) GetCompanyPatentsInfo(name string) (Map, error) {
|
||||
url := "/getCompanyPatentsInfo/"
|
||||
|
||||
body, err := that.basePost(url, name)
|
||||
return ObjToMap(body), err
|
||||
}
|
||||
|
||||
// GetCompanyTrademarksInfo 获取商标信息
|
||||
func (that *company) GetCompanyTrademarksInfo(name string) (Map, error) {
|
||||
url := "/getCompanyTrademarksInfo/"
|
||||
|
||||
body, err := that.basePost(url, name)
|
||||
return ObjToMap(body), err
|
||||
}
|
||||
|
||||
// GetCompanySoftwareCopyrightsInfo 获取软著信息
|
||||
func (that *company) GetCompanySoftwareCopyrightsInfo(name string) (Map, error) {
|
||||
url := "/getCompanySoftwareCopyrightsInfo/"
|
||||
|
||||
body, err := that.basePost(url, name)
|
||||
return ObjToMap(body), err
|
||||
}
|
||||
|
||||
// GetCompanyOtherCopyrightsInfo 获取其他著作信息
|
||||
func (that *company) GetCompanyOtherCopyrightsInfo(name string) (Map, error) {
|
||||
url := "/getCompanyOtherCopyrightsInfo/"
|
||||
|
||||
body, err := that.basePost(url, name)
|
||||
return ObjToMap(body), err
|
||||
}
|
||||
|
||||
// GetCompanyProfileTags 获取大数据标签
|
||||
func (that *company) GetCompanyProfileTags(name string) (Map, error) {
|
||||
url := "/getCompanyProfileTags/"
|
||||
body, err := that.basePost(url, name)
|
||||
return ObjToMap(body), err
|
||||
}
|
||||
func (that *company) basePost(url string, name string) (string, error) {
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
reqest, err := http.NewRequest("GET", that.Url+url+name+"/?isRaiseErrorCode=1", nil)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
reqest.Header.Add("Authorization", "APPCODE "+that.ApiCode)
|
||||
response, err := client.Do(reqest)
|
||||
defer response.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res := string(body)
|
||||
fmt.Println(res)
|
||||
return res, err
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package baidu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type baiduMap struct {
|
||||
Ak string
|
||||
Url string
|
||||
}
|
||||
|
||||
var BaiDuMap = baiduMap{}
|
||||
|
||||
func (that *baiduMap) Init(Ak string) {
|
||||
//"ak=ZeT902EZvVgIoGVWEFK3osUm"
|
||||
that.Ak = Ak
|
||||
that.Url = "https://api.map.baidu.com/place/v2/suggestion?output=json" + "&ak=" + Ak
|
||||
//query
|
||||
}
|
||||
|
||||
// GetPosition 获取定位列表
|
||||
func (that *baiduMap) GetPosition(name string, region string) (string, error) {
|
||||
|
||||
client := &http.Client{}
|
||||
if region == "" {
|
||||
region = "全国"
|
||||
}
|
||||
reqest, err := http.NewRequest("GET", that.Url+"&query="+url.PathEscape(name)+"®ion="+url.PathEscape(region), nil)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
return "", err
|
||||
}
|
||||
response, err := client.Do(reqest)
|
||||
defer response.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
//fmt.Println(string(body))
|
||||
|
||||
return string(body), err
|
||||
|
||||
}
|
||||
+18
-16
@@ -10,31 +10,33 @@ import (
|
||||
//"fmt"
|
||||
)
|
||||
|
||||
type DDY struct {
|
||||
type dingdongyun struct {
|
||||
ApiKey string
|
||||
YzmUrl string
|
||||
TzUrl string
|
||||
}
|
||||
|
||||
func (this *DDY) Init(apikey string) {
|
||||
this.ApiKey = apikey
|
||||
this.YzmUrl = "https://api.dingdongcloud.com/v2/sms/single_send"
|
||||
this.TzUrl = "https://api.dingdongcloud.com/v1/sms/notice/multi_send"
|
||||
var DDY = dingdongyun{}
|
||||
|
||||
func (that *dingdongyun) Init(apikey string) {
|
||||
that.ApiKey = apikey
|
||||
that.YzmUrl = "https://api.dingdongcloud.com/v2/sms/captcha/send.json"
|
||||
that.TzUrl = "https://api.dingdongcloud.com/v2/sms/notice/send.json"
|
||||
}
|
||||
|
||||
//发送短信验证码 code验证码如:123456 返回true表示发送成功flase表示发送失败
|
||||
func (this *DDY) SendYZM(umoblie string, tpt string, data map[string]string) (bool, error) {
|
||||
// SendYZM 发送短信验证码 code验证码如:123456 返回true表示发送成功flase表示发送失败
|
||||
func (that *dingdongyun) SendYZM(umoblie string, tpt string, data map[string]string) (bool, error) {
|
||||
for k, v := range data {
|
||||
tpt = strings.Replace(tpt, "{"+k+"}", v, -1)
|
||||
}
|
||||
|
||||
return this.send(this.YzmUrl, umoblie, tpt)
|
||||
return that.send(that.YzmUrl, umoblie, tpt)
|
||||
}
|
||||
|
||||
//发送通知
|
||||
func (this *DDY) SendTz(umoblie []string, tpt string, data map[string]string) (bool, error) {
|
||||
// SendTz 发送通知
|
||||
func (that *dingdongyun) SendTz(umoblie []string, tpt string, data map[string]string) (bool, error) {
|
||||
for k, v := range data {
|
||||
tpt = strings.Replace(tpt, "["+k+"]", v, -1)
|
||||
tpt = strings.Replace(tpt, "{"+k+"}", v, -1)
|
||||
}
|
||||
umobleStr := ""
|
||||
for i, v := range umoblie {
|
||||
@@ -44,14 +46,14 @@ func (this *DDY) SendTz(umoblie []string, tpt string, data map[string]string) (b
|
||||
}
|
||||
umobleStr += "," + v
|
||||
}
|
||||
return this.send(this.TzUrl, umobleStr, tpt)
|
||||
return that.send(that.TzUrl, umobleStr, tpt)
|
||||
}
|
||||
|
||||
//发送短信
|
||||
func (this *DDY) send(mUrl string, umoblie string, content string) (bool, error) {
|
||||
func (that *dingdongyun) send(mUrl string, umoblie string, content string) (bool, error) {
|
||||
|
||||
data_send_sms_yzm := url.Values{"apikey": {this.ApiKey}, "mobile": {umoblie}, "content": {content}}
|
||||
res, err := this.httpsPostForm(mUrl, data_send_sms_yzm)
|
||||
data_send_sms_yzm := url.Values{"apikey": {that.ApiKey}, "mobile": {umoblie}, "content": {content}}
|
||||
res, err := that.httpsPostForm(mUrl, data_send_sms_yzm)
|
||||
if err != nil && res == "" {
|
||||
return false, errors.New("连接错误")
|
||||
}
|
||||
@@ -73,7 +75,7 @@ func (this *DDY) send(mUrl string, umoblie string, content string) (bool, error)
|
||||
}
|
||||
|
||||
//调用url发送短信的连接
|
||||
func (this *DDY) httpsPostForm(url string, data url.Values) (string, error) {
|
||||
func (that *dingdongyun) httpsPostForm(url string, data url.Values) (string, error) {
|
||||
resp, err := http.PostForm(url, data)
|
||||
|
||||
if err != nil {
|
||||
|
||||
+11
-10
@@ -2,37 +2,38 @@ package download
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go.hoteas.com/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
//下载文件
|
||||
func Down(url, path, name string) bool {
|
||||
// Down 下载文件
|
||||
func Down(url, path, name string, e ...*Error) bool {
|
||||
|
||||
os.MkdirAll(path, os.ModeDir)
|
||||
if hotime.Substr(path, len(path)-1, len(path)) != "/" {
|
||||
if Substr(path, len(path)-1, len(path)) != "/" {
|
||||
path = path + "/"
|
||||
}
|
||||
out, err := os.Create(path + name)
|
||||
e := hotime.Error{}
|
||||
if err != nil {
|
||||
e.SetError(err)
|
||||
|
||||
if err != nil && e[0] != nil {
|
||||
e[0].SetError(err)
|
||||
return false
|
||||
}
|
||||
defer out.Close()
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
if err != nil && e[0] != nil {
|
||||
e[0].SetError(err)
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
pix, err := ioutil.ReadAll(resp.Body)
|
||||
_, err = io.Copy(out, bytes.NewReader(pix))
|
||||
if err != nil {
|
||||
e.SetError(err)
|
||||
if err != nil && e[0] != nil {
|
||||
e[0].SetError(err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package rsa
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func FileGet(path string) []byte {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
//读取文件的内容
|
||||
info, _ := file.Stat()
|
||||
buf := make([]byte, info.Size())
|
||||
file.Read(buf)
|
||||
return buf
|
||||
}
|
||||
|
||||
// RSA_Encrypt RSA加密
|
||||
// plainText 要加密的数据
|
||||
// path 公钥匙文件地址
|
||||
func RSA_Encrypt(plainText []byte, buf []byte) []byte {
|
||||
//pem解码
|
||||
block, _ := pem.Decode(buf)
|
||||
//x509解码
|
||||
|
||||
publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//类型断言
|
||||
publicKey := publicKeyInterface.(*rsa.PublicKey)
|
||||
//对明文进行加密
|
||||
cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//返回密文
|
||||
return cipherText
|
||||
}
|
||||
|
||||
// RSA_Decrypt RSA解密
|
||||
// cipherText 需要解密的byte数据
|
||||
// path 私钥文件路径
|
||||
func RSA_Decrypt(cipherText []byte, buf []byte) []byte {
|
||||
|
||||
//pem解码
|
||||
block, _ := pem.Decode(buf)
|
||||
//X509解码
|
||||
private, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//对密文进行解密
|
||||
//plainText,_:=rsa.DecryptPKCS1v15(rand.Reader,privateKey,cipherText)
|
||||
|
||||
v, err := rsa.DecryptPKCS1v15(rand.Reader, private.(*rsa.PrivateKey), cipherText)
|
||||
//返回明文
|
||||
return v
|
||||
}
|
||||
func MarshalPKCS8PrivateKey(key *rsa.PrivateKey) []byte {
|
||||
info := struct {
|
||||
Version int
|
||||
PrivateKeyAlgorithm []asn1.ObjectIdentifier
|
||||
PrivateKey []byte
|
||||
}{}
|
||||
info.Version = 0
|
||||
info.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 1)
|
||||
info.PrivateKeyAlgorithm[0] = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
|
||||
info.PrivateKey = x509.MarshalPKCS1PrivateKey(key)
|
||||
|
||||
k, err := asn1.Marshal(info)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func Demo() {
|
||||
//生成密钥对,保存到文件
|
||||
GenerateRSAKey(2048, "./")
|
||||
//加密
|
||||
data := []byte("hello world")
|
||||
encrypt := RSA_Encrypt(data, FileGet("public.pem"))
|
||||
fmt.Println(string(encrypt))
|
||||
|
||||
// 解密
|
||||
decrypt := RSA_Decrypt(encrypt, FileGet("private.pem"))
|
||||
fmt.Println(string(decrypt))
|
||||
}
|
||||
|
||||
// GenerateRSAKey 生成RSA私钥和公钥,保存到文件中
|
||||
// bits 证书大小
|
||||
func GenerateRSAKey(bits int, path string) {
|
||||
//GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥
|
||||
//Reader是一个全局、共享的密码用强随机数生成器
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//保存私钥
|
||||
//通过x509标准将得到的ras私钥序列化为ASN.1 的 DER编码字符串
|
||||
X509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey)
|
||||
//使用pem格式对x509输出的内容进行编码
|
||||
//创建文件保存私钥
|
||||
privateFile, err := os.Create(path + "private.pem")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer privateFile.Close()
|
||||
//构建一个pem.Block结构体对象
|
||||
privateBlock := pem.Block{Type: "RSA Private Key", Bytes: X509PrivateKey}
|
||||
//将数据保存到文件
|
||||
pem.Encode(privateFile, &privateBlock)
|
||||
|
||||
//保存公钥
|
||||
//获取公钥的数据
|
||||
publicKey := privateKey.PublicKey
|
||||
//X509对公钥编码
|
||||
X509PublicKey, err := x509.MarshalPKIXPublicKey(&publicKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//pem格式编码
|
||||
//创建用于保存公钥的文件
|
||||
publicFile, err := os.Create(path + "public.pem")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer publicFile.Close()
|
||||
//创建一个pem.Block结构体对象
|
||||
publicBlock := pem.Block{Type: "RSA Public Key", Bytes: X509PublicKey}
|
||||
//保存到文件
|
||||
pem.Encode(publicFile, &publicBlock)
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package tencent
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
gourl "net/url"
|
||||
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type company struct {
|
||||
secretId string
|
||||
secretKey string
|
||||
}
|
||||
|
||||
var Company = company{}
|
||||
|
||||
func (that *company) Init(secretId, secretKey string) {
|
||||
// 云市场分配的密钥Id
|
||||
//secretId := "xxxx"
|
||||
//// 云市场分配的密钥Key
|
||||
//secretKey := "xxxx"
|
||||
that.secretId = secretId
|
||||
that.secretKey = secretKey
|
||||
}
|
||||
|
||||
func (that *company) calcAuthorization(source string) (auth string, datetime string, err error) {
|
||||
|
||||
timeLocation, _ := time.LoadLocation("Etc/GMT")
|
||||
datetime = time.Now().In(timeLocation).Format("Mon, 02 Jan 2006 15:04:05 GMT")
|
||||
signStr := fmt.Sprintf("x-date: %s\nx-source: %s", datetime, source)
|
||||
|
||||
// hmac-sha1
|
||||
mac := hmac.New(sha1.New, []byte(that.secretKey))
|
||||
mac.Write([]byte(signStr))
|
||||
sign := base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||
|
||||
auth = fmt.Sprintf("hmac id=\"%s\", algorithm=\"hmac-sha1\", headers=\"x-date x-source\", signature=\"%s\"",
|
||||
that.secretId, sign)
|
||||
|
||||
return auth, datetime, nil
|
||||
}
|
||||
|
||||
func (that *company) urlencode(params map[string]string) string {
|
||||
var p = gourl.Values{}
|
||||
for k, v := range params {
|
||||
p.Add(k, v)
|
||||
}
|
||||
return p.Encode()
|
||||
}
|
||||
|
||||
func (that *company) GetCompany(name string) Map {
|
||||
// 云市场分配的密钥Id
|
||||
//secretId := "xxxx"
|
||||
//// 云市场分配的密钥Key
|
||||
//secretKey := "xxxx"
|
||||
source := "market"
|
||||
|
||||
// 签名
|
||||
auth, datetime, _ := that.calcAuthorization(source)
|
||||
|
||||
// 请求方法
|
||||
method := "GET"
|
||||
// 请求头
|
||||
headers := map[string]string{"X-Source": source, "X-Date": datetime, "Authorization": auth}
|
||||
|
||||
// 查询参数
|
||||
queryParams := make(map[string]string)
|
||||
queryParams["keyword"] = name
|
||||
// body参数
|
||||
bodyParams := make(map[string]string)
|
||||
|
||||
// url参数拼接
|
||||
url := "https://service-3jnh3ku8-1256140209.gz.apigw.tencentcs.com/release/business4/geet"
|
||||
if len(queryParams) > 0 {
|
||||
url = fmt.Sprintf("%s?%s", url, that.urlencode(queryParams))
|
||||
}
|
||||
|
||||
bodyMethods := map[string]bool{"POST": true, "PUT": true, "PATCH": true}
|
||||
var body io.Reader = nil
|
||||
if bodyMethods[method] {
|
||||
body = strings.NewReader(that.urlencode(bodyParams))
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
request, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil
|
||||
}
|
||||
for k, v := range headers {
|
||||
request.Header.Set(k, v)
|
||||
}
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
bodyBytes, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil
|
||||
}
|
||||
res := string(bodyBytes)
|
||||
fmt.Println(res)
|
||||
return ObjToMap(res)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package tencent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||
ocr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ocr/v20181119"
|
||||
)
|
||||
|
||||
type tencent struct {
|
||||
secretId string
|
||||
secretKey string
|
||||
credential *common.Credential
|
||||
}
|
||||
|
||||
var Tencent = tencent{}
|
||||
|
||||
func (that *tencent) Init(secretId, secretKey string) {
|
||||
// 云市场分配的密钥Id
|
||||
//secretId := "xxxx"
|
||||
//// 云市场分配的密钥Key
|
||||
//secretKey := "xxxx"
|
||||
that.secretId = secretId
|
||||
that.secretKey = secretKey
|
||||
that.credential = common.NewCredential(
|
||||
that.secretId,
|
||||
that.secretKey,
|
||||
)
|
||||
}
|
||||
|
||||
func (that *tencent) OCRCOMPANY(base64Str string) string {
|
||||
|
||||
cpf := profile.NewClientProfile()
|
||||
cpf.HttpProfile.Endpoint = "ocr.tencentcloudapi.com"
|
||||
client, _ := ocr.NewClient(that.credential, "ap-guangzhou", cpf)
|
||||
|
||||
request := ocr.NewBizLicenseOCRRequest()
|
||||
|
||||
//request.ImageUrl = common.StringPtr("https://img0.baidu.com/it/u=2041013181,3227632688&fm=26&fmt=auto")
|
||||
request.ImageBase64 = common.StringPtr(base64Str)
|
||||
|
||||
response, err := client.BizLicenseOCR(request)
|
||||
if _, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||
fmt.Println("An API error has returned: %s", err)
|
||||
return ""
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println("An API error has returned: %s", err)
|
||||
return ""
|
||||
}
|
||||
//fmt.Printf("%s", response.ToJsonString())
|
||||
|
||||
return response.ToJsonString()
|
||||
}
|
||||
|
||||
func (that *tencent) OCR(base64Str string) string {
|
||||
|
||||
cpf := profile.NewClientProfile()
|
||||
cpf.HttpProfile.Endpoint = "ocr.tencentcloudapi.com"
|
||||
client, _ := ocr.NewClient(that.credential, "ap-guangzhou", cpf)
|
||||
|
||||
request := ocr.NewGeneralAccurateOCRRequest()
|
||||
|
||||
//request.ImageUrl = common.StringPtr("https://img0.baidu.com/it/u=2041013181,3227632688&fm=26&fmt=auto")
|
||||
request.ImageBase64 = common.StringPtr(base64Str)
|
||||
|
||||
response, err := client.GeneralAccurateOCR(request)
|
||||
if _, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||
fmt.Println("An API error has returned: %s", err)
|
||||
return ""
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println("An API error has returned: %s", err)
|
||||
return ""
|
||||
}
|
||||
//fmt.Printf("%s", response.ToJsonString())
|
||||
|
||||
return response.ToJsonString()
|
||||
}
|
||||
|
||||
func (that *tencent) Qrcode(base64Str string) string {
|
||||
|
||||
cpf := profile.NewClientProfile()
|
||||
cpf.HttpProfile.Endpoint = "ocr.tencentcloudapi.com"
|
||||
client, _ := ocr.NewClient(that.credential, "ap-guangzhou", cpf)
|
||||
|
||||
request := ocr.NewQrcodeOCRRequest()
|
||||
|
||||
request.ImageBase64 = common.StringPtr(base64Str)
|
||||
|
||||
response, err := client.QrcodeOCR(request)
|
||||
if _, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||
fmt.Println("An API error has returned: %s", err)
|
||||
return ""
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println("An API error has returned: %s", err)
|
||||
return ""
|
||||
}
|
||||
//fmt.Printf("%s", response.ToJsonString())
|
||||
|
||||
return response.ToJsonString()
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package upload
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"errors"
|
||||
"go.hoteas.com/hotime"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
@@ -15,7 +15,7 @@ type Upload struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func (this *Upload) UpFile(Request *http.Request, fieldName, savefilepath, savePath string) (string, error) {
|
||||
func (that *Upload) UpFile(Request *http.Request, fieldName, savefilepath, savePath string) (string, error) {
|
||||
Request.ParseMultipartForm(32 << 20)
|
||||
var filePath string
|
||||
files := Request.MultipartForm.File
|
||||
@@ -36,7 +36,7 @@ func (this *Upload) UpFile(Request *http.Request, fieldName, savefilepath, saveP
|
||||
data := time.Unix(int64(t), 0).Format("2006-01")
|
||||
path := ""
|
||||
if strings.EqualFold(savefilepath, "") {
|
||||
path = this.Path + data
|
||||
path = that.Path + data
|
||||
} else {
|
||||
path = savefilepath + data
|
||||
}
|
||||
@@ -50,8 +50,8 @@ func (this *Upload) UpFile(Request *http.Request, fieldName, savefilepath, saveP
|
||||
}
|
||||
}
|
||||
}
|
||||
filename := time.Unix(int64(t), 0).Format("2006-01-02-15-22-25") + hotime.ObjToStr(hotime.Rand(6))
|
||||
filePath = path + "/" + filename + this.Path
|
||||
filename := time.Unix(int64(t), 0).Format("2006-01-02-15-22-25") + ObjToStr(Rand(6))
|
||||
filePath = path + "/" + filename + that.Path
|
||||
} else {
|
||||
filePath = savePath
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"github.com/silenceper/wechat/v2"
|
||||
"github.com/silenceper/wechat/v2/cache"
|
||||
"github.com/silenceper/wechat/v2/officialaccount"
|
||||
h5config "github.com/silenceper/wechat/v2/officialaccount/config"
|
||||
"github.com/silenceper/wechat/v2/officialaccount/js"
|
||||
"github.com/silenceper/wechat/v2/officialaccount/oauth"
|
||||
)
|
||||
|
||||
//基于此文档开发
|
||||
//https://github.com/silenceper/wechat/blob/v2/doc/api/officialaccount.md
|
||||
type h5Program struct {
|
||||
Memory *cache.Memory
|
||||
Config *h5config.Config
|
||||
*officialaccount.OfficialAccount
|
||||
weixin *wechat.Wechat //微信登录实例
|
||||
}
|
||||
|
||||
var H5Program = h5Program{}
|
||||
|
||||
// Init 初始化
|
||||
func (that *h5Program) Init(appid string, appsecret string) {
|
||||
that.weixin = wechat.NewWechat()
|
||||
that.Memory = cache.NewMemory()
|
||||
that.Config = &h5config.Config{
|
||||
AppID: appid,
|
||||
AppSecret: appsecret,
|
||||
//Token: "xxx",
|
||||
//EncodingAESKey: "xxxx",
|
||||
Cache: that.Memory,
|
||||
}
|
||||
that.OfficialAccount = that.weixin.GetOfficialAccount(that.Config)
|
||||
}
|
||||
|
||||
// GetUserInfo 获取用户信息
|
||||
func (that *h5Program) GetUserInfo(code string) (appid string, resToken oauth.ResAccessToken, userInfo oauth.UserInfo, err error) {
|
||||
auth := that.GetOauth()
|
||||
//weixin.GetOpenPlatform()
|
||||
resToken, err = auth.GetUserAccessToken(code)
|
||||
if err != nil {
|
||||
|
||||
return auth.AppID, resToken, userInfo, err
|
||||
}
|
||||
|
||||
//getUserInfo
|
||||
userInfo, err = auth.GetUserInfo(resToken.AccessToken, resToken.OpenID, "")
|
||||
if err != nil {
|
||||
return auth.AppID, resToken, userInfo, err
|
||||
}
|
||||
|
||||
return auth.AppID, resToken, userInfo, err
|
||||
}
|
||||
|
||||
// GetSignUrl js url签名
|
||||
func (that *h5Program) GetSignUrl(signUrl string) (*js.Config, error) {
|
||||
|
||||
js := that.OfficialAccount.GetJs()
|
||||
cfg1, e := js.GetConfig(signUrl)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return cfg1, nil
|
||||
}
|
||||
|
||||
// GetSignUrl js url签名
|
||||
//func (that *h5Program) GetJsPay(signUrl string) (*js.Config, error) {
|
||||
// //
|
||||
// //js := that.OfficialAccount().GetJs()
|
||||
// //
|
||||
// //cfg1, e := js.GetConfig(signUrl)
|
||||
// //if e != nil {
|
||||
// // return nil, e
|
||||
// //}
|
||||
//
|
||||
// return cfg1, nil
|
||||
//}
|
||||
@@ -0,0 +1,66 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/silenceper/wechat/v2"
|
||||
"github.com/silenceper/wechat/v2/cache"
|
||||
"github.com/silenceper/wechat/v2/miniprogram"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/auth"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/config"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/encryptor"
|
||||
)
|
||||
|
||||
type miniProgram struct {
|
||||
Memory *cache.Memory
|
||||
Config *config.Config
|
||||
weixin *wechat.Wechat //微信登录实例
|
||||
*miniprogram.MiniProgram
|
||||
}
|
||||
|
||||
var MiniProgram = miniProgram{}
|
||||
|
||||
// Init 初始化
|
||||
func (that *miniProgram) Init(appid string, appsecret string) {
|
||||
|
||||
that.weixin = wechat.NewWechat()
|
||||
that.Memory = cache.NewMemory()
|
||||
that.Config = &config.Config{
|
||||
AppID: appid,
|
||||
AppSecret: appsecret,
|
||||
//Token: "xxx",
|
||||
//EncodingAESKey: "xxxx",
|
||||
Cache: that.Memory,
|
||||
}
|
||||
that.MiniProgram = that.weixin.GetMiniProgram(that.Config)
|
||||
}
|
||||
|
||||
func (that *miniProgram) GetBaseUserInfo(code string) (appid string, re auth.ResCode2Session, err error) {
|
||||
appid = that.Config.AppID
|
||||
a := that.GetAuth()
|
||||
re, err = a.Code2Session(code)
|
||||
|
||||
if err != nil {
|
||||
return appid, re, err
|
||||
}
|
||||
|
||||
return appid, re, err
|
||||
}
|
||||
|
||||
func (that *miniProgram) GetPhoneNumber(sessionkey, encryptedData, iv string) (appid string, re *encryptor.PlainData, err error) {
|
||||
appid = that.Config.AppID
|
||||
|
||||
if sessionkey == "" || encryptedData == "" || iv == "" {
|
||||
return appid, re, errors.New("参数不足")
|
||||
}
|
||||
|
||||
eny := that.GetEncryptor()
|
||||
|
||||
re, err = eny.Decrypt(sessionkey, encryptedData, iv)
|
||||
|
||||
if err != nil {
|
||||
return appid, re, err
|
||||
}
|
||||
|
||||
return appid, re, err
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/wechat/v3"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
//基于此文档开发
|
||||
//https://github.com/silenceper/wechat/blob/v2/doc/api/officialaccount.md
|
||||
type wxpay struct {
|
||||
client *wechat.ClientV3
|
||||
ctx context.Context
|
||||
apiV3Key string
|
||||
MchId string
|
||||
}
|
||||
|
||||
var WxPay = wxpay{}
|
||||
|
||||
// Init 初始化
|
||||
func (that *wxpay) Init(MchId, SerialNo, APIv3Key, PrivateKey string) {
|
||||
client, err := wechat.NewClientV3(MchId, SerialNo, APIv3Key, PrivateKey)
|
||||
if err != nil {
|
||||
//xlog.Error(err)
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
that.client = client
|
||||
that.apiV3Key = APIv3Key
|
||||
that.MchId = MchId
|
||||
// 设置微信平台API证书和序列号(如开启自动验签,请忽略此步骤)
|
||||
//client.SetPlatformCert([]byte(""), "")
|
||||
that.ctx = context.Background()
|
||||
// 启用自动同步返回验签,并定时更新微信平台API证书(开启自动验签时,无需单独设置微信平台API证书和序列号)
|
||||
err = client.AutoVerifySign()
|
||||
if err != nil {
|
||||
//xlog.Error(err)
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 打开Debug开关,输出日志,默认是关闭的
|
||||
client.DebugSwitch = gopay.DebugOn
|
||||
}
|
||||
|
||||
// GetUserInfo 获取用户信息
|
||||
func (that *wxpay) GetJsOrder(money int64, appid, openid, name, tradeNo, notifyUrl string) (jsApiParams *wechat.JSAPIPayParams, err error) {
|
||||
fmt.Println("dasdas", money, appid, name, tradeNo, notifyUrl)
|
||||
PrepayId, err := that.getPrepayId(money, appid, that.MchId, openid, name, tradeNo, notifyUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//小程序
|
||||
jsapi, err := that.client.PaySignOfJSAPI(appid, PrepayId)
|
||||
return jsapi, err
|
||||
}
|
||||
|
||||
func (that *wxpay) CallbackJsOrder(req *http.Request) (*wechat.V3DecryptResult, error) {
|
||||
|
||||
notifyReq, err := wechat.V3ParseNotify(req)
|
||||
if err != nil {
|
||||
//xlog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// wxPublicKey 通过 client.WxPublicKey() 获取
|
||||
err = notifyReq.VerifySignByPK(that.client.WxPublicKey())
|
||||
if err != nil {
|
||||
//xlog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ========异步通知敏感信息解密========
|
||||
// 普通支付通知解密
|
||||
result, err := notifyReq.DecryptCipherText(that.apiV3Key)
|
||||
|
||||
//that.client.V3TransactionQueryOrder(that.ctx,result.BankType,result.OR)
|
||||
|
||||
return result, err
|
||||
|
||||
// 合单支付通知解密
|
||||
//result, err := notifyReq.DecryptCombineCipherText(apiV3Key)
|
||||
//// 退款通知解密
|
||||
//result, err := notifyReq.DecryptRefundCipherText(apiV3Key)
|
||||
|
||||
// ========异步通知应答========
|
||||
// 退款通知http应答码为200且返回状态码为SUCCESS才会当做商户接收成功,否则会重试。
|
||||
// 注意:重试过多会导致微信支付端积压过多通知而堵塞,影响其他正常通知。
|
||||
|
||||
// 此写法是 gin 框架返回微信的写法
|
||||
//c.JSON(http.StatusOK, &wechat.V3NotifyRsp{Code: gopay.SUCCESS, Message: "成功"})
|
||||
//
|
||||
//// 此写法是 echo 框架返回微信的写法
|
||||
//return c.JSON(http.StatusOK, &wechat.V3NotifyRsp{Code: gopay.SUCCESS, Message: "成功"})
|
||||
}
|
||||
|
||||
// GetUserInfo 获取用户信息
|
||||
//func (that *wxpay) GetMiniOrder(money int64,appid,name,tradeNo,notifyUrl string) (jsApiParams *wechat.AppletParams,err error){
|
||||
//
|
||||
// PrepayId,err:=that.getPrepayId(money,name,tradeNo,notifyUrl)
|
||||
// if err!=nil{
|
||||
// return nil,err
|
||||
// }
|
||||
//
|
||||
// //小程序
|
||||
// applet, err := that.client.PaySignOfApplet(appid,PrepayId)
|
||||
//
|
||||
// return applet,err
|
||||
//}
|
||||
|
||||
func (that *wxpay) getPrepayId(money int64, appid, mchid, openid, name, tradeNo, notifyUrl string) (prepayid string, err error) {
|
||||
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
|
||||
// 初始化 BodyMap
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("appid", appid).
|
||||
Set("mchid", mchid).
|
||||
//Set("sub_mchid", "sub_mchid").
|
||||
Set("description", name).
|
||||
Set("out_trade_no", tradeNo).
|
||||
Set("time_expire", expire).
|
||||
Set("notify_url", notifyUrl).
|
||||
SetBodyMap("amount", func(bm gopay.BodyMap) {
|
||||
bm.Set("total", money).
|
||||
Set("currency", "CNY")
|
||||
}).
|
||||
SetBodyMap("payer", func(bm gopay.BodyMap) {
|
||||
bm.Set("openid", openid)
|
||||
})
|
||||
//ctx:=context.Context()
|
||||
|
||||
wxRsp, err := that.client.V3TransactionJsapi(that.ctx, bm)
|
||||
fmt.Println("获取PrepayId", wxRsp, err)
|
||||
if err != nil {
|
||||
//xlog.Error(err)
|
||||
return "", err
|
||||
}
|
||||
return wxRsp.Response.PrepayId, nil
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
//框架层处理错误
|
||||
type Error struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (this *Error) GetError() error {
|
||||
|
||||
return this.err
|
||||
|
||||
}
|
||||
|
||||
func (this *Error) SetError(err error, loglevel ...int) {
|
||||
|
||||
this.err = nil
|
||||
if err == nil {
|
||||
this.err = err
|
||||
return
|
||||
}
|
||||
|
||||
lev := Config.GetInt("logLevel")
|
||||
|
||||
if len(loglevel) != 0 {
|
||||
lev = loglevel[0]
|
||||
}
|
||||
|
||||
if lev == LOG_FMT {
|
||||
log.Println(err)
|
||||
}
|
||||
if lev == LOG_FILE {
|
||||
log.Println(err)
|
||||
}
|
||||
this.err = err
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ArticleCtr = Ctr{
|
||||
"info": func(that *Context) {
|
||||
sn := that.Req.FormValue("sn")
|
||||
article := that.Db.Get("article", Map{"[><]ctg_article": "article.id=ctg_article.article_id"}, "article.*,ctg_article.ctg_id AS sctg_id", Map{"ctg_article.sn": sn})
|
||||
if article == nil {
|
||||
that.Display(4, "找不到对应数据")
|
||||
return
|
||||
}
|
||||
ctgId := article.GetCeilInt64("sctg_id")
|
||||
ctg := that.Db.Get("ctg", "*", Map{"id": ctgId})
|
||||
parents := []Map{}
|
||||
parentId := ctg.GetCeilInt64("parent_id")
|
||||
article["tongji"] = that.Db.Select("ctg", "sn,name,img,parent_id", Map{"parent_id": parentId})
|
||||
for true {
|
||||
|
||||
if parentId == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
parent := that.Db.Get("ctg", "sn,name,img,parent_id", Map{"id": parentId})
|
||||
if parent == nil {
|
||||
break
|
||||
}
|
||||
|
||||
parents = append(parents, parent)
|
||||
parentId = parent.GetCeilInt64("parent_id")
|
||||
|
||||
}
|
||||
|
||||
ctg["parents"] = parents
|
||||
|
||||
article["ctg"] = ctg
|
||||
that.Display(0, article)
|
||||
},
|
||||
"list": func(that *Context) {
|
||||
sn := that.Req.FormValue("ctg_sn") //ctgsn
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page == 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize == 0 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
|
||||
lunbo := ObjToInt(that.Req.FormValue("lunbo"))
|
||||
|
||||
sort := that.Req.FormValue("sort")
|
||||
|
||||
where := Map{"article.push_time[<]": time.Now().Format("2006-01-02 15:04"), "article.state": 0}
|
||||
if sn != "" {
|
||||
ctg := that.Db.Get("ctg", "id", Map{"sn": sn})
|
||||
if ctg != nil {
|
||||
where["ctg_article.ctg_id"] = ctg.GetCeilInt("id")
|
||||
}
|
||||
}
|
||||
|
||||
startTime := that.Req.FormValue("start_time") //ctgsn
|
||||
finishTime := that.Req.FormValue("finish_time") //ctgsn
|
||||
|
||||
if lunbo != 0 {
|
||||
where["article.lunbo"] = lunbo
|
||||
}
|
||||
|
||||
if len(startTime) > 5 {
|
||||
where["article.push_time[>=]"] = startTime
|
||||
}
|
||||
if len(finishTime) > 5 {
|
||||
where["article.push_time[<=]"] = finishTime
|
||||
}
|
||||
|
||||
if keywords != "" {
|
||||
where["OR"] = Map{"article.title[~]": keywords, "article.description[~]": keywords, "article.author[~]": keywords, "article.sn[~]": keywords, "article.origin[~]": keywords, "article.url[~]": keywords}
|
||||
}
|
||||
|
||||
if len(where) > 1 {
|
||||
where = Map{"AND": where}
|
||||
}
|
||||
|
||||
if sort == "" {
|
||||
where["ORDER"] = Slice{"article.sort DESC", "article.push_time DESC"}
|
||||
}
|
||||
|
||||
if sort == "time" {
|
||||
where["ORDER"] = "article.push_time DESC"
|
||||
}
|
||||
count := that.Db.Count("article", Map{"[><]ctg_article": "article.id=ctg_article.article_id"}, where)
|
||||
article := that.Db.Page(page, pageSize).PageSelect("article", Map{"[><]ctg_article": "article.id=ctg_article.article_id"}, "ctg_article.sn,article.img,article.title,article.description,article.push_time,article.lunbo,article.author,article.origin,article.url", where)
|
||||
|
||||
that.Display(0, Map{"count": count, "data": article})
|
||||
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var CtgCtr = Ctr{
|
||||
"info": func(that *Context) {
|
||||
sn := that.Req.FormValue("sn")
|
||||
ctg := that.Db.Get("ctg", "*", Map{"sn": sn})
|
||||
parents := []Map{}
|
||||
parentId := ctg.GetCeilInt64("parent_id")
|
||||
ctg["tongji"] = that.Db.Select("ctg", "sn,name,img,parent_id", Map{"parent_id": parentId})
|
||||
for true {
|
||||
|
||||
if parentId == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
parent := that.Db.Get("ctg", "sn,name,img,parent_id", Map{"id": parentId})
|
||||
if parent == nil {
|
||||
break
|
||||
}
|
||||
|
||||
parents = append(parents, parent)
|
||||
parentId = parent.GetCeilInt64("parent_id")
|
||||
|
||||
}
|
||||
|
||||
if ctg.GetCeilInt64("article_id") != 0 {
|
||||
ctg["article"] = that.Db.Get("article", "*", Map{"id": ctg.GetCeilInt64("article_id")})
|
||||
}
|
||||
|
||||
ctg["parents"] = parents
|
||||
that.Display(0, ctg)
|
||||
},
|
||||
"list": func(that *Context) {
|
||||
sn := that.Req.FormValue("sn") //ctgsn
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page == 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize == 0 {
|
||||
pageSize = 50
|
||||
}
|
||||
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
|
||||
//sort:=that.Req.FormValue("sort")
|
||||
|
||||
where := Map{"state": 0}
|
||||
if sn != "" {
|
||||
ctg := that.Db.Get("ctg", "id", Map{"sn": sn})
|
||||
if ctg != nil {
|
||||
where["parent_id"] = ctg.GetCeilInt("id")
|
||||
}
|
||||
}
|
||||
|
||||
if keywords != "" {
|
||||
where["OR"] = Map{"name[~]": keywords, "url[~]": keywords, "sn[~]": keywords}
|
||||
}
|
||||
|
||||
if len(where) > 1 {
|
||||
where = Map{"AND": where}
|
||||
}
|
||||
|
||||
where["ORDER"] = Slice{"sort DESC", "id DESC"}
|
||||
|
||||
article := that.Db.Page(page, pageSize).PageSelect("ctg", "name,sn,sort,url,img", where)
|
||||
|
||||
that.Display(0, article)
|
||||
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"time"
|
||||
)
|
||||
|
||||
var AppProj = Proj{
|
||||
"article": ArticleCtr,
|
||||
"org": OrgCtr,
|
||||
"ctg": CtgCtr,
|
||||
"mail": MailCtr,
|
||||
"test": {
|
||||
"test": func(that *Context) {
|
||||
//data:=that.Db.Table("admin").Order("id DESC").Select("*")
|
||||
//data1:=that.Db.Table("admin").Where(Map{"name[~]":"m"}).Order("id DESC").Select("*")
|
||||
//
|
||||
//data3:=that.Db.Select("admin","*",Map{"name[~]":"m"})
|
||||
data2 := that.Db.Table("article").Where(Map{"title[~]": "m"}).Order("id DESC").Page(1, 10).Select("*")
|
||||
c := that.Db.Table("article").Where(Map{"title[~]": "m"}).Order("id DESC").Group("title").Select("*")
|
||||
//that.Display(0,Slice{data1,data,data3,data2})
|
||||
that.Display(0, Slice{data2, c})
|
||||
},
|
||||
"res": func(that *Context) {
|
||||
ebw_res := that.Db.Select("ebw_res", "*")
|
||||
|
||||
for _, v := range ebw_res {
|
||||
data := Map{"id": v.GetCeilInt("id"), "name": v.GetString("name"),
|
||||
"parent_id": v.GetCeilInt64("pid"),
|
||||
"sn": v.GetString("url"), "create_time": time.Now().Format("2006-01-02 15:04"),
|
||||
"modify_time": time.Now().Format("2006-01-02 15:04"), "admin_id": 1}
|
||||
if data.GetCeilInt("parent_id") == 0 {
|
||||
data["parent_id"] = nil
|
||||
}
|
||||
that.Db.Insert("ctg", data)
|
||||
}
|
||||
|
||||
that.Db.Exec("UPDATE ctg SET parent_id =NULL WHERE parent_id=id")
|
||||
|
||||
ss(0, that)
|
||||
|
||||
that.Display(0, len(ebw_res))
|
||||
|
||||
},
|
||||
"news": func(that *Context) {
|
||||
ebw_news := that.Db.Select("ebw_news", "*")
|
||||
|
||||
for _, v := range ebw_news {
|
||||
ctg := that.Db.Get("ctg", "*", Map{"sn": v.GetString("type")})
|
||||
data := Map{"sn": v.GetString("id"), "title": v.GetString("title"),
|
||||
"content": v.GetString("content"), "push_time": v.GetString("timedate"),
|
||||
"author": v.GetString("owner"), "origin": v.GetString("source"), "click_num": v.GetString("readtime"),
|
||||
"sort": v.GetCeilInt("zhiding"), "create_time": time.Now().Format("2006-01-02 15:04"),
|
||||
"modify_time": time.Now().Format("2006-01-02 15:04"), "admin_id": 1}
|
||||
if ctg != nil {
|
||||
data["ctg_id"] = ctg.GetCeilInt("id")
|
||||
}
|
||||
that.Db.Insert("article", data)
|
||||
}
|
||||
|
||||
that.Display(0, len(ebw_news))
|
||||
|
||||
},
|
||||
"res2news": func(that *Context) {
|
||||
ebw_news_addition_res := that.Db.Select("ebw_news_addition_res", "*")
|
||||
|
||||
for _, v := range ebw_news_addition_res {
|
||||
ctg := that.Db.Get("ctg", "*", Map{"sn": v.GetString("fk_res")})
|
||||
article := that.Db.Get("article", "*", Map{"sn": v.GetString("fk_newsid")})
|
||||
data := Map{"sn": Md5(ObjToStr(time.Now().UnixNano()) + ObjToStr(RandX(10000, 100000))),
|
||||
"create_time": time.Now().Format("2006-01-02 15:04"),
|
||||
"modify_time": time.Now().Format("2006-01-02 15:04"), "admin_id": 1}
|
||||
if ctg != nil {
|
||||
data["ctg_id"] = ctg.GetCeilInt("id")
|
||||
}
|
||||
if article != nil {
|
||||
data["article_id"] = article.GetCeilInt("id")
|
||||
}
|
||||
that.Db.Insert("ctg_article", data)
|
||||
}
|
||||
|
||||
that.Display(0, len(ebw_news_addition_res))
|
||||
},
|
||||
//将文章没有关联的ctg_article进行关联
|
||||
"article": func(that *Context) {
|
||||
articles := that.Db.Select("article", "id,ctg_id")
|
||||
for _, v := range articles {
|
||||
ctg_article := that.Db.Get("ctg_article", "id", Map{"article_id": v.GetCeilInt("id")})
|
||||
if ctg_article == nil {
|
||||
|
||||
data := Map{"sn": Md5(ObjToStr(time.Now().UnixNano()) + ObjToStr(RandX(10000, 100000))),
|
||||
"create_time": time.Now().Format("2006-01-02 15:04"),
|
||||
"modify_time": time.Now().Format("2006-01-02 15:04"), "admin_id": 1}
|
||||
if v.GetCeilInt("ctg_id") == 0 || v.GetCeilInt("id") == 0 {
|
||||
continue
|
||||
}
|
||||
data["ctg_id"] = v.GetCeilInt("ctg_id")
|
||||
data["article_id"] = v.GetCeilInt("id")
|
||||
that.Db.Insert("ctg_article", data)
|
||||
}
|
||||
|
||||
}
|
||||
that.Display(0, len(articles))
|
||||
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func ss(parent_id int, that *Context) {
|
||||
var ctgs []Map
|
||||
ctg := that.Db.Get("ctg", "*", Map{"id": parent_id})
|
||||
if parent_id == 0 {
|
||||
ctgs = that.Db.Select("ctg", "*", Map{"parent_id": nil})
|
||||
} else {
|
||||
ctgs = that.Db.Select("ctg", "*", Map{"parent_id": parent_id})
|
||||
}
|
||||
|
||||
for _, v := range ctgs {
|
||||
if ctg == nil {
|
||||
ctg = Map{"parent_ids": ","}
|
||||
}
|
||||
ids := ctg.GetString("parent_ids") + ObjToStr(v.GetCeilInt("id")) + ","
|
||||
that.Db.Update("ctg", Map{"parent_ids": ids}, Map{"id": v.GetCeilInt("id")})
|
||||
ss(v.GetCeilInt("id"), that)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"time"
|
||||
)
|
||||
|
||||
var MailCtr = Ctr{
|
||||
"add": func(that *Context) {
|
||||
title := that.Req.FormValue("title")
|
||||
name := that.Req.FormValue("name")
|
||||
phone := that.Req.FormValue("phone")
|
||||
content := that.Req.FormValue("content")
|
||||
|
||||
tp := ObjToInt(that.Req.FormValue("type"))
|
||||
show := ObjToInt(that.Req.FormValue("show"))
|
||||
|
||||
if len(title) < 5 {
|
||||
that.Display(3, "标题过短")
|
||||
return
|
||||
}
|
||||
if len(name) < 2 {
|
||||
that.Display(3, "姓名错误")
|
||||
return
|
||||
}
|
||||
if len(phone) < 8 {
|
||||
that.Display(3, "联系方式错误")
|
||||
return
|
||||
}
|
||||
if len(content) < 10 {
|
||||
that.Display(3, "内容过短")
|
||||
return
|
||||
}
|
||||
|
||||
data := Map{
|
||||
"sn": Md5(ObjToStr(time.Now().UnixNano()) + ObjToStr(RandX(10000, 100000))),
|
||||
"name": name, "title": title, "phone": phone, "content": content, "type": tp, "show": show,
|
||||
"modify_time[#]": "NOW()", "create_time[#]": "NOW()",
|
||||
}
|
||||
id := that.Db.Insert("mail", data)
|
||||
if id == 0 {
|
||||
that.Display(4, "创建失败")
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(0, "成功")
|
||||
return
|
||||
|
||||
},
|
||||
"info": func(that *Context) {
|
||||
sn := that.Req.FormValue("sn")
|
||||
|
||||
mail := that.Db.Get("mail", "*", Map{"sn": sn})
|
||||
|
||||
that.Display(0, mail)
|
||||
},
|
||||
"list": func(that *Context) {
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page == 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize == 0 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
//keywords:=that.Req.FormValue("keywords")
|
||||
|
||||
//sort:=that.Req.FormValue("sort")
|
||||
|
||||
where := Map{"state": 0, "show": 1}
|
||||
|
||||
//if keywords!=""{
|
||||
// where["OR"]=Map{"title[~]":keywords,"description[~]":keywords,"author[~]":keywords,"sn[~]":keywords,"origin[~]":keywords,"url[~]":keywords}
|
||||
//}
|
||||
|
||||
if len(where) > 1 {
|
||||
where = Map{"AND": where}
|
||||
}
|
||||
|
||||
//if sort==""{
|
||||
// where["ORDER"]=Slice{"sort DESC","push_time DESC"}
|
||||
//}
|
||||
//
|
||||
//if sort=="time"{
|
||||
where["ORDER"] = "create_time DESC"
|
||||
//}
|
||||
count := that.Db.Count("mail", where)
|
||||
mail := that.Db.Page(page, pageSize).PageSelect("mail", "*", where)
|
||||
|
||||
that.Display(0, Map{"count": count, "data": mail})
|
||||
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"time"
|
||||
)
|
||||
|
||||
var OrgCtr = Ctr{
|
||||
"info": func(that *Context) {
|
||||
sn := that.Req.FormValue("sn")
|
||||
article := that.Db.Get("article", "*", Map{"sn": sn})
|
||||
that.Display(0, article)
|
||||
},
|
||||
"list": func(that *Context) {
|
||||
sn := that.Req.FormValue("sn") //orgsn
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page == 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize == 0 {
|
||||
pageSize = 50
|
||||
}
|
||||
|
||||
keywords := that.Req.FormValue("keywords")
|
||||
|
||||
sort := that.Req.FormValue("sort")
|
||||
|
||||
where := Map{"push_time[<=]": time.Now().Format("2006-01-02 15:04"), "state": 0}
|
||||
if sn != "" {
|
||||
org := that.Db.Get("org", "id", Map{"sn": sn})
|
||||
if org != nil {
|
||||
where["org_id"] = org.GetCeilInt("id")
|
||||
}
|
||||
}
|
||||
|
||||
if keywords != "" {
|
||||
where["OR"] = Map{"title[~]": keywords, "description[~]": keywords, "author[~]": keywords, "sn[~]": keywords, "origin[~]": keywords, "url[~]": keywords}
|
||||
}
|
||||
|
||||
if len(where) > 1 {
|
||||
where = Map{"AND": where}
|
||||
}
|
||||
|
||||
if sort == "" {
|
||||
where["ORDER"] = Slice{"sort DESC", "id DESC"}
|
||||
}
|
||||
|
||||
if sort == "time" {
|
||||
where["ORDER"] = "push_time DESC"
|
||||
}
|
||||
|
||||
article := that.Db.Page(page, pageSize).PageSelect("article", "sn,title,description,push_time,lunbo,author,origin,url", where)
|
||||
|
||||
that.Display(0, article)
|
||||
|
||||
},
|
||||
}
|
||||
+34
-15
@@ -1,13 +1,36 @@
|
||||
{
|
||||
"cacheLongTime": 2592000,
|
||||
"cacheShortTime": 7200,
|
||||
"dbCached": 0,
|
||||
"dbHost": "127.0.0.1",
|
||||
"dbName": "test",
|
||||
"dbPort": "3306",
|
||||
"dbPwd": "root",
|
||||
"dbUser": "root",
|
||||
"debug": 1,
|
||||
"cache": {
|
||||
"db": {
|
||||
"db": false,
|
||||
"session": true,
|
||||
"timeout": 7200
|
||||
},
|
||||
"memory": {
|
||||
"db": true,
|
||||
"session": true,
|
||||
"timeout": 7200
|
||||
}
|
||||
},
|
||||
"codeConfig": [
|
||||
{
|
||||
"config": "config/admin.json",
|
||||
"configDB": "config/adminDB.json",
|
||||
"mode": 0,
|
||||
"name": "",
|
||||
"rule": "config/rule.json",
|
||||
"table": "admin"
|
||||
}
|
||||
],
|
||||
"db": {
|
||||
"mysql": {
|
||||
"host": "192.168.2.20",
|
||||
"name": "gov_crawler",
|
||||
"password": "fh22y8b882d",
|
||||
"port": "3306",
|
||||
"prefix": "",
|
||||
"user": "root"
|
||||
}
|
||||
},
|
||||
"defFile": [
|
||||
"index.html",
|
||||
"index.htm"
|
||||
@@ -19,12 +42,8 @@
|
||||
"4": "数据处理异常",
|
||||
"5": "数据结果异常"
|
||||
},
|
||||
"logLevel": 1,
|
||||
"modeRouterStrict": false,
|
||||
"port": "0",
|
||||
"mode": 2,
|
||||
"port": "8081",
|
||||
"sessionName": "HOTIME",
|
||||
"tlsCert": "",
|
||||
"tlsKey": "",
|
||||
"tlsPort": "0",
|
||||
"tpt": "tpt"
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"cache": {
|
||||
"db": {
|
||||
"db": "默认false,非必须,缓存数据库,启用后能减少数据库的读写压力",
|
||||
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
|
||||
"timeout": "默认60 * 60 * 24 * 30,非必须,过期时间,超时自动删除"
|
||||
},
|
||||
"memory": {
|
||||
"db": "默认true,非必须,缓存数据库,启用后能减少数据库的读写压力",
|
||||
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
|
||||
"timeout": "默认60 * 60 * 2,非必须,过期时间,超时自动删除"
|
||||
},
|
||||
"redis": {
|
||||
"db": "默认true,非必须,缓存数据库,启用后能减少数据库的读写压力",
|
||||
"host": "默认服务ip:127.0.0.1,必须,如果需要使用redis服务时配置,",
|
||||
"password": "默认密码空,必须,如果需要使用redis服务时配置,默认密码空",
|
||||
"port": "默认服务端口:6379,必须,如果需要使用redis服务时配置,",
|
||||
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
|
||||
"timeout": "默认60 * 60 * 24 * 15,非必须,过期时间,超时自动删除"
|
||||
},
|
||||
"注释": "可配置memory,db,redis,默认启用memory,默认优先级为memory\u003eredis\u003edb,memory与数据库缓存设置项一致,缓存数据填充会自动反方向反哺,加入memory缓存过期将自动从redis更新,但memory永远不会更新redis,如果是集群建议不要开启memory,配置即启用"
|
||||
},
|
||||
"codeConfig": [
|
||||
"注释:配置即启用,非必须,默认无",
|
||||
{
|
||||
"config": "默认config/app.json,必须,接口描述配置文件",
|
||||
"configDB": "默认无,非必须,有则每次将数据库数据生成到此目录用于配置读写,无则不生成",
|
||||
"mode": "默认0,非必须,0为内嵌代码模式,1为生成代码模式",
|
||||
"name": "默认无,非必须,有则生成代码到此目录,无则采用缺省模式使用表名,如设置为:admin,将在admin目录生成包名为admin的代码",
|
||||
"rule": "默认config/rule.json,非必须,有则按改规则生成接口,无则按系统内嵌方式生成",
|
||||
"table": "默认admin,必须,根据数据库内当前表名做为用户生成数据"
|
||||
}
|
||||
],
|
||||
"crossDomain": "默认空 非必须,空字符串为不开启,如果需要跨域设置,auto为智能开启所有网站允许跨域,http://www.baidu.com为指定域允许跨域",
|
||||
"db": {
|
||||
"mysql": {
|
||||
"host": "默认127.0.0.1,必须,数据库ip地址",
|
||||
"name": "默认test,必须,数据库名称",
|
||||
"password": "默认root,必须,数据库密码",
|
||||
"port": "默认3306,必须,数据库端口",
|
||||
"prefix": "默认空,非必须,数据表前缀",
|
||||
"slave": {
|
||||
"host": "默认127.0.0.1,必须,数据库ip地址",
|
||||
"name": "默认test,必须,数据库名称",
|
||||
"password": "默认root,必须,数据库密码",
|
||||
"port": "默认3306,必须,数据库端口",
|
||||
"user": "默认root,必须,数据库用户名",
|
||||
"注释": "从数据库配置,mysql里配置slave项即启用主从读写,减少数据库压力"
|
||||
},
|
||||
"user": "默认root,必须,数据库用户名",
|
||||
"注释": "除prefix及主从数据库slave项,其他全部必须"
|
||||
},
|
||||
"sqlite": {
|
||||
"path": "默认config/data.db,必须,数据库位置"
|
||||
},
|
||||
"注释": "配置即启用,非必须,默认使用sqlite数据库"
|
||||
},
|
||||
"defFile": "默认访问index.html或者index.htm文件,必须,默认访问文件类型",
|
||||
"error": {
|
||||
"1": "内部系统异常,在环境配置,文件访问权限等基础运行环境条件不足造成严重错误时使用",
|
||||
"2": "访问权限异常,没有登录或者登录异常等时候使用",
|
||||
"3": "请求参数异常,request参数不满足要求,比如参数不足,参数类型错误,参数不满足要求等时候使用",
|
||||
"4": "数据处理异常,数据库操作或者三方请求返回的结果非正常结果,比如数据库突然中断等时候使用",
|
||||
"5": "数据结果异常,一般用于无法给出response要求的格式要求下使用,比如response需要的是string格式但你只能提供int数据时",
|
||||
"注释": "web服务内置错误提示,自定义异常建议10开始"
|
||||
},
|
||||
"logFile": "无默认,非必须,如果需要存储日志文件时使用,保存格式为:a/b/c/20060102150405.txt,将生成:a/b/c/年月日时分秒.txt,按需设置",
|
||||
"logLevel": "默认0,必须,0关闭,1打印,日志等级",
|
||||
"mode": "默认0,非必须,0生产模式,1,测试模式,2开发模式,3内嵌代码模式,在开发模式下会显示更多的数据用于开发测试,并能够辅助研发,自动生成配置文件、代码等功能,web无缓存,数据库不启用缓存",
|
||||
"modeRouterStrict": "默认false,必须,路由严格模式false,为大小写忽略必须匹配,true必须大小写匹配",
|
||||
"port": "默认80,必须,web服务开启Http端口,0为不启用http服务,默认80",
|
||||
"sessionName": "默认HOTIME,必须,设置session的cookie名",
|
||||
"tlsCert": "默认空,非必须,https证书",
|
||||
"tlsKey": "默认空,非必须,https密钥",
|
||||
"tlsPort": "默认空,非必须,web服务https端口,0为不启用https服务",
|
||||
"tpt": "默认tpt,必须,web静态文件目录,默认为程序目录下tpt目录",
|
||||
"webConnectLogFile": "无默认,非必须,webConnectLogShow开启之后才能使用,如果需要存储日志文件时使用,保存格式为:a/b/c/20060102150405.txt,将生成:a/b/c/年月日时分秒.txt,按需设置",
|
||||
"webConnectLogShow": "默认true,非必须,访问日志如果需要web访问链接、访问ip、访问时间打印,false为关闭true开启此功能"
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"cacheLongTime": "两级缓存,长缓存存储时间60 * 60 * 24 * 30,一般为数据库或者redis缓存",
|
||||
"cacheShortTime": "两级缓存,短缓存存储时间60 * 60 * 2,一般为内存缓存",
|
||||
"connectLogShow": "如果需要web访问链接、访问ip、访问时间打印,0为关闭其他数字开启此功能",
|
||||
"crossDomain": "如果需要跨域设置,空字符串为不开启,*为开启所有网站允许跨域,http://www.baidu.com为指定域允许跨域",
|
||||
"dbCached": "是否开启数据库缓存0为关闭,其他开启",
|
||||
"dbHost": "数据库ip地址默认127.0.0.1",
|
||||
"dbName": "数据库名称,sqlite为文件路径比如a/x.db",
|
||||
"dbPort": "数据库端口",
|
||||
"dbPwd": "数据库密码",
|
||||
"dbType": "如果需要使用自动数据库配置,请设置此项,手动配置数据库不需要,目前支持mysql,sqlite",
|
||||
"dbUser": "数据库用户名",
|
||||
"debug": "是否开启debug模式,0关闭,其他开启,debug模式下日志展示更全",
|
||||
"defFile": "默认访问文件,默认访问index.html或者index.htm文件",
|
||||
"error": {
|
||||
"1": "内部系统异常,在环境配置,文件访问权限等基础运行环境条件不足造成严重错误时使用",
|
||||
"2": "访问权限异常,没有登录或者登录异常等时候使用",
|
||||
"3": "请求参数异常,request参数不满足要求,比如参数不足,参数类型错误,参数不满足要求等时候使用",
|
||||
"4": "数据处理异常,数据库操作或者三方请求返回的结果非正常结果,比如数据库突然中断等时候使用",
|
||||
"5": "数据结果异常,一般用于无法给出response要求的格式要求下使用,比如response需要的是string格式但你只能提供int数据时",
|
||||
"注释": "web服务内置错误提示,自定义异常建议10开始"
|
||||
},
|
||||
"logLevel": "日志等级,0打印,1关闭,2,记录到文件",
|
||||
"modeRouterStrict": "路由严格模式false,为大小写忽略必须匹配,true必须大小写匹配",
|
||||
"port": "web服务开启Http端口,0为不启用http服务",
|
||||
"redisHost": "如果需要使用redis服务时配置,默认服务ip:127.0.0.1",
|
||||
"redisPort": "如果需要使用redis服务时配置,默认服务端口:6379",
|
||||
"redisPwd": "如果需要使用redis服务时配置,默认服务密码:123456",
|
||||
"sessionName": "设置session的cookie名默认HOTIME",
|
||||
"tlsCert": "https证书",
|
||||
"tlsKey": "https密钥",
|
||||
"tlsPort": "web服务https端口,0为不启用https服务",
|
||||
"tpt": "静态文件目录,默认为程序目录下tpt目录"
|
||||
}
|
||||
Binary file not shown.
+6
-94
@@ -1,106 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"code.hoteas.com/hoteas/hotime"
|
||||
"code.hoteas.com/hoteas/hotime/tools/db"
|
||||
"fmt"
|
||||
"strings"
|
||||
//"go.hoteas.com/hotime/cache"
|
||||
"golang.org/x/net/websocket"
|
||||
"time"
|
||||
//"go.hoteas.com/hotime/cache"
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
"code.hoteas.com/golang/hotime/example/app"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
appIns := hotime.Application{}
|
||||
appIns := Init("config/config.json")
|
||||
appIns.SetConnectListener(func(that *Context) (isFinished bool) {
|
||||
|
||||
appIns.SetConnectListener(func(context *hotime.Context) bool {
|
||||
fmt.Println(context.HandlerStr + time.Now().Format(" 2006-01-02 15:04 ") + hotime.Substr(context.Req.RemoteAddr, 0, strings.Index(context.Req.RemoteAddr, ":")))
|
||||
|
||||
//this.HandlerStr = "/test.html"
|
||||
return true
|
||||
return isFinished
|
||||
})
|
||||
|
||||
//手动模式,
|
||||
appIns.SetConfig("example/config/config.json")
|
||||
//redis缓存接入
|
||||
//ca:=hotime.CacheIns(&cache.CacheRedis{Host:appIns.Config.GetString("redisHost"),Pwd:appIns.Config.GetString("redisPwd"),Time:appIns.Config.GetCeilInt64("cacheLongTime")})
|
||||
//ca.Cache("xyzm","dasdas")
|
||||
//ca.Cache("xyzn","dasdas")
|
||||
//ca.Cache("xyzo","dasdas")
|
||||
//ca.Cache("xyz*",nil)
|
||||
//fmt.Println(ca.Cache("xyzm").Data)
|
||||
//mysql
|
||||
//mysql.SetDB(&appIns)
|
||||
//自动选择数据库
|
||||
db.SetDB(&appIns)
|
||||
appIns.Run(Router{"app": app.AppProj})
|
||||
|
||||
//appIns.SetConnectDB(func(err ...*hotime.Error) *sql.DB {
|
||||
// query := appIns.Config.GetString("dbUser") + ":" + appIns.Config.GetString("dbPwd") +
|
||||
// "@tcp(" + appIns.Config.GetString("dbHost") + ":" + appIns.Config.GetString("dbPort") + ")/" + appIns.Config.GetString("dbName") + "?charset=utf8"
|
||||
// DB, e := sql.Open("mysql", query)
|
||||
// if e != nil && len(err) != 0 {
|
||||
// err[0].SetError(e)
|
||||
// }
|
||||
// return DB
|
||||
//})
|
||||
//内存缓存数据库数据,错误则删除
|
||||
// appIns.Db.CacheIns=hotime.CacheIns(&hotime.CacheMemory{})
|
||||
|
||||
appIns.SetSession(hotime.CacheIns(&hotime.CacheMemory{}), hotime.CacheIns(&hotime.CacheDb{Db: &appIns.Db, Time: appIns.Config.GetInt64("cacheTime")}))
|
||||
appIns.SetCache(hotime.CacheIns(&hotime.CacheMemory{}))
|
||||
|
||||
//快捷模式
|
||||
//appIns.SetDefault(func(err ...*hotime.Error) *sql.DB {
|
||||
// query := appIns.Config.GetString("dbUser") + ":" + appIns.Config.GetString("dbPwd") +
|
||||
// "@tcp(" + appIns.Config.GetString("dbHost") + ":" + appIns.Config.GetString("dbPort") + ")/" + appIns.Config.GetString("dbName") + "?charset=utf8"
|
||||
// DB, e := sql.Open("mysql", query)
|
||||
// if e != nil && len(err) != 0 {
|
||||
// err[0].SetError(e)
|
||||
// }
|
||||
// return DB
|
||||
//})
|
||||
|
||||
appIns.Run(hotime.Router{
|
||||
"app": hotime.Proj{
|
||||
"index": hotime.Ctr{
|
||||
"test": func(this *hotime.Context) {
|
||||
fmt.Println(this.Db.GetTag())
|
||||
//x:=this.Db.Action(func(db hotime.HoTimeDB) bool {
|
||||
//
|
||||
// db.Insert("user",hotime.Map{"unickname":"dasdas"})
|
||||
//
|
||||
// return true
|
||||
//})
|
||||
this.Display(5, "dsadas")
|
||||
},
|
||||
"websocket": func(this *hotime.Context) {
|
||||
hdler := websocket.Handler(func(ws *websocket.Conn) {
|
||||
for true {
|
||||
msg := make([]byte, 5120)
|
||||
n, err := ws.Read(msg)
|
||||
go func() {
|
||||
time.Sleep(time.Second * 5)
|
||||
ws.Write([]byte("dsadasdasgregergrerge"))
|
||||
|
||||
}()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Printf("Receive: %s\n", msg[:n])
|
||||
|
||||
send_msg := "[" + string(msg[:n]) + "]"
|
||||
m, err := ws.Write([]byte(send_msg))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Printf("Send: %s\n", msg[:m])
|
||||
}
|
||||
})
|
||||
hdler.ServeHTTP(this.Resp, this.Req)
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<title>Sample of websocket with golang</title>
|
||||
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
var ws = new WebSocket("ws://localhost:8080/app/index/websocket");
|
||||
ws.onmessage = function(e) {
|
||||
|
||||
$('<li>').text(e.data).appendTo($ul);
|
||||
};
|
||||
var $ul = $('#msg-list');
|
||||
$('#sendBtn').click(function(){
|
||||
var data = $('#name').val();
|
||||
ws.send(data);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<input id="name" type="text"/>
|
||||
<input type="button" id="sendBtn" value="send"/>
|
||||
<ul id="msg-list"></ul>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
module code.hoteas.com/golang/hotime
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/360EntSecGroup-Skylar/excelize v1.4.1
|
||||
github.com/garyburd/redigo v1.6.3
|
||||
github.com/go-pay/gopay v1.5.78
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/mattn/go-sqlite3 v1.14.12
|
||||
github.com/silenceper/wechat/v2 v2.1.2
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.364
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ocr v1.0.364
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
|
||||
)
|
||||
@@ -0,0 +1,77 @@
|
||||
github.com/360EntSecGroup-Skylar/excelize v1.4.1 h1:l55mJb6rkkaUzOpSsgEeKYtS6/0gHwBYyfo5Jcjv/Ks=
|
||||
github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/garyburd/redigo v1.6.3 h1:HCeeRluvAgMusMomi1+6Y5dmFOdYV/JzoRrrbFlkGIc=
|
||||
github.com/garyburd/redigo v1.6.3/go.mod h1:rTb6epsqigu3kYKBnaF028A7Tf/Aw5s0cqA47doKKqw=
|
||||
github.com/go-pay/gopay v1.5.78 h1:wIHp8g/jK0ik5bZo2MWt3jAQsktT3nkdXZxlRZvljko=
|
||||
github.com/go-pay/gopay v1.5.78/go.mod h1:M6Nlk2VdZHCbWphOw3rtbnz4SiOk6Xvxg6mxwDfg+Ps=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/gomodule/redigo v1.8.5 h1:nRAxCa+SVsyjSBrtZmG/cqb6VbTmuRzpg/PoTFlpumc=
|
||||
github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
|
||||
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/silenceper/wechat/v2 v2.1.2 h1:+QfIMiYfwST2ZloTwmYp0O0p5Y1LYRZxfLWfMuSE30k=
|
||||
github.com/silenceper/wechat/v2 v2.1.2/go.mod h1:0OprxYCCp2CZAKw06BBlnaczInTk2KxOLsKeiopshGg=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.364 h1:X1Jws4XqrTH+p7FBQ7BpjW4qFXObKHWm0/XhW/GvqRs=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.364/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ocr v1.0.364 h1:kbor60vo37v7Hu+i17gooox9Rw281fVHNna8zwtDG1w=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ocr v1.0.364/go.mod h1:LeIUBOLhc+Y5YCEpZrULPD9lgoXXV4/EmIcoEvmHz9c=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetLog(path string, showCodeLine bool) *log.Logger {
|
||||
|
||||
hook := MyHook{
|
||||
Field: "line",
|
||||
Skip: 5,
|
||||
Path: path, ShowCodeLine: showCodeLine,
|
||||
}
|
||||
|
||||
loger := log.New()
|
||||
loger.SetFormatter(&log.TextFormatter{})
|
||||
loger.AddHook(&hook)
|
||||
return loger
|
||||
}
|
||||
|
||||
// MyHook ...
|
||||
type MyHook struct {
|
||||
Path string //存储日志的位置
|
||||
ShowCodeLine bool //输出代码文件名称和日志行
|
||||
Field string
|
||||
Skip int
|
||||
levels []log.Level
|
||||
}
|
||||
|
||||
// Levels 只定义 error 和 panic 等级的日志,其他日志等级不会触发 hook
|
||||
func (that *MyHook) Levels() []log.Level {
|
||||
return log.AllLevels
|
||||
}
|
||||
|
||||
// Fire 将异常日志写入到指定日志文件中
|
||||
func (that *MyHook) Fire(entry *log.Entry) error {
|
||||
if that.ShowCodeLine {
|
||||
entry.Data[that.Field] = findCaller(that.Skip)
|
||||
}
|
||||
//不需要存储到文件
|
||||
if that.Path == "" {
|
||||
return nil
|
||||
}
|
||||
//存储到文件
|
||||
logFilePath := time.Now().Format(that.Path)
|
||||
|
||||
err := os.MkdirAll(filepath.Dir(logFilePath), os.ModeAppend)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//os.Create(logFilePath)
|
||||
f, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bte, _ := entry.Bytes()
|
||||
_, err = f.Write(bte)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 对caller进行递归查询, 直到找到非logrus包产生的第一个调用.
|
||||
// 因为filename我获取到了上层目录名, 因此所有logrus包的调用的文件名都是 logrus/...
|
||||
// 因此通过排除logrus开头的文件名, 就可以排除所有logrus包的自己的函数调用
|
||||
func findCaller(skip int) string {
|
||||
file := ""
|
||||
line := 0
|
||||
for i := 0; i < 10; i++ {
|
||||
file, line = getCaller(skip + i)
|
||||
if !strings.HasPrefix(file, "logrus") {
|
||||
j := 0
|
||||
for true {
|
||||
j++
|
||||
if file == "common/error.go" {
|
||||
file, line = getCaller(skip + i + j)
|
||||
}
|
||||
if file == "db/hotimedb.go" {
|
||||
file, line = getCaller(skip + i + j)
|
||||
}
|
||||
if file == "code/makecode.go" {
|
||||
file, line = getCaller(skip + i + j)
|
||||
}
|
||||
if strings.Index(file, "common/") == 0 {
|
||||
file, line = getCaller(skip + i + j)
|
||||
}
|
||||
if strings.Contains(file, "application.go") {
|
||||
file, line = getCaller(skip + i + j)
|
||||
}
|
||||
if j == 5 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
|
||||
// 这里其实可以获取函数名称的: fnName := runtime.FuncForPC(pc).Name()
|
||||
// 但是我觉得有 文件名和行号就够定位问题, 因此忽略了caller返回的第一个值:pc
|
||||
// 在标准库log里面我们可以选择记录文件的全路径或者文件名, 但是在使用过程成并发最合适的,
|
||||
// 因为文件的全路径往往很长, 而文件名在多个包中往往有重复, 因此这里选择多取一层, 取到文件所在的上层目录那层.
|
||||
func getCaller(skip int) (string, int) {
|
||||
_, file, line, ok := runtime.Caller(skip)
|
||||
//fmt.Println(file)
|
||||
//fmt.Println(line)
|
||||
if !ok {
|
||||
return "", 0
|
||||
}
|
||||
n := 0
|
||||
for i := len(file) - 1; i > 0; i-- {
|
||||
if file[i] == '/' {
|
||||
n++
|
||||
if n >= 2 {
|
||||
file = file[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return file, line
|
||||
}
|
||||
+7
-7
@@ -1,9 +1,9 @@
|
||||
package manage
|
||||
|
||||
import (
|
||||
"code.hoteas.com/golang/hotime"
|
||||
"fmt"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"go.hoteas.com/hotime"
|
||||
"golang.org/x/net/websocket"
|
||||
"io"
|
||||
"os"
|
||||
@@ -44,17 +44,17 @@ func Run() {
|
||||
appIns.Run(hotime.Router{
|
||||
"app": hotime.Proj{
|
||||
"index": hotime.Ctr{
|
||||
"test": func(this *hotime.Context) {
|
||||
fmt.Println(this.Db.GetTag())
|
||||
//x := this.Db.Action(func(db hotime.HoTimeDB) bool {
|
||||
"test": func(that *hotime.Context) {
|
||||
fmt.Println(that.Db.GetTag())
|
||||
//x := that.Db.Action(func(db hotime.HoTimeDB) bool {
|
||||
//
|
||||
// db.Insert("user", hotime.Map{"unickname": "dasdas"})
|
||||
//
|
||||
// return true
|
||||
//})
|
||||
this.Display(0, 1)
|
||||
that.Display(0, 1)
|
||||
},
|
||||
"websocket": func(this *hotime.Context) {
|
||||
"websocket": func(that *hotime.Context) {
|
||||
hdler := websocket.Handler(func(ws *websocket.Conn) {
|
||||
for true {
|
||||
msg := make([]byte, 5120)
|
||||
@@ -73,7 +73,7 @@ func Run() {
|
||||
fmt.Printf("Send: %s\n", msg[:m])
|
||||
}
|
||||
})
|
||||
hdler.ServeHTTP(this.Resp, this.Req)
|
||||
hdler.ServeHTTP(that.Resp, that.Req)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
//hotime的常用map
|
||||
type Map map[string]interface{}
|
||||
|
||||
//获取string
|
||||
func (this Map) GetString(key string, err ...*Error) string {
|
||||
|
||||
if len(err) != 0 {
|
||||
err[0].SetError(nil)
|
||||
}
|
||||
return ObjToStr((this)[key])
|
||||
|
||||
}
|
||||
|
||||
func (this *Map) Pointer() *Map {
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
//增加接口
|
||||
func (this Map) Put(key string, value interface{}) {
|
||||
//if this==nil{
|
||||
// this=Map{}
|
||||
//}
|
||||
this[key] = value
|
||||
}
|
||||
|
||||
//删除接口
|
||||
func (this Map) Delete(key string) {
|
||||
delete(this, key)
|
||||
|
||||
}
|
||||
|
||||
//获取Int
|
||||
func (this Map) GetInt(key string, err ...*Error) int {
|
||||
v := ObjToInt((this)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取Int
|
||||
func (this Map) GetInt64(key string, err ...*Error) int64 {
|
||||
v := ObjToInt64((this)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取向上取整Int64
|
||||
func (this Map) GetCeilInt64(key string, err ...*Error) int64 {
|
||||
v := ObjToCeilInt64((this)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取向上取整Int
|
||||
func (this Map) GetCeilInt(key string, err ...*Error) int {
|
||||
v := ObjToCeilInt((this)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取向上取整float64
|
||||
func (this Map) GetCeilFloat64(key string, err ...*Error) float64 {
|
||||
v := ObjToCeilFloat64((this)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取Float64
|
||||
func (this Map) GetFloat64(key string, err ...*Error) float64 {
|
||||
|
||||
v := ObjToFloat64((this)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (this Map) GetSlice(key string, err ...*Error) Slice {
|
||||
|
||||
//var v Slice
|
||||
v := ObjToSlice((this)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (this Map) GetMap(key string, err ...*Error) Map {
|
||||
//var data Slice
|
||||
|
||||
v := ObjToMap((this)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (this Map) Get(key string, err ...*Error) interface{} {
|
||||
|
||||
if v, ok := (this)[key]; ok {
|
||||
return v
|
||||
}
|
||||
e := errors.New("没有存储key及对应的数据")
|
||||
|
||||
if len(err) != 0 {
|
||||
err[0].SetError(e)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//请传递指针过来
|
||||
func (this Map) ToStruct(stct interface{}) {
|
||||
|
||||
data := reflect.ValueOf(stct).Elem()
|
||||
for k, v := range this {
|
||||
ks := StrFirstToUpper(k)
|
||||
dkey := data.FieldByName(ks)
|
||||
if !dkey.IsValid() {
|
||||
continue
|
||||
}
|
||||
switch dkey.Type().String() {
|
||||
case "int":
|
||||
dkey.SetInt(this.GetInt64(k))
|
||||
case "int64":
|
||||
dkey.Set(reflect.ValueOf(this.GetInt64(k)))
|
||||
case "float64":
|
||||
dkey.Set(reflect.ValueOf(this.GetFloat64(k)))
|
||||
case "string":
|
||||
dkey.Set(reflect.ValueOf(this.GetString(k)))
|
||||
case "interface{}":
|
||||
dkey.Set(reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (this Map) ToJsonString() string {
|
||||
return ObjToStr(this)
|
||||
|
||||
}
|
||||
|
||||
func (this Map) JsonToMap(jsonStr string, err ...*Error) {
|
||||
e := json.Unmarshal([]byte(jsonStr), &this)
|
||||
if e != nil && len(err) != 0 {
|
||||
err[0].SetError(e)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package hotime
|
||||
|
||||
//对象封装方便取用
|
||||
type Obj struct {
|
||||
Data interface{}
|
||||
contextBase
|
||||
}
|
||||
|
||||
func (this *Obj) Put(data interface{}) {
|
||||
this.Data = data
|
||||
}
|
||||
|
||||
func (this *Obj) ToInt(err ...Error) int {
|
||||
if len(err) != 0 {
|
||||
this.Error = err[0]
|
||||
}
|
||||
return ObjToInt(this.Data, &this.Error)
|
||||
}
|
||||
|
||||
func (this *Obj) ToInt64(err ...Error) int64 {
|
||||
if len(err) != 0 {
|
||||
this.Error = err[0]
|
||||
}
|
||||
return ObjToInt64(this.Data, &this.Error)
|
||||
}
|
||||
|
||||
func (this *Obj) ToFloat64(err ...Error) float64 {
|
||||
if len(err) != 0 {
|
||||
this.Error = err[0]
|
||||
}
|
||||
return ObjToFloat64(this.Data, &this.Error)
|
||||
}
|
||||
|
||||
//获取向上取整float64
|
||||
func (this *Obj) ToCeilFloat64(err ...*Error) float64 {
|
||||
if len(err) != 0 {
|
||||
this.Error = *err[0]
|
||||
}
|
||||
v := ObjToCeilFloat64(this.Data, err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (this *Obj) ToStr() string {
|
||||
|
||||
return ObjToStr(this.Data)
|
||||
}
|
||||
|
||||
func (this *Obj) ToMap(err ...Error) Map {
|
||||
if len(err) != 0 {
|
||||
this.Error = err[0]
|
||||
}
|
||||
return ObjToMap(this.Data, &this.Error)
|
||||
}
|
||||
|
||||
func (this *Obj) ToSlice(err ...Error) Slice {
|
||||
if len(err) != 0 {
|
||||
this.Error = err[0]
|
||||
}
|
||||
return ObjToSlice(this.Data, &this.Error)
|
||||
}
|
||||
|
||||
func (this *Obj) ToMapArray(err ...Error) []Map {
|
||||
if len(err) != 0 {
|
||||
this.Error = err[0]
|
||||
}
|
||||
return ObjToMapArray(this.Data, &this.Error)
|
||||
}
|
||||
|
||||
func (this *Obj) ToObj() interface{} {
|
||||
|
||||
return this.Data
|
||||
}
|
||||
|
||||
//获取向上取整Int64
|
||||
func (this *Obj) ToCeilInt64(err ...*Error) int64 {
|
||||
if len(err) != 0 {
|
||||
this.Error = *err[0]
|
||||
}
|
||||
v := ObjToCeilInt64(this.Data, err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取向上取整Int
|
||||
func (this *Obj) ToCeilInt(err ...*Error) int {
|
||||
if len(err) != 0 {
|
||||
this.Error = *err[0]
|
||||
}
|
||||
v := ObjToCeilInt(this.Data, err...)
|
||||
return v
|
||||
|
||||
}
|
||||
+26
-41
@@ -1,71 +1,56 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/cache"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
//session对象
|
||||
type SessionIns struct {
|
||||
ShortCache CacheIns
|
||||
LongCache CacheIns
|
||||
SessionId string
|
||||
*HoTimeCache
|
||||
SessionId string
|
||||
Map
|
||||
contextBase
|
||||
ContextBase
|
||||
}
|
||||
|
||||
func (this *SessionIns) set() {
|
||||
|
||||
if this.ShortCache != nil {
|
||||
this.ShortCache.Cache(HEAD_SESSION_ADD+this.SessionId, this.Map)
|
||||
}
|
||||
if this.LongCache != nil {
|
||||
this.LongCache.Cache(HEAD_SESSION_ADD+this.SessionId, this.Map)
|
||||
}
|
||||
|
||||
func (that *SessionIns) set() {
|
||||
that.HoTimeCache.Session(HEAD_SESSION_ADD+that.SessionId, that.Map)
|
||||
}
|
||||
|
||||
func (this *SessionIns) Session(key string, data ...interface{}) *Obj {
|
||||
func (that *SessionIns) Session(key string, data ...interface{}) *Obj {
|
||||
|
||||
if this.Map == nil {
|
||||
this.get()
|
||||
if that.Map == nil {
|
||||
that.get()
|
||||
}
|
||||
|
||||
if len(data) != 0 {
|
||||
if data[0] == nil {
|
||||
delete(this.Map, key)
|
||||
this.set()
|
||||
delete(that.Map, key)
|
||||
that.set()
|
||||
} else {
|
||||
this.Map[key] = data[0]
|
||||
this.set()
|
||||
that.Map[key] = data[0]
|
||||
that.set()
|
||||
}
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
|
||||
return &Obj{Data: this.Map.Get(key)}
|
||||
return &Obj{Data: that.Map.Get(key)}
|
||||
|
||||
}
|
||||
|
||||
func (this *SessionIns) get() {
|
||||
func (that *SessionIns) get() {
|
||||
|
||||
if this.ShortCache != nil {
|
||||
this.Map = this.ShortCache.Cache(HEAD_SESSION_ADD + this.SessionId).ToMap()
|
||||
if this.Map != nil {
|
||||
return
|
||||
}
|
||||
that.Map = that.HoTimeCache.Session(HEAD_SESSION_ADD + that.SessionId).ToMap()
|
||||
if that.Map != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if this.LongCache != nil {
|
||||
this.Map = this.LongCache.Cache(HEAD_SESSION_ADD + this.SessionId).ToMap()
|
||||
if this.Map != nil {
|
||||
if this.ShortCache != nil {
|
||||
this.ShortCache.Cache(HEAD_SESSION_ADD+this.SessionId, this.Map)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
this.Map = Map{}
|
||||
this.ShortCache.Cache(HEAD_SESSION_ADD+this.SessionId, this.Map)
|
||||
that.Map = Map{}
|
||||
that.HoTimeCache.Session(HEAD_SESSION_ADD+that.SessionId, that.Map)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *SessionIns) Init(short CacheIns, long CacheIns) {
|
||||
this.ShortCache = short
|
||||
this.LongCache = long
|
||||
func (that *SessionIns) Init(cache *HoTimeCache) {
|
||||
that.HoTimeCache = cache
|
||||
}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Slice []interface{}
|
||||
|
||||
//获取string
|
||||
func (this Slice) GetString(key int, err ...*Error) string {
|
||||
if len(err) != 0 {
|
||||
err[0].SetError(nil)
|
||||
}
|
||||
return ObjToStr((this)[key])
|
||||
}
|
||||
|
||||
//获取Int
|
||||
func (this Slice) GetInt(key int, err ...*Error) int {
|
||||
v := ObjToInt((this)[key], err...)
|
||||
return v
|
||||
}
|
||||
|
||||
//获取Int
|
||||
func (this Slice) GetInt64(key int, err ...*Error) int64 {
|
||||
v := ObjToInt64((this)[key], err...)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
//获取向上取整Int64
|
||||
func (this Slice) GetCeilInt64(key int, err ...*Error) int64 {
|
||||
v := ObjToCeilInt64((this)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取向上取整Int
|
||||
func (this Slice) GetCeilInt(key int, err ...*Error) int {
|
||||
v := ObjToCeilInt((this)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取向上取整float64
|
||||
func (this Slice) GetCeilFloat64(key int, err ...*Error) float64 {
|
||||
v := ObjToCeilFloat64((this)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
//获取Float64
|
||||
func (this Slice) GetFloat64(key int, err ...*Error) float64 {
|
||||
v := ObjToFloat64((this)[key], err...)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (this Slice) GetSlice(key int, err ...*Error) Slice {
|
||||
v := ObjToSlice((this)[key], err...)
|
||||
return v
|
||||
}
|
||||
|
||||
func (this Slice) GetMap(key int, err ...*Error) Map {
|
||||
//var v Map
|
||||
v := ObjToMap((this)[key], err...)
|
||||
return v
|
||||
}
|
||||
|
||||
func (this Slice) Get(key int, err ...*Error) interface{} {
|
||||
|
||||
if key < len(this) {
|
||||
return this[key]
|
||||
}
|
||||
e := errors.New("没有存储key及对应的数据")
|
||||
if len(err) != 0 {
|
||||
err[0].SetError(e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this Slice) Put(key int, value interface{}) {
|
||||
this[key] = value
|
||||
}
|
||||
|
||||
func (this Slice) ToJsonString() string {
|
||||
return ObjToStr(this)
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"code.hoteas.com/hoteas/hotime"
|
||||
"code.hoteas.com/hoteas/hotime/tools/mysql"
|
||||
"code.hoteas.com/hoteas/hotime/tools/sqlite"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func SetDB(appIns *hotime.Application) {
|
||||
if appIns.Config.GetString("dbType") == "sqlite" || strings.Index(appIns.Config.GetString("dbName"), ".db") == len(appIns.Config.GetString("dbName"))-3 {
|
||||
appIns.Config["dbType"] = "sqlite"
|
||||
sqlite.SetDB(appIns)
|
||||
} else {
|
||||
appIns.Config["dbType"] = "mysql"
|
||||
mysql.SetDB(appIns)
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"code.hoteas.com/hoteas/hotime"
|
||||
"database/sql"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
func SetDB(appIns *hotime.Application) {
|
||||
appIns.SetConnectDB(func(err ...*hotime.Error) *sql.DB {
|
||||
query := appIns.Config.GetString("dbUser") + ":" + appIns.Config.GetString("dbPwd") +
|
||||
"@tcp(" + appIns.Config.GetString("dbHost") + ":" + appIns.Config.GetString("dbPort") + ")/" + appIns.Config.GetString("dbName") + "?charset=utf8"
|
||||
DB, e := sql.Open("mysql", query)
|
||||
if e != nil && len(err) != 0 {
|
||||
err[0].SetError(e)
|
||||
}
|
||||
return DB
|
||||
})
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"code.hoteas.com/hoteas/hotime"
|
||||
"database/sql"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func SetDB(appIns *hotime.Application) {
|
||||
appIns.SetConnectDB(func(err ...*hotime.Error) *sql.DB {
|
||||
db, e := sql.Open("sqlite3", appIns.Config.GetString("dbName"))
|
||||
if e != nil && len(err) != 0 {
|
||||
err[0].SetError(e)
|
||||
}
|
||||
return db
|
||||
})
|
||||
}
|
||||
@@ -1,21 +1,8 @@
|
||||
package hotime
|
||||
|
||||
//控制器
|
||||
// Ctr 控制器
|
||||
type Ctr map[string]Method
|
||||
type Proj map[string]Ctr
|
||||
type Router map[string]Proj
|
||||
type MethodRouter map[string]Method //直接字符串关联函数
|
||||
type Method func(this *Context)
|
||||
|
||||
type CacheIns interface {
|
||||
//set(key string, value interface{}, time int64)
|
||||
//get(key string) interface{}
|
||||
//delete(key string)
|
||||
Cache(key string, data ...interface{}) *Obj
|
||||
}
|
||||
|
||||
//单条缓存数据
|
||||
type cacheData struct {
|
||||
time int64
|
||||
data interface{}
|
||||
}
|
||||
type Method func(that *Context)
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var IsRun = false //当前状态
|
||||
var App = map[string]*Application{} //整个项目
|
||||
|
||||
//var Db = HoTimeDB{} //数据库实例
|
||||
|
||||
var Config = Map{
|
||||
"debug": 1, //debug 0关闭1开启
|
||||
"logLevel": LOG_NIL,
|
||||
"dbHost": "127.0.0.1",
|
||||
"dbName": "test",
|
||||
"dbUser": "root",
|
||||
"dbPwd": "root",
|
||||
"dbPort": "3306",
|
||||
"dbCached": 0, //0不开启缓存
|
||||
"cacheShortTime": 60 * 60 * 2,
|
||||
"cacheLongTime": 60 * 60 * 24 * 30,
|
||||
"mode": 2, //模式 0生产模式,1,测试模式,2,开发模式
|
||||
//"codeConfig": Map{
|
||||
// "admin": "config/app.json",
|
||||
//},
|
||||
"codeConfig": Slice{
|
||||
Map{
|
||||
"table": "admin", //默认admin,必须,有则根据数据库内当前表名做为用户生成数据
|
||||
"name": "", //默认admin,非必须,有则生成代码到此目录,无则采用缺省模式使用表名
|
||||
"config": "config/app.json", //默认config/app.json,必须,接口描述配置文件,无则自动生成
|
||||
"rule": "config/rule.json", //默认config/rule.json,非必须,有则按改规则生成接口,无则按系统内嵌方式生成
|
||||
"mode": 0, //默认0,非必须,0为内嵌代码模式,1为生成代码模式
|
||||
},
|
||||
},
|
||||
"db": Map{
|
||||
"sqlite": Map{
|
||||
"path": "config/data.db",
|
||||
},
|
||||
},
|
||||
"cache": Map{
|
||||
"memory": Map{
|
||||
"timeout": 60 * 60 * 2,
|
||||
"db": true,
|
||||
"session": true,
|
||||
},
|
||||
},
|
||||
"error": Map{
|
||||
"1": "内部系统异常",
|
||||
"2": "访问权限异常",
|
||||
@@ -23,31 +42,81 @@ var Config = Map{
|
||||
"4": "数据处理异常",
|
||||
"5": "数据结果异常",
|
||||
},
|
||||
"tpt": "tpt",
|
||||
"defFile": []string{"index.html", "index.htm"},
|
||||
"modeRouterStrict": false, //路由严格模式/a/b/c
|
||||
"port": "0",
|
||||
"sessionName": "HOTIME",
|
||||
"tlsPort": "0",
|
||||
"tlsKey": "",
|
||||
"tlsCert": "",
|
||||
"tpt": "tpt",
|
||||
"defFile": []string{"index.html", "index.htm"},
|
||||
"port": "80",
|
||||
"sessionName": "HOTIME",
|
||||
}
|
||||
|
||||
var ConfigNote = Map{
|
||||
"logLevel": "日志等级,0打印,1关闭,2,记录到文件",
|
||||
"debug": "是否开启debug模式,0关闭,其他开启,debug模式下日志展示更全", //debug 0关闭1开启
|
||||
"dbHost": "数据库ip地址默认127.0.0.1",
|
||||
"dbName": "数据库名称,sqlite为文件路径比如a/x.db",
|
||||
"dbUser": "数据库用户名",
|
||||
"dbPwd": "数据库密码",
|
||||
"dbPort": "数据库端口",
|
||||
"dbType": "如果需要使用自动数据库配置,请设置此项,手动配置数据库不需要,目前支持mysql,sqlite",
|
||||
"dbCached": "是否开启数据库缓存0为关闭,其他开启", //0不开启缓存
|
||||
"redisHost": "如果需要使用redis服务时配置,默认服务ip:127.0.0.1",
|
||||
"redisPort": "如果需要使用redis服务时配置,默认服务端口:6379",
|
||||
"redisPwd": "如果需要使用redis服务时配置,默认服务密码:123456",
|
||||
"cacheShortTime": "两级缓存,短缓存存储时间60 * 60 * 2,一般为内存缓存",
|
||||
"cacheLongTime": "两级缓存,长缓存存储时间60 * 60 * 24 * 30,一般为数据库或者redis缓存",
|
||||
"logLevel": "默认0,必须,0关闭,1打印,日志等级",
|
||||
"logFile": "无默认,非必须,如果需要存储日志文件时使用,保存格式为:a/b/c/20060102150405.txt,将生成:a/b/c/年月日时分秒.txt,按需设置",
|
||||
"webConnectLogShow": "默认true,非必须,访问日志如果需要web访问链接、访问ip、访问时间打印,false为关闭true开启此功能",
|
||||
"webConnectLogFile": "无默认,非必须,webConnectLogShow开启之后才能使用,如果需要存储日志文件时使用,保存格式为:a/b/c/20060102150405.txt,将生成:a/b/c/年月日时分秒.txt,按需设置",
|
||||
"mode": "默认0,非必须,0生产模式,1,测试模式,2开发模式,3内嵌代码模式,在开发模式下会显示更多的数据用于开发测试,并能够辅助研发,自动生成配置文件、代码等功能,web无缓存,数据库不启用缓存", //debug 0关闭1开启
|
||||
//"codeConfig": Map{
|
||||
// "注释": "配置即启用,非必须,默认无",
|
||||
// //"package":"默认admin,必须,mode模式为2时会自动生成包文件夹和代码文件",
|
||||
// //"path":""
|
||||
//
|
||||
// "packageName": "默认无,必须,包名称以及应用名,生成代码的配置文件地址,比如config/app.json,数据库有更新时自动更新配置文件以及对应的生成文件",
|
||||
//},
|
||||
"codeConfig": Slice{
|
||||
"注释:配置即启用,非必须,默认无",
|
||||
Map{ //默认无,必须,接口类别名称
|
||||
//"注释": "", //
|
||||
"table": "默认admin,必须,根据数据库内当前表名做为用户生成数据",
|
||||
"name": "默认无,非必须,有则生成代码到此目录,无则采用缺省模式使用表名,如设置为:admin,将在admin目录生成包名为admin的代码",
|
||||
"config": "默认config/app.json,必须,接口描述配置文件", //
|
||||
"configDB": "默认无,非必须,有则每次将数据库数据生成到此目录用于配置读写,无则不生成", //
|
||||
"rule": "默认config/rule.json,非必须,有则按改规则生成接口,无则按系统内嵌方式生成",
|
||||
"mode": "默认0,非必须,0为内嵌代码模式,1为生成代码模式",
|
||||
},
|
||||
},
|
||||
"db": Map{
|
||||
"注释": "配置即启用,非必须,默认使用sqlite数据库",
|
||||
"mysql": Map{
|
||||
"注释": "除prefix及主从数据库slave项,其他全部必须",
|
||||
"host": "默认127.0.0.1,必须,数据库ip地址",
|
||||
"name": "默认test,必须,数据库名称",
|
||||
"user": "默认root,必须,数据库用户名",
|
||||
"password": "默认root,必须,数据库密码",
|
||||
"port": "默认3306,必须,数据库端口",
|
||||
"prefix": "默认空,非必须,数据表前缀",
|
||||
"slave": Map{
|
||||
"注释": "从数据库配置,mysql里配置slave项即启用主从读写,减少数据库压力",
|
||||
"host": "默认127.0.0.1,必须,数据库ip地址",
|
||||
"name": "默认test,必须,数据库名称",
|
||||
"user": "默认root,必须,数据库用户名",
|
||||
"password": "默认root,必须,数据库密码",
|
||||
"port": "默认3306,必须,数据库端口",
|
||||
},
|
||||
},
|
||||
"sqlite": Map{
|
||||
"path": "默认config/data.db,必须,数据库位置",
|
||||
},
|
||||
},
|
||||
"cache": Map{
|
||||
"注释": "可配置memory,db,redis,默认启用memory,默认优先级为memory>redis>db,memory与数据库缓存设置项一致,缓存数据填充会自动反方向反哺,加入memory缓存过期将自动从redis更新,但memory永远不会更新redis,如果是集群建议不要开启memory,配置即启用",
|
||||
"memory": Map{
|
||||
"timeout": "默认60 * 60 * 2,非必须,过期时间,超时自动删除",
|
||||
"db": "默认true,非必须,缓存数据库,启用后能减少数据库的读写压力",
|
||||
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
|
||||
},
|
||||
"db": Map{
|
||||
"timeout": "默认60 * 60 * 24 * 30,非必须,过期时间,超时自动删除",
|
||||
"db": "默认false,非必须,缓存数据库,启用后能减少数据库的读写压力",
|
||||
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
|
||||
},
|
||||
"redis": Map{
|
||||
"host": "默认服务ip:127.0.0.1,必须,如果需要使用redis服务时配置,",
|
||||
"port": "默认服务端口:6379,必须,如果需要使用redis服务时配置,",
|
||||
"password": "默认密码空,必须,如果需要使用redis服务时配置,默认密码空",
|
||||
"timeout": "默认60 * 60 * 24 * 15,非必须,过期时间,超时自动删除",
|
||||
"db": "默认true,非必须,缓存数据库,启用后能减少数据库的读写压力",
|
||||
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
|
||||
},
|
||||
},
|
||||
"error": Map{
|
||||
"1": "内部系统异常,在环境配置,文件访问权限等基础运行环境条件不足造成严重错误时使用",
|
||||
"2": "访问权限异常,没有登录或者登录异常等时候使用",
|
||||
@@ -56,14 +125,13 @@ var ConfigNote = Map{
|
||||
"5": "数据结果异常,一般用于无法给出response要求的格式要求下使用,比如response需要的是string格式但你只能提供int数据时",
|
||||
"注释": "web服务内置错误提示,自定义异常建议10开始",
|
||||
},
|
||||
"tpt": "静态文件目录,默认为程序目录下tpt目录",
|
||||
"defFile": "默认访问文件,默认访问index.html或者index.htm文件",
|
||||
"crossDomain": "如果需要跨域设置,空字符串为不开启,*为开启所有网站允许跨域,http://www.baidu.com为指定域允许跨域", //是否开启跨域
|
||||
"modeRouterStrict": "路由严格模式false,为大小写忽略必须匹配,true必须大小写匹配", //路由严格模式/a/b/c
|
||||
"connectLogShow": "如果需要web访问链接、访问ip、访问时间打印,0为关闭其他数字开启此功能",
|
||||
"sessionName": "设置session的cookie名默认HOTIME",
|
||||
"port": "web服务开启Http端口,0为不启用http服务",
|
||||
"tlsPort": "web服务https端口,0为不启用https服务",
|
||||
"tlsKey": "https密钥",
|
||||
"tlsCert": "https证书",
|
||||
"tpt": "默认tpt,必须,web静态文件目录,默认为程序目录下tpt目录",
|
||||
"defFile": "默认访问index.html或者index.htm文件,必须,默认访问文件类型",
|
||||
"crossDomain": "默认空 非必须,空字符串为不开启,如果需要跨域设置,auto为智能开启所有网站允许跨域,http://www.baidu.com为指定域允许跨域", //是否开启跨域
|
||||
"modeRouterStrict": "默认false,必须,路由严格模式false,为大小写忽略必须匹配,true必须大小写匹配", //路由严格模式/a/b/c
|
||||
"sessionName": "默认HOTIME,必须,设置session的cookie名",
|
||||
"port": "默认80,必须,web服务开启Http端口,0为不启用http服务,默认80",
|
||||
"tlsPort": "默认空,非必须,web服务https端口,0为不启用https服务",
|
||||
"tlsKey": "默认空,非必须,https密钥",
|
||||
"tlsCert": "默认空,非必须,https证书",
|
||||
}
|
||||
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+718
@@ -0,0 +1,718 @@
|
||||
/*
|
||||
Copyright 2011 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package memcache provides a client for the memcached cache server.
|
||||
package memcache
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Similar to:
|
||||
// https://godoc.org/google.golang.org/appengine/memcache
|
||||
|
||||
var (
|
||||
// ErrCacheMiss means that a Get failed because the item wasn't present.
|
||||
ErrCacheMiss = errors.New("memcache: cache miss")
|
||||
|
||||
// ErrCASConflict means that a CompareAndSwap call failed due to the
|
||||
// cached value being modified between the Get and the CompareAndSwap.
|
||||
// If the cached value was simply evicted rather than replaced,
|
||||
// ErrNotStored will be returned instead.
|
||||
ErrCASConflict = errors.New("memcache: compare-and-swap conflict")
|
||||
|
||||
// ErrNotStored means that a conditional write operation (i.e. Add or
|
||||
// CompareAndSwap) failed because the condition was not satisfied.
|
||||
ErrNotStored = errors.New("memcache: item not stored")
|
||||
|
||||
// ErrServer means that a server error occurred.
|
||||
ErrServerError = errors.New("memcache: server error")
|
||||
|
||||
// ErrNoStats means that no statistics were available.
|
||||
ErrNoStats = errors.New("memcache: no statistics available")
|
||||
|
||||
// ErrMalformedKey is returned when an invalid key is used.
|
||||
// Keys must be at maximum 250 bytes long and not
|
||||
// contain whitespace or control characters.
|
||||
ErrMalformedKey = errors.New("malformed: key is too long or contains invalid characters")
|
||||
|
||||
// ErrNoServers is returned when no servers are configured or available.
|
||||
ErrNoServers = errors.New("memcache: no servers configured or available")
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultTimeout is the default socket read/write timeout.
|
||||
DefaultTimeout = 100 * time.Millisecond
|
||||
|
||||
// DefaultMaxIdleConns is the default maximum number of idle connections
|
||||
// kept for any single address.
|
||||
DefaultMaxIdleConns = 2
|
||||
)
|
||||
|
||||
const buffered = 8 // arbitrary buffered channel size, for readability
|
||||
|
||||
// resumableError returns true if err is only a protocol-level cache error.
|
||||
// This is used to determine whether or not a server connection should
|
||||
// be re-used or not. If an error occurs, by default we don't reuse the
|
||||
// connection, unless it was just a cache error.
|
||||
func resumableError(err error) bool {
|
||||
switch err {
|
||||
case ErrCacheMiss, ErrCASConflict, ErrNotStored, ErrMalformedKey:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func legalKey(key string) bool {
|
||||
if len(key) > 250 {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(key); i++ {
|
||||
if key[i] <= ' ' || key[i] == 0x7f {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
crlf = []byte("\r\n")
|
||||
space = []byte(" ")
|
||||
resultOK = []byte("OK\r\n")
|
||||
resultStored = []byte("STORED\r\n")
|
||||
resultNotStored = []byte("NOT_STORED\r\n")
|
||||
resultExists = []byte("EXISTS\r\n")
|
||||
resultNotFound = []byte("NOT_FOUND\r\n")
|
||||
resultDeleted = []byte("DELETED\r\n")
|
||||
resultEnd = []byte("END\r\n")
|
||||
resultOk = []byte("OK\r\n")
|
||||
resultTouched = []byte("TOUCHED\r\n")
|
||||
|
||||
resultClientErrorPrefix = []byte("CLIENT_ERROR ")
|
||||
versionPrefix = []byte("VERSION")
|
||||
)
|
||||
|
||||
// New returns a memcache client using the provided server(s)
|
||||
// with equal weight. If a server is listed multiple times,
|
||||
// it gets a proportional amount of weight.
|
||||
func New(server ...string) *Client {
|
||||
ss := new(ServerList)
|
||||
ss.SetServers(server...)
|
||||
return NewFromSelector(ss)
|
||||
}
|
||||
|
||||
// NewFromSelector returns a new Client using the provided ServerSelector.
|
||||
func NewFromSelector(ss ServerSelector) *Client {
|
||||
return &Client{selector: ss}
|
||||
}
|
||||
|
||||
// Client is a memcache client.
|
||||
// It is safe for unlocked use by multiple concurrent goroutines.
|
||||
type Client struct {
|
||||
// Timeout specifies the socket read/write timeout.
|
||||
// If zero, DefaultTimeout is used.
|
||||
Timeout time.Duration
|
||||
|
||||
// MaxIdleConns specifies the maximum number of idle connections that will
|
||||
// be maintained per address. If less than one, DefaultMaxIdleConns will be
|
||||
// used.
|
||||
//
|
||||
// Consider your expected traffic rates and latency carefully. This should
|
||||
// be set to a number higher than your peak parallel requests.
|
||||
MaxIdleConns int
|
||||
|
||||
selector ServerSelector
|
||||
|
||||
lk sync.Mutex
|
||||
freeconn map[string][]*conn
|
||||
}
|
||||
|
||||
// Item is an item to be got or stored in a memcached server.
|
||||
type Item struct {
|
||||
// Key is the Item's key (250 bytes maximum).
|
||||
Key string
|
||||
|
||||
// Value is the Item's value.
|
||||
Value []byte
|
||||
|
||||
// Flags are server-opaque flags whose semantics are entirely
|
||||
// up to the app.
|
||||
Flags uint32
|
||||
|
||||
// Expiration is the cache expiration time, in seconds: either a relative
|
||||
// time from now (up to 1 month), or an absolute Unix epoch time.
|
||||
// Zero means the Item has no expiration time.
|
||||
Expiration int32
|
||||
|
||||
// Compare and swap ID.
|
||||
casid uint64
|
||||
}
|
||||
|
||||
// conn is a connection to a server.
|
||||
type conn struct {
|
||||
nc net.Conn
|
||||
rw *bufio.ReadWriter
|
||||
addr net.Addr
|
||||
c *Client
|
||||
}
|
||||
|
||||
// release returns this connection back to the client's free pool
|
||||
func (cn *conn) release() {
|
||||
cn.c.putFreeConn(cn.addr, cn)
|
||||
}
|
||||
|
||||
func (cn *conn) extendDeadline() {
|
||||
cn.nc.SetDeadline(time.Now().Add(cn.c.netTimeout()))
|
||||
}
|
||||
|
||||
// condRelease releases this connection if the error pointed to by err
|
||||
// is nil (not an error) or is only a protocol level error (e.g. a
|
||||
// cache miss). The purpose is to not recycle TCP connections that
|
||||
// are bad.
|
||||
func (cn *conn) condRelease(err *error) {
|
||||
if *err == nil || resumableError(*err) {
|
||||
cn.release()
|
||||
} else {
|
||||
cn.nc.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) putFreeConn(addr net.Addr, cn *conn) {
|
||||
c.lk.Lock()
|
||||
defer c.lk.Unlock()
|
||||
if c.freeconn == nil {
|
||||
c.freeconn = make(map[string][]*conn)
|
||||
}
|
||||
freelist := c.freeconn[addr.String()]
|
||||
if len(freelist) >= c.maxIdleConns() {
|
||||
cn.nc.Close()
|
||||
return
|
||||
}
|
||||
c.freeconn[addr.String()] = append(freelist, cn)
|
||||
}
|
||||
|
||||
func (c *Client) getFreeConn(addr net.Addr) (cn *conn, ok bool) {
|
||||
c.lk.Lock()
|
||||
defer c.lk.Unlock()
|
||||
if c.freeconn == nil {
|
||||
return nil, false
|
||||
}
|
||||
freelist, ok := c.freeconn[addr.String()]
|
||||
if !ok || len(freelist) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
cn = freelist[len(freelist)-1]
|
||||
c.freeconn[addr.String()] = freelist[:len(freelist)-1]
|
||||
return cn, true
|
||||
}
|
||||
|
||||
func (c *Client) netTimeout() time.Duration {
|
||||
if c.Timeout != 0 {
|
||||
return c.Timeout
|
||||
}
|
||||
return DefaultTimeout
|
||||
}
|
||||
|
||||
func (c *Client) maxIdleConns() int {
|
||||
if c.MaxIdleConns > 0 {
|
||||
return c.MaxIdleConns
|
||||
}
|
||||
return DefaultMaxIdleConns
|
||||
}
|
||||
|
||||
// ConnectTimeoutError is the error type used when it takes
|
||||
// too long to connect to the desired host. This level of
|
||||
// detail can generally be ignored.
|
||||
type ConnectTimeoutError struct {
|
||||
Addr net.Addr
|
||||
}
|
||||
|
||||
func (cte *ConnectTimeoutError) Error() string {
|
||||
return "memcache: connect timeout to " + cte.Addr.String()
|
||||
}
|
||||
|
||||
func (c *Client) dial(addr net.Addr) (net.Conn, error) {
|
||||
type connError struct {
|
||||
cn net.Conn
|
||||
err error
|
||||
}
|
||||
|
||||
nc, err := net.DialTimeout(addr.Network(), addr.String(), c.netTimeout())
|
||||
if err == nil {
|
||||
return nc, nil
|
||||
}
|
||||
|
||||
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
||||
return nil, &ConnectTimeoutError{addr}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (c *Client) getConn(addr net.Addr) (*conn, error) {
|
||||
cn, ok := c.getFreeConn(addr)
|
||||
if ok {
|
||||
cn.extendDeadline()
|
||||
return cn, nil
|
||||
}
|
||||
nc, err := c.dial(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cn = &conn{
|
||||
nc: nc,
|
||||
addr: addr,
|
||||
rw: bufio.NewReadWriter(bufio.NewReader(nc), bufio.NewWriter(nc)),
|
||||
c: c,
|
||||
}
|
||||
cn.extendDeadline()
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
func (c *Client) onItem(item *Item, fn func(*Client, *bufio.ReadWriter, *Item) error) error {
|
||||
addr, err := c.selector.PickServer(item.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cn, err := c.getConn(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cn.condRelease(&err)
|
||||
if err = fn(c, cn.rw, item); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) FlushAll() error {
|
||||
return c.selector.Each(c.flushAllFromAddr)
|
||||
}
|
||||
|
||||
// Get gets the item for the given key. ErrCacheMiss is returned for a
|
||||
// memcache cache miss. The key must be at most 250 bytes in length.
|
||||
func (c *Client) Get(key string) (item *Item, err error) {
|
||||
err = c.withKeyAddr(key, func(addr net.Addr) error {
|
||||
return c.getFromAddr(addr, []string{key}, func(it *Item) { item = it })
|
||||
})
|
||||
if err == nil && item == nil {
|
||||
err = ErrCacheMiss
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Touch updates the expiry for the given key. The seconds parameter is either
|
||||
// a Unix timestamp or, if seconds is less than 1 month, the number of seconds
|
||||
// into the future at which time the item will expire. Zero means the item has
|
||||
// no expiration time. ErrCacheMiss is returned if the key is not in the cache.
|
||||
// The key must be at most 250 bytes in length.
|
||||
func (c *Client) Touch(key string, seconds int32) (err error) {
|
||||
return c.withKeyAddr(key, func(addr net.Addr) error {
|
||||
return c.touchFromAddr(addr, []string{key}, seconds)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) withKeyAddr(key string, fn func(net.Addr) error) (err error) {
|
||||
if !legalKey(key) {
|
||||
return ErrMalformedKey
|
||||
}
|
||||
addr, err := c.selector.PickServer(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fn(addr)
|
||||
}
|
||||
|
||||
func (c *Client) withAddrRw(addr net.Addr, fn func(*bufio.ReadWriter) error) (err error) {
|
||||
cn, err := c.getConn(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cn.condRelease(&err)
|
||||
return fn(cn.rw)
|
||||
}
|
||||
|
||||
func (c *Client) withKeyRw(key string, fn func(*bufio.ReadWriter) error) error {
|
||||
return c.withKeyAddr(key, func(addr net.Addr) error {
|
||||
return c.withAddrRw(addr, fn)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) getFromAddr(addr net.Addr, keys []string, cb func(*Item)) error {
|
||||
return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error {
|
||||
if _, err := fmt.Fprintf(rw, "gets %s\r\n", strings.Join(keys, " ")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := rw.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := parseGetResponse(rw.Reader, cb); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// flushAllFromAddr send the flush_all command to the given addr
|
||||
func (c *Client) flushAllFromAddr(addr net.Addr) error {
|
||||
return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error {
|
||||
if _, err := fmt.Fprintf(rw, "flush_all\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := rw.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
line, err := rw.ReadSlice('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case bytes.Equal(line, resultOk):
|
||||
break
|
||||
default:
|
||||
return fmt.Errorf("memcache: unexpected response line from flush_all: %q", string(line))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ping sends the version command to the given addr
|
||||
func (c *Client) ping(addr net.Addr) error {
|
||||
return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error {
|
||||
if _, err := fmt.Fprintf(rw, "version\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := rw.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
line, err := rw.ReadSlice('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case bytes.HasPrefix(line, versionPrefix):
|
||||
break
|
||||
default:
|
||||
return fmt.Errorf("memcache: unexpected response line from ping: %q", string(line))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) touchFromAddr(addr net.Addr, keys []string, expiration int32) error {
|
||||
return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error {
|
||||
for _, key := range keys {
|
||||
if _, err := fmt.Fprintf(rw, "touch %s %d\r\n", key, expiration); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := rw.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
line, err := rw.ReadSlice('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case bytes.Equal(line, resultTouched):
|
||||
break
|
||||
case bytes.Equal(line, resultNotFound):
|
||||
return ErrCacheMiss
|
||||
default:
|
||||
return fmt.Errorf("memcache: unexpected response line from touch: %q", string(line))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetMulti is a batch version of Get. The returned map from keys to
|
||||
// items may have fewer elements than the input slice, due to memcache
|
||||
// cache misses. Each key must be at most 250 bytes in length.
|
||||
// If no error is returned, the returned map will also be non-nil.
|
||||
func (c *Client) GetMulti(keys []string) (map[string]*Item, error) {
|
||||
var lk sync.Mutex
|
||||
m := make(map[string]*Item)
|
||||
addItemToMap := func(it *Item) {
|
||||
lk.Lock()
|
||||
defer lk.Unlock()
|
||||
m[it.Key] = it
|
||||
}
|
||||
|
||||
keyMap := make(map[net.Addr][]string)
|
||||
for _, key := range keys {
|
||||
if !legalKey(key) {
|
||||
return nil, ErrMalformedKey
|
||||
}
|
||||
addr, err := c.selector.PickServer(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyMap[addr] = append(keyMap[addr], key)
|
||||
}
|
||||
|
||||
ch := make(chan error, buffered)
|
||||
for addr, keys := range keyMap {
|
||||
go func(addr net.Addr, keys []string) {
|
||||
ch <- c.getFromAddr(addr, keys, addItemToMap)
|
||||
}(addr, keys)
|
||||
}
|
||||
|
||||
var err error
|
||||
for _ = range keyMap {
|
||||
if ge := <-ch; ge != nil {
|
||||
err = ge
|
||||
}
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
||||
// parseGetResponse reads a GET response from r and calls cb for each
|
||||
// read and allocated Item
|
||||
func parseGetResponse(r *bufio.Reader, cb func(*Item)) error {
|
||||
for {
|
||||
line, err := r.ReadSlice('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bytes.Equal(line, resultEnd) {
|
||||
return nil
|
||||
}
|
||||
it := new(Item)
|
||||
size, err := scanGetResponseLine(line, it)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
it.Value = make([]byte, size+2)
|
||||
_, err = io.ReadFull(r, it.Value)
|
||||
if err != nil {
|
||||
it.Value = nil
|
||||
return err
|
||||
}
|
||||
if !bytes.HasSuffix(it.Value, crlf) {
|
||||
it.Value = nil
|
||||
return fmt.Errorf("memcache: corrupt get result read")
|
||||
}
|
||||
it.Value = it.Value[:size]
|
||||
cb(it)
|
||||
}
|
||||
}
|
||||
|
||||
// scanGetResponseLine populates it and returns the declared size of the item.
|
||||
// It does not read the bytes of the item.
|
||||
func scanGetResponseLine(line []byte, it *Item) (size int, err error) {
|
||||
pattern := "VALUE %s %d %d %d\r\n"
|
||||
dest := []interface{}{&it.Key, &it.Flags, &size, &it.casid}
|
||||
if bytes.Count(line, space) == 3 {
|
||||
pattern = "VALUE %s %d %d\r\n"
|
||||
dest = dest[:3]
|
||||
}
|
||||
n, err := fmt.Sscanf(string(line), pattern, dest...)
|
||||
if err != nil || n != len(dest) {
|
||||
return -1, fmt.Errorf("memcache: unexpected line in get response: %q", line)
|
||||
}
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// Set writes the given item, unconditionally.
|
||||
func (c *Client) Set(item *Item) error {
|
||||
return c.onItem(item, (*Client).set)
|
||||
}
|
||||
|
||||
func (c *Client) set(rw *bufio.ReadWriter, item *Item) error {
|
||||
return c.populateOne(rw, "set", item)
|
||||
}
|
||||
|
||||
// Add writes the given item, if no value already exists for its
|
||||
// key. ErrNotStored is returned if that condition is not met.
|
||||
func (c *Client) Add(item *Item) error {
|
||||
return c.onItem(item, (*Client).add)
|
||||
}
|
||||
|
||||
func (c *Client) add(rw *bufio.ReadWriter, item *Item) error {
|
||||
return c.populateOne(rw, "add", item)
|
||||
}
|
||||
|
||||
// Replace writes the given item, but only if the server *does*
|
||||
// already hold data for this key
|
||||
func (c *Client) Replace(item *Item) error {
|
||||
return c.onItem(item, (*Client).replace)
|
||||
}
|
||||
|
||||
func (c *Client) replace(rw *bufio.ReadWriter, item *Item) error {
|
||||
return c.populateOne(rw, "replace", item)
|
||||
}
|
||||
|
||||
// CompareAndSwap writes the given item that was previously returned
|
||||
// by Get, if the value was neither modified or evicted between the
|
||||
// Get and the CompareAndSwap calls. The item's Key should not change
|
||||
// between calls but all other item fields may differ. ErrCASConflict
|
||||
// is returned if the value was modified in between the
|
||||
// calls. ErrNotStored is returned if the value was evicted in between
|
||||
// the calls.
|
||||
func (c *Client) CompareAndSwap(item *Item) error {
|
||||
return c.onItem(item, (*Client).cas)
|
||||
}
|
||||
|
||||
func (c *Client) cas(rw *bufio.ReadWriter, item *Item) error {
|
||||
return c.populateOne(rw, "cas", item)
|
||||
}
|
||||
|
||||
func (c *Client) populateOne(rw *bufio.ReadWriter, verb string, item *Item) error {
|
||||
if !legalKey(item.Key) {
|
||||
return ErrMalformedKey
|
||||
}
|
||||
var err error
|
||||
if verb == "cas" {
|
||||
_, err = fmt.Fprintf(rw, "%s %s %d %d %d %d\r\n",
|
||||
verb, item.Key, item.Flags, item.Expiration, len(item.Value), item.casid)
|
||||
} else {
|
||||
_, err = fmt.Fprintf(rw, "%s %s %d %d %d\r\n",
|
||||
verb, item.Key, item.Flags, item.Expiration, len(item.Value))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = rw.Write(item.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := rw.Write(crlf); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := rw.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
line, err := rw.ReadSlice('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case bytes.Equal(line, resultStored):
|
||||
return nil
|
||||
case bytes.Equal(line, resultNotStored):
|
||||
return ErrNotStored
|
||||
case bytes.Equal(line, resultExists):
|
||||
return ErrCASConflict
|
||||
case bytes.Equal(line, resultNotFound):
|
||||
return ErrCacheMiss
|
||||
}
|
||||
return fmt.Errorf("memcache: unexpected response line from %q: %q", verb, string(line))
|
||||
}
|
||||
|
||||
func writeReadLine(rw *bufio.ReadWriter, format string, args ...interface{}) ([]byte, error) {
|
||||
_, err := fmt.Fprintf(rw, format, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rw.Flush(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
line, err := rw.ReadSlice('\n')
|
||||
return line, err
|
||||
}
|
||||
|
||||
func writeExpectf(rw *bufio.ReadWriter, expect []byte, format string, args ...interface{}) error {
|
||||
line, err := writeReadLine(rw, format, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case bytes.Equal(line, resultOK):
|
||||
return nil
|
||||
case bytes.Equal(line, expect):
|
||||
return nil
|
||||
case bytes.Equal(line, resultNotStored):
|
||||
return ErrNotStored
|
||||
case bytes.Equal(line, resultExists):
|
||||
return ErrCASConflict
|
||||
case bytes.Equal(line, resultNotFound):
|
||||
return ErrCacheMiss
|
||||
}
|
||||
return fmt.Errorf("memcache: unexpected response line: %q", string(line))
|
||||
}
|
||||
|
||||
// Delete deletes the item with the provided key. The error ErrCacheMiss is
|
||||
// returned if the item didn't already exist in the cache.
|
||||
func (c *Client) Delete(key string) error {
|
||||
return c.withKeyRw(key, func(rw *bufio.ReadWriter) error {
|
||||
return writeExpectf(rw, resultDeleted, "delete %s\r\n", key)
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteAll deletes all items in the cache.
|
||||
func (c *Client) DeleteAll() error {
|
||||
return c.withKeyRw("", func(rw *bufio.ReadWriter) error {
|
||||
return writeExpectf(rw, resultDeleted, "flush_all\r\n")
|
||||
})
|
||||
}
|
||||
|
||||
// Ping checks all instances if they are alive. Returns error if any
|
||||
// of them is down.
|
||||
func (c *Client) Ping() error {
|
||||
return c.selector.Each(c.ping)
|
||||
}
|
||||
|
||||
// Increment atomically increments key by delta. The return value is
|
||||
// the new value after being incremented or an error. If the value
|
||||
// didn't exist in memcached the error is ErrCacheMiss. The value in
|
||||
// memcached must be an decimal number, or an error will be returned.
|
||||
// On 64-bit overflow, the new value wraps around.
|
||||
func (c *Client) Increment(key string, delta uint64) (newValue uint64, err error) {
|
||||
return c.incrDecr("incr", key, delta)
|
||||
}
|
||||
|
||||
// Decrement atomically decrements key by delta. The return value is
|
||||
// the new value after being decremented or an error. If the value
|
||||
// didn't exist in memcached the error is ErrCacheMiss. The value in
|
||||
// memcached must be an decimal number, or an error will be returned.
|
||||
// On underflow, the new value is capped at zero and does not wrap
|
||||
// around.
|
||||
func (c *Client) Decrement(key string, delta uint64) (newValue uint64, err error) {
|
||||
return c.incrDecr("decr", key, delta)
|
||||
}
|
||||
|
||||
func (c *Client) incrDecr(verb, key string, delta uint64) (uint64, error) {
|
||||
var val uint64
|
||||
err := c.withKeyRw(key, func(rw *bufio.ReadWriter) error {
|
||||
line, err := writeReadLine(rw, "%s %s %d\r\n", verb, key, delta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case bytes.Equal(line, resultNotFound):
|
||||
return ErrCacheMiss
|
||||
case bytes.HasPrefix(line, resultClientErrorPrefix):
|
||||
errMsg := line[len(resultClientErrorPrefix) : len(line)-2]
|
||||
return errors.New("memcache: client error: " + string(errMsg))
|
||||
}
|
||||
val, err = strconv.ParseUint(string(line[:len(line)-2]), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return val, err
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
Copyright 2011 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package memcache
|
||||
|
||||
import (
|
||||
"hash/crc32"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ServerSelector is the interface that selects a memcache server
|
||||
// as a function of the item's key.
|
||||
//
|
||||
// All ServerSelector implementations must be safe for concurrent use
|
||||
// by multiple goroutines.
|
||||
type ServerSelector interface {
|
||||
// PickServer returns the server address that a given item
|
||||
// should be shared onto.
|
||||
PickServer(key string) (net.Addr, error)
|
||||
Each(func(net.Addr) error) error
|
||||
}
|
||||
|
||||
// ServerList is a simple ServerSelector. Its zero value is usable.
|
||||
type ServerList struct {
|
||||
mu sync.RWMutex
|
||||
addrs []net.Addr
|
||||
}
|
||||
|
||||
// staticAddr caches the Network() and String() values from any net.Addr.
|
||||
type staticAddr struct {
|
||||
ntw, str string
|
||||
}
|
||||
|
||||
func newStaticAddr(a net.Addr) net.Addr {
|
||||
return &staticAddr{
|
||||
ntw: a.Network(),
|
||||
str: a.String(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *staticAddr) Network() string { return s.ntw }
|
||||
func (s *staticAddr) String() string { return s.str }
|
||||
|
||||
// SetServers changes a ServerList's set of servers at runtime and is
|
||||
// safe for concurrent use by multiple goroutines.
|
||||
//
|
||||
// Each server is given equal weight. A server is given more weight
|
||||
// if it's listed multiple times.
|
||||
//
|
||||
// SetServers returns an error if any of the server names fail to
|
||||
// resolve. No attempt is made to connect to the server. If any error
|
||||
// is returned, no changes are made to the ServerList.
|
||||
func (ss *ServerList) SetServers(servers ...string) error {
|
||||
naddr := make([]net.Addr, len(servers))
|
||||
for i, server := range servers {
|
||||
if strings.Contains(server, "/") {
|
||||
addr, err := net.ResolveUnixAddr("unix", server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
naddr[i] = newStaticAddr(addr)
|
||||
} else {
|
||||
tcpaddr, err := net.ResolveTCPAddr("tcp", server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
naddr[i] = newStaticAddr(tcpaddr)
|
||||
}
|
||||
}
|
||||
|
||||
ss.mu.Lock()
|
||||
defer ss.mu.Unlock()
|
||||
ss.addrs = naddr
|
||||
return nil
|
||||
}
|
||||
|
||||
// Each iterates over each server calling the given function
|
||||
func (ss *ServerList) Each(f func(net.Addr) error) error {
|
||||
ss.mu.RLock()
|
||||
defer ss.mu.RUnlock()
|
||||
for _, a := range ss.addrs {
|
||||
if err := f(a); nil != err {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// keyBufPool returns []byte buffers for use by PickServer's call to
|
||||
// crc32.ChecksumIEEE to avoid allocations. (but doesn't avoid the
|
||||
// copies, which at least are bounded in size and small)
|
||||
var keyBufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
b := make([]byte, 256)
|
||||
return &b
|
||||
},
|
||||
}
|
||||
|
||||
func (ss *ServerList) PickServer(key string) (net.Addr, error) {
|
||||
ss.mu.RLock()
|
||||
defer ss.mu.RUnlock()
|
||||
if len(ss.addrs) == 0 {
|
||||
return nil, ErrNoServers
|
||||
}
|
||||
if len(ss.addrs) == 1 {
|
||||
return ss.addrs[0], nil
|
||||
}
|
||||
bufp := keyBufPool.Get().(*[]byte)
|
||||
n := copy(*bufp, key)
|
||||
cs := crc32.ChecksumIEEE((*bufp)[:n])
|
||||
keyBufPool.Put(bufp)
|
||||
|
||||
return ss.addrs[cs%uint32(len(ss.addrs))], nil
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- tip
|
||||
sudo: false
|
||||
before_install:
|
||||
- go get github.com/axw/gocov/gocov
|
||||
- go get github.com/mattn/goveralls
|
||||
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Fatih Arslan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
# Structs [](http://godoc.org/github.com/fatih/structs) [](https://travis-ci.org/fatih/structs) [](https://coveralls.io/r/fatih/structs)
|
||||
|
||||
Structs contains various utilities to work with Go (Golang) structs. It was
|
||||
initially used by me to convert a struct into a `map[string]interface{}`. With
|
||||
time I've added other utilities for structs. It's basically a high level
|
||||
package based on primitives from the reflect package. Feel free to add new
|
||||
functions or improve the existing code.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/fatih/structs
|
||||
```
|
||||
|
||||
## Usage and Examples
|
||||
|
||||
Just like the standard lib `strings`, `bytes` and co packages, `structs` has
|
||||
many global functions to manipulate or organize your struct data. Lets define
|
||||
and declare a struct:
|
||||
|
||||
```go
|
||||
type Server struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
ID int
|
||||
Enabled bool
|
||||
users []string // not exported
|
||||
http.Server // embedded
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
Name: "gopher",
|
||||
ID: 123456,
|
||||
Enabled: true,
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// Convert a struct to a map[string]interface{}
|
||||
// => {"Name":"gopher", "ID":123456, "Enabled":true}
|
||||
m := structs.Map(server)
|
||||
|
||||
// Convert the values of a struct to a []interface{}
|
||||
// => ["gopher", 123456, true]
|
||||
v := structs.Values(server)
|
||||
|
||||
// Convert the names of a struct to a []string
|
||||
// (see "Names methods" for more info about fields)
|
||||
n := structs.Names(server)
|
||||
|
||||
// Convert the values of a struct to a []*Field
|
||||
// (see "Field methods" for more info about fields)
|
||||
f := structs.Fields(server)
|
||||
|
||||
// Return the struct name => "Server"
|
||||
n := structs.Name(server)
|
||||
|
||||
// Check if any field of a struct is initialized or not.
|
||||
h := structs.HasZero(server)
|
||||
|
||||
// Check if all fields of a struct is initialized or not.
|
||||
z := structs.IsZero(server)
|
||||
|
||||
// Check if server is a struct or a pointer to struct
|
||||
i := structs.IsStruct(server)
|
||||
```
|
||||
|
||||
### Struct methods
|
||||
|
||||
The structs functions can be also used as independent methods by creating a new
|
||||
`*structs.Struct`. This is handy if you want to have more control over the
|
||||
structs (such as retrieving a single Field).
|
||||
|
||||
```go
|
||||
// Create a new struct type:
|
||||
s := structs.New(server)
|
||||
|
||||
m := s.Map() // Get a map[string]interface{}
|
||||
v := s.Values() // Get a []interface{}
|
||||
f := s.Fields() // Get a []*Field
|
||||
n := s.Names() // Get a []string
|
||||
f := s.Field(name) // Get a *Field based on the given field name
|
||||
f, ok := s.FieldOk(name) // Get a *Field based on the given field name
|
||||
n := s.Name() // Get the struct name
|
||||
h := s.HasZero() // Check if any field is uninitialized
|
||||
z := s.IsZero() // Check if all fields are uninitialized
|
||||
```
|
||||
|
||||
### Field methods
|
||||
|
||||
We can easily examine a single Field for more detail. Below you can see how we
|
||||
get and interact with various field methods:
|
||||
|
||||
|
||||
```go
|
||||
s := structs.New(server)
|
||||
|
||||
// Get the Field struct for the "Name" field
|
||||
name := s.Field("Name")
|
||||
|
||||
// Get the underlying value, value => "gopher"
|
||||
value := name.Value().(string)
|
||||
|
||||
// Set the field's value
|
||||
name.Set("another gopher")
|
||||
|
||||
// Get the field's kind, kind => "string"
|
||||
name.Kind()
|
||||
|
||||
// Check if the field is exported or not
|
||||
if name.IsExported() {
|
||||
fmt.Println("Name field is exported")
|
||||
}
|
||||
|
||||
// Check if the value is a zero value, such as "" for string, 0 for int
|
||||
if !name.IsZero() {
|
||||
fmt.Println("Name is initialized")
|
||||
}
|
||||
|
||||
// Check if the field is an anonymous (embedded) field
|
||||
if !name.IsEmbedded() {
|
||||
fmt.Println("Name is not an embedded field")
|
||||
}
|
||||
|
||||
// Get the Field's tag value for tag name "json", tag value => "name,omitempty"
|
||||
tagValue := name.Tag("json")
|
||||
```
|
||||
|
||||
Nested structs are supported too:
|
||||
|
||||
```go
|
||||
addrField := s.Field("Server").Field("Addr")
|
||||
|
||||
// Get the value for addr
|
||||
a := addrField.Value().(string)
|
||||
|
||||
// Or get all fields
|
||||
httpServer := s.Field("Server").Fields()
|
||||
```
|
||||
|
||||
We can also get a slice of Fields from the Struct type to iterate over all
|
||||
fields. This is handy if you wish to examine all fields:
|
||||
|
||||
```go
|
||||
s := structs.New(server)
|
||||
|
||||
for _, f := range s.Fields() {
|
||||
fmt.Printf("field name: %+v\n", f.Name())
|
||||
|
||||
if f.IsExported() {
|
||||
fmt.Printf("value : %+v\n", f.Value())
|
||||
fmt.Printf("is zero : %+v\n", f.IsZero())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
* [Fatih Arslan](https://github.com/fatih)
|
||||
* [Cihangir Savas](https://github.com/cihangir)
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT) - see LICENSE.md for more details
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var (
|
||||
errNotExported = errors.New("field is not exported")
|
||||
errNotSettable = errors.New("field is not settable")
|
||||
)
|
||||
|
||||
// Field represents a single struct field that encapsulates high level
|
||||
// functions around the field.
|
||||
type Field struct {
|
||||
value reflect.Value
|
||||
field reflect.StructField
|
||||
defaultTag string
|
||||
}
|
||||
|
||||
// Tag returns the value associated with key in the tag string. If there is no
|
||||
// such key in the tag, Tag returns the empty string.
|
||||
func (f *Field) Tag(key string) string {
|
||||
return f.field.Tag.Get(key)
|
||||
}
|
||||
|
||||
// Value returns the underlying value of the field. It panics if the field
|
||||
// is not exported.
|
||||
func (f *Field) Value() interface{} {
|
||||
return f.value.Interface()
|
||||
}
|
||||
|
||||
// IsEmbedded returns true if the given field is an anonymous field (embedded)
|
||||
func (f *Field) IsEmbedded() bool {
|
||||
return f.field.Anonymous
|
||||
}
|
||||
|
||||
// IsExported returns true if the given field is exported.
|
||||
func (f *Field) IsExported() bool {
|
||||
return f.field.PkgPath == ""
|
||||
}
|
||||
|
||||
// IsZero returns true if the given field is not initialized (has a zero value).
|
||||
// It panics if the field is not exported.
|
||||
func (f *Field) IsZero() bool {
|
||||
zero := reflect.Zero(f.value.Type()).Interface()
|
||||
current := f.Value()
|
||||
|
||||
return reflect.DeepEqual(current, zero)
|
||||
}
|
||||
|
||||
// Name returns the name of the given field
|
||||
func (f *Field) Name() string {
|
||||
return f.field.Name
|
||||
}
|
||||
|
||||
// Kind returns the fields kind, such as "string", "map", "bool", etc ..
|
||||
func (f *Field) Kind() reflect.Kind {
|
||||
return f.value.Kind()
|
||||
}
|
||||
|
||||
// Set sets the field to given value v. It returns an error if the field is not
|
||||
// settable (not addressable or not exported) or if the given value's type
|
||||
// doesn't match the fields type.
|
||||
func (f *Field) Set(val interface{}) error {
|
||||
// we can't set unexported fields, so be sure this field is exported
|
||||
if !f.IsExported() {
|
||||
return errNotExported
|
||||
}
|
||||
|
||||
// do we get here? not sure...
|
||||
if !f.value.CanSet() {
|
||||
return errNotSettable
|
||||
}
|
||||
|
||||
given := reflect.ValueOf(val)
|
||||
|
||||
if f.value.Kind() != given.Kind() {
|
||||
return fmt.Errorf("wrong kind. got: %s want: %s", given.Kind(), f.value.Kind())
|
||||
}
|
||||
|
||||
f.value.Set(given)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Zero sets the field to its zero value. It returns an error if the field is not
|
||||
// settable (not addressable or not exported).
|
||||
func (f *Field) Zero() error {
|
||||
zero := reflect.Zero(f.value.Type()).Interface()
|
||||
return f.Set(zero)
|
||||
}
|
||||
|
||||
// Fields returns a slice of Fields. This is particular handy to get the fields
|
||||
// of a nested struct . A struct tag with the content of "-" ignores the
|
||||
// checking of that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field *http.Request `structs:"-"`
|
||||
//
|
||||
// It panics if field is not exported or if field's kind is not struct
|
||||
func (f *Field) Fields() []*Field {
|
||||
return getFields(f.value, f.defaultTag)
|
||||
}
|
||||
|
||||
// Field returns the field from a nested struct. It panics if the nested struct
|
||||
// is not exported or if the field was not found.
|
||||
func (f *Field) Field(name string) *Field {
|
||||
field, ok := f.FieldOk(name)
|
||||
if !ok {
|
||||
panic("field not found")
|
||||
}
|
||||
|
||||
return field
|
||||
}
|
||||
|
||||
// FieldOk returns the field from a nested struct. The boolean returns whether
|
||||
// the field was found (true) or not (false).
|
||||
func (f *Field) FieldOk(name string) (*Field, bool) {
|
||||
value := &f.value
|
||||
// value must be settable so we need to make sure it holds the address of the
|
||||
// variable and not a copy, so we can pass the pointer to strctVal instead of a
|
||||
// copy (which is not assigned to any variable, hence not settable).
|
||||
// see "https://blog.golang.org/laws-of-reflection#TOC_8."
|
||||
if f.value.Kind() != reflect.Ptr {
|
||||
a := f.value.Addr()
|
||||
value = &a
|
||||
}
|
||||
v := strctVal(value.Interface())
|
||||
t := v.Type()
|
||||
|
||||
field, ok := t.FieldByName(name)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &Field{
|
||||
field: field,
|
||||
value: v.FieldByName(name),
|
||||
}, true
|
||||
}
|
||||
+584
@@ -0,0 +1,584 @@
|
||||
// Package structs contains various utilities functions to work with structs.
|
||||
package structs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultTagName is the default tag name for struct fields which provides
|
||||
// a more granular to tweak certain structs. Lookup the necessary functions
|
||||
// for more info.
|
||||
DefaultTagName = "structs" // struct's field default tag name
|
||||
)
|
||||
|
||||
// Struct encapsulates a struct type to provide several high level functions
|
||||
// around the struct.
|
||||
type Struct struct {
|
||||
raw interface{}
|
||||
value reflect.Value
|
||||
TagName string
|
||||
}
|
||||
|
||||
// New returns a new *Struct with the struct s. It panics if the s's kind is
|
||||
// not struct.
|
||||
func New(s interface{}) *Struct {
|
||||
return &Struct{
|
||||
raw: s,
|
||||
value: strctVal(s),
|
||||
TagName: DefaultTagName,
|
||||
}
|
||||
}
|
||||
|
||||
// Map converts the given struct to a map[string]interface{}, where the keys
|
||||
// of the map are the field names and the values of the map the associated
|
||||
// values of the fields. The default key string is the struct field name but
|
||||
// can be changed in the struct field's tag value. The "structs" key in the
|
||||
// struct's field tag value is the key name. Example:
|
||||
//
|
||||
// // Field appears in map as key "myName".
|
||||
// Name string `structs:"myName"`
|
||||
//
|
||||
// A tag value with the content of "-" ignores that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// A tag value with the content of "string" uses the stringer to get the value. Example:
|
||||
//
|
||||
// // The value will be output of Animal's String() func.
|
||||
// // Map will panic if Animal does not implement String().
|
||||
// Field *Animal `structs:"field,string"`
|
||||
//
|
||||
// A tag value with the option of "flatten" used in a struct field is to flatten its fields
|
||||
// in the output map. Example:
|
||||
//
|
||||
// // The FieldStruct's fields will be flattened into the output map.
|
||||
// FieldStruct time.Time `structs:",flatten"`
|
||||
//
|
||||
// A tag value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Field is not processed further by this package.
|
||||
// Field time.Time `structs:"myName,omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// A tag value with the option of "omitempty" ignores that particular field if
|
||||
// the field value is empty. Example:
|
||||
//
|
||||
// // Field appears in map as key "myName", but the field is
|
||||
// // skipped if empty.
|
||||
// Field string `structs:"myName,omitempty"`
|
||||
//
|
||||
// // Field appears in map as key "Field" (the default), but
|
||||
// // the field is skipped if empty.
|
||||
// Field string `structs:",omitempty"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected.
|
||||
func (s *Struct) Map() map[string]interface{} {
|
||||
out := make(map[string]interface{})
|
||||
s.FillMap(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// FillMap is the same as Map. Instead of returning the output, it fills the
|
||||
// given map.
|
||||
func (s *Struct) FillMap(out map[string]interface{}) {
|
||||
if out == nil {
|
||||
return
|
||||
}
|
||||
|
||||
fields := s.structFields()
|
||||
|
||||
for _, field := range fields {
|
||||
name := field.Name
|
||||
val := s.value.FieldByName(name)
|
||||
isSubStruct := false
|
||||
var finalVal interface{}
|
||||
|
||||
tagName, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
if tagName != "" {
|
||||
name = tagName
|
||||
}
|
||||
|
||||
// if the value is a zero value and the field is marked as omitempty do
|
||||
// not include
|
||||
if tagOpts.Has("omitempty") {
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
current := val.Interface()
|
||||
|
||||
if reflect.DeepEqual(current, zero) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if !tagOpts.Has("omitnested") {
|
||||
finalVal = s.nested(val)
|
||||
|
||||
v := reflect.ValueOf(val.Interface())
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Map, reflect.Struct:
|
||||
isSubStruct = true
|
||||
}
|
||||
} else {
|
||||
finalVal = val.Interface()
|
||||
}
|
||||
|
||||
if tagOpts.Has("string") {
|
||||
s, ok := val.Interface().(fmt.Stringer)
|
||||
if ok {
|
||||
out[name] = s.String()
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if isSubStruct && (tagOpts.Has("flatten")) {
|
||||
for k := range finalVal.(map[string]interface{}) {
|
||||
out[k] = finalVal.(map[string]interface{})[k]
|
||||
}
|
||||
} else {
|
||||
out[name] = finalVal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Values converts the given s struct's field values to a []interface{}. A
|
||||
// struct tag with the content of "-" ignores the that particular field.
|
||||
// Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field int `structs:"-"`
|
||||
//
|
||||
// A value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Fields is not processed further by this package.
|
||||
// Field time.Time `structs:",omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// A tag value with the option of "omitempty" ignores that particular field and
|
||||
// is not added to the values if the field value is empty. Example:
|
||||
//
|
||||
// // Field is skipped if empty
|
||||
// Field string `structs:",omitempty"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected.
|
||||
func (s *Struct) Values() []interface{} {
|
||||
fields := s.structFields()
|
||||
|
||||
var t []interface{}
|
||||
|
||||
for _, field := range fields {
|
||||
val := s.value.FieldByName(field.Name)
|
||||
|
||||
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
|
||||
// if the value is a zero value and the field is marked as omitempty do
|
||||
// not include
|
||||
if tagOpts.Has("omitempty") {
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
current := val.Interface()
|
||||
|
||||
if reflect.DeepEqual(current, zero) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if tagOpts.Has("string") {
|
||||
s, ok := val.Interface().(fmt.Stringer)
|
||||
if ok {
|
||||
t = append(t, s.String())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
// look out for embedded structs, and convert them to a
|
||||
// []interface{} to be added to the final values slice
|
||||
t = append(t, Values(val.Interface())...)
|
||||
} else {
|
||||
t = append(t, val.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// Fields returns a slice of Fields. A struct tag with the content of "-"
|
||||
// ignores the checking of that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// It panics if s's kind is not struct.
|
||||
func (s *Struct) Fields() []*Field {
|
||||
return getFields(s.value, s.TagName)
|
||||
}
|
||||
|
||||
// Names returns a slice of field names. A struct tag with the content of "-"
|
||||
// ignores the checking of that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// It panics if s's kind is not struct.
|
||||
func (s *Struct) Names() []string {
|
||||
fields := getFields(s.value, s.TagName)
|
||||
|
||||
names := make([]string, len(fields))
|
||||
|
||||
for i, field := range fields {
|
||||
names[i] = field.Name()
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
func getFields(v reflect.Value, tagName string) []*Field {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
t := v.Type()
|
||||
|
||||
var fields []*Field
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
|
||||
if tag := field.Tag.Get(tagName); tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
f := &Field{
|
||||
field: field,
|
||||
value: v.FieldByName(field.Name),
|
||||
}
|
||||
|
||||
fields = append(fields, f)
|
||||
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
// Field returns a new Field struct that provides several high level functions
|
||||
// around a single struct field entity. It panics if the field is not found.
|
||||
func (s *Struct) Field(name string) *Field {
|
||||
f, ok := s.FieldOk(name)
|
||||
if !ok {
|
||||
panic("field not found")
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// FieldOk returns a new Field struct that provides several high level functions
|
||||
// around a single struct field entity. The boolean returns true if the field
|
||||
// was found.
|
||||
func (s *Struct) FieldOk(name string) (*Field, bool) {
|
||||
t := s.value.Type()
|
||||
|
||||
field, ok := t.FieldByName(name)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &Field{
|
||||
field: field,
|
||||
value: s.value.FieldByName(name),
|
||||
defaultTag: s.TagName,
|
||||
}, true
|
||||
}
|
||||
|
||||
// IsZero returns true if all fields in a struct is a zero value (not
|
||||
// initialized) A struct tag with the content of "-" ignores the checking of
|
||||
// that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// A value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Field is not processed further by this package.
|
||||
// Field time.Time `structs:"myName,omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected. It panics if s's kind is not struct.
|
||||
func (s *Struct) IsZero() bool {
|
||||
fields := s.structFields()
|
||||
|
||||
for _, field := range fields {
|
||||
val := s.value.FieldByName(field.Name)
|
||||
|
||||
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
ok := IsZero(val.Interface())
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// zero value of the given field, such as "" for string, 0 for int
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
|
||||
// current value of the given field
|
||||
current := val.Interface()
|
||||
|
||||
if !reflect.DeepEqual(current, zero) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// HasZero returns true if a field in a struct is not initialized (zero value).
|
||||
// A struct tag with the content of "-" ignores the checking of that particular
|
||||
// field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// A value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Field is not processed further by this package.
|
||||
// Field time.Time `structs:"myName,omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected. It panics if s's kind is not struct.
|
||||
func (s *Struct) HasZero() bool {
|
||||
fields := s.structFields()
|
||||
|
||||
for _, field := range fields {
|
||||
val := s.value.FieldByName(field.Name)
|
||||
|
||||
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
ok := HasZero(val.Interface())
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// zero value of the given field, such as "" for string, 0 for int
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
|
||||
// current value of the given field
|
||||
current := val.Interface()
|
||||
|
||||
if reflect.DeepEqual(current, zero) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Name returns the structs's type name within its package. For more info refer
|
||||
// to Name() function.
|
||||
func (s *Struct) Name() string {
|
||||
return s.value.Type().Name()
|
||||
}
|
||||
|
||||
// structFields returns the exported struct fields for a given s struct. This
|
||||
// is a convenient helper method to avoid duplicate code in some of the
|
||||
// functions.
|
||||
func (s *Struct) structFields() []reflect.StructField {
|
||||
t := s.value.Type()
|
||||
|
||||
var f []reflect.StructField
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
// we can't access the value of unexported fields
|
||||
if field.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// don't check if it's omitted
|
||||
if tag := field.Tag.Get(s.TagName); tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
f = append(f, field)
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func strctVal(s interface{}) reflect.Value {
|
||||
v := reflect.ValueOf(s)
|
||||
|
||||
// if pointer get the underlying element≤
|
||||
for v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if v.Kind() != reflect.Struct {
|
||||
panic("not struct")
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Map converts the given struct to a map[string]interface{}. For more info
|
||||
// refer to Struct types Map() method. It panics if s's kind is not struct.
|
||||
func Map(s interface{}) map[string]interface{} {
|
||||
return New(s).Map()
|
||||
}
|
||||
|
||||
// FillMap is the same as Map. Instead of returning the output, it fills the
|
||||
// given map.
|
||||
func FillMap(s interface{}, out map[string]interface{}) {
|
||||
New(s).FillMap(out)
|
||||
}
|
||||
|
||||
// Values converts the given struct to a []interface{}. For more info refer to
|
||||
// Struct types Values() method. It panics if s's kind is not struct.
|
||||
func Values(s interface{}) []interface{} {
|
||||
return New(s).Values()
|
||||
}
|
||||
|
||||
// Fields returns a slice of *Field. For more info refer to Struct types
|
||||
// Fields() method. It panics if s's kind is not struct.
|
||||
func Fields(s interface{}) []*Field {
|
||||
return New(s).Fields()
|
||||
}
|
||||
|
||||
// Names returns a slice of field names. For more info refer to Struct types
|
||||
// Names() method. It panics if s's kind is not struct.
|
||||
func Names(s interface{}) []string {
|
||||
return New(s).Names()
|
||||
}
|
||||
|
||||
// IsZero returns true if all fields is equal to a zero value. For more info
|
||||
// refer to Struct types IsZero() method. It panics if s's kind is not struct.
|
||||
func IsZero(s interface{}) bool {
|
||||
return New(s).IsZero()
|
||||
}
|
||||
|
||||
// HasZero returns true if any field is equal to a zero value. For more info
|
||||
// refer to Struct types HasZero() method. It panics if s's kind is not struct.
|
||||
func HasZero(s interface{}) bool {
|
||||
return New(s).HasZero()
|
||||
}
|
||||
|
||||
// IsStruct returns true if the given variable is a struct or a pointer to
|
||||
// struct.
|
||||
func IsStruct(s interface{}) bool {
|
||||
v := reflect.ValueOf(s)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
// uninitialized zero value of a struct
|
||||
if v.Kind() == reflect.Invalid {
|
||||
return false
|
||||
}
|
||||
|
||||
return v.Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
// Name returns the structs's type name within its package. It returns an
|
||||
// empty string for unnamed types. It panics if s's kind is not struct.
|
||||
func Name(s interface{}) string {
|
||||
return New(s).Name()
|
||||
}
|
||||
|
||||
// nested retrieves recursively all types for the given value and returns the
|
||||
// nested value.
|
||||
func (s *Struct) nested(val reflect.Value) interface{} {
|
||||
var finalVal interface{}
|
||||
|
||||
v := reflect.ValueOf(val.Interface())
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
n := New(val.Interface())
|
||||
n.TagName = s.TagName
|
||||
m := n.Map()
|
||||
|
||||
// do not add the converted value if there are no exported fields, ie:
|
||||
// time.Time
|
||||
if len(m) == 0 {
|
||||
finalVal = val.Interface()
|
||||
} else {
|
||||
finalVal = m
|
||||
}
|
||||
case reflect.Map:
|
||||
// get the element type of the map
|
||||
mapElem := val.Type()
|
||||
switch val.Type().Kind() {
|
||||
case reflect.Ptr, reflect.Array, reflect.Map,
|
||||
reflect.Slice, reflect.Chan:
|
||||
mapElem = val.Type().Elem()
|
||||
if mapElem.Kind() == reflect.Ptr {
|
||||
mapElem = mapElem.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// only iterate over struct types, ie: map[string]StructType,
|
||||
// map[string][]StructType,
|
||||
if mapElem.Kind() == reflect.Struct ||
|
||||
(mapElem.Kind() == reflect.Slice &&
|
||||
mapElem.Elem().Kind() == reflect.Struct) {
|
||||
m := make(map[string]interface{}, val.Len())
|
||||
for _, k := range val.MapKeys() {
|
||||
m[k.String()] = s.nested(val.MapIndex(k))
|
||||
}
|
||||
finalVal = m
|
||||
break
|
||||
}
|
||||
|
||||
// TODO(arslan): should this be optional?
|
||||
finalVal = val.Interface()
|
||||
case reflect.Slice, reflect.Array:
|
||||
if val.Type().Kind() == reflect.Interface {
|
||||
finalVal = val.Interface()
|
||||
break
|
||||
}
|
||||
|
||||
// TODO(arslan): should this be optional?
|
||||
// do not iterate of non struct types, just pass the value. Ie: []int,
|
||||
// []string, co... We only iterate further if it's a struct.
|
||||
// i.e []foo or []*foo
|
||||
if val.Type().Elem().Kind() != reflect.Struct &&
|
||||
!(val.Type().Elem().Kind() == reflect.Ptr &&
|
||||
val.Type().Elem().Elem().Kind() == reflect.Struct) {
|
||||
finalVal = val.Interface()
|
||||
break
|
||||
}
|
||||
|
||||
slices := make([]interface{}, val.Len())
|
||||
for x := 0; x < val.Len(); x++ {
|
||||
slices[x] = s.nested(val.Index(x))
|
||||
}
|
||||
finalVal = slices
|
||||
default:
|
||||
finalVal = val.Interface()
|
||||
}
|
||||
|
||||
return finalVal
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package structs
|
||||
|
||||
import "strings"
|
||||
|
||||
// tagOptions contains a slice of tag options
|
||||
type tagOptions []string
|
||||
|
||||
// Has returns true if the given option is available in tagOptions
|
||||
func (t tagOptions) Has(opt string) bool {
|
||||
for _, tagOpt := range t {
|
||||
if tagOpt == opt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// parseTag splits a struct field's tag into its name and a list of options
|
||||
// which comes after a name. A tag is in the form of: "name,option1,option2".
|
||||
// The name can be neglectected.
|
||||
func parseTag(tag string) (string, tagOptions) {
|
||||
// tag is one of followings:
|
||||
// ""
|
||||
// "name"
|
||||
// "name,opt"
|
||||
// "name,opt,opt2"
|
||||
// ",opt"
|
||||
|
||||
res := strings.Split(tag, ",")
|
||||
return res[0], res[1:]
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
// Copyright 2014 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package internal // import "github.com/garyburd/redigo/internal"
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
WatchState = 1 << iota
|
||||
MultiState
|
||||
SubscribeState
|
||||
MonitorState
|
||||
)
|
||||
|
||||
type CommandInfo struct {
|
||||
Set, Clear int
|
||||
}
|
||||
|
||||
var commandInfos = map[string]CommandInfo{
|
||||
"WATCH": {Set: WatchState},
|
||||
"UNWATCH": {Clear: WatchState},
|
||||
"MULTI": {Set: MultiState},
|
||||
"EXEC": {Clear: WatchState | MultiState},
|
||||
"DISCARD": {Clear: WatchState | MultiState},
|
||||
"PSUBSCRIBE": {Set: SubscribeState},
|
||||
"SUBSCRIBE": {Set: SubscribeState},
|
||||
"MONITOR": {Set: MonitorState},
|
||||
}
|
||||
|
||||
func init() {
|
||||
for n, ci := range commandInfos {
|
||||
commandInfos[strings.ToLower(n)] = ci
|
||||
}
|
||||
}
|
||||
|
||||
func LookupCommandInfo(commandName string) CommandInfo {
|
||||
if ci, ok := commandInfos[commandName]; ok {
|
||||
return ci
|
||||
}
|
||||
return commandInfos[strings.ToUpper(commandName)]
|
||||
}
|
||||
+673
@@ -0,0 +1,673 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
_ ConnWithTimeout = (*conn)(nil)
|
||||
)
|
||||
|
||||
// conn is the low-level implementation of Conn
|
||||
type conn struct {
|
||||
// Shared
|
||||
mu sync.Mutex
|
||||
pending int
|
||||
err error
|
||||
conn net.Conn
|
||||
|
||||
// Read
|
||||
readTimeout time.Duration
|
||||
br *bufio.Reader
|
||||
|
||||
// Write
|
||||
writeTimeout time.Duration
|
||||
bw *bufio.Writer
|
||||
|
||||
// Scratch space for formatting argument length.
|
||||
// '*' or '$', length, "\r\n"
|
||||
lenScratch [32]byte
|
||||
|
||||
// Scratch space for formatting integers and floats.
|
||||
numScratch [40]byte
|
||||
}
|
||||
|
||||
// DialTimeout acts like Dial but takes timeouts for establishing the
|
||||
// connection to the server, writing a command and reading a reply.
|
||||
//
|
||||
// Deprecated: Use Dial with options instead.
|
||||
func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) {
|
||||
return Dial(network, address,
|
||||
DialConnectTimeout(connectTimeout),
|
||||
DialReadTimeout(readTimeout),
|
||||
DialWriteTimeout(writeTimeout))
|
||||
}
|
||||
|
||||
// DialOption specifies an option for dialing a Redis server.
|
||||
type DialOption struct {
|
||||
f func(*dialOptions)
|
||||
}
|
||||
|
||||
type dialOptions struct {
|
||||
readTimeout time.Duration
|
||||
writeTimeout time.Duration
|
||||
dialer *net.Dialer
|
||||
dial func(network, addr string) (net.Conn, error)
|
||||
db int
|
||||
password string
|
||||
useTLS bool
|
||||
skipVerify bool
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
// DialReadTimeout specifies the timeout for reading a single command reply.
|
||||
func DialReadTimeout(d time.Duration) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.readTimeout = d
|
||||
}}
|
||||
}
|
||||
|
||||
// DialWriteTimeout specifies the timeout for writing a single command.
|
||||
func DialWriteTimeout(d time.Duration) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.writeTimeout = d
|
||||
}}
|
||||
}
|
||||
|
||||
// DialConnectTimeout specifies the timeout for connecting to the Redis server when
|
||||
// no DialNetDial option is specified.
|
||||
func DialConnectTimeout(d time.Duration) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.dialer.Timeout = d
|
||||
}}
|
||||
}
|
||||
|
||||
// DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server
|
||||
// when no DialNetDial option is specified.
|
||||
// If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then
|
||||
// the default of 5 minutes is used to ensure that half-closed TCP sessions are detected.
|
||||
func DialKeepAlive(d time.Duration) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.dialer.KeepAlive = d
|
||||
}}
|
||||
}
|
||||
|
||||
// DialNetDial specifies a custom dial function for creating TCP
|
||||
// connections, otherwise a net.Dialer customized via the other options is used.
|
||||
// DialNetDial overrides DialConnectTimeout and DialKeepAlive.
|
||||
func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.dial = dial
|
||||
}}
|
||||
}
|
||||
|
||||
// DialDatabase specifies the database to select when dialing a connection.
|
||||
func DialDatabase(db int) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.db = db
|
||||
}}
|
||||
}
|
||||
|
||||
// DialPassword specifies the password to use when connecting to
|
||||
// the Redis server.
|
||||
func DialPassword(password string) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.password = password
|
||||
}}
|
||||
}
|
||||
|
||||
// DialTLSConfig specifies the config to use when a TLS connection is dialed.
|
||||
// Has no effect when not dialing a TLS connection.
|
||||
func DialTLSConfig(c *tls.Config) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.tlsConfig = c
|
||||
}}
|
||||
}
|
||||
|
||||
// DialTLSSkipVerify disables server name verification when connecting over
|
||||
// TLS. Has no effect when not dialing a TLS connection.
|
||||
func DialTLSSkipVerify(skip bool) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.skipVerify = skip
|
||||
}}
|
||||
}
|
||||
|
||||
// DialUseTLS specifies whether TLS should be used when connecting to the
|
||||
// server. This option is ignore by DialURL.
|
||||
func DialUseTLS(useTLS bool) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.useTLS = useTLS
|
||||
}}
|
||||
}
|
||||
|
||||
// Dial connects to the Redis server at the given network and
|
||||
// address using the specified options.
|
||||
func Dial(network, address string, options ...DialOption) (Conn, error) {
|
||||
do := dialOptions{
|
||||
dialer: &net.Dialer{
|
||||
KeepAlive: time.Minute * 5,
|
||||
},
|
||||
}
|
||||
for _, option := range options {
|
||||
option.f(&do)
|
||||
}
|
||||
if do.dial == nil {
|
||||
do.dial = do.dialer.Dial
|
||||
}
|
||||
|
||||
netConn, err := do.dial(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if do.useTLS {
|
||||
var tlsConfig *tls.Config
|
||||
if do.tlsConfig == nil {
|
||||
tlsConfig = &tls.Config{InsecureSkipVerify: do.skipVerify}
|
||||
} else {
|
||||
tlsConfig = cloneTLSConfig(do.tlsConfig)
|
||||
}
|
||||
if tlsConfig.ServerName == "" {
|
||||
host, _, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
netConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.ServerName = host
|
||||
}
|
||||
|
||||
tlsConn := tls.Client(netConn, tlsConfig)
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
netConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
netConn = tlsConn
|
||||
}
|
||||
|
||||
c := &conn{
|
||||
conn: netConn,
|
||||
bw: bufio.NewWriter(netConn),
|
||||
br: bufio.NewReader(netConn),
|
||||
readTimeout: do.readTimeout,
|
||||
writeTimeout: do.writeTimeout,
|
||||
}
|
||||
|
||||
if do.password != "" {
|
||||
if _, err := c.Do("AUTH", do.password); err != nil {
|
||||
netConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if do.db != 0 {
|
||||
if _, err := c.Do("SELECT", do.db); err != nil {
|
||||
netConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`)
|
||||
|
||||
// DialURL connects to a Redis server at the given URL using the Redis
|
||||
// URI scheme. URLs should follow the draft IANA specification for the
|
||||
// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis).
|
||||
func DialURL(rawurl string, options ...DialOption) (Conn, error) {
|
||||
u, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.Scheme != "redis" && u.Scheme != "rediss" {
|
||||
return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme)
|
||||
}
|
||||
|
||||
// As per the IANA draft spec, the host defaults to localhost and
|
||||
// the port defaults to 6379.
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
// assume port is missing
|
||||
host = u.Host
|
||||
port = "6379"
|
||||
}
|
||||
if host == "" {
|
||||
host = "localhost"
|
||||
}
|
||||
address := net.JoinHostPort(host, port)
|
||||
|
||||
if u.User != nil {
|
||||
password, isSet := u.User.Password()
|
||||
if isSet {
|
||||
options = append(options, DialPassword(password))
|
||||
}
|
||||
}
|
||||
|
||||
match := pathDBRegexp.FindStringSubmatch(u.Path)
|
||||
if len(match) == 2 {
|
||||
db := 0
|
||||
if len(match[1]) > 0 {
|
||||
db, err = strconv.Atoi(match[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
|
||||
}
|
||||
}
|
||||
if db != 0 {
|
||||
options = append(options, DialDatabase(db))
|
||||
}
|
||||
} else if u.Path != "" {
|
||||
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
|
||||
}
|
||||
|
||||
options = append(options, DialUseTLS(u.Scheme == "rediss"))
|
||||
|
||||
return Dial("tcp", address, options...)
|
||||
}
|
||||
|
||||
// NewConn returns a new Redigo connection for the given net connection.
|
||||
func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn {
|
||||
return &conn{
|
||||
conn: netConn,
|
||||
bw: bufio.NewWriter(netConn),
|
||||
br: bufio.NewReader(netConn),
|
||||
readTimeout: readTimeout,
|
||||
writeTimeout: writeTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) Close() error {
|
||||
c.mu.Lock()
|
||||
err := c.err
|
||||
if c.err == nil {
|
||||
c.err = errors.New("redigo: closed")
|
||||
err = c.conn.Close()
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) fatal(err error) error {
|
||||
c.mu.Lock()
|
||||
if c.err == nil {
|
||||
c.err = err
|
||||
// Close connection to force errors on subsequent calls and to unblock
|
||||
// other reader or writer.
|
||||
c.conn.Close()
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) Err() error {
|
||||
c.mu.Lock()
|
||||
err := c.err
|
||||
c.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) writeLen(prefix byte, n int) error {
|
||||
c.lenScratch[len(c.lenScratch)-1] = '\n'
|
||||
c.lenScratch[len(c.lenScratch)-2] = '\r'
|
||||
i := len(c.lenScratch) - 3
|
||||
for {
|
||||
c.lenScratch[i] = byte('0' + n%10)
|
||||
i -= 1
|
||||
n = n / 10
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
c.lenScratch[i] = prefix
|
||||
_, err := c.bw.Write(c.lenScratch[i:])
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) writeString(s string) error {
|
||||
c.writeLen('$', len(s))
|
||||
c.bw.WriteString(s)
|
||||
_, err := c.bw.WriteString("\r\n")
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) writeBytes(p []byte) error {
|
||||
c.writeLen('$', len(p))
|
||||
c.bw.Write(p)
|
||||
_, err := c.bw.WriteString("\r\n")
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) writeInt64(n int64) error {
|
||||
return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10))
|
||||
}
|
||||
|
||||
func (c *conn) writeFloat64(n float64) error {
|
||||
return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64))
|
||||
}
|
||||
|
||||
func (c *conn) writeCommand(cmd string, args []interface{}) error {
|
||||
c.writeLen('*', 1+len(args))
|
||||
if err := c.writeString(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, arg := range args {
|
||||
if err := c.writeArg(arg, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) {
|
||||
switch arg := arg.(type) {
|
||||
case string:
|
||||
return c.writeString(arg)
|
||||
case []byte:
|
||||
return c.writeBytes(arg)
|
||||
case int:
|
||||
return c.writeInt64(int64(arg))
|
||||
case int64:
|
||||
return c.writeInt64(arg)
|
||||
case float64:
|
||||
return c.writeFloat64(arg)
|
||||
case bool:
|
||||
if arg {
|
||||
return c.writeString("1")
|
||||
} else {
|
||||
return c.writeString("0")
|
||||
}
|
||||
case nil:
|
||||
return c.writeString("")
|
||||
case Argument:
|
||||
if argumentTypeOK {
|
||||
return c.writeArg(arg.RedisArg(), false)
|
||||
}
|
||||
// See comment in default clause below.
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, arg)
|
||||
return c.writeBytes(buf.Bytes())
|
||||
default:
|
||||
// This default clause is intended to handle builtin numeric types.
|
||||
// The function should return an error for other types, but this is not
|
||||
// done for compatibility with previous versions of the package.
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, arg)
|
||||
return c.writeBytes(buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
type protocolError string
|
||||
|
||||
func (pe protocolError) Error() string {
|
||||
return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe))
|
||||
}
|
||||
|
||||
func (c *conn) readLine() ([]byte, error) {
|
||||
p, err := c.br.ReadSlice('\n')
|
||||
if err == bufio.ErrBufferFull {
|
||||
return nil, protocolError("long response line")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := len(p) - 2
|
||||
if i < 0 || p[i] != '\r' {
|
||||
return nil, protocolError("bad response line terminator")
|
||||
}
|
||||
return p[:i], nil
|
||||
}
|
||||
|
||||
// parseLen parses bulk string and array lengths.
|
||||
func parseLen(p []byte) (int, error) {
|
||||
if len(p) == 0 {
|
||||
return -1, protocolError("malformed length")
|
||||
}
|
||||
|
||||
if p[0] == '-' && len(p) == 2 && p[1] == '1' {
|
||||
// handle $-1 and $-1 null replies.
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
var n int
|
||||
for _, b := range p {
|
||||
n *= 10
|
||||
if b < '0' || b > '9' {
|
||||
return -1, protocolError("illegal bytes in length")
|
||||
}
|
||||
n += int(b - '0')
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// parseInt parses an integer reply.
|
||||
func parseInt(p []byte) (interface{}, error) {
|
||||
if len(p) == 0 {
|
||||
return 0, protocolError("malformed integer")
|
||||
}
|
||||
|
||||
var negate bool
|
||||
if p[0] == '-' {
|
||||
negate = true
|
||||
p = p[1:]
|
||||
if len(p) == 0 {
|
||||
return 0, protocolError("malformed integer")
|
||||
}
|
||||
}
|
||||
|
||||
var n int64
|
||||
for _, b := range p {
|
||||
n *= 10
|
||||
if b < '0' || b > '9' {
|
||||
return 0, protocolError("illegal bytes in length")
|
||||
}
|
||||
n += int64(b - '0')
|
||||
}
|
||||
|
||||
if negate {
|
||||
n = -n
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
var (
|
||||
okReply interface{} = "OK"
|
||||
pongReply interface{} = "PONG"
|
||||
)
|
||||
|
||||
func (c *conn) readReply() (interface{}, error) {
|
||||
line, err := c.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(line) == 0 {
|
||||
return nil, protocolError("short response line")
|
||||
}
|
||||
switch line[0] {
|
||||
case '+':
|
||||
switch {
|
||||
case len(line) == 3 && line[1] == 'O' && line[2] == 'K':
|
||||
// Avoid allocation for frequent "+OK" response.
|
||||
return okReply, nil
|
||||
case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G':
|
||||
// Avoid allocation in PING command benchmarks :)
|
||||
return pongReply, nil
|
||||
default:
|
||||
return string(line[1:]), nil
|
||||
}
|
||||
case '-':
|
||||
return Error(string(line[1:])), nil
|
||||
case ':':
|
||||
return parseInt(line[1:])
|
||||
case '$':
|
||||
n, err := parseLen(line[1:])
|
||||
if n < 0 || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := make([]byte, n)
|
||||
_, err = io.ReadFull(c.br, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if line, err := c.readLine(); err != nil {
|
||||
return nil, err
|
||||
} else if len(line) != 0 {
|
||||
return nil, protocolError("bad bulk string format")
|
||||
}
|
||||
return p, nil
|
||||
case '*':
|
||||
n, err := parseLen(line[1:])
|
||||
if n < 0 || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := make([]interface{}, n)
|
||||
for i := range r {
|
||||
r[i], err = c.readReply()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
return nil, protocolError("unexpected response line")
|
||||
}
|
||||
|
||||
func (c *conn) Send(cmd string, args ...interface{}) error {
|
||||
c.mu.Lock()
|
||||
c.pending += 1
|
||||
c.mu.Unlock()
|
||||
if c.writeTimeout != 0 {
|
||||
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
||||
}
|
||||
if err := c.writeCommand(cmd, args); err != nil {
|
||||
return c.fatal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Flush() error {
|
||||
if c.writeTimeout != 0 {
|
||||
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
||||
}
|
||||
if err := c.bw.Flush(); err != nil {
|
||||
return c.fatal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Receive() (interface{}, error) {
|
||||
return c.ReceiveWithTimeout(c.readTimeout)
|
||||
}
|
||||
|
||||
func (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) {
|
||||
var deadline time.Time
|
||||
if timeout != 0 {
|
||||
deadline = time.Now().Add(timeout)
|
||||
}
|
||||
c.conn.SetReadDeadline(deadline)
|
||||
|
||||
if reply, err = c.readReply(); err != nil {
|
||||
return nil, c.fatal(err)
|
||||
}
|
||||
// When using pub/sub, the number of receives can be greater than the
|
||||
// number of sends. To enable normal use of the connection after
|
||||
// unsubscribing from all channels, we do not decrement pending to a
|
||||
// negative value.
|
||||
//
|
||||
// The pending field is decremented after the reply is read to handle the
|
||||
// case where Receive is called before Send.
|
||||
c.mu.Lock()
|
||||
if c.pending > 0 {
|
||||
c.pending -= 1
|
||||
}
|
||||
c.mu.Unlock()
|
||||
if err, ok := reply.(Error); ok {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
|
||||
return c.DoWithTimeout(c.readTimeout, cmd, args...)
|
||||
}
|
||||
|
||||
func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {
|
||||
c.mu.Lock()
|
||||
pending := c.pending
|
||||
c.pending = 0
|
||||
c.mu.Unlock()
|
||||
|
||||
if cmd == "" && pending == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if c.writeTimeout != 0 {
|
||||
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
||||
}
|
||||
|
||||
if cmd != "" {
|
||||
if err := c.writeCommand(cmd, args); err != nil {
|
||||
return nil, c.fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.bw.Flush(); err != nil {
|
||||
return nil, c.fatal(err)
|
||||
}
|
||||
|
||||
var deadline time.Time
|
||||
if readTimeout != 0 {
|
||||
deadline = time.Now().Add(readTimeout)
|
||||
}
|
||||
c.conn.SetReadDeadline(deadline)
|
||||
|
||||
if cmd == "" {
|
||||
reply := make([]interface{}, pending)
|
||||
for i := range reply {
|
||||
r, e := c.readReply()
|
||||
if e != nil {
|
||||
return nil, c.fatal(e)
|
||||
}
|
||||
reply[i] = r
|
||||
}
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
var reply interface{}
|
||||
for i := 0; i <= pending; i++ {
|
||||
var e error
|
||||
if reply, e = c.readReply(); e != nil {
|
||||
return nil, c.fatal(e)
|
||||
}
|
||||
if e, ok := reply.(Error); ok && err == nil {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
return reply, err
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
// Package redis is a client for the Redis database.
|
||||
//
|
||||
// The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more
|
||||
// documentation about this package.
|
||||
//
|
||||
// Connections
|
||||
//
|
||||
// The Conn interface is the primary interface for working with Redis.
|
||||
// Applications create connections by calling the Dial, DialWithTimeout or
|
||||
// NewConn functions. In the future, functions will be added for creating
|
||||
// sharded and other types of connections.
|
||||
//
|
||||
// The application must call the connection Close method when the application
|
||||
// is done with the connection.
|
||||
//
|
||||
// Executing Commands
|
||||
//
|
||||
// The Conn interface has a generic method for executing Redis commands:
|
||||
//
|
||||
// Do(commandName string, args ...interface{}) (reply interface{}, err error)
|
||||
//
|
||||
// The Redis command reference (http://redis.io/commands) lists the available
|
||||
// commands. An example of using the Redis APPEND command is:
|
||||
//
|
||||
// n, err := conn.Do("APPEND", "key", "value")
|
||||
//
|
||||
// The Do method converts command arguments to bulk strings for transmission
|
||||
// to the server as follows:
|
||||
//
|
||||
// Go Type Conversion
|
||||
// []byte Sent as is
|
||||
// string Sent as is
|
||||
// int, int64 strconv.FormatInt(v)
|
||||
// float64 strconv.FormatFloat(v, 'g', -1, 64)
|
||||
// bool true -> "1", false -> "0"
|
||||
// nil ""
|
||||
// all other types fmt.Fprint(w, v)
|
||||
//
|
||||
// Redis command reply types are represented using the following Go types:
|
||||
//
|
||||
// Redis type Go type
|
||||
// error redis.Error
|
||||
// integer int64
|
||||
// simple string string
|
||||
// bulk string []byte or nil if value not present.
|
||||
// array []interface{} or nil if value not present.
|
||||
//
|
||||
// Use type assertions or the reply helper functions to convert from
|
||||
// interface{} to the specific Go type for the command result.
|
||||
//
|
||||
// Pipelining
|
||||
//
|
||||
// Connections support pipelining using the Send, Flush and Receive methods.
|
||||
//
|
||||
// Send(commandName string, args ...interface{}) error
|
||||
// Flush() error
|
||||
// Receive() (reply interface{}, err error)
|
||||
//
|
||||
// Send writes the command to the connection's output buffer. Flush flushes the
|
||||
// connection's output buffer to the server. Receive reads a single reply from
|
||||
// the server. The following example shows a simple pipeline.
|
||||
//
|
||||
// c.Send("SET", "foo", "bar")
|
||||
// c.Send("GET", "foo")
|
||||
// c.Flush()
|
||||
// c.Receive() // reply from SET
|
||||
// v, err = c.Receive() // reply from GET
|
||||
//
|
||||
// The Do method combines the functionality of the Send, Flush and Receive
|
||||
// methods. The Do method starts by writing the command and flushing the output
|
||||
// buffer. Next, the Do method receives all pending replies including the reply
|
||||
// for the command just sent by Do. If any of the received replies is an error,
|
||||
// then Do returns the error. If there are no errors, then Do returns the last
|
||||
// reply. If the command argument to the Do method is "", then the Do method
|
||||
// will flush the output buffer and receive pending replies without sending a
|
||||
// command.
|
||||
//
|
||||
// Use the Send and Do methods to implement pipelined transactions.
|
||||
//
|
||||
// c.Send("MULTI")
|
||||
// c.Send("INCR", "foo")
|
||||
// c.Send("INCR", "bar")
|
||||
// r, err := c.Do("EXEC")
|
||||
// fmt.Println(r) // prints [1, 1]
|
||||
//
|
||||
// Concurrency
|
||||
//
|
||||
// Connections support one concurrent caller to the Receive method and one
|
||||
// concurrent caller to the Send and Flush methods. No other concurrency is
|
||||
// supported including concurrent calls to the Do method.
|
||||
//
|
||||
// For full concurrent access to Redis, use the thread-safe Pool to get, use
|
||||
// and release a connection from within a goroutine. Connections returned from
|
||||
// a Pool have the concurrency restrictions described in the previous
|
||||
// paragraph.
|
||||
//
|
||||
// Publish and Subscribe
|
||||
//
|
||||
// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers.
|
||||
//
|
||||
// c.Send("SUBSCRIBE", "example")
|
||||
// c.Flush()
|
||||
// for {
|
||||
// reply, err := c.Receive()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// // process pushed message
|
||||
// }
|
||||
//
|
||||
// The PubSubConn type wraps a Conn with convenience methods for implementing
|
||||
// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods
|
||||
// send and flush a subscription management command. The receive method
|
||||
// converts a pushed message to convenient types for use in a type switch.
|
||||
//
|
||||
// psc := redis.PubSubConn{Conn: c}
|
||||
// psc.Subscribe("example")
|
||||
// for {
|
||||
// switch v := psc.Receive().(type) {
|
||||
// case redis.Message:
|
||||
// fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
|
||||
// case redis.Subscription:
|
||||
// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
|
||||
// case error:
|
||||
// return v
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Reply Helpers
|
||||
//
|
||||
// The Bool, Int, Bytes, String, Strings and Values functions convert a reply
|
||||
// to a value of a specific type. To allow convenient wrapping of calls to the
|
||||
// connection Do and Receive methods, the functions take a second argument of
|
||||
// type error. If the error is non-nil, then the helper function returns the
|
||||
// error. If the error is nil, the function converts the reply to the specified
|
||||
// type:
|
||||
//
|
||||
// exists, err := redis.Bool(c.Do("EXISTS", "foo"))
|
||||
// if err != nil {
|
||||
// // handle error return from c.Do or type conversion error.
|
||||
// }
|
||||
//
|
||||
// The Scan function converts elements of a array reply to Go types:
|
||||
//
|
||||
// var value1 int
|
||||
// var value2 string
|
||||
// reply, err := redis.Values(c.Do("MGET", "key1", "key2"))
|
||||
// if err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
// if _, err := redis.Scan(reply, &value1, &value2); err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
//
|
||||
// Errors
|
||||
//
|
||||
// Connection methods return error replies from the server as type redis.Error.
|
||||
//
|
||||
// Call the connection Err() method to determine if the connection encountered
|
||||
// non-recoverable error such as a network error or protocol parsing error. If
|
||||
// Err() returns a non-nil value, then the connection is not usable and should
|
||||
// be closed.
|
||||
package redis // import "github.com/garyburd/redigo/redis"
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// +build !go1.7
|
||||
|
||||
package redis
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||
return &tls.Config{
|
||||
Rand: cfg.Rand,
|
||||
Time: cfg.Time,
|
||||
Certificates: cfg.Certificates,
|
||||
NameToCertificate: cfg.NameToCertificate,
|
||||
GetCertificate: cfg.GetCertificate,
|
||||
RootCAs: cfg.RootCAs,
|
||||
NextProtos: cfg.NextProtos,
|
||||
ServerName: cfg.ServerName,
|
||||
ClientAuth: cfg.ClientAuth,
|
||||
ClientCAs: cfg.ClientCAs,
|
||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||
CipherSuites: cfg.CipherSuites,
|
||||
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
|
||||
ClientSessionCache: cfg.ClientSessionCache,
|
||||
MinVersion: cfg.MinVersion,
|
||||
MaxVersion: cfg.MaxVersion,
|
||||
CurvePreferences: cfg.CurvePreferences,
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// +build go1.7,!go1.8
|
||||
|
||||
package redis
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||
return &tls.Config{
|
||||
Rand: cfg.Rand,
|
||||
Time: cfg.Time,
|
||||
Certificates: cfg.Certificates,
|
||||
NameToCertificate: cfg.NameToCertificate,
|
||||
GetCertificate: cfg.GetCertificate,
|
||||
RootCAs: cfg.RootCAs,
|
||||
NextProtos: cfg.NextProtos,
|
||||
ServerName: cfg.ServerName,
|
||||
ClientAuth: cfg.ClientAuth,
|
||||
ClientCAs: cfg.ClientCAs,
|
||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||
CipherSuites: cfg.CipherSuites,
|
||||
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
|
||||
ClientSessionCache: cfg.ClientSessionCache,
|
||||
MinVersion: cfg.MinVersion,
|
||||
MaxVersion: cfg.MaxVersion,
|
||||
CurvePreferences: cfg.CurvePreferences,
|
||||
DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled,
|
||||
Renegotiation: cfg.Renegotiation,
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// +build go1.8
|
||||
|
||||
package redis
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||
return cfg.Clone()
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
_ ConnWithTimeout = (*loggingConn)(nil)
|
||||
)
|
||||
|
||||
// NewLoggingConn returns a logging wrapper around a connection.
|
||||
func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn {
|
||||
if prefix != "" {
|
||||
prefix = prefix + "."
|
||||
}
|
||||
return &loggingConn{conn, logger, prefix}
|
||||
}
|
||||
|
||||
type loggingConn struct {
|
||||
Conn
|
||||
logger *log.Logger
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (c *loggingConn) Close() error {
|
||||
err := c.Conn.Close()
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err)
|
||||
c.logger.Output(2, buf.String())
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) {
|
||||
const chop = 32
|
||||
switch v := v.(type) {
|
||||
case []byte:
|
||||
if len(v) > chop {
|
||||
fmt.Fprintf(buf, "%q...", v[:chop])
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%q", v)
|
||||
}
|
||||
case string:
|
||||
if len(v) > chop {
|
||||
fmt.Fprintf(buf, "%q...", v[:chop])
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%q", v)
|
||||
}
|
||||
case []interface{}:
|
||||
if len(v) == 0 {
|
||||
buf.WriteString("[]")
|
||||
} else {
|
||||
sep := "["
|
||||
fin := "]"
|
||||
if len(v) > chop {
|
||||
v = v[:chop]
|
||||
fin = "...]"
|
||||
}
|
||||
for _, vv := range v {
|
||||
buf.WriteString(sep)
|
||||
c.printValue(buf, vv)
|
||||
sep = ", "
|
||||
}
|
||||
buf.WriteString(fin)
|
||||
}
|
||||
default:
|
||||
fmt.Fprint(buf, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%s%s(", c.prefix, method)
|
||||
if method != "Receive" {
|
||||
buf.WriteString(commandName)
|
||||
for _, arg := range args {
|
||||
buf.WriteString(", ")
|
||||
c.printValue(&buf, arg)
|
||||
}
|
||||
}
|
||||
buf.WriteString(") -> (")
|
||||
if method != "Send" {
|
||||
c.printValue(&buf, reply)
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
fmt.Fprintf(&buf, "%v)", err)
|
||||
c.logger.Output(3, buf.String())
|
||||
}
|
||||
|
||||
func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) {
|
||||
reply, err := c.Conn.Do(commandName, args...)
|
||||
c.print("Do", commandName, args, reply, err)
|
||||
return reply, err
|
||||
}
|
||||
|
||||
func (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) {
|
||||
reply, err := DoWithTimeout(c.Conn, timeout, commandName, args...)
|
||||
c.print("DoWithTimeout", commandName, args, reply, err)
|
||||
return reply, err
|
||||
}
|
||||
|
||||
func (c *loggingConn) Send(commandName string, args ...interface{}) error {
|
||||
err := c.Conn.Send(commandName, args...)
|
||||
c.print("Send", commandName, args, nil, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *loggingConn) Receive() (interface{}, error) {
|
||||
reply, err := c.Conn.Receive()
|
||||
c.print("Receive", "", nil, reply, err)
|
||||
return reply, err
|
||||
}
|
||||
|
||||
func (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) {
|
||||
reply, err := ReceiveWithTimeout(c.Conn, timeout)
|
||||
c.print("ReceiveWithTimeout", "", nil, reply, err)
|
||||
return reply, err
|
||||
}
|
||||
+562
@@ -0,0 +1,562 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/internal"
|
||||
)
|
||||
|
||||
var (
|
||||
_ ConnWithTimeout = (*activeConn)(nil)
|
||||
_ ConnWithTimeout = (*errorConn)(nil)
|
||||
)
|
||||
|
||||
var nowFunc = time.Now // for testing
|
||||
|
||||
// ErrPoolExhausted is returned from a pool connection method (Do, Send,
|
||||
// Receive, Flush, Err) when the maximum number of database connections in the
|
||||
// pool has been reached.
|
||||
var ErrPoolExhausted = errors.New("redigo: connection pool exhausted")
|
||||
|
||||
var (
|
||||
errPoolClosed = errors.New("redigo: connection pool closed")
|
||||
errConnClosed = errors.New("redigo: connection closed")
|
||||
)
|
||||
|
||||
// Pool maintains a pool of connections. The application calls the Get method
|
||||
// to get a connection from the pool and the connection's Close method to
|
||||
// return the connection's resources to the pool.
|
||||
//
|
||||
// The following example shows how to use a pool in a web application. The
|
||||
// application creates a pool at application startup and makes it available to
|
||||
// request handlers using a package level variable. The pool configuration used
|
||||
// here is an example, not a recommendation.
|
||||
//
|
||||
// func newPool(addr string) *redis.Pool {
|
||||
// return &redis.Pool{
|
||||
// MaxIdle: 3,
|
||||
// IdleTimeout: 240 * time.Second,
|
||||
// Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) },
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var (
|
||||
// pool *redis.Pool
|
||||
// redisServer = flag.String("redisServer", ":6379", "")
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// flag.Parse()
|
||||
// pool = newPool(*redisServer)
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// A request handler gets a connection from the pool and closes the connection
|
||||
// when the handler is done:
|
||||
//
|
||||
// func serveHome(w http.ResponseWriter, r *http.Request) {
|
||||
// conn := pool.Get()
|
||||
// defer conn.Close()
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// Use the Dial function to authenticate connections with the AUTH command or
|
||||
// select a database with the SELECT command:
|
||||
//
|
||||
// pool := &redis.Pool{
|
||||
// // Other pool configuration not shown in this example.
|
||||
// Dial: func () (redis.Conn, error) {
|
||||
// c, err := redis.Dial("tcp", server)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// if _, err := c.Do("AUTH", password); err != nil {
|
||||
// c.Close()
|
||||
// return nil, err
|
||||
// }
|
||||
// if _, err := c.Do("SELECT", db); err != nil {
|
||||
// c.Close()
|
||||
// return nil, err
|
||||
// }
|
||||
// return c, nil
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// Use the TestOnBorrow function to check the health of an idle connection
|
||||
// before the connection is returned to the application. This example PINGs
|
||||
// connections that have been idle more than a minute:
|
||||
//
|
||||
// pool := &redis.Pool{
|
||||
// // Other pool configuration not shown in this example.
|
||||
// TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
// if time.Since(t) < time.Minute {
|
||||
// return nil
|
||||
// }
|
||||
// _, err := c.Do("PING")
|
||||
// return err
|
||||
// },
|
||||
// }
|
||||
//
|
||||
type Pool struct {
|
||||
// Dial is an application supplied function for creating and configuring a
|
||||
// connection.
|
||||
//
|
||||
// The connection returned from Dial must not be in a special state
|
||||
// (subscribed to pubsub channel, transaction started, ...).
|
||||
Dial func() (Conn, error)
|
||||
|
||||
// TestOnBorrow is an optional application supplied function for checking
|
||||
// the health of an idle connection before the connection is used again by
|
||||
// the application. Argument t is the time that the connection was returned
|
||||
// to the pool. If the function returns an error, then the connection is
|
||||
// closed.
|
||||
TestOnBorrow func(c Conn, t time.Time) error
|
||||
|
||||
// Maximum number of idle connections in the pool.
|
||||
MaxIdle int
|
||||
|
||||
// Maximum number of connections allocated by the pool at a given time.
|
||||
// When zero, there is no limit on the number of connections in the pool.
|
||||
MaxActive int
|
||||
|
||||
// Close connections after remaining idle for this duration. If the value
|
||||
// is zero, then idle connections are not closed. Applications should set
|
||||
// the timeout to a value less than the server's timeout.
|
||||
IdleTimeout time.Duration
|
||||
|
||||
// If Wait is true and the pool is at the MaxActive limit, then Get() waits
|
||||
// for a connection to be returned to the pool before returning.
|
||||
Wait bool
|
||||
|
||||
// Close connections older than this duration. If the value is zero, then
|
||||
// the pool does not close connections based on age.
|
||||
MaxConnLifetime time.Duration
|
||||
|
||||
chInitialized uint32 // set to 1 when field ch is initialized
|
||||
|
||||
mu sync.Mutex // mu protects the following fields
|
||||
closed bool // set to true when the pool is closed.
|
||||
active int // the number of open connections in the pool
|
||||
ch chan struct{} // limits open connections when p.Wait is true
|
||||
idle idleList // idle connections
|
||||
}
|
||||
|
||||
// NewPool creates a new pool.
|
||||
//
|
||||
// Deprecated: Initialize the Pool directory as shown in the example.
|
||||
func NewPool(newFn func() (Conn, error), maxIdle int) *Pool {
|
||||
return &Pool{Dial: newFn, MaxIdle: maxIdle}
|
||||
}
|
||||
|
||||
// Get gets a connection. The application must close the returned connection.
|
||||
// This method always returns a valid connection so that applications can defer
|
||||
// error handling to the first use of the connection. If there is an error
|
||||
// getting an underlying connection, then the connection Err, Do, Send, Flush
|
||||
// and Receive methods return that error.
|
||||
func (p *Pool) Get() Conn {
|
||||
pc, err := p.get(nil)
|
||||
if err != nil {
|
||||
return errorConn{err}
|
||||
}
|
||||
return &activeConn{p: p, pc: pc}
|
||||
}
|
||||
|
||||
// PoolStats contains pool statistics.
|
||||
type PoolStats struct {
|
||||
// ActiveCount is the number of connections in the pool. The count includes
|
||||
// idle connections and connections in use.
|
||||
ActiveCount int
|
||||
// IdleCount is the number of idle connections in the pool.
|
||||
IdleCount int
|
||||
}
|
||||
|
||||
// Stats returns pool's statistics.
|
||||
func (p *Pool) Stats() PoolStats {
|
||||
p.mu.Lock()
|
||||
stats := PoolStats{
|
||||
ActiveCount: p.active,
|
||||
IdleCount: p.idle.count,
|
||||
}
|
||||
p.mu.Unlock()
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
// ActiveCount returns the number of connections in the pool. The count
|
||||
// includes idle connections and connections in use.
|
||||
func (p *Pool) ActiveCount() int {
|
||||
p.mu.Lock()
|
||||
active := p.active
|
||||
p.mu.Unlock()
|
||||
return active
|
||||
}
|
||||
|
||||
// IdleCount returns the number of idle connections in the pool.
|
||||
func (p *Pool) IdleCount() int {
|
||||
p.mu.Lock()
|
||||
idle := p.idle.count
|
||||
p.mu.Unlock()
|
||||
return idle
|
||||
}
|
||||
|
||||
// Close releases the resources used by the pool.
|
||||
func (p *Pool) Close() error {
|
||||
p.mu.Lock()
|
||||
if p.closed {
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
p.closed = true
|
||||
p.active -= p.idle.count
|
||||
pc := p.idle.front
|
||||
p.idle.count = 0
|
||||
p.idle.front, p.idle.back = nil, nil
|
||||
if p.ch != nil {
|
||||
close(p.ch)
|
||||
}
|
||||
p.mu.Unlock()
|
||||
for ; pc != nil; pc = pc.next {
|
||||
pc.c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pool) lazyInit() {
|
||||
// Fast path.
|
||||
if atomic.LoadUint32(&p.chInitialized) == 1 {
|
||||
return
|
||||
}
|
||||
// Slow path.
|
||||
p.mu.Lock()
|
||||
if p.chInitialized == 0 {
|
||||
p.ch = make(chan struct{}, p.MaxActive)
|
||||
if p.closed {
|
||||
close(p.ch)
|
||||
} else {
|
||||
for i := 0; i < p.MaxActive; i++ {
|
||||
p.ch <- struct{}{}
|
||||
}
|
||||
}
|
||||
atomic.StoreUint32(&p.chInitialized, 1)
|
||||
}
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
// get prunes stale connections and returns a connection from the idle list or
|
||||
// creates a new connection.
|
||||
func (p *Pool) get(ctx interface {
|
||||
Done() <-chan struct{}
|
||||
Err() error
|
||||
}) (*poolConn, error) {
|
||||
|
||||
// Handle limit for p.Wait == true.
|
||||
if p.Wait && p.MaxActive > 0 {
|
||||
p.lazyInit()
|
||||
if ctx == nil {
|
||||
<-p.ch
|
||||
} else {
|
||||
select {
|
||||
case <-p.ch:
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
|
||||
// Prune stale connections at the back of the idle list.
|
||||
if p.IdleTimeout > 0 {
|
||||
n := p.idle.count
|
||||
for i := 0; i < n && p.idle.back != nil && p.idle.back.t.Add(p.IdleTimeout).Before(nowFunc()); i++ {
|
||||
pc := p.idle.back
|
||||
p.idle.popBack()
|
||||
p.mu.Unlock()
|
||||
pc.c.Close()
|
||||
p.mu.Lock()
|
||||
p.active--
|
||||
}
|
||||
}
|
||||
|
||||
// Get idle connection from the front of idle list.
|
||||
for p.idle.front != nil {
|
||||
pc := p.idle.front
|
||||
p.idle.popFront()
|
||||
p.mu.Unlock()
|
||||
if (p.TestOnBorrow == nil || p.TestOnBorrow(pc.c, pc.t) == nil) &&
|
||||
(p.MaxConnLifetime == 0 || nowFunc().Sub(pc.created) < p.MaxConnLifetime) {
|
||||
return pc, nil
|
||||
}
|
||||
pc.c.Close()
|
||||
p.mu.Lock()
|
||||
p.active--
|
||||
}
|
||||
|
||||
// Check for pool closed before dialing a new connection.
|
||||
if p.closed {
|
||||
p.mu.Unlock()
|
||||
return nil, errors.New("redigo: get on closed pool")
|
||||
}
|
||||
|
||||
// Handle limit for p.Wait == false.
|
||||
if !p.Wait && p.MaxActive > 0 && p.active >= p.MaxActive {
|
||||
p.mu.Unlock()
|
||||
return nil, ErrPoolExhausted
|
||||
}
|
||||
|
||||
p.active++
|
||||
p.mu.Unlock()
|
||||
c, err := p.Dial()
|
||||
if err != nil {
|
||||
c = nil
|
||||
p.mu.Lock()
|
||||
p.active--
|
||||
if p.ch != nil && !p.closed {
|
||||
p.ch <- struct{}{}
|
||||
}
|
||||
p.mu.Unlock()
|
||||
}
|
||||
return &poolConn{c: c, created: nowFunc()}, err
|
||||
}
|
||||
|
||||
func (p *Pool) put(pc *poolConn, forceClose bool) error {
|
||||
p.mu.Lock()
|
||||
if !p.closed && !forceClose {
|
||||
pc.t = nowFunc()
|
||||
p.idle.pushFront(pc)
|
||||
if p.idle.count > p.MaxIdle {
|
||||
pc = p.idle.back
|
||||
p.idle.popBack()
|
||||
} else {
|
||||
pc = nil
|
||||
}
|
||||
}
|
||||
|
||||
if pc != nil {
|
||||
p.mu.Unlock()
|
||||
pc.c.Close()
|
||||
p.mu.Lock()
|
||||
p.active--
|
||||
}
|
||||
|
||||
if p.ch != nil && !p.closed {
|
||||
p.ch <- struct{}{}
|
||||
}
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
type activeConn struct {
|
||||
p *Pool
|
||||
pc *poolConn
|
||||
state int
|
||||
}
|
||||
|
||||
var (
|
||||
sentinel []byte
|
||||
sentinelOnce sync.Once
|
||||
)
|
||||
|
||||
func initSentinel() {
|
||||
p := make([]byte, 64)
|
||||
if _, err := rand.Read(p); err == nil {
|
||||
sentinel = p
|
||||
} else {
|
||||
h := sha1.New()
|
||||
io.WriteString(h, "Oops, rand failed. Use time instead.")
|
||||
io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10))
|
||||
sentinel = h.Sum(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (ac *activeConn) Close() error {
|
||||
pc := ac.pc
|
||||
if pc == nil {
|
||||
return nil
|
||||
}
|
||||
ac.pc = nil
|
||||
|
||||
if ac.state&internal.MultiState != 0 {
|
||||
pc.c.Send("DISCARD")
|
||||
ac.state &^= (internal.MultiState | internal.WatchState)
|
||||
} else if ac.state&internal.WatchState != 0 {
|
||||
pc.c.Send("UNWATCH")
|
||||
ac.state &^= internal.WatchState
|
||||
}
|
||||
if ac.state&internal.SubscribeState != 0 {
|
||||
pc.c.Send("UNSUBSCRIBE")
|
||||
pc.c.Send("PUNSUBSCRIBE")
|
||||
// To detect the end of the message stream, ask the server to echo
|
||||
// a sentinel value and read until we see that value.
|
||||
sentinelOnce.Do(initSentinel)
|
||||
pc.c.Send("ECHO", sentinel)
|
||||
pc.c.Flush()
|
||||
for {
|
||||
p, err := pc.c.Receive()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) {
|
||||
ac.state &^= internal.SubscribeState
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
pc.c.Do("")
|
||||
ac.p.put(pc, ac.state != 0 || pc.c.Err() != nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ac *activeConn) Err() error {
|
||||
pc := ac.pc
|
||||
if pc == nil {
|
||||
return errConnClosed
|
||||
}
|
||||
return pc.c.Err()
|
||||
}
|
||||
|
||||
func (ac *activeConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||
pc := ac.pc
|
||||
if pc == nil {
|
||||
return nil, errConnClosed
|
||||
}
|
||||
ci := internal.LookupCommandInfo(commandName)
|
||||
ac.state = (ac.state | ci.Set) &^ ci.Clear
|
||||
return pc.c.Do(commandName, args...)
|
||||
}
|
||||
|
||||
func (ac *activeConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||
pc := ac.pc
|
||||
if pc == nil {
|
||||
return nil, errConnClosed
|
||||
}
|
||||
cwt, ok := pc.c.(ConnWithTimeout)
|
||||
if !ok {
|
||||
return nil, errTimeoutNotSupported
|
||||
}
|
||||
ci := internal.LookupCommandInfo(commandName)
|
||||
ac.state = (ac.state | ci.Set) &^ ci.Clear
|
||||
return cwt.DoWithTimeout(timeout, commandName, args...)
|
||||
}
|
||||
|
||||
func (ac *activeConn) Send(commandName string, args ...interface{}) error {
|
||||
pc := ac.pc
|
||||
if pc == nil {
|
||||
return errConnClosed
|
||||
}
|
||||
ci := internal.LookupCommandInfo(commandName)
|
||||
ac.state = (ac.state | ci.Set) &^ ci.Clear
|
||||
return pc.c.Send(commandName, args...)
|
||||
}
|
||||
|
||||
func (ac *activeConn) Flush() error {
|
||||
pc := ac.pc
|
||||
if pc == nil {
|
||||
return errConnClosed
|
||||
}
|
||||
return pc.c.Flush()
|
||||
}
|
||||
|
||||
func (ac *activeConn) Receive() (reply interface{}, err error) {
|
||||
pc := ac.pc
|
||||
if pc == nil {
|
||||
return nil, errConnClosed
|
||||
}
|
||||
return pc.c.Receive()
|
||||
}
|
||||
|
||||
func (ac *activeConn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) {
|
||||
pc := ac.pc
|
||||
if pc == nil {
|
||||
return nil, errConnClosed
|
||||
}
|
||||
cwt, ok := pc.c.(ConnWithTimeout)
|
||||
if !ok {
|
||||
return nil, errTimeoutNotSupported
|
||||
}
|
||||
return cwt.ReceiveWithTimeout(timeout)
|
||||
}
|
||||
|
||||
type errorConn struct{ err error }
|
||||
|
||||
func (ec errorConn) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err }
|
||||
func (ec errorConn) DoWithTimeout(time.Duration, string, ...interface{}) (interface{}, error) {
|
||||
return nil, ec.err
|
||||
}
|
||||
func (ec errorConn) Send(string, ...interface{}) error { return ec.err }
|
||||
func (ec errorConn) Err() error { return ec.err }
|
||||
func (ec errorConn) Close() error { return nil }
|
||||
func (ec errorConn) Flush() error { return ec.err }
|
||||
func (ec errorConn) Receive() (interface{}, error) { return nil, ec.err }
|
||||
func (ec errorConn) ReceiveWithTimeout(time.Duration) (interface{}, error) { return nil, ec.err }
|
||||
|
||||
type idleList struct {
|
||||
count int
|
||||
front, back *poolConn
|
||||
}
|
||||
|
||||
type poolConn struct {
|
||||
c Conn
|
||||
t time.Time
|
||||
created time.Time
|
||||
next, prev *poolConn
|
||||
}
|
||||
|
||||
func (l *idleList) pushFront(pc *poolConn) {
|
||||
pc.next = l.front
|
||||
pc.prev = nil
|
||||
if l.count == 0 {
|
||||
l.back = pc
|
||||
} else {
|
||||
l.front.prev = pc
|
||||
}
|
||||
l.front = pc
|
||||
l.count++
|
||||
return
|
||||
}
|
||||
|
||||
func (l *idleList) popFront() {
|
||||
pc := l.front
|
||||
l.count--
|
||||
if l.count == 0 {
|
||||
l.front, l.back = nil, nil
|
||||
} else {
|
||||
pc.next.prev = nil
|
||||
l.front = pc.next
|
||||
}
|
||||
pc.next, pc.prev = nil, nil
|
||||
}
|
||||
|
||||
func (l *idleList) popBack() {
|
||||
pc := l.back
|
||||
l.count--
|
||||
if l.count == 0 {
|
||||
l.front, l.back = nil, nil
|
||||
} else {
|
||||
pc.prev.next = nil
|
||||
l.back = pc.prev
|
||||
}
|
||||
pc.next, pc.prev = nil, nil
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright 2018 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package redis
|
||||
|
||||
import "context"
|
||||
|
||||
// GetContext gets a connection using the provided context.
|
||||
//
|
||||
// The provided Context must be non-nil. If the context expires before the
|
||||
// connection is complete, an error is returned. Any expiration on the context
|
||||
// will not affect the returned connection.
|
||||
//
|
||||
// If the function completes without error, then the application must close the
|
||||
// returned connection.
|
||||
func (p *Pool) GetContext(ctx context.Context) (Conn, error) {
|
||||
pc, err := p.get(ctx)
|
||||
if err != nil {
|
||||
return errorConn{err}, err
|
||||
}
|
||||
return &activeConn{p: p, pc: pc}, nil
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Subscription represents a subscribe or unsubscribe notification.
|
||||
type Subscription struct {
|
||||
// Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe"
|
||||
Kind string
|
||||
|
||||
// The channel that was changed.
|
||||
Channel string
|
||||
|
||||
// The current number of subscriptions for connection.
|
||||
Count int
|
||||
}
|
||||
|
||||
// Message represents a message notification.
|
||||
type Message struct {
|
||||
// The originating channel.
|
||||
Channel string
|
||||
|
||||
// The message data.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// PMessage represents a pmessage notification.
|
||||
type PMessage struct {
|
||||
// The matched pattern.
|
||||
Pattern string
|
||||
|
||||
// The originating channel.
|
||||
Channel string
|
||||
|
||||
// The message data.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// Pong represents a pubsub pong notification.
|
||||
type Pong struct {
|
||||
Data string
|
||||
}
|
||||
|
||||
// PubSubConn wraps a Conn with convenience methods for subscribers.
|
||||
type PubSubConn struct {
|
||||
Conn Conn
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
func (c PubSubConn) Close() error {
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
// Subscribe subscribes the connection to the specified channels.
|
||||
func (c PubSubConn) Subscribe(channel ...interface{}) error {
|
||||
c.Conn.Send("SUBSCRIBE", channel...)
|
||||
return c.Conn.Flush()
|
||||
}
|
||||
|
||||
// PSubscribe subscribes the connection to the given patterns.
|
||||
func (c PubSubConn) PSubscribe(channel ...interface{}) error {
|
||||
c.Conn.Send("PSUBSCRIBE", channel...)
|
||||
return c.Conn.Flush()
|
||||
}
|
||||
|
||||
// Unsubscribe unsubscribes the connection from the given channels, or from all
|
||||
// of them if none is given.
|
||||
func (c PubSubConn) Unsubscribe(channel ...interface{}) error {
|
||||
c.Conn.Send("UNSUBSCRIBE", channel...)
|
||||
return c.Conn.Flush()
|
||||
}
|
||||
|
||||
// PUnsubscribe unsubscribes the connection from the given patterns, or from all
|
||||
// of them if none is given.
|
||||
func (c PubSubConn) PUnsubscribe(channel ...interface{}) error {
|
||||
c.Conn.Send("PUNSUBSCRIBE", channel...)
|
||||
return c.Conn.Flush()
|
||||
}
|
||||
|
||||
// Ping sends a PING to the server with the specified data.
|
||||
//
|
||||
// The connection must be subscribed to at least one channel or pattern when
|
||||
// calling this method.
|
||||
func (c PubSubConn) Ping(data string) error {
|
||||
c.Conn.Send("PING", data)
|
||||
return c.Conn.Flush()
|
||||
}
|
||||
|
||||
// Receive returns a pushed message as a Subscription, Message, PMessage, Pong
|
||||
// or error. The return value is intended to be used directly in a type switch
|
||||
// as illustrated in the PubSubConn example.
|
||||
func (c PubSubConn) Receive() interface{} {
|
||||
return c.receiveInternal(c.Conn.Receive())
|
||||
}
|
||||
|
||||
// ReceiveWithTimeout is like Receive, but it allows the application to
|
||||
// override the connection's default timeout.
|
||||
func (c PubSubConn) ReceiveWithTimeout(timeout time.Duration) interface{} {
|
||||
return c.receiveInternal(ReceiveWithTimeout(c.Conn, timeout))
|
||||
}
|
||||
|
||||
func (c PubSubConn) receiveInternal(replyArg interface{}, errArg error) interface{} {
|
||||
reply, err := Values(replyArg, errArg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var kind string
|
||||
reply, err = Scan(reply, &kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case "message":
|
||||
var m Message
|
||||
if _, err := Scan(reply, &m.Channel, &m.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
return m
|
||||
case "pmessage":
|
||||
var pm PMessage
|
||||
if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
return pm
|
||||
case "subscribe", "psubscribe", "unsubscribe", "punsubscribe":
|
||||
s := Subscription{Kind: kind}
|
||||
if _, err := Scan(reply, &s.Channel, &s.Count); err != nil {
|
||||
return err
|
||||
}
|
||||
return s
|
||||
case "pong":
|
||||
var p Pong
|
||||
if _, err := Scan(reply, &p.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
return p
|
||||
}
|
||||
return errors.New("redigo: unknown pubsub notification")
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Error represents an error returned in a command reply.
|
||||
type Error string
|
||||
|
||||
func (err Error) Error() string { return string(err) }
|
||||
|
||||
// Conn represents a connection to a Redis server.
|
||||
type Conn interface {
|
||||
// Close closes the connection.
|
||||
Close() error
|
||||
|
||||
// Err returns a non-nil value when the connection is not usable.
|
||||
Err() error
|
||||
|
||||
// Do sends a command to the server and returns the received reply.
|
||||
Do(commandName string, args ...interface{}) (reply interface{}, err error)
|
||||
|
||||
// Send writes the command to the client's output buffer.
|
||||
Send(commandName string, args ...interface{}) error
|
||||
|
||||
// Flush flushes the output buffer to the Redis server.
|
||||
Flush() error
|
||||
|
||||
// Receive receives a single reply from the Redis server
|
||||
Receive() (reply interface{}, err error)
|
||||
}
|
||||
|
||||
// Argument is the interface implemented by an object which wants to control how
|
||||
// the object is converted to Redis bulk strings.
|
||||
type Argument interface {
|
||||
// RedisArg returns a value to be encoded as a bulk string per the
|
||||
// conversions listed in the section 'Executing Commands'.
|
||||
// Implementations should typically return a []byte or string.
|
||||
RedisArg() interface{}
|
||||
}
|
||||
|
||||
// Scanner is implemented by an object which wants to control its value is
|
||||
// interpreted when read from Redis.
|
||||
type Scanner interface {
|
||||
// RedisScan assigns a value from a Redis value. The argument src is one of
|
||||
// the reply types listed in the section `Executing Commands`.
|
||||
//
|
||||
// An error should be returned if the value cannot be stored without
|
||||
// loss of information.
|
||||
RedisScan(src interface{}) error
|
||||
}
|
||||
|
||||
// ConnWithTimeout is an optional interface that allows the caller to override
|
||||
// a connection's default read timeout. This interface is useful for executing
|
||||
// the BLPOP, BRPOP, BRPOPLPUSH, XREAD and other commands that block at the
|
||||
// server.
|
||||
//
|
||||
// A connection's default read timeout is set with the DialReadTimeout dial
|
||||
// option. Applications should rely on the default timeout for commands that do
|
||||
// not block at the server.
|
||||
//
|
||||
// All of the Conn implementations in this package satisfy the ConnWithTimeout
|
||||
// interface.
|
||||
//
|
||||
// Use the DoWithTimeout and ReceiveWithTimeout helper functions to simplify
|
||||
// use of this interface.
|
||||
type ConnWithTimeout interface {
|
||||
Conn
|
||||
|
||||
// Do sends a command to the server and returns the received reply.
|
||||
// The timeout overrides the read timeout set when dialing the
|
||||
// connection.
|
||||
DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error)
|
||||
|
||||
// Receive receives a single reply from the Redis server. The timeout
|
||||
// overrides the read timeout set when dialing the connection.
|
||||
ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error)
|
||||
}
|
||||
|
||||
var errTimeoutNotSupported = errors.New("redis: connection does not support ConnWithTimeout")
|
||||
|
||||
// DoWithTimeout executes a Redis command with the specified read timeout. If
|
||||
// the connection does not satisfy the ConnWithTimeout interface, then an error
|
||||
// is returned.
|
||||
func DoWithTimeout(c Conn, timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {
|
||||
cwt, ok := c.(ConnWithTimeout)
|
||||
if !ok {
|
||||
return nil, errTimeoutNotSupported
|
||||
}
|
||||
return cwt.DoWithTimeout(timeout, cmd, args...)
|
||||
}
|
||||
|
||||
// ReceiveWithTimeout receives a reply with the specified read timeout. If the
|
||||
// connection does not satisfy the ConnWithTimeout interface, then an error is
|
||||
// returned.
|
||||
func ReceiveWithTimeout(c Conn, timeout time.Duration) (interface{}, error) {
|
||||
cwt, ok := c.(ConnWithTimeout)
|
||||
if !ok {
|
||||
return nil, errTimeoutNotSupported
|
||||
}
|
||||
return cwt.ReceiveWithTimeout(timeout)
|
||||
}
|
||||
+479
@@ -0,0 +1,479 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ErrNil indicates that a reply value is nil.
|
||||
var ErrNil = errors.New("redigo: nil returned")
|
||||
|
||||
// Int is a helper that converts a command reply to an integer. If err is not
|
||||
// equal to nil, then Int returns 0, err. Otherwise, Int converts the
|
||||
// reply to an int as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// integer int(reply), nil
|
||||
// bulk string parsed reply, nil
|
||||
// nil 0, ErrNil
|
||||
// other 0, error
|
||||
func Int(reply interface{}, err error) (int, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case int64:
|
||||
x := int(reply)
|
||||
if int64(x) != reply {
|
||||
return 0, strconv.ErrRange
|
||||
}
|
||||
return x, nil
|
||||
case []byte:
|
||||
n, err := strconv.ParseInt(string(reply), 10, 0)
|
||||
return int(n), err
|
||||
case nil:
|
||||
return 0, ErrNil
|
||||
case Error:
|
||||
return 0, reply
|
||||
}
|
||||
return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply)
|
||||
}
|
||||
|
||||
// Int64 is a helper that converts a command reply to 64 bit integer. If err is
|
||||
// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the
|
||||
// reply to an int64 as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// integer reply, nil
|
||||
// bulk string parsed reply, nil
|
||||
// nil 0, ErrNil
|
||||
// other 0, error
|
||||
func Int64(reply interface{}, err error) (int64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case int64:
|
||||
return reply, nil
|
||||
case []byte:
|
||||
n, err := strconv.ParseInt(string(reply), 10, 64)
|
||||
return n, err
|
||||
case nil:
|
||||
return 0, ErrNil
|
||||
case Error:
|
||||
return 0, reply
|
||||
}
|
||||
return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply)
|
||||
}
|
||||
|
||||
var errNegativeInt = errors.New("redigo: unexpected value for Uint64")
|
||||
|
||||
// Uint64 is a helper that converts a command reply to 64 bit integer. If err is
|
||||
// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the
|
||||
// reply to an int64 as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// integer reply, nil
|
||||
// bulk string parsed reply, nil
|
||||
// nil 0, ErrNil
|
||||
// other 0, error
|
||||
func Uint64(reply interface{}, err error) (uint64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case int64:
|
||||
if reply < 0 {
|
||||
return 0, errNegativeInt
|
||||
}
|
||||
return uint64(reply), nil
|
||||
case []byte:
|
||||
n, err := strconv.ParseUint(string(reply), 10, 64)
|
||||
return n, err
|
||||
case nil:
|
||||
return 0, ErrNil
|
||||
case Error:
|
||||
return 0, reply
|
||||
}
|
||||
return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply)
|
||||
}
|
||||
|
||||
// Float64 is a helper that converts a command reply to 64 bit float. If err is
|
||||
// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts
|
||||
// the reply to an int as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// bulk string parsed reply, nil
|
||||
// nil 0, ErrNil
|
||||
// other 0, error
|
||||
func Float64(reply interface{}, err error) (float64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []byte:
|
||||
n, err := strconv.ParseFloat(string(reply), 64)
|
||||
return n, err
|
||||
case nil:
|
||||
return 0, ErrNil
|
||||
case Error:
|
||||
return 0, reply
|
||||
}
|
||||
return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply)
|
||||
}
|
||||
|
||||
// String is a helper that converts a command reply to a string. If err is not
|
||||
// equal to nil, then String returns "", err. Otherwise String converts the
|
||||
// reply to a string as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// bulk string string(reply), nil
|
||||
// simple string reply, nil
|
||||
// nil "", ErrNil
|
||||
// other "", error
|
||||
func String(reply interface{}, err error) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []byte:
|
||||
return string(reply), nil
|
||||
case string:
|
||||
return reply, nil
|
||||
case nil:
|
||||
return "", ErrNil
|
||||
case Error:
|
||||
return "", reply
|
||||
}
|
||||
return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply)
|
||||
}
|
||||
|
||||
// Bytes is a helper that converts a command reply to a slice of bytes. If err
|
||||
// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts
|
||||
// the reply to a slice of bytes as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// bulk string reply, nil
|
||||
// simple string []byte(reply), nil
|
||||
// nil nil, ErrNil
|
||||
// other nil, error
|
||||
func Bytes(reply interface{}, err error) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []byte:
|
||||
return reply, nil
|
||||
case string:
|
||||
return []byte(reply), nil
|
||||
case nil:
|
||||
return nil, ErrNil
|
||||
case Error:
|
||||
return nil, reply
|
||||
}
|
||||
return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply)
|
||||
}
|
||||
|
||||
// Bool is a helper that converts a command reply to a boolean. If err is not
|
||||
// equal to nil, then Bool returns false, err. Otherwise Bool converts the
|
||||
// reply to boolean as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// integer value != 0, nil
|
||||
// bulk string strconv.ParseBool(reply)
|
||||
// nil false, ErrNil
|
||||
// other false, error
|
||||
func Bool(reply interface{}, err error) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case int64:
|
||||
return reply != 0, nil
|
||||
case []byte:
|
||||
return strconv.ParseBool(string(reply))
|
||||
case nil:
|
||||
return false, ErrNil
|
||||
case Error:
|
||||
return false, reply
|
||||
}
|
||||
return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply)
|
||||
}
|
||||
|
||||
// MultiBulk is a helper that converts an array command reply to a []interface{}.
|
||||
//
|
||||
// Deprecated: Use Values instead.
|
||||
func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) }
|
||||
|
||||
// Values is a helper that converts an array command reply to a []interface{}.
|
||||
// If err is not equal to nil, then Values returns nil, err. Otherwise, Values
|
||||
// converts the reply as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// array reply, nil
|
||||
// nil nil, ErrNil
|
||||
// other nil, error
|
||||
func Values(reply interface{}, err error) ([]interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []interface{}:
|
||||
return reply, nil
|
||||
case nil:
|
||||
return nil, ErrNil
|
||||
case Error:
|
||||
return nil, reply
|
||||
}
|
||||
return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply)
|
||||
}
|
||||
|
||||
func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []interface{}:
|
||||
makeSlice(len(reply))
|
||||
for i := range reply {
|
||||
if reply[i] == nil {
|
||||
continue
|
||||
}
|
||||
if err := assign(i, reply[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case nil:
|
||||
return ErrNil
|
||||
case Error:
|
||||
return reply
|
||||
}
|
||||
return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply)
|
||||
}
|
||||
|
||||
// Float64s is a helper that converts an array command reply to a []float64. If
|
||||
// err is not equal to nil, then Float64s returns nil, err. Nil array items are
|
||||
// converted to 0 in the output slice. Floats64 returns an error if an array
|
||||
// item is not a bulk string or nil.
|
||||
func Float64s(reply interface{}, err error) ([]float64, error) {
|
||||
var result []float64
|
||||
err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error {
|
||||
p, ok := v.([]byte)
|
||||
if !ok {
|
||||
return fmt.Errorf("redigo: unexpected element type for Floats64, got type %T", v)
|
||||
}
|
||||
f, err := strconv.ParseFloat(string(p), 64)
|
||||
result[i] = f
|
||||
return err
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Strings is a helper that converts an array command reply to a []string. If
|
||||
// err is not equal to nil, then Strings returns nil, err. Nil array items are
|
||||
// converted to "" in the output slice. Strings returns an error if an array
|
||||
// item is not a bulk string or nil.
|
||||
func Strings(reply interface{}, err error) ([]string, error) {
|
||||
var result []string
|
||||
err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
result[i] = v
|
||||
return nil
|
||||
case []byte:
|
||||
result[i] = string(v)
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v)
|
||||
}
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// ByteSlices is a helper that converts an array command reply to a [][]byte.
|
||||
// If err is not equal to nil, then ByteSlices returns nil, err. Nil array
|
||||
// items are stay nil. ByteSlices returns an error if an array item is not a
|
||||
// bulk string or nil.
|
||||
func ByteSlices(reply interface{}, err error) ([][]byte, error) {
|
||||
var result [][]byte
|
||||
err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error {
|
||||
p, ok := v.([]byte)
|
||||
if !ok {
|
||||
return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v)
|
||||
}
|
||||
result[i] = p
|
||||
return nil
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Int64s is a helper that converts an array command reply to a []int64.
|
||||
// If err is not equal to nil, then Int64s returns nil, err. Nil array
|
||||
// items are stay nil. Int64s returns an error if an array item is not a
|
||||
// bulk string or nil.
|
||||
func Int64s(reply interface{}, err error) ([]int64, error) {
|
||||
var result []int64
|
||||
err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error {
|
||||
switch v := v.(type) {
|
||||
case int64:
|
||||
result[i] = v
|
||||
return nil
|
||||
case []byte:
|
||||
n, err := strconv.ParseInt(string(v), 10, 64)
|
||||
result[i] = n
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v)
|
||||
}
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Ints is a helper that converts an array command reply to a []in.
|
||||
// If err is not equal to nil, then Ints returns nil, err. Nil array
|
||||
// items are stay nil. Ints returns an error if an array item is not a
|
||||
// bulk string or nil.
|
||||
func Ints(reply interface{}, err error) ([]int, error) {
|
||||
var result []int
|
||||
err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error {
|
||||
switch v := v.(type) {
|
||||
case int64:
|
||||
n := int(v)
|
||||
if int64(n) != v {
|
||||
return strconv.ErrRange
|
||||
}
|
||||
result[i] = n
|
||||
return nil
|
||||
case []byte:
|
||||
n, err := strconv.Atoi(string(v))
|
||||
result[i] = n
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v)
|
||||
}
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// StringMap is a helper that converts an array of strings (alternating key, value)
|
||||
// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format.
|
||||
// Requires an even number of values in result.
|
||||
func StringMap(result interface{}, err error) (map[string]string, error) {
|
||||
values, err := Values(result, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(values)%2 != 0 {
|
||||
return nil, errors.New("redigo: StringMap expects even number of values result")
|
||||
}
|
||||
m := make(map[string]string, len(values)/2)
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
key, okKey := values[i].([]byte)
|
||||
value, okValue := values[i+1].([]byte)
|
||||
if !okKey || !okValue {
|
||||
return nil, errors.New("redigo: StringMap key not a bulk string value")
|
||||
}
|
||||
m[string(key)] = string(value)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// IntMap is a helper that converts an array of strings (alternating key, value)
|
||||
// into a map[string]int. The HGETALL commands return replies in this format.
|
||||
// Requires an even number of values in result.
|
||||
func IntMap(result interface{}, err error) (map[string]int, error) {
|
||||
values, err := Values(result, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(values)%2 != 0 {
|
||||
return nil, errors.New("redigo: IntMap expects even number of values result")
|
||||
}
|
||||
m := make(map[string]int, len(values)/2)
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
key, ok := values[i].([]byte)
|
||||
if !ok {
|
||||
return nil, errors.New("redigo: IntMap key not a bulk string value")
|
||||
}
|
||||
value, err := Int(values[i+1], nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[string(key)] = value
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Int64Map is a helper that converts an array of strings (alternating key, value)
|
||||
// into a map[string]int64. The HGETALL commands return replies in this format.
|
||||
// Requires an even number of values in result.
|
||||
func Int64Map(result interface{}, err error) (map[string]int64, error) {
|
||||
values, err := Values(result, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(values)%2 != 0 {
|
||||
return nil, errors.New("redigo: Int64Map expects even number of values result")
|
||||
}
|
||||
m := make(map[string]int64, len(values)/2)
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
key, ok := values[i].([]byte)
|
||||
if !ok {
|
||||
return nil, errors.New("redigo: Int64Map key not a bulk string value")
|
||||
}
|
||||
value, err := Int64(values[i+1], nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[string(key)] = value
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Positions is a helper that converts an array of positions (lat, long)
|
||||
// into a [][2]float64. The GEOPOS command returns replies in this format.
|
||||
func Positions(result interface{}, err error) ([]*[2]float64, error) {
|
||||
values, err := Values(result, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
positions := make([]*[2]float64, len(values))
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
continue
|
||||
}
|
||||
p, ok := values[i].([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("redigo: unexpected element type for interface slice, got type %T", values[i])
|
||||
}
|
||||
if len(p) != 2 {
|
||||
return nil, fmt.Errorf("redigo: unexpected number of values for a member position, got %d", len(p))
|
||||
}
|
||||
lat, err := Float64(p[0], nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
long, err := Float64(p[1], nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
positions[i] = &[2]float64{lat, long}
|
||||
}
|
||||
return positions, nil
|
||||
}
|
||||
+585
@@ -0,0 +1,585 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func ensureLen(d reflect.Value, n int) {
|
||||
if n > d.Cap() {
|
||||
d.Set(reflect.MakeSlice(d.Type(), n, n))
|
||||
} else {
|
||||
d.SetLen(n)
|
||||
}
|
||||
}
|
||||
|
||||
func cannotConvert(d reflect.Value, s interface{}) error {
|
||||
var sname string
|
||||
switch s.(type) {
|
||||
case string:
|
||||
sname = "Redis simple string"
|
||||
case Error:
|
||||
sname = "Redis error"
|
||||
case int64:
|
||||
sname = "Redis integer"
|
||||
case []byte:
|
||||
sname = "Redis bulk string"
|
||||
case []interface{}:
|
||||
sname = "Redis array"
|
||||
default:
|
||||
sname = reflect.TypeOf(s).String()
|
||||
}
|
||||
return fmt.Errorf("cannot convert from %s to %s", sname, d.Type())
|
||||
}
|
||||
|
||||
func convertAssignBulkString(d reflect.Value, s []byte) (err error) {
|
||||
switch d.Type().Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
var x float64
|
||||
x, err = strconv.ParseFloat(string(s), d.Type().Bits())
|
||||
d.SetFloat(x)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
var x int64
|
||||
x, err = strconv.ParseInt(string(s), 10, d.Type().Bits())
|
||||
d.SetInt(x)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
var x uint64
|
||||
x, err = strconv.ParseUint(string(s), 10, d.Type().Bits())
|
||||
d.SetUint(x)
|
||||
case reflect.Bool:
|
||||
var x bool
|
||||
x, err = strconv.ParseBool(string(s))
|
||||
d.SetBool(x)
|
||||
case reflect.String:
|
||||
d.SetString(string(s))
|
||||
case reflect.Slice:
|
||||
if d.Type().Elem().Kind() != reflect.Uint8 {
|
||||
err = cannotConvert(d, s)
|
||||
} else {
|
||||
d.SetBytes(s)
|
||||
}
|
||||
default:
|
||||
err = cannotConvert(d, s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func convertAssignInt(d reflect.Value, s int64) (err error) {
|
||||
switch d.Type().Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
d.SetInt(s)
|
||||
if d.Int() != s {
|
||||
err = strconv.ErrRange
|
||||
d.SetInt(0)
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if s < 0 {
|
||||
err = strconv.ErrRange
|
||||
} else {
|
||||
x := uint64(s)
|
||||
d.SetUint(x)
|
||||
if d.Uint() != x {
|
||||
err = strconv.ErrRange
|
||||
d.SetUint(0)
|
||||
}
|
||||
}
|
||||
case reflect.Bool:
|
||||
d.SetBool(s != 0)
|
||||
default:
|
||||
err = cannotConvert(d, s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func convertAssignValue(d reflect.Value, s interface{}) (err error) {
|
||||
if d.Kind() != reflect.Ptr {
|
||||
if d.CanAddr() {
|
||||
d2 := d.Addr()
|
||||
if d2.CanInterface() {
|
||||
if scanner, ok := d2.Interface().(Scanner); ok {
|
||||
return scanner.RedisScan(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if d.CanInterface() {
|
||||
// Already a reflect.Ptr
|
||||
if d.IsNil() {
|
||||
d.Set(reflect.New(d.Type().Elem()))
|
||||
}
|
||||
if scanner, ok := d.Interface().(Scanner); ok {
|
||||
return scanner.RedisScan(s)
|
||||
}
|
||||
}
|
||||
|
||||
switch s := s.(type) {
|
||||
case []byte:
|
||||
err = convertAssignBulkString(d, s)
|
||||
case int64:
|
||||
err = convertAssignInt(d, s)
|
||||
default:
|
||||
err = cannotConvert(d, s)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func convertAssignArray(d reflect.Value, s []interface{}) error {
|
||||
if d.Type().Kind() != reflect.Slice {
|
||||
return cannotConvert(d, s)
|
||||
}
|
||||
ensureLen(d, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
if err := convertAssignValue(d.Index(i), s[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertAssign(d interface{}, s interface{}) (err error) {
|
||||
if scanner, ok := d.(Scanner); ok {
|
||||
return scanner.RedisScan(s)
|
||||
}
|
||||
|
||||
// Handle the most common destination types using type switches and
|
||||
// fall back to reflection for all other types.
|
||||
switch s := s.(type) {
|
||||
case nil:
|
||||
// ignore
|
||||
case []byte:
|
||||
switch d := d.(type) {
|
||||
case *string:
|
||||
*d = string(s)
|
||||
case *int:
|
||||
*d, err = strconv.Atoi(string(s))
|
||||
case *bool:
|
||||
*d, err = strconv.ParseBool(string(s))
|
||||
case *[]byte:
|
||||
*d = s
|
||||
case *interface{}:
|
||||
*d = s
|
||||
case nil:
|
||||
// skip value
|
||||
default:
|
||||
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
|
||||
err = cannotConvert(d, s)
|
||||
} else {
|
||||
err = convertAssignBulkString(d.Elem(), s)
|
||||
}
|
||||
}
|
||||
case int64:
|
||||
switch d := d.(type) {
|
||||
case *int:
|
||||
x := int(s)
|
||||
if int64(x) != s {
|
||||
err = strconv.ErrRange
|
||||
x = 0
|
||||
}
|
||||
*d = x
|
||||
case *bool:
|
||||
*d = s != 0
|
||||
case *interface{}:
|
||||
*d = s
|
||||
case nil:
|
||||
// skip value
|
||||
default:
|
||||
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
|
||||
err = cannotConvert(d, s)
|
||||
} else {
|
||||
err = convertAssignInt(d.Elem(), s)
|
||||
}
|
||||
}
|
||||
case string:
|
||||
switch d := d.(type) {
|
||||
case *string:
|
||||
*d = s
|
||||
case *interface{}:
|
||||
*d = s
|
||||
case nil:
|
||||
// skip value
|
||||
default:
|
||||
err = cannotConvert(reflect.ValueOf(d), s)
|
||||
}
|
||||
case []interface{}:
|
||||
switch d := d.(type) {
|
||||
case *[]interface{}:
|
||||
*d = s
|
||||
case *interface{}:
|
||||
*d = s
|
||||
case nil:
|
||||
// skip value
|
||||
default:
|
||||
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
|
||||
err = cannotConvert(d, s)
|
||||
} else {
|
||||
err = convertAssignArray(d.Elem(), s)
|
||||
}
|
||||
}
|
||||
case Error:
|
||||
err = s
|
||||
default:
|
||||
err = cannotConvert(reflect.ValueOf(d), s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Scan copies from src to the values pointed at by dest.
|
||||
//
|
||||
// Scan uses RedisScan if available otherwise:
|
||||
//
|
||||
// The values pointed at by dest must be an integer, float, boolean, string,
|
||||
// []byte, interface{} or slices of these types. Scan uses the standard strconv
|
||||
// package to convert bulk strings to numeric and boolean types.
|
||||
//
|
||||
// If a dest value is nil, then the corresponding src value is skipped.
|
||||
//
|
||||
// If a src element is nil, then the corresponding dest value is not modified.
|
||||
//
|
||||
// To enable easy use of Scan in a loop, Scan returns the slice of src
|
||||
// following the copied values.
|
||||
func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) {
|
||||
if len(src) < len(dest) {
|
||||
return nil, errors.New("redigo.Scan: array short")
|
||||
}
|
||||
var err error
|
||||
for i, d := range dest {
|
||||
err = convertAssign(d, src[i])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err)
|
||||
break
|
||||
}
|
||||
}
|
||||
return src[len(dest):], err
|
||||
}
|
||||
|
||||
type fieldSpec struct {
|
||||
name string
|
||||
index []int
|
||||
omitEmpty bool
|
||||
}
|
||||
|
||||
type structSpec struct {
|
||||
m map[string]*fieldSpec
|
||||
l []*fieldSpec
|
||||
}
|
||||
|
||||
func (ss *structSpec) fieldSpec(name []byte) *fieldSpec {
|
||||
return ss.m[string(name)]
|
||||
}
|
||||
|
||||
func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) {
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
switch {
|
||||
case f.PkgPath != "" && !f.Anonymous:
|
||||
// Ignore unexported fields.
|
||||
case f.Anonymous:
|
||||
// TODO: Handle pointers. Requires change to decoder and
|
||||
// protection against infinite recursion.
|
||||
if f.Type.Kind() == reflect.Struct {
|
||||
compileStructSpec(f.Type, depth, append(index, i), ss)
|
||||
}
|
||||
default:
|
||||
fs := &fieldSpec{name: f.Name}
|
||||
tag := f.Tag.Get("redis")
|
||||
p := strings.Split(tag, ",")
|
||||
if len(p) > 0 {
|
||||
if p[0] == "-" {
|
||||
continue
|
||||
}
|
||||
if len(p[0]) > 0 {
|
||||
fs.name = p[0]
|
||||
}
|
||||
for _, s := range p[1:] {
|
||||
switch s {
|
||||
case "omitempty":
|
||||
fs.omitEmpty = true
|
||||
default:
|
||||
panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
d, found := depth[fs.name]
|
||||
if !found {
|
||||
d = 1 << 30
|
||||
}
|
||||
switch {
|
||||
case len(index) == d:
|
||||
// At same depth, remove from result.
|
||||
delete(ss.m, fs.name)
|
||||
j := 0
|
||||
for i := 0; i < len(ss.l); i++ {
|
||||
if fs.name != ss.l[i].name {
|
||||
ss.l[j] = ss.l[i]
|
||||
j += 1
|
||||
}
|
||||
}
|
||||
ss.l = ss.l[:j]
|
||||
case len(index) < d:
|
||||
fs.index = make([]int, len(index)+1)
|
||||
copy(fs.index, index)
|
||||
fs.index[len(index)] = i
|
||||
depth[fs.name] = len(index)
|
||||
ss.m[fs.name] = fs
|
||||
ss.l = append(ss.l, fs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
structSpecMutex sync.RWMutex
|
||||
structSpecCache = make(map[reflect.Type]*structSpec)
|
||||
defaultFieldSpec = &fieldSpec{}
|
||||
)
|
||||
|
||||
func structSpecForType(t reflect.Type) *structSpec {
|
||||
|
||||
structSpecMutex.RLock()
|
||||
ss, found := structSpecCache[t]
|
||||
structSpecMutex.RUnlock()
|
||||
if found {
|
||||
return ss
|
||||
}
|
||||
|
||||
structSpecMutex.Lock()
|
||||
defer structSpecMutex.Unlock()
|
||||
ss, found = structSpecCache[t]
|
||||
if found {
|
||||
return ss
|
||||
}
|
||||
|
||||
ss = &structSpec{m: make(map[string]*fieldSpec)}
|
||||
compileStructSpec(t, make(map[string]int), nil, ss)
|
||||
structSpecCache[t] = ss
|
||||
return ss
|
||||
}
|
||||
|
||||
var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct")
|
||||
|
||||
// ScanStruct scans alternating names and values from src to a struct. The
|
||||
// HGETALL and CONFIG GET commands return replies in this format.
|
||||
//
|
||||
// ScanStruct uses exported field names to match values in the response. Use
|
||||
// 'redis' field tag to override the name:
|
||||
//
|
||||
// Field int `redis:"myName"`
|
||||
//
|
||||
// Fields with the tag redis:"-" are ignored.
|
||||
//
|
||||
// Each field uses RedisScan if available otherwise:
|
||||
// Integer, float, boolean, string and []byte fields are supported. Scan uses the
|
||||
// standard strconv package to convert bulk string values to numeric and
|
||||
// boolean types.
|
||||
//
|
||||
// If a src element is nil, then the corresponding field is not modified.
|
||||
func ScanStruct(src []interface{}, dest interface{}) error {
|
||||
d := reflect.ValueOf(dest)
|
||||
if d.Kind() != reflect.Ptr || d.IsNil() {
|
||||
return errScanStructValue
|
||||
}
|
||||
d = d.Elem()
|
||||
if d.Kind() != reflect.Struct {
|
||||
return errScanStructValue
|
||||
}
|
||||
ss := structSpecForType(d.Type())
|
||||
|
||||
if len(src)%2 != 0 {
|
||||
return errors.New("redigo.ScanStruct: number of values not a multiple of 2")
|
||||
}
|
||||
|
||||
for i := 0; i < len(src); i += 2 {
|
||||
s := src[i+1]
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
name, ok := src[i].([]byte)
|
||||
if !ok {
|
||||
return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i)
|
||||
}
|
||||
fs := ss.fieldSpec(name)
|
||||
if fs == nil {
|
||||
continue
|
||||
}
|
||||
if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {
|
||||
return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct")
|
||||
)
|
||||
|
||||
// ScanSlice scans src to the slice pointed to by dest. The elements the dest
|
||||
// slice must be integer, float, boolean, string, struct or pointer to struct
|
||||
// values.
|
||||
//
|
||||
// Struct fields must be integer, float, boolean or string values. All struct
|
||||
// fields are used unless a subset is specified using fieldNames.
|
||||
func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error {
|
||||
d := reflect.ValueOf(dest)
|
||||
if d.Kind() != reflect.Ptr || d.IsNil() {
|
||||
return errScanSliceValue
|
||||
}
|
||||
d = d.Elem()
|
||||
if d.Kind() != reflect.Slice {
|
||||
return errScanSliceValue
|
||||
}
|
||||
|
||||
isPtr := false
|
||||
t := d.Type().Elem()
|
||||
if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
|
||||
isPtr = true
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Struct {
|
||||
ensureLen(d, len(src))
|
||||
for i, s := range src {
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
if err := convertAssignValue(d.Index(i), s); err != nil {
|
||||
return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
ss := structSpecForType(t)
|
||||
fss := ss.l
|
||||
if len(fieldNames) > 0 {
|
||||
fss = make([]*fieldSpec, len(fieldNames))
|
||||
for i, name := range fieldNames {
|
||||
fss[i] = ss.m[name]
|
||||
if fss[i] == nil {
|
||||
return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(fss) == 0 {
|
||||
return errors.New("redigo.ScanSlice: no struct fields")
|
||||
}
|
||||
|
||||
n := len(src) / len(fss)
|
||||
if n*len(fss) != len(src) {
|
||||
return errors.New("redigo.ScanSlice: length not a multiple of struct field count")
|
||||
}
|
||||
|
||||
ensureLen(d, n)
|
||||
for i := 0; i < n; i++ {
|
||||
d := d.Index(i)
|
||||
if isPtr {
|
||||
if d.IsNil() {
|
||||
d.Set(reflect.New(t))
|
||||
}
|
||||
d = d.Elem()
|
||||
}
|
||||
for j, fs := range fss {
|
||||
s := src[i*len(fss)+j]
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {
|
||||
return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Args is a helper for constructing command arguments from structured values.
|
||||
type Args []interface{}
|
||||
|
||||
// Add returns the result of appending value to args.
|
||||
func (args Args) Add(value ...interface{}) Args {
|
||||
return append(args, value...)
|
||||
}
|
||||
|
||||
// AddFlat returns the result of appending the flattened value of v to args.
|
||||
//
|
||||
// Maps are flattened by appending the alternating keys and map values to args.
|
||||
//
|
||||
// Slices are flattened by appending the slice elements to args.
|
||||
//
|
||||
// Structs are flattened by appending the alternating names and values of
|
||||
// exported fields to args. If v is a nil struct pointer, then nothing is
|
||||
// appended. The 'redis' field tag overrides struct field names. See ScanStruct
|
||||
// for more information on the use of the 'redis' field tag.
|
||||
//
|
||||
// Other types are appended to args as is.
|
||||
func (args Args) AddFlat(v interface{}) Args {
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Struct:
|
||||
args = flattenStruct(args, rv)
|
||||
case reflect.Slice:
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
args = append(args, rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map:
|
||||
for _, k := range rv.MapKeys() {
|
||||
args = append(args, k.Interface(), rv.MapIndex(k).Interface())
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if rv.Type().Elem().Kind() == reflect.Struct {
|
||||
if !rv.IsNil() {
|
||||
args = flattenStruct(args, rv.Elem())
|
||||
}
|
||||
} else {
|
||||
args = append(args, v)
|
||||
}
|
||||
default:
|
||||
args = append(args, v)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func flattenStruct(args Args, v reflect.Value) Args {
|
||||
ss := structSpecForType(v.Type())
|
||||
for _, fs := range ss.l {
|
||||
fv := v.FieldByIndex(fs.index)
|
||||
if fs.omitEmpty {
|
||||
var empty = false
|
||||
switch fv.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
empty = fv.Len() == 0
|
||||
case reflect.Bool:
|
||||
empty = !fv.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
empty = fv.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
empty = fv.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
empty = fv.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
empty = fv.IsNil()
|
||||
}
|
||||
if empty {
|
||||
continue
|
||||
}
|
||||
}
|
||||
args = append(args, fs.name, fv.Interface())
|
||||
}
|
||||
return args
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Script encapsulates the source, hash and key count for a Lua script. See
|
||||
// http://redis.io/commands/eval for information on scripts in Redis.
|
||||
type Script struct {
|
||||
keyCount int
|
||||
src string
|
||||
hash string
|
||||
}
|
||||
|
||||
// NewScript returns a new script object. If keyCount is greater than or equal
|
||||
// to zero, then the count is automatically inserted in the EVAL command
|
||||
// argument list. If keyCount is less than zero, then the application supplies
|
||||
// the count as the first value in the keysAndArgs argument to the Do, Send and
|
||||
// SendHash methods.
|
||||
func NewScript(keyCount int, src string) *Script {
|
||||
h := sha1.New()
|
||||
io.WriteString(h, src)
|
||||
return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))}
|
||||
}
|
||||
|
||||
func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} {
|
||||
var args []interface{}
|
||||
if s.keyCount < 0 {
|
||||
args = make([]interface{}, 1+len(keysAndArgs))
|
||||
args[0] = spec
|
||||
copy(args[1:], keysAndArgs)
|
||||
} else {
|
||||
args = make([]interface{}, 2+len(keysAndArgs))
|
||||
args[0] = spec
|
||||
args[1] = s.keyCount
|
||||
copy(args[2:], keysAndArgs)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// Hash returns the script hash.
|
||||
func (s *Script) Hash() string {
|
||||
return s.hash
|
||||
}
|
||||
|
||||
// Do evaluates the script. Under the covers, Do optimistically evaluates the
|
||||
// script using the EVALSHA command. If the command fails because the script is
|
||||
// not loaded, then Do evaluates the script using the EVAL command (thus
|
||||
// causing the script to load).
|
||||
func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) {
|
||||
v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...)
|
||||
if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") {
|
||||
v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
// SendHash evaluates the script without waiting for the reply. The script is
|
||||
// evaluated with the EVALSHA command. The application must ensure that the
|
||||
// script is loaded by a previous call to Send, Do or Load methods.
|
||||
func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error {
|
||||
return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...)
|
||||
}
|
||||
|
||||
// Send evaluates the script without waiting for the reply.
|
||||
func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error {
|
||||
return c.Send("EVAL", s.args(s.src, keysAndArgs)...)
|
||||
}
|
||||
|
||||
// Load loads the script without evaluating it.
|
||||
func (s *Script) Load(c Conn) error {
|
||||
_, err := c.Do("SCRIPT", "LOAD", s.src)
|
||||
return err
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: gopay
|
||||
|
||||
clone:
|
||||
depth: 1
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: helloworld
|
||||
pull: if-not-exists
|
||||
image: hello-world
|
||||
|
||||
- name: ci_1.16
|
||||
pull: if-not-exists
|
||||
image: golang:1.16
|
||||
environment:
|
||||
GO111MODULE: "on"
|
||||
GOPROXY: "https://goproxy.cn,direct"
|
||||
GOSUMDB: "off"
|
||||
CGO_ENABLED: "0"
|
||||
GOOS: "linux"
|
||||
depends_on:
|
||||
- helloworld
|
||||
commands:
|
||||
- go version
|
||||
- go env
|
||||
- go mod tidy
|
||||
- go test ./...
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
- tag
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/.idea
|
||||
|
||||
.vscode
|
||||
|
||||
settings.json
|
||||
|
||||
vendor
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2019 Jerry
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<div align=center><img width="240" height="240" alt="Logo was Loading Faild!" src="https://raw.githubusercontent.com/go-pay/gopay/main/logo.png"/></div>
|
||||
|
||||
# GoPay
|
||||
|
||||
### 微信、支付宝、PayPal、QQ 的 Golang 版本SDK
|
||||
|
||||
[](https://github.com/iGoogle-ink)
|
||||
[](https://github.com/go-pay/gopay/fork)
|
||||
|
||||
[](https://golang.google.cn)
|
||||
[](https://pkg.go.dev/github.com/go-pay/gopay)
|
||||
[](https://cloud.drone.io/go-pay/gopay)
|
||||
[](https://github.com/go-pay/gopay/releases)
|
||||
[](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
[](https://github.com/go-pay/gopay)
|
||||
|
||||
---
|
||||
|
||||
- #### 近期计划:
|
||||
|
||||
> 将 gopay 库中,非支付相关的一些接口方法独立出去另外的 sdk 库,在 go-pay 组织下新建 `wechat-sdk` 和 `alipay-sdk` 两个项目,分别实现各个平台相关接口方法,优先进行 `wechat-sdk` 开发。
|
||||
|
||||
> 微信小程序或公众号相关接口方法:已从 `微信v2` 移步替换成 `github.com/go-pay/wechat-sdk`
|
||||
|
||||
<br>
|
||||
|
||||
# 一、安装
|
||||
|
||||
```bash
|
||||
go get -u github.com/go-pay/gopay
|
||||
```
|
||||
|
||||
#### 查看 GoPay 版本
|
||||
|
||||
[版本更新记录](https://github.com/go-pay/gopay/blob/main/release_note.txt)
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/pkg/xlog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
xlog.Info("GoPay Version: ", gopay.Version)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<br>
|
||||
|
||||
# 二、文档目录
|
||||
|
||||
> ### 点击查看不同支付方式的使用文档。方便的话,请留下您认可的小星星,十分感谢!
|
||||
|
||||
* #### [Alipay](https://github.com/go-pay/gopay/blob/main/doc/alipay.md)
|
||||
* #### [Wechat](https://github.com/go-pay/gopay/blob/main/doc/wechat_v3.md)
|
||||
* #### [QQ](https://github.com/go-pay/gopay/blob/main/doc/qq.md)
|
||||
* #### [Paypal](https://github.com/go-pay/gopay/blob/main/doc/paypal.md)
|
||||
* #### [Apple](https://github.com/go-pay/gopay/blob/main/doc/apple.md)
|
||||
|
||||
---
|
||||
|
||||
<br>
|
||||
|
||||
# 三、其他说明
|
||||
|
||||
* 各支付方式接入,请仔细查看 `xxx_test.go` 使用方式
|
||||
* `gopay/wechat/v3/client_test.go`
|
||||
* `gopay/alipay/client_test.go`
|
||||
* `gopay/qq/client_test.go`
|
||||
* `gopay/paypal/client_test.go`
|
||||
* `gopay/apple/verify_test.go`
|
||||
* 或 examples
|
||||
* 有问题请加QQ群(加群验证答案:gopay),或加微信好友拉群。在此,非常感谢提出宝贵意见和反馈问题的同志们!
|
||||
* 开发过程中,请尽量使用正式环境,1分钱测试法!
|
||||
|
||||
QQ群:
|
||||
<img width="280" height="280" src="https://raw.githubusercontent.com/go-pay/gopay/main/qq_gopay.png"/>
|
||||
加微信拉群:
|
||||
<img width="280" height="280" src="https://raw.githubusercontent.com/go-pay/gopay/main/wechat_jerry.png"/>
|
||||
|
||||
---
|
||||
|
||||
<br>
|
||||
|
||||
## 赞赏多少是您的心意,感谢支持!
|
||||
|
||||
微信赞赏码: <img width="200" height="200" src="https://raw.githubusercontent.com/go-pay/gopay/main/zanshang.png"/>
|
||||
支付宝赞助码: <img width="200" height="200" src="https://raw.githubusercontent.com/go-pay/gopay/main/zanshang_zfb.png"/>
|
||||
|
||||
## License
|
||||
|
||||
```
|
||||
Copyright 2019 Jerry
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
```
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
+242
@@ -0,0 +1,242 @@
|
||||
package gopay
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
type BodyMap map[string]interface{}
|
||||
|
||||
type xmlMapMarshal struct {
|
||||
XMLName xml.Name
|
||||
Value interface{} `xml:",cdata"`
|
||||
}
|
||||
|
||||
type xmlMapUnmarshal struct {
|
||||
XMLName xml.Name
|
||||
Value string `xml:",cdata"`
|
||||
}
|
||||
|
||||
// 设置参数
|
||||
func (bm BodyMap) Set(key string, value interface{}) BodyMap {
|
||||
bm[key] = value
|
||||
return bm
|
||||
}
|
||||
|
||||
func (bm BodyMap) SetBodyMap(key string, value func(b BodyMap)) BodyMap {
|
||||
_bm := make(BodyMap)
|
||||
value(_bm)
|
||||
bm[key] = _bm
|
||||
return bm
|
||||
}
|
||||
|
||||
// 设置 FormFile
|
||||
func (bm BodyMap) SetFormFile(key string, file *util.File) BodyMap {
|
||||
bm[key] = file
|
||||
return bm
|
||||
}
|
||||
|
||||
// 获取参数,同 GetString()
|
||||
func (bm BodyMap) Get(key string) string {
|
||||
return bm.GetString(key)
|
||||
}
|
||||
|
||||
// 获取参数转换string
|
||||
func (bm BodyMap) GetString(key string) string {
|
||||
if bm == nil {
|
||||
return NULL
|
||||
}
|
||||
value, ok := bm[key]
|
||||
if !ok {
|
||||
return NULL
|
||||
}
|
||||
v, ok := value.(string)
|
||||
if !ok {
|
||||
return convertToString(value)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// 获取原始参数
|
||||
func (bm BodyMap) GetInterface(key string) interface{} {
|
||||
if bm == nil {
|
||||
return nil
|
||||
}
|
||||
return bm[key]
|
||||
}
|
||||
|
||||
// 删除参数
|
||||
func (bm BodyMap) Remove(key string) {
|
||||
delete(bm, key)
|
||||
}
|
||||
|
||||
// 置空BodyMap
|
||||
func (bm BodyMap) Reset() {
|
||||
for k := range bm {
|
||||
delete(bm, k)
|
||||
}
|
||||
}
|
||||
|
||||
func (bm BodyMap) JsonBody() (jb string) {
|
||||
bs, err := json.Marshal(bm)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
jb = string(bs)
|
||||
return jb
|
||||
}
|
||||
|
||||
// Unmarshal to struct or slice point
|
||||
func (bm BodyMap) Unmarshal(ptr interface{}) (err error) {
|
||||
bs, err := json.Marshal(bm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(bs, ptr)
|
||||
}
|
||||
|
||||
func (bm BodyMap) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
|
||||
if len(bm) == 0 {
|
||||
return nil
|
||||
}
|
||||
start.Name = xml.Name{Space: NULL, Local: "xml"}
|
||||
if err = e.EncodeToken(start); err != nil {
|
||||
return
|
||||
}
|
||||
for k := range bm {
|
||||
if v := bm.GetString(k); v != NULL {
|
||||
e.Encode(xmlMapMarshal{XMLName: xml.Name{Local: k}, Value: v})
|
||||
}
|
||||
}
|
||||
return e.EncodeToken(start.End())
|
||||
}
|
||||
|
||||
func (bm *BodyMap) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) {
|
||||
for {
|
||||
var e xmlMapUnmarshal
|
||||
err = d.Decode(&e)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
bm.Set(e.XMLName.Local, e.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// ("bar=baz&foo=quux") sorted by key.
|
||||
func (bm BodyMap) EncodeWeChatSignParams(apiKey string) string {
|
||||
if bm == nil {
|
||||
return NULL
|
||||
}
|
||||
var (
|
||||
buf strings.Builder
|
||||
keyList []string
|
||||
)
|
||||
for k := range bm {
|
||||
keyList = append(keyList, k)
|
||||
}
|
||||
sort.Strings(keyList)
|
||||
for _, k := range keyList {
|
||||
if v := bm.GetString(k); v != NULL {
|
||||
buf.WriteString(k)
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(v)
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
}
|
||||
buf.WriteString("key")
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(apiKey)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// ("bar=baz&foo=quux") sorted by key.
|
||||
func (bm BodyMap) EncodeAliPaySignParams() string {
|
||||
if bm == nil {
|
||||
return NULL
|
||||
}
|
||||
var (
|
||||
buf strings.Builder
|
||||
keyList []string
|
||||
)
|
||||
for k := range bm {
|
||||
keyList = append(keyList, k)
|
||||
}
|
||||
sort.Strings(keyList)
|
||||
for _, k := range keyList {
|
||||
if v := bm.GetString(k); v != NULL {
|
||||
buf.WriteString(k)
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(v)
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
}
|
||||
if buf.Len() <= 0 {
|
||||
return NULL
|
||||
}
|
||||
return buf.String()[:buf.Len()-1]
|
||||
}
|
||||
|
||||
// ("bar=baz&foo=quux") sorted by key.
|
||||
func (bm BodyMap) EncodeURLParams() string {
|
||||
if bm == nil {
|
||||
return NULL
|
||||
}
|
||||
var (
|
||||
buf strings.Builder
|
||||
keys []string
|
||||
)
|
||||
for k := range bm {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
if v := bm.GetString(k); v != NULL {
|
||||
buf.WriteString(url.QueryEscape(k))
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(url.QueryEscape(v))
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
}
|
||||
if buf.Len() <= 0 {
|
||||
return NULL
|
||||
}
|
||||
return buf.String()[:buf.Len()-1]
|
||||
}
|
||||
|
||||
func (bm BodyMap) CheckEmptyError(keys ...string) error {
|
||||
var emptyKeys []string
|
||||
for _, k := range keys {
|
||||
if v := bm.GetString(k); v == NULL {
|
||||
emptyKeys = append(emptyKeys, k)
|
||||
}
|
||||
}
|
||||
if len(emptyKeys) > 0 {
|
||||
return fmt.Errorf("[%w], %v", MissParamErr, strings.Join(emptyKeys, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertToString(v interface{}) (str string) {
|
||||
if v == nil {
|
||||
return NULL
|
||||
}
|
||||
var (
|
||||
bs []byte
|
||||
err error
|
||||
)
|
||||
if bs, err = json.Marshal(v); err != nil {
|
||||
return NULL
|
||||
}
|
||||
str = string(bs)
|
||||
return
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user