Compare commits

...

181 Commits

Author SHA1 Message Date
hoteas adfb2236dc V2.0封测 2022-08-02 03:02:57 +08:00
hoteas 6034598bb4 权限管理优化 2022-08-01 18:48:08 +08:00
hoteas 6fe44cb1cb 权限管理优化 2022-08-01 16:45:24 +08:00
hoteas 39d67d1775 增加导出功能 2022-08-01 03:40:09 +08:00
hoteas a8f9f12bb8 大岗山水电站初版完成 2022-07-28 12:08:47 +08:00
hoteas 2c1abb1d11 大岗山水电站初版完成 2022-07-28 11:08:51 +08:00
hoteas 21e0219568 V2半封测 2022-07-27 17:09:56 +08:00
hoteas 952c3a1243 技术性调整 2022-07-26 17:37:17 +08:00
hoteas 33cd7ebd6b 技术性调整 2022-07-25 05:44:38 +08:00
hoteas be02b3418d 技术性调整 2022-07-25 05:37:19 +08:00
hoteas f390617167 技术性调整 2022-07-24 00:00:36 +08:00
hoteas f7410200e3 增加权限管理 2022-07-21 00:24:39 +08:00
hoteas 9cfdfbac78 增加权限管理 2022-07-18 19:45:06 +08:00
hoteas b015e78fa0 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	example/config/configNote.json
#	var.go
2022-07-18 19:44:52 +08:00
hoteas bb9092c3e5 增加权限管理 2022-07-18 19:44:00 +08:00
hoteas cc1bad8d3f 技术性调整 2022-07-18 08:46:09 +08:00
hoteas 8f7380d796 优化日志及config 2022-07-11 19:13:20 +08:00
hoteas f37b9aee70 优化日志及config 2022-07-11 11:07:17 +08:00
hoteas 1ab9027c93 技术性调整 2022-07-08 09:55:02 +08:00
hoteas 96d341aaab 修复数据库及时间bug 2022-06-21 00:06:34 +08:00
hoteas 1f25f511cc 增加mysql断线重连优化 2022-06-14 11:29:54 +08:00
hoteas 07c7a628d1 增加time兼容性 2022-06-14 09:52:49 +08:00
hoteas d7a59845eb 接口层复用 2022-06-09 04:05:50 +08:00
hoteas 98619083a1 接口层复用 2022-06-09 03:54:33 +08:00
hoteas c8a9038efe 数据库where增加[##]操作 2022-05-27 15:00:44 +08:00
hoteas b638d8bd65 增加vendor 2022-05-24 13:49:25 +08:00
hoteas 77753a123f 删除example代码 2022-05-19 15:45:51 +08:00
zhoupengwei 2c9d475a45 导出功能修改关联salesman关联关系 2022-05-18 17:52:49 +08:00
zhoupengwei 3540209b26 科创券优化 2022-05-17 12:31:47 +08:00
zhoupengwei 797681d76c 添source_type字段(券的来源:1-vip获得,2-认证获得,3-拉新获得),用来判断券的来源,
userinfo接口返回企业统一社会信用代码和营业执照路径
2022-05-13 18:43:35 +08:00
zhoupengwei 0ec83f9a95 科服券列表+领券,企业认证,购买vip领券 2022-05-13 15:50:45 +08:00
zhoupengwei ec798bd624 科服券列表+领券,企业认证,购买vip领券 2022-05-13 15:45:50 +08:00
zhoupengwei 6b57201e59 Merge branch 'zct-v2-dengluyang' of https://code.hoteas.com/golang/hotime into zhoupengwei 2022-05-13 15:34:53 +08:00
zhoupengwei 4e35dfe6ed 科服券列表+领券,企业认证,购买vip领券 2022-05-13 15:34:15 +08:00
hoteas 970bdb0157 增加time类型直接转换 2022-05-13 15:31:55 +08:00
zhoupengwei d656faf915 Merge branch 'zct-v2-dengluyang' of https://code.hoteas.com/golang/hotime into zhoupengwei 2022-05-13 09:36:46 +08:00
hoteas 2ecc70288e 打印影响延迟优化 2022-05-13 09:30:09 +08:00
hoteas ce95cdc021 打印影响延迟优化 2022-05-12 18:27:01 +08:00
zhoupengwei 22130c7afe vip当日购买信息导出excel的sql调整,添加新字段:用户关联parentid时间,用户关联salesmanid时间 2022-05-11 18:19:30 +08:00
zhoupengwei d85c65faef Merge branch 'zct-v2-dengluyang' of https://code.hoteas.com/golang/hotime into zhoupengwei 2022-05-11 18:17:40 +08:00
hoteas c9f33eefec 打印影响延迟优化 2022-05-11 14:55:28 +08:00
hoteas 1826e76a34 打印影响延迟优化 2022-05-11 13:56:53 +08:00
zhoupengwei b9a6b7af22 Merge branch 'zct-v2-dengluyang' of https://code.hoteas.com/golang/hotime into zhoupengwei 2022-05-11 09:51:35 +08:00
hoteas 95bfea4feb 对政策匹配进行优化 2022-05-11 00:44:52 +08:00
zhoupengwei e4bb8ac94a vip当日购买信息导出excel 2022-05-10 19:47:20 +08:00
zhoupengwei d7a49bf56d vip当日购买信息导出excel 2022-05-10 19:45:00 +08:00
zhoupengwei 3d1a1670cc Merge branch 'zct-v2-dengluyang' of https://code.hoteas.com/golang/hotime into zhoupengwei 2022-05-10 19:43:36 +08:00
hoteas 7de6a79611 对政策匹配进行优化 2022-05-10 12:54:24 +08:00
zhoupengwei 83198b39cb Merge branch 'zct-v2-dengluyang' of https://code.hoteas.com/golang/hotime into zhoupengwei
 Conflicts:
	example/app/declare.go
	example/provider/company.go
2022-05-10 09:43:03 +08:00
hoteas db0724f09b 对政策匹配进行优化 2022-05-09 19:19:15 +08:00
hoteas c1b013c926 对政策匹配进行优化 2022-05-09 15:07:01 +08:00
zhoupengwei 40bc84ea3d Merge branch 'zct-v2' of https://code.hoteas.com/golang/hotime into zhoupengwei 2022-05-07 20:56:36 +08:00
zhoupengwei e977a97013 Merge pull request '修复 bug 客户管理领域信息没存起来,高层次人才单选的形式存储的' (#1) from liyun into zct-v2
Reviewed-on: #1
2022-05-07 12:55:28 +00:00
michael 9330f7f5ea 修复 bug 客户管理领域信息没存起来,高层次人才单选的形式存储的 2022-05-07 20:47:58 +08:00
zhoupengwei d20bcc162c Merge branch 'zct-v2' of https://code.hoteas.com/golang/hotime into zhoupengwei 2022-05-07 17:11:27 +08:00
hoteas b48ad793da 修正企服商那边不显示名称问题 2022-05-07 13:55:57 +08:00
hoteas 3754734130 临时增加state为1企服商列表不显示企业 2022-05-07 11:08:11 +08:00
hoteas 918b88332b 增加限制条件 2022-05-07 11:02:12 +08:00
hoteas 9ba182d65c 增加限制条件 2022-05-06 13:08:13 +08:00
hoteas 8cf54ce25b 增加限制条件 2022-05-06 11:35:40 +08:00
hoteas 22d00739ff 增加join的orm兼容性 2022-05-05 18:33:27 +08:00
hoteas 3b2a317d2b 增加U盘登录 2022-05-05 16:07:01 +08:00
zhoupengwei afa9b173ba Merge branch 'zct-v2' of https://code.hoteas.com/golang/hotime into zhoupengwei 2022-05-05 10:44:10 +08:00
hoteas 53f24c033c 初步版本修正 2022-05-05 00:29:30 +08:00
zhoupengwei 1ffcaaef8e 只在main.go中加了双//,测试是否能够提交成功 2022-05-04 21:50:52 +08:00
hoteas 62ba00270a 调优 2022-05-03 22:40:02 +08:00
hoteas fe31335cf5 验证数据 2022-05-03 15:17:27 +08:00
hoteas b709b58fef 基础整理 2022-05-03 09:41:18 +08:00
hoteas 76b220aa5d 基础整理 2022-05-03 08:09:25 +08:00
hoteas dbf8f91696 基础整理 2022-05-02 15:42:54 +08:00
hoteas 335e8730da 用户登录 2022-05-02 15:39:50 +08:00
hoteas cbed318832 科创地图打包 2022-04-29 17:58:14 +08:00
hoteas 31a6568cbe 科创地图打包 2022-04-29 03:08:32 +08:00
hoteas 2aa86361e1 增加普通时间类型,以及增加富文本类型 2022-04-28 14:44:56 +08:00
hoteas 2a21dad90a Merge branch 'zct-wechat' into zct-manage
# Conflicts:
#	example/config/config.json
#	example/main.go
#	example/zct_manage.exe
2022-04-26 09:24:51 +08:00
hoteas bae9f60012 增加普通时间类型,以及增加富文本类型 2022-04-25 20:08:16 +08:00
hoteas 79d1189803 增加普通时间类型,以及增加富文本类型 2022-04-25 19:58:49 +08:00
hoteas c3d0c8921b 系统优化 2022-04-22 15:48:17 +08:00
hoteas 1da98a058b 系统优化 2022-04-21 02:20:37 +08:00
hoteas 152386c5a0 系统优化 2022-04-21 02:20:27 +08:00
hoteas 5e2845d3a1 系统优化 2022-04-20 22:49:07 +08:00
hoteas bab49c9a66 系统优化 2022-04-20 22:47:58 +08:00
hoteas 1d3bfcaee5 系统优化 2022-04-20 18:36:05 +08:00
hoteas a37a33321f 系统优化 2022-04-18 06:28:01 +08:00
hoteas 48042ecce4 系统优化 2022-03-29 05:31:02 +08:00
hoteas 5d8227d8bb 初步完成 2022-03-28 04:52:10 +08:00
hoteas 38d3d77c59 初步完成 2022-03-28 01:14:09 +08:00
hoteas fec195b3a1 框架优化 2022-03-26 16:53:40 +08:00
hoteas 5945ed64c6 框架优化 2022-03-16 09:58:24 +08:00
hoteas ce515d991a 框架优化 2022-03-14 16:48:19 +08:00
hoteas cb04dc6c34 框架优化 2022-03-13 17:02:19 +08:00
hoteas 17905c2d21 框架优化 2022-03-13 05:13:32 +08:00
hoteas b7c243823a 框架优化 2022-03-13 05:06:28 +08:00
hoteas ebea14c74f Merge remote-tracking branch 'origin/master' into rfcb
# Conflicts:
#	code.go
2022-03-13 02:16:31 +08:00
hoteas d6ef790010 优化整体 2022-03-13 02:00:40 +08:00
hoteas 5c64580378 优化整体 2022-03-13 01:48:54 +08:00
hoteas e49164fa81 使用go mod 2022-03-13 01:12:29 +08:00
hoteas 8265e24add 使用go mod 2022-03-13 01:00:59 +08:00
hoteas 283985df52 删除无关代码 2022-03-13 00:26:01 +08:00
hoteas 95adca04bd 删除无关代码 2022-03-13 00:24:36 +08:00
hoteas 2e65138222 hotimego V1.0.1 2022-03-12 23:57:48 +08:00
hoteas 87039524a7 政企超链接开始集成 2022-03-12 23:52:42 +08:00
hoteas 66cd21c35f 政企超链接开始集成 2022-03-12 23:52:25 +08:00
hoteas 7e64131931 政企超链接开始集成 2022-03-06 23:55:14 +08:00
hoteas e30d40cfbb 政企超链接开始集成 2022-03-04 00:06:26 +08:00
hoteas 751ed0003d 政企超链接开始集成 2022-03-04 00:06:04 +08:00
hoteas 6d9f89a1d4 政企超链接开始集成 2022-03-03 21:23:57 +08:00
hoteas bc329be03b 政企超链接开始集成 2022-03-02 18:19:15 +08:00
hoteas 4597a0a50d 政企超链接开始集成 2022-03-02 12:14:07 +08:00
hoteas 3dda86d170 政企超链接开始集成 2022-02-28 19:37:52 +08:00
hoteas d847da7591 政企超链接开始集成 2022-02-28 19:08:48 +08:00
hoteas e1eca90835 政企超链接开始集成 2022-02-28 09:59:05 +08:00
hoteas 9283b8c284 政企超链接开始集成 2022-02-28 08:53:38 +08:00
hoteas d8d9afa4d2 政企超链接开始集成 2022-02-24 06:26:36 +08:00
hoteas 4d3829c345 简阳版本 2022-02-19 21:38:35 +08:00
hoteas a23037a437 简阳版本 2022-02-19 21:38:17 +08:00
hoteas 181295e54e 更新研发 2022-02-17 04:10:53 +08:00
hoteas 8cc2499e21 更新研发 2022-02-14 19:11:38 +08:00
hoteas a2c49452b4 更新研发 2022-02-14 19:10:58 +08:00
hoteas ebf0df5ca8 更新研发 2022-01-27 23:41:40 +08:00
hoteas 73bf691ccc 更新研发 2022-01-26 11:45:17 +08:00
hoteas b2e9701826 更新研发 2022-01-23 05:13:19 +08:00
hoteas 789b0a14d1 更新研发 2022-01-22 16:12:02 +08:00
hoteas 3ee64fcd8c 更新研发 2022-01-20 07:15:14 +08:00
hoteas 527503b0d9 企业画像 2022-01-19 05:38:49 +08:00
hoteas a95b2fccd7 更新研发 2022-01-17 04:47:39 +08:00
hoteas 7e2abb43e3 权限模块开发 2022-01-12 17:33:20 +08:00
hoteas 48fd753a96 权限模块开发 2022-01-10 13:23:39 +08:00
hoteas 136232dd57 修复太过于严格的bug 2022-01-03 01:42:35 +08:00
hoteas 008f19355c 增加自适应模式 2021-12-31 02:38:51 +08:00
hoteas 1fc0a5bbbc 增加自适应模式 2021-12-30 22:19:52 +08:00
hoteas ce099b8fc8 增加自适应模式 2021-12-29 00:46:07 +08:00
hoteas 9c00ac6ba1 增加自适应模式 2021-12-28 09:26:26 +08:00
hoteas a5c002bbce 增加自适应模式 2021-12-27 21:30:33 +08:00
hoteas cf50a699a6 增加自适应模式 2021-12-27 21:30:24 +08:00
hoteas 662003c0ca 增加自适应模式 2021-12-27 20:40:16 +08:00
hoteas f24de2562a 优化系统 2021-12-27 18:21:41 +08:00
hoteas b84bd7e16f 优化系统 2021-12-27 14:09:49 +08:00
hoteas 2fb6abf53a Merge branch 'master' into myhs
# Conflicts:
#	example/admin/init.go
#	example/bzyy.exe
#	example/main.go
2021-12-27 14:02:39 +08:00
hoteas 2eab29f428 优化系统 2021-12-27 14:01:04 +08:00
hoteas 84ee0d259f 优化系统 2021-12-27 14:00:08 +08:00
hoteas eccab42fc8 优化系统 2021-12-18 00:07:35 +08:00
hoteas 1465ba36d3 优化系统 2021-12-17 14:37:41 +08:00
hoteas 8380b097b2 调试 2021-12-17 01:21:08 +08:00
hoteas 6b8f901163 调试 2021-12-16 13:40:19 +08:00
hoteas bb16f0a75b 调试 2021-12-16 13:24:31 +08:00
hoteas 84dbc5d71e 出入库完成 2021-12-16 10:49:35 +08:00
hoteas 166eeee64c 出入库完成 2021-12-15 09:24:41 +08:00
hoteas 7a649fd652 出入库完成 2021-12-15 05:56:31 +08:00
hoteas 94ae568526 书写前端接口 2021-12-12 04:22:30 +08:00
hoteas 9ec597f26c 书写前端接口 2021-12-11 23:53:47 +08:00
hoteas 388bc5006d 书写前端代码 2021-12-11 17:59:02 +08:00
hoteas 0e33c2ad9d ocr增加 2021-12-09 11:16:15 +08:00
hoteas af726fbcfb ocr增加 2021-12-09 10:58:53 +08:00
hoteas 1e82a5964d 优化makecode 2021-11-29 03:27:07 +08:00
hoteas d62ba8b0cd 优化makecode 2021-11-23 18:31:00 +08:00
hoteas 320ebe25ec 优化makecode 2021-11-18 12:22:14 +08:00
hoteas 68257d1742 绵阳宏升代码优化 2021-11-15 03:35:13 +08:00
hoteas 80d167cb1b 绵阳宏升代码优化 2021-11-15 03:34:45 +08:00
hoteas cd911e8fa4 巴中代码提交 2021-11-15 03:32:50 +08:00
hoteas faa6fcb00c 初次提交 2021-11-15 03:30:21 +08:00
hoteas 836c1f461f 初次提交 2021-11-15 03:28:14 +08:00
hoteas 97b2f54383 初次提交 2021-11-15 03:26:54 +08:00
hoteas 9d5f8438c5 初次提交 2021-11-13 21:06:29 +08:00
hoteas 3f7963e7cf 初次提交 2021-11-08 09:16:28 +08:00
hoteas 269df85d27 初次提交 2021-11-08 09:15:52 +08:00
hoteas d28c6863e4 初次提交 2021-11-08 06:50:37 +08:00
hoteas bba4990ccc 完善访问日志 2021-11-08 06:49:34 +08:00
hoteas c57afd6a5d 修复短信发送bug 2021-10-27 00:27:24 +08:00
hoteas 25f355c8d0 增加天府通办登录 2021-10-25 23:18:27 +08:00
hoteas b1b0673e69 修复bug 2021-10-21 23:20:58 +08:00
hoteas 72c086d726 修复bug 2021-10-21 23:19:32 +08:00
hoteas 3810ba6c0b 修复bug 2021-10-21 22:52:40 +08:00
hoteas 5c775b4539 修复bug 2021-10-18 04:49:55 +08:00
hoteas deb3e54cc3 修复bug 2021-09-25 04:36:07 +08:00
hoteas d1a8f6c668 短信变更 2021-09-24 15:01:03 +08:00
hoteas 2291ba7402 巴中恩阳适配 2021-09-24 10:49:07 +08:00
hoteas 20390091f2 巴中恩阳适配 2021-09-17 04:45:43 +08:00
hoteas 123d819b0f 开始新增代码生成配置2.0功能,配置文件格式规定完成 2021-09-17 04:00:49 +08:00
hoteas 30e275a27d 开始新增代码生成配置2.0功能,配置文件格式规定完成 2021-09-16 04:33:10 +08:00
hoteas 1a53f587e8 增加表关联新建功能,同时修复数据库bug 2021-09-12 06:14:01 +08:00
801 changed files with 513699 additions and 1330 deletions
+3 -1
View File
@@ -1,2 +1,4 @@
/.idea/*
.idea
.idea
/example/config/app.json
/example/tpt/demo/
View File
+11 -11
View File
@@ -7,17 +7,17 @@ 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.
@@ -26,31 +26,31 @@ 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,
@@ -59,7 +59,7 @@ 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
@@ -74,7 +74,7 @@ for the purpose of discussing and improving the Work, but excluding communicatio
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
@@ -142,7 +142,7 @@ 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,
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.
+287 -58
View File
@@ -1,11 +1,11 @@
package hotime
import (
. "./cache"
"./code"
. "./common"
. "./db"
. "./log"
. "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"
"github.com/sirupsen/logrus"
"io/ioutil"
@@ -15,10 +15,11 @@ import (
"path/filepath"
"strconv"
"strings"
"time"
)
type Application struct {
*code.MakeCode
MakeCodeRouter map[string]*code.MakeCode
MethodRouter
Router
ContextBase
@@ -27,7 +28,7 @@ type Application struct {
WebConnectLog *logrus.Logger
Port string //端口号
TLSPort string //ssl访问端口号
connectListener []func(this *Context) bool //所有的访问监听,true按原计划继续使用,false表示有监听器处理
connectListener []func(that *Context) bool //所有的访问监听,true按原计划继续使用,false表示有监听器处理
connectDbFunc func(err ...*Error) (master, slave *sql.DB)
configPath string
Config Map
@@ -63,15 +64,35 @@ func (that *Application) Run(router Router) {
//
//}
that.Router = router
//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
}
}
}
//重新设置MethodRouter//直达路由
that.MethodRouter = MethodRouter{}
modeRouterStrict := true
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)
}
@@ -248,13 +269,13 @@ func (that *Application) SetConfig(configPath ...string) {
}
// SetConnectListener 连接判断,返回true继续传输至控制层,false则停止传输
func (that *Application) SetConnectListener(lis func(this *Context) bool) {
// 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) {
//
//}
@@ -281,6 +302,7 @@ func (that *Application) urlSer(url string) (string, []string) {
//访问
func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
nowUnixTime := time.Now()
_, s := that.urlSer(req.RequestURI)
//获取cookie
@@ -292,21 +314,22 @@ func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
// 没有保存就生成随机的session
cookie, err := req.Cookie(that.Config.GetString("sessionName"))
sessionId := Md5(strconv.Itoa(Rand(10)))
token := req.FormValue("token")
needSetCookie := ""
token := req.Header.Get("Authorization")
if len(token) != 32 {
if err != nil || (len(token) == 32 && cookie.Value != token) {
if len(token) == 32 {
sessionId = token
}
//没有跨域设置
if that.Config.GetString("crossDomain") == "" {
http.SetCookie(w, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
} else {
//跨域允许需要设置cookie的允许跨域https才有效果
w.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; SameSite=None; Secure")
}
} else {
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)
@@ -325,22 +348,36 @@ func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
context.HandlerStr, context.RouterString = that.urlSer(context.HandlerStr)
//跨域设置
that.crossDomain(&context)
//是否展示日志
if that.WebConnectLog != nil {
that.WebConnectLog.Infoln(Substr(context.Req.RemoteAddr, 0, strings.Index(context.Req.RemoteAddr, ":")), context.Req.Method, context.HandlerStr)
}
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(that.connectListener)
if connectListenerLen != 0 {
for i := 0; i < connectListenerLen; i++ {
if !that.connectListener[i](&context) {
for i := connectListenerLen - 1; i >= 0; i-- {
context.View()
return
}
if that.connectListener[i](&context) {
context.View()
return
}
}
@@ -394,8 +431,14 @@ func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
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)
@@ -403,35 +446,66 @@ func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
}
func (that *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", "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")
//不跨域,则不设置
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 {
@@ -446,31 +520,83 @@ func (that *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 {
func Init(config string) *Application {
appIns := Application{}
//手动模式,
appIns.SetConfig(config)
SetDB(&appIns)
appIns.SetCache()
appIns.MakeCode = &code.MakeCode{}
codeConfig := appIns.Config.GetMap("codeConfig")
if codeConfig != nil {
codeConfig := appIns.Config.GetSlice("codeConfig")
if codeConfig != nil {
for k, _ := range codeConfig {
if appIns.Config.GetInt("mode") == 2{
appIns.MakeCode.Db2JSON(k, codeConfig.GetString(k), &appIns.Db)
}else{
appIns.MakeCode.Db2JSON(k, codeConfig.GetString(k), nil)
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")
}
if appIns.Config.GetInt("mode") > 0 {
appIns.MakeCodeRouter[codeMake.GetString("name")] = &code.MakeCode{Error: appIns.Error}
appIns.MakeCodeRouter[codeMake.GetString("name")].Db2JSON(&appIns.Db, codeMake)
} else {
appIns.MakeCodeRouter[codeMake.GetString("name")] = &code.MakeCode{Error: appIns.Error}
appIns.MakeCodeRouter[codeMake.GetString("name")].Db2JSON(nil, 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
return &appIns
}
// SetDB 智能数据库设置
@@ -490,6 +616,8 @@ 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") +
@@ -519,6 +647,8 @@ 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 {
@@ -529,3 +659,102 @@ func SetSqliteDB(appIns *Application, config Map) {
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
})
}
+18 -18
View File
@@ -1,7 +1,7 @@
package cache
import (
. "../common"
. "code.hoteas.com/golang/hotime/common"
"errors"
)
@@ -21,7 +21,7 @@ func (that *HoTimeCache) Session(key string, data ...interface{}) *Obj {
//内存缓存有
if that.memoryCache != nil && that.memoryCache.SessionSet {
reData = that.memoryCache.Cache(key, data...)
if reData != nil {
if reData.Data != nil {
return reData
}
}
@@ -29,9 +29,9 @@ func (that *HoTimeCache) Session(key string, data ...interface{}) *Obj {
//redis缓存有
if that.redisCache != nil && that.redisCache.SessionSet {
reData = that.redisCache.Cache(key, data...)
if reData != nil {
if reData.Data != nil {
if that.memoryCache != nil && that.memoryCache.SessionSet {
that.memoryCache.Cache(key, reData)
that.memoryCache.Cache(key, reData.Data)
}
return reData
}
@@ -40,12 +40,12 @@ func (that *HoTimeCache) Session(key string, data ...interface{}) *Obj {
//db缓存有
if that.dbCache != nil && that.dbCache.SessionSet {
reData = that.dbCache.Cache(key, data...)
if reData != nil {
if reData.Data != nil {
if that.memoryCache != nil && that.memoryCache.SessionSet {
that.memoryCache.Cache(key, reData)
that.memoryCache.Cache(key, reData.Data)
}
if that.redisCache != nil && that.redisCache.SessionSet {
that.redisCache.Cache(key, reData)
that.redisCache.Cache(key, reData.Data)
}
return reData
@@ -76,7 +76,7 @@ func (that *HoTimeCache) Db(key string, data ...interface{}) *Obj {
//内存缓存有
if that.memoryCache != nil && that.memoryCache.DbSet {
reData = that.memoryCache.Cache(key, data...)
if reData != nil {
if reData.Data != nil {
return reData
}
}
@@ -84,9 +84,9 @@ func (that *HoTimeCache) Db(key string, data ...interface{}) *Obj {
//redis缓存有
if that.redisCache != nil && that.redisCache.DbSet {
reData = that.redisCache.Cache(key, data...)
if reData != nil {
if reData.Data != nil {
if that.memoryCache != nil && that.memoryCache.DbSet {
that.memoryCache.Cache(key, reData)
that.memoryCache.Cache(key, reData.Data)
}
return reData
}
@@ -95,13 +95,13 @@ func (that *HoTimeCache) Db(key string, data ...interface{}) *Obj {
//redis缓存有
if that.dbCache != nil && that.dbCache.DbSet {
reData = that.dbCache.Cache(key, data...)
if reData != nil {
if reData.Data != nil {
if that.memoryCache != nil && that.memoryCache.DbSet {
that.memoryCache.Cache(key, reData)
that.memoryCache.Cache(key, reData.Data)
}
if that.redisCache != nil && that.redisCache.DbSet {
that.redisCache.Cache(key, reData)
that.redisCache.Cache(key, reData.Data)
}
return reData
@@ -140,10 +140,10 @@ func (that *HoTimeCache) Cache(key string, data ...interface{}) *Obj {
//redis缓存有
if that.redisCache != nil {
reData = that.redisCache.Cache(key, data...)
if reData != nil {
if reData.Data != nil {
if that.memoryCache != nil {
that.memoryCache.Cache(key, reData)
that.memoryCache.Cache(key, reData.Data)
}
return reData
}
@@ -152,12 +152,12 @@ func (that *HoTimeCache) Cache(key string, data ...interface{}) *Obj {
//redis缓存有
if that.dbCache != nil {
reData = that.dbCache.Cache(key, data...)
if reData != nil {
if reData.Data != nil {
if that.memoryCache != nil {
that.memoryCache.Cache(key, reData)
that.memoryCache.Cache(key, reData.Data)
}
if that.redisCache != nil {
that.redisCache.Cache(key, reData)
that.redisCache.Cache(key, reData.Data)
}
return reData
}
+14 -14
View File
@@ -1,7 +1,7 @@
package cache
import (
. "../common"
. "code.hoteas.com/golang/hotime/common"
"database/sql"
"encoding/json"
"strings"
@@ -30,14 +30,14 @@ type CacheDb struct {
isInit bool
}
func (this *CacheDb) GetError() *Error {
func (that *CacheDb) GetError() *Error {
return this.Error
return that.Error
}
func (this *CacheDb) SetError(err *Error) {
this.Error = err
func (that *CacheDb) SetError(err *Error) {
that.Error = err
}
func (that *CacheDb) initDbTable() {
@@ -58,7 +58,7 @@ func (that *CacheDb) initDbTable() {
return
}
_, e := that.Db.Exec("CREATE TABLE `" + that.Db.GetPrefix() + "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")
_, 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
}
@@ -74,8 +74,8 @@ func (that *CacheDb) initDbTable() {
}
_, e := that.Db.Exec(`CREATE TABLE "` + that.Db.GetPrefix() + `cached" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"ckey" TEXT(60),
"cvalue" TEXT(2000),
"key" TEXT(60),
"value" TEXT(2000),
"time" integer,
"endtime" integer
);`)
@@ -90,7 +90,7 @@ func (that *CacheDb) initDbTable() {
//获取Cache键只能为string类型
func (that *CacheDb) get(key string) interface{} {
cached := that.Db.Get("cached", "*", Map{"ckey": key})
cached := that.Db.Get("cached", "*", Map{"key": key})
if cached == nil {
return nil
@@ -103,7 +103,7 @@ func (that *CacheDb) get(key string) interface{} {
}
data := Map{}
data.JsonToMap(cached.GetString("cvalue"))
data.JsonToMap(cached.GetString("value"))
return data.Get("data")
}
@@ -113,9 +113,9 @@ func (that *CacheDb) set(key string, value interface{}, tim int64) {
bte, _ := json.Marshal(Map{"data": value})
num := that.Db.Update("cached", Map{"cvalue": string(bte), "time": time.Now().UnixNano(), "endtime": tim}, Map{"ckey": key})
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{"cvalue": string(bte), "time": time.Now().UnixNano(), "endtime": tim, "ckey": key})
that.Db.Insert("cached", Map{"value": string(bte), "time": time.Now().UnixNano(), "endtime": tim, "key": key})
}
//随机执行删除命令
@@ -130,10 +130,10 @@ func (that *CacheDb) delete(key string) {
//如果通配删除
if del != -1 {
key = Substr(key, 0, del)
that.Db.Delete("cached", Map{"ckey": key + "%"})
that.Db.Delete("cached", Map{"key": key + "%"})
} else {
that.Db.Delete("cached", Map{"ckey": key})
that.Db.Delete("cached", Map{"key": key})
}
}
+45 -45
View File
@@ -1,7 +1,7 @@
package cache
import (
. "../common"
. "code.hoteas.com/golang/hotime/common"
"strings"
"sync"
"time"
@@ -17,47 +17,47 @@ type CacheMemory struct {
mutex *sync.RWMutex
}
func (this *CacheMemory) GetError() *Error {
func (that *CacheMemory) GetError() *Error {
return this.Error
return that.Error
}
func (this *CacheMemory) SetError(err *Error) {
this.Error = err
func (that *CacheMemory) SetError(err *Error) {
that.Error = err
}
//获取Cache键只能为string类型
func (this *CacheMemory) get(key string) interface{} {
this.Error.SetError(nil)
if this.Map == nil {
this.Map = Map{}
func (that *CacheMemory) get(key string) interface{} {
that.Error.SetError(nil)
if that.Map == nil {
that.Map = Map{}
}
if this.Map[key] == nil {
if that.Map[key] == nil {
return nil
}
data := this.Map.Get(key, this.Error).(cacheData)
if this.Error.GetError() != nil {
data := that.Map.Get(key, that.Error).(cacheData)
if that.Error.GetError() != nil {
return nil
}
if data.time < time.Now().Unix() {
delete(this.Map, key)
delete(that.Map, key)
return nil
}
return data.data
}
func (this *CacheMemory) refreshMap() {
func (that *CacheMemory) refreshMap() {
go func() {
this.mutex.Lock()
defer this.mutex.Unlock()
for key, v := range this.Map {
that.mutex.Lock()
defer that.mutex.Unlock()
for key, v := range that.Map {
data := v.(cacheData)
if data.time <= time.Now().Unix() {
delete(this.Map, key)
delete(that.Map, key)
}
}
@@ -66,15 +66,15 @@ func (this *CacheMemory) refreshMap() {
}
//key value ,时间为时间戳
func (this *CacheMemory) set(key string, value interface{}, time int64) {
this.Error.SetError(nil)
func (that *CacheMemory) set(key string, value interface{}, time int64) {
that.Error.SetError(nil)
var data cacheData
if this.Map == nil {
this.Map = Map{}
if that.Map == nil {
that.Map = Map{}
}
dd := this.Map[key]
dd := that.Map[key]
if dd == nil {
data = cacheData{}
@@ -85,74 +85,74 @@ func (this *CacheMemory) set(key string, value interface{}, time int64) {
data.time = time
data.data = value
this.Map.Put(key, data)
that.Map.Put(key, data)
}
func (this *CacheMemory) delete(key string) {
func (that *CacheMemory) delete(key string) {
del := strings.Index(key, "*")
//如果通配删除
if del != -1 {
key = Substr(key, 0, del)
for k, _ := range this.Map {
for k, _ := range that.Map {
if strings.Index(k, key) != -1 {
delete(this.Map, k)
delete(that.Map, k)
}
}
} else {
delete(this.Map, key)
delete(that.Map, key)
}
}
func (this *CacheMemory) Cache(key string, data ...interface{}) *Obj {
func (that *CacheMemory) Cache(key string, data ...interface{}) *Obj {
x := RandX(1, 100000)
if x > 99950 {
this.refreshMap()
that.refreshMap()
}
if this.mutex == nil {
this.mutex = &sync.RWMutex{}
if that.mutex == nil {
that.mutex = &sync.RWMutex{}
}
reData := &Obj{Data: nil}
if len(data) == 0 {
this.mutex.RLock()
reData.Data = this.get(key)
this.mutex.RUnlock()
that.mutex.RLock()
reData.Data = that.get(key)
that.mutex.RUnlock()
return reData
}
tim := time.Now().Unix()
if len(data) == 1 && data[0] == nil {
this.mutex.Lock()
this.delete(key)
this.mutex.Unlock()
that.mutex.Lock()
that.delete(key)
that.mutex.Unlock()
return reData
}
if len(data) == 1 {
tim = tim + this.TimeOut
tim = 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.Error.GetError() == nil {
} else if that.Error.GetError() == nil {
tim = tim + tempt
}
}
this.mutex.Lock()
this.set(key, data[0], tim)
this.mutex.Unlock()
that.mutex.Lock()
that.set(key, data[0], tim)
that.mutex.Unlock()
return reData
}
+51 -51
View File
@@ -1,7 +1,7 @@
package cache
import (
. "../common"
. "code.hoteas.com/golang/hotime/common"
"github.com/garyburd/redigo/redis"
"strings"
"time"
@@ -20,65 +20,65 @@ type CacheRedis struct {
*Error
}
func (this *CacheRedis) GetError() *Error {
func (that *CacheRedis) GetError() *Error {
return this.Error
return that.Error
}
func (this *CacheRedis) SetError(err *Error) {
this.Error = err
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+":"+ObjToStr(this.Port))
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
}
if this.Pwd != "" {
_, err = this.conn.Do("AUTH", this.Pwd)
if that.Pwd != "" {
_, err = that.conn.Do("AUTH", that.Pwd)
if err != nil {
this.conn = nil
this.Error.SetError(err)
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)
}
}
}
@@ -86,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))
}
}
@@ -121,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
}
@@ -132,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.TimeOut == 0 {
//this.Time = Config.GetInt64("cacheShortTime")
if that.TimeOut == 0 {
//that.Time = Config.GetInt64("cacheShortTime")
}
tim += this.TimeOut
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
+1 -1
View File
@@ -1,7 +1,7 @@
package cache
import (
. "../common"
. "code.hoteas.com/golang/hotime/common"
)
type CacheIns interface {
+1175
View File
File diff suppressed because it is too large Load Diff
+31 -13
View File
@@ -1,19 +1,25 @@
package code
import (
. "../common"
. "code.hoteas.com/golang/hotime/common"
)
var Config = Map{
"name": "HoTimeDashBoard",
"id": "2f92h3herh23rh2y8",
"name": "HoTimeDashBoard",
//"id": "2f92h3herh23rh2y8",
"label": "HoTime管理平台",
"menus": []Map{
{"label": "平台首页", "name": "HelloWorld", "icon": "el-icon-s-home"},
//{"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"},
// {"label": "用户管理", "table": "user",
// "default": {
// "path": "info",
// "id": "1"
// },
// "auth": ["show","edit","info","add","delete"],
// },
// {"label": "组织管理", "table": "organization"},
// {"label": "地区管理", "table": "area"},
// {"label": "角色管理", "table": "role"},
@@ -33,6 +39,7 @@ var ColumnDataType = map[string]string{
"float": "number",
"double": "number",
"decimal": "number",
"integer": "number", //sqlite3
"char": "text",
"text": "text",
"blob": "text",
@@ -56,9 +63,14 @@ 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", false, 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},
@@ -67,27 +79,33 @@ var ColumnNameType = []ColumnShow{
{"latitude", false, true, true, false, "", false},
{"longitude", false, true, true, false, "", false},
{"index", false, false, false, false, "", false},
{"index", false, false, false, false, "index", false},
{"password", false, true, false, false, "password", false},
{"pwd", false, true, false, false, "password", false},
{"info", false, true, true, false, "", 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, "", false},
{"address", 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, "", false},
{"modify_time", false, false, 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, false, 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},
}
+638 -212
View File
File diff suppressed because it is too large Load Diff
+134 -48
View File
@@ -3,8 +3,8 @@ package code
var InitTpt = `package {{name}}
import (
. "../../../hotime"
. "../../../hotime/common"
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
)
var ID = "{{id}}"
@@ -14,39 +14,84 @@ var Project = Proj{
//"user": UserCtr,
{{tablesCtr}}
"hotime":Ctr{
"login": func(this *Context) {
name:=this.Req.FormValue("name")
password:=this.Req.FormValue("password")
if name==""||password==""{
this.Display(3,"参数不足")
"login": func(that *Context) {
name := that.Req.FormValue("name")
password := that.Req.FormValue("password")
if name == "" || password == "" {
that.Display(3, "参数不足")
return
}
user:=this.Db.Get("{{name}}","*",Map{"AND":Map{"name":name,"password":Md5(password)}})
if user==nil{
this.Display(5,"登录失败")
user := that.Db.Get("admin", "*", Map{"AND": Map{"OR":Map{"name": name,"phone":name}, "password": Md5(password)}})
if user == nil {
that.Display(5, "登录失败")
return
}
this.Session("id",user.GetCeilInt("id"))
this.Session("name",name)
this.Display(0,"登录成功")
that.Session("admin_id", user.GetCeilInt("id"))
that.Session("admin_name", name)
that.Display(0, that.SessionId)
},
"logout": func(this *Context) {
this.Session("id",nil)
this.Session("name",nil)
this.Display(0,"退出登录成功")
"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 (
. "../../../hotime"
. "../../../hotime/common"
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
"strings"
)
var {{table}}Ctr = Ctr{
"info": func(that *Context) {
re := that.Db.Get(that.RouterString[1], that.MakeCode.Info(that.RouterString[1]), Map{"id": that.RouterString[2]})
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, "找不到对应信息")
@@ -54,39 +99,67 @@ var {{table}}Ctr = Ctr{
}
for k, v := range re {
column:=that.MakeCode.TableColumns[that.RouterString[1]][k]
if column==nil{
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"), column.GetString("value"), Map{"id": v})
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) {
inData := that.MakeCode.Add(that.RouterString[1], that.Req)
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) {
inData := that.MakeCode.Edit(that.RouterString[1], that.Req)
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 {
@@ -97,54 +170,67 @@ var {{table}}Ctr = Ctr{
that.Display(0, re)
},
"remove": func(that *Context) {
re := that.Db.Delete(that.RouterString[1], Map{"id": that.RouterString[2]})
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, re)
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)
columnStr,leftJoin,where := that.MakeCode.Search(that.RouterString[1], that.Req,that.Db)
page:=ObjToInt(that.Req.FormValue("page"))
pageSize:=ObjToInt(that.Req.FormValue("pageSize"))
page := ObjToInt(that.Req.FormValue("page"))
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
if page<1{
page=1
if page < 1 {
page = 1
}
if pageSize<=0{
pageSize=20
if pageSize <= 0 {
pageSize = 20
}
count:=that.Db.Count(that.RouterString[1],leftJoin,where)
count := that.Db.Count(that.RouterString[1], leftJoin, where)
reData := that.Db.Page(page, pageSize).
PageSelect(that.RouterString[1],leftJoin,columnStr, where)
PageSelect(that.RouterString[1], leftJoin, columnStr, where)
for _, v := range reData {
for k, _ := range v {
column:=that.MakeCode.TableColumns[that.RouterString[1]][k]
if column==nil{
column := that.MakeCodeRouter[hotimeName].TableColumns[that.RouterString[1]][k]
if column == nil {
continue
}
if column["list"]!=false&&column["name"]=="parent_id"&&column.GetString("link")!=""{
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"))
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})
that.Display(0, Map{"count": count, "data": reData})
},
}
`
+4 -4
View File
@@ -9,10 +9,10 @@ type ContextBase struct {
}
//唯一标志
func (this *ContextBase) GetTag() string {
func (that *ContextBase) GetTag() string {
if this.tag == "" {
this.tag = ObjToStr(time.Now().Unix()) + ":" + ObjToStr(Random())
if that.tag == "" {
that.tag = ObjToStr(time.Now().Unix()) + ":" + ObjToStr(Random())
}
return this.tag
return that.tag
}
+40 -4
View File
@@ -36,6 +36,42 @@ 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)
@@ -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
+66 -40
View File
@@ -4,114 +4,140 @@ import (
"encoding/json"
"errors"
"reflect"
"sort"
"time"
)
//hotime的常用map
type Map map[string]interface{}
//获取string
func (this Map) GetString(key string, err ...*Error) string {
func (that Map) GetString(key string, err ...*Error) string {
if len(err) != 0 {
err[0].SetError(nil)
}
return ObjToStr((this)[key])
return ObjToStr((that)[key])
}
func (this *Map) Pointer() *Map {
func (that *Map) Pointer() *Map {
return this
return that
}
//增加接口
func (this Map) Put(key string, value interface{}) {
//if this==nil{
// this=Map{}
func (that Map) Put(key string, value interface{}) {
//if that==nil{
// that=Map{}
//}
this[key] = value
that[key] = value
}
//删除接口
func (this Map) Delete(key string) {
delete(this, key)
func (that Map) Delete(key string) {
delete(that, key)
}
//获取Int
func (this Map) GetInt(key string, err ...*Error) int {
v := ObjToInt((this)[key], err...)
func (that Map) GetInt(key string, err ...*Error) int {
v := ObjToInt((that)[key], err...)
return v
}
//获取Int
func (this Map) GetInt64(key string, err ...*Error) int64 {
v := ObjToInt64((this)[key], err...)
func (that Map) GetInt64(key string, err ...*Error) int64 {
v := ObjToInt64((that)[key], err...)
return v
}
//获取向上取整Int64
func (this Map) GetCeilInt64(key string, err ...*Error) int64 {
v := ObjToCeilInt64((this)[key], err...)
func (that Map) GetCeilInt64(key string, err ...*Error) int64 {
v := ObjToCeilInt64((that)[key], err...)
return v
}
//获取向上取整Int
func (this Map) GetCeilInt(key string, err ...*Error) int {
v := ObjToCeilInt((this)[key], err...)
func (that Map) GetCeilInt(key string, err ...*Error) int {
v := ObjToCeilInt((that)[key], err...)
return v
}
//获取向上取整float64
func (this Map) GetCeilFloat64(key string, err ...*Error) float64 {
v := ObjToCeilFloat64((this)[key], err...)
func (that Map) GetCeilFloat64(key string, err ...*Error) float64 {
v := ObjToCeilFloat64((that)[key], err...)
return v
}
//获取Float64
func (this Map) GetFloat64(key string, err ...*Error) float64 {
func (that Map) GetFloat64(key string, err ...*Error) float64 {
v := ObjToFloat64((this)[key], err...)
v := ObjToFloat64((that)[key], err...)
return v
}
func (this Map) GetSlice(key string, err ...*Error) Slice {
func (that Map) GetSlice(key string, err ...*Error) Slice {
//var v Slice
v := ObjToSlice((this)[key], err...)
v := ObjToSlice((that)[key], err...)
return v
}
func (this Map) GetBool(key string, err ...*Error) bool {
func (that Map) GetBool(key string, err ...*Error) bool {
//var v Slice
v := ObjToBool((this)[key], err...)
v := ObjToBool((that)[key], err...)
return v
}
func (this Map) GetMap(key string, err ...*Error) Map {
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((this)[key], err...)
v := ObjToMap((that)[key], err...)
return v
}
func (this Map) Get(key string, err ...*Error) interface{} {
func (that Map) Get(key string, err ...*Error) interface{} {
if v, ok := (this)[key]; ok {
if v, ok := (that)[key]; ok {
return v
}
e := errors.New("没有存储key及对应的数据")
@@ -124,10 +150,10 @@ func (this Map) Get(key string, err ...*Error) interface{} {
}
//请传递指针过来
func (this Map) ToStruct(stct interface{}) {
func (that Map) ToStruct(stct interface{}) {
data := reflect.ValueOf(stct).Elem()
for k, v := range this {
for k, v := range that {
ks := StrFirstToUpper(k)
dkey := data.FieldByName(ks)
if !dkey.IsValid() {
@@ -135,13 +161,13 @@ func (this Map) ToStruct(stct interface{}) {
}
switch dkey.Type().String() {
case "int":
dkey.SetInt(this.GetInt64(k))
dkey.SetInt(that.GetInt64(k))
case "int64":
dkey.Set(reflect.ValueOf(this.GetInt64(k)))
dkey.Set(reflect.ValueOf(that.GetInt64(k)))
case "float64":
dkey.Set(reflect.ValueOf(this.GetFloat64(k)))
dkey.Set(reflect.ValueOf(that.GetFloat64(k)))
case "string":
dkey.Set(reflect.ValueOf(this.GetString(k)))
dkey.Set(reflect.ValueOf(that.GetString(k)))
case "interface{}":
dkey.Set(reflect.ValueOf(v))
}
@@ -149,13 +175,13 @@ func (this Map) ToStruct(stct interface{}) {
}
func (this Map) ToJsonString() string {
return ObjToStr(this)
func (that Map) ToJsonString() string {
return ObjToStr(that)
}
func (this Map) JsonToMap(jsonStr string, err ...*Error) {
e := json.Unmarshal([]byte(jsonStr), &this)
func (that Map) JsonToMap(jsonStr string, err ...*Error) {
e := json.Unmarshal([]byte(jsonStr), &that)
if e != nil && len(err) != 0 {
err[0].SetError(e)
}
+9
View File
@@ -1,5 +1,7 @@
package common
import "time"
//对象封装方便取用
type Obj struct {
Data interface{}
@@ -18,6 +20,13 @@ func (that *Obj) ToInt(err ...Error) int {
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]
+72
View File
@@ -5,6 +5,7 @@ import (
"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)
}
+8
View File
@@ -2,6 +2,7 @@ package common
import (
"errors"
"time"
)
type Slice []interface{}
@@ -14,6 +15,13 @@ func (that Slice) GetString(key int, err ...*Error) string {
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...)
+26 -3
View File
@@ -1,23 +1,27 @@
package hotime
import (
. "./cache"
. "./common"
. "./db"
. "code.hoteas.com/golang/hotime/cache"
. "code.hoteas.com/golang/hotime/common"
. "code.hoteas.com/golang/hotime/db"
"encoding/json"
"net/http"
"time"
)
type Context struct {
*Application
Resp http.ResponseWriter
Req *http.Request
Log Map //日志有则创建
RouterString []string
Config Map
Db *HoTimeDB
RespData Map
RespFunc func()
CacheIns
SessionIns
DataSize int
HandlerStr string //复写请求url
}
@@ -56,13 +60,32 @@ func (that *Context) Display(statu int, data interface{}) {
}
func (that *Context) View() {
if that.RespFunc != nil {
that.RespFunc()
}
if that.RespData == nil {
return
}
//创建日志
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
}
that.DataSize = len(d)
that.RespData = nil
that.Resp.Write(d)
return
}
+347 -120
View File
@@ -1,15 +1,17 @@
package db
import (
"../cache"
. "../common"
"code.hoteas.com/golang/hotime/cache"
. "code.hoteas.com/golang/hotime/common"
"database/sql"
"encoding/json"
"errors"
_ "github.com/go-sql-driver/mysql"
_ "github.com/mattn/go-sqlite3"
"github.com/sirupsen/logrus"
"os"
"reflect"
"sort"
"strings"
)
@@ -18,6 +20,7 @@ type HoTimeDB struct {
ContextBase
DBName string
*cache.HoTimeCache
Log *logrus.Logger
Type string
Prefix string
LastQuery string
@@ -27,6 +30,153 @@ type HoTimeDB struct {
limit Slice
*sql.Tx //事务对象
SlaveDB *sql.DB
Mode int //mode为0生产模式,1、为测试模式、2为开发模式
}
type HotimeDBBuilder struct {
*HoTimeDB
table string
selects []interface{}
join Slice
where Map
lastWhere Map
page int
pageRow int
}
func (that *HoTimeDB) Table(table string) *HotimeDBBuilder {
return &HotimeDBBuilder{HoTimeDB: that, table: table, where: Map{}}
}
func (that *HotimeDBBuilder) Get(qu ...interface{}) Map {
return that.HoTimeDB.Get(that.table, that.join, qu, that.where)
}
func (that *HotimeDBBuilder) Count() int {
return that.HoTimeDB.Count(that.table, that.join, that.where)
}
func (that *HotimeDBBuilder) Page(page, pageRow int) *HotimeDBBuilder {
that.page = page
that.pageRow = pageRow
return that
}
func (that *HotimeDBBuilder) Select(qu ...interface{}) []Map {
if that.page != 0 {
return that.HoTimeDB.Page(that.page, that.pageRow).PageSelect(that.table, that.join, qu, that.where)
}
return that.HoTimeDB.Select(that.table, that.join, qu, that.where)
}
func (that *HotimeDBBuilder) Update(data Map) int64 {
return that.HoTimeDB.Update(that.table, data, that.where)
}
func (that *HotimeDBBuilder) Delete() int64 {
return that.HoTimeDB.Delete(that.table, that.where)
}
func (that *HotimeDBBuilder) LeftJoin(table, joinStr string) *HotimeDBBuilder {
that.Join(Map{"[>]" + table: joinStr})
return that
}
func (that *HotimeDBBuilder) RightJoin(table, joinStr string) *HotimeDBBuilder {
that.Join(Map{"[<]" + table: joinStr})
return that
}
func (that *HotimeDBBuilder) InnerJoin(table, joinStr string) *HotimeDBBuilder {
that.Join(Map{"[><]" + table: joinStr})
return that
}
func (that *HotimeDBBuilder) FullJoin(table, joinStr string) *HotimeDBBuilder {
that.Join(Map{"[<>]" + table: joinStr})
return that
}
func (that *HotimeDBBuilder) Join(join Map) *HotimeDBBuilder {
if that.join == nil {
that.join = Slice{}
}
if join == nil {
return that
}
that.join = append(that.join, join)
return that
}
func (that *HotimeDBBuilder) And(where Map) *HotimeDBBuilder {
if where == nil {
return that
}
if that.lastWhere != nil {
that.lastWhere["AND"] = where
that.lastWhere = where
return that
}
that.lastWhere = where
that.where = Map{"AND": where}
return that
}
func (that *HotimeDBBuilder) Or(where Map) *HotimeDBBuilder {
if where == nil {
return that
}
if that.lastWhere != nil {
that.lastWhere["OR"] = where
that.lastWhere = where
return that
}
that.lastWhere = where
that.where = Map{"Or": where}
return that
}
func (that *HotimeDBBuilder) Where(where Map) *HotimeDBBuilder {
if where == nil {
return that
}
if that.lastWhere != nil {
that.lastWhere["AND"] = where
that.lastWhere = where
return that
}
that.lastWhere = where
that.where = Map{"AND": that.lastWhere}
return that
}
func (that *HotimeDBBuilder) From(table string) *HotimeDBBuilder {
that.table = table
return that
}
func (that *HotimeDBBuilder) Order(qu ...interface{}) *HotimeDBBuilder {
that.where["ORDER"] = ObjToSlice(qu)
return that
}
func (that *HotimeDBBuilder) Limit(qu ...interface{}) *HotimeDBBuilder {
that.where["LIMIT"] = ObjToSlice(qu)
return that
}
func (that *HotimeDBBuilder) Group(qu ...interface{}) *HotimeDBBuilder {
that.where["GROUP"] = ObjToSlice(qu)
return that
}
// SetConnect 设置数据库配置连接
@@ -42,32 +192,32 @@ func (that *HoTimeDB) GetType() string {
}
// Action 事务,如果action返回true则执行成功;false则回滚
func (that *HoTimeDB) Action(action func(db HoTimeDB) bool) bool {
func (that *HoTimeDB) Action(action func(db HoTimeDB) (isSuccess bool)) (isSuccess bool) {
db := HoTimeDB{DB: that.DB, HoTimeCache: that.HoTimeCache, Prefix: that.Prefix}
tx, err := db.Begin()
if err != nil {
that.LastErr.SetError(err)
return false
return isSuccess
}
db.Tx = tx
result := action(db)
isSuccess = action(db)
if !result {
if !isSuccess {
err = db.Tx.Rollback()
if err != nil {
that.LastErr.SetError(err)
return false
return isSuccess
}
return result
return isSuccess
}
err = db.Tx.Commit()
if err != nil {
that.LastErr.SetError(err)
return false
}
return result
return true
}
func (that *HoTimeDB) InitDb(err ...*Error) *Error {
@@ -165,7 +315,7 @@ func (that *HoTimeDB) Row(resl *sql.Rows) []Map {
//
////code=0,1,2 0 backup all,1 backup data,2 backup ddl
//func (this *HoTimeDB) Backup(path string, code int) {
//func (that *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"]))
@@ -197,7 +347,7 @@ func (that *HoTimeDB) Row(resl *sql.Rows) []Map {
// //db := ``
// //fmt.Println(db)
// //
// //tables := this.Query("show tables")
// //tables := that.Query("show tables")
// //lth := len(tables)
// //if lth == 0 {
// // return
@@ -212,7 +362,7 @@ func (that *HoTimeDB) Row(resl *sql.Rows) []Map {
// //
// //for i := 0; i < lth; i++ {
// // tt := tables[i]["Tables_in_"+db].(string)
// // this.backupSave(path, tt, code)
// // that.backupSave(path, tt, code)
// // debug.FreeOSMemory()
// //}
//
@@ -259,7 +409,7 @@ func (that *HoTimeDB) backupCol(tt string) string {
for k := range data[0] {
if tempLthData == lthCol-1 {
str += "`" + k + "`)"
str += "`" + k + "`) "
} else {
str += "`" + k + "`,"
}
@@ -283,7 +433,7 @@ func (that *HoTimeDB) backupCol(tt string) string {
}
if m == lthCol-1 {
str += v + ")"
str += v + ") "
} else {
str += v + ","
@@ -305,7 +455,11 @@ func (that *HoTimeDB) md5(query string, args ...interface{}) string {
}
func (that *HoTimeDB) Query(query string, args ...interface{}) []Map {
defer func() {
if that.Mode == 2 {
that.Log.Info("SQL:"+that.LastQuery, " DATA:", that.LastData, " ERROR:", that.LastErr.GetError())
}
}()
//fmt.Println(query)
var err error
var resl *sql.Rows
@@ -347,7 +501,11 @@ func (that *HoTimeDB) Query(query string, args ...interface{}) []Map {
}
func (that *HoTimeDB) Exec(query string, args ...interface{}) (sql.Result, *Error) {
defer func() {
if that.Mode == 2 {
that.Log.Info("SQL: "+that.LastQuery, " DATA: ", that.LastData, " ERROR: ", that.LastErr.GetError())
}
}()
that.LastQuery = query
that.LastData = args
var e error
@@ -378,12 +536,13 @@ func (that *HoTimeDB) Exec(query string, args ...interface{}) (sql.Result, *Erro
}
return that.Exec(query, args...)
}
return resl, that.LastErr
}
return resl, that.LastErr
}
//func (this *HoTimeDB)copy(data []Map)[]Map{
//func (that *HoTimeDB)copy(data []Map)[]Map{
// if data==nil{
// return nil
// }
@@ -420,8 +579,9 @@ func (that *HoTimeDB) Select(table string, qu ...interface{}) []Map {
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)
data := ObjToSlice(qu[intQs])
for i := 0; i < len(data); i++ {
k := data.GetString(i)
if strings.Contains(k, " AS ") {
query += " " + k + " "
@@ -430,7 +590,7 @@ func (that *HoTimeDB) Select(table string, qu ...interface{}) []Map {
query += " `" + k + "` "
}
if i+1 != len(qu[intQs].(Slice)) {
if i+1 != len(data) {
query = query + ", "
}
@@ -441,24 +601,48 @@ func (that *HoTimeDB) Select(table string, qu ...interface{}) []Map {
query += " *"
}
if !strings.Contains(table, ".") && !strings.Contains(table, " AS ") {
query += " FROM `" + that.Prefix + table + "`"
query += " FROM `" + that.Prefix + table + "` "
} else {
query += " FROM " + that.Prefix + table
query += " FROM " + that.Prefix + table + " "
}
if join {
for k, v := range qu[0].(Map) {
var testQu = []string{}
testQuData := Map{}
if reflect.ValueOf(qu[0]).Type().String() == "common.Map" {
testQuData = qu[0].(Map)
for key, _ := range testQuData {
//fmt.Println(key, ":", value)
testQu = append(testQu, key)
}
}
if reflect.ValueOf(qu[0]).Type().String() == "common.Slice" {
for key, _ := range testQuData {
v := testQuData.GetMap(key)
for k1, v1 := range v {
testQu = append(testQu, k1)
testQuData[k1] = v1
}
}
}
sort.Strings(testQu)
for _, k := range testQu {
v := testQuData[k]
switch Substr(k, 0, 3) {
case "[>]":
query += " LEFT JOIN " + Substr(k, 3, len(k)-3) + " ON " + v.(string)
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)
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)
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)
query += " INNER JOIN `" + Substr(k, 4, len(k)-4) + "` ON " + v.(string) + " "
}
}
}
@@ -469,7 +653,7 @@ func (that *HoTimeDB) Select(table string, qu ...interface{}) []Map {
temp, resWhere := that.where(where)
query += temp
query += temp + ";"
qs = append(qs, resWhere...)
md5 := that.md5(query, qs...)
@@ -560,7 +744,15 @@ func (that *HoTimeDB) where(data Map) (string, []interface{}) {
res := make([]interface{}, 0)
//AND OR判断
for k, v := range data {
testQu := []string{}
//testQuData:= qu[0].(Map)
for key, _ := range data {
//fmt.Println(key, ":", value)
testQu = append(testQu, key)
}
sort.Strings(testQu)
for _, k := range testQu {
v := data[k]
x := 0
for i := 0; i < len(condition); i++ {
@@ -588,8 +780,7 @@ func (that *HoTimeDB) where(data Map) (string, []interface{}) {
if v != nil && reflect.ValueOf(v).Type().String() == "common.Slice" && len(v.(Slice)) == 0 {
continue
}
if v != nil && reflect.ValueOf(v).Type().String() == "[]interface {}" && len(v.([]interface{})) == 0 {
if v != nil && strings.Contains(reflect.ValueOf(v).Type().String(), "[]") && len(ObjToSlice(v)) == 0 {
continue
}
@@ -602,39 +793,56 @@ func (that *HoTimeDB) where(data Map) (string, []interface{}) {
}
if len(where) != 0 {
where = " WHERE " + where
hasWhere := true
for _, v := range vcond {
if strings.Index(where, v) == 0 {
hasWhere = false
}
}
if hasWhere {
where = " WHERE " + where + " "
}
}
//特殊字符
for j := 0; j < len(vcond); j++ {
for k, v := range data {
testQu := []string{}
//testQuData:= qu[0].(Map)
for key, _ := range data {
//fmt.Println(key, ":", value)
testQu = append(testQu, key)
}
sort.Strings(testQu)
for _, k := range testQu {
v := data[k]
if vcond[j] == k {
if k == "ORDER" {
where += " " + k + " BY "
where += k + " BY "
//fmt.Println(reflect.ValueOf(v).Type())
//break
} else if k == "GROUP" {
where += " " + k + " BY "
where += k + " BY "
} else {
where += " " + k
where += k
}
if reflect.ValueOf(v).Type().String() == "common.Slice" {
for i := 0; i < len(v.(Slice)); i++ {
where += " " + ObjToStr(v.(Slice)[i])
where += " " + ObjToStr(v.(Slice)[i]) + " "
if len(v.(Slice)) != i+1 {
where += ","
where += ", "
}
}
} else {
//fmt.Println(v)
where += " " + ObjToStr(v)
where += " " + ObjToStr(v) + " "
}
break
@@ -652,53 +860,58 @@ func (that *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) {
where := ""
res := make([]interface{}, 0)
length := len(k)
if length > 0 && strings.Contains(k, "[") && k[length-1] == ']' {
if k == "[#]" {
k = strings.Replace(k, "[#]", "", -1)
where += " " + ObjToStr(v) + " "
} else if length > 0 && strings.Contains(k, "[") && k[length-1] == ']' {
def := false
switch Substr(k, length-3, 3) {
case "[>]":
k = strings.Replace(k, "[>]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
where += k + ">? "
res = append(res, v)
case "[<]":
k = strings.Replace(k, "[<]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
where += k + "<? "
res = append(res, v)
case "[!]":
k = strings.Replace(k, "[!]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
where, res = that.notIn(k, v, where, res)
case "[#]":
k = strings.Replace(k, "[#]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
where += " " + k + "=" + ObjToStr(v)
where += " " + k + "=" + ObjToStr(v) + " "
case "[##]": //直接添加value到sql,需要考虑防注入,value比如:"a>b"
where += " " + ObjToStr(v)
case "[#!]":
k = strings.Replace(k, "[#!]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
where += " " + k + "!=" + ObjToStr(v)
where += " " + k + "!=" + ObjToStr(v) + " "
case "[!#]":
k = strings.Replace(k, "[!#]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
where += " " + k + "!=" + ObjToStr(v)
where += " " + k + "!=" + ObjToStr(v) + " "
case "[~]":
k = strings.Replace(k, "[~]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
where += k + " LIKE ? "
v = "%" + ObjToStr(v) + "%"
@@ -706,15 +919,15 @@ func (that *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) {
case "[!~]": //左边任意
k = strings.Replace(k, "[~]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
where += k + " LIKE ? "
v = "%" + ObjToStr(v)
v = "%" + ObjToStr(v) + ""
res = append(res, v)
case "[~!]": //右边任意
k = strings.Replace(k, "[~]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
where += k + " LIKE ? "
v = ObjToStr(v) + "%"
@@ -722,7 +935,7 @@ func (that *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) {
case "[~~]": //手动任意
k = strings.Replace(k, "[~]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
where += k + " LIKE ? "
//v = ObjToStr(v)
@@ -736,21 +949,21 @@ func (that *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) {
case "[>=]":
k = strings.Replace(k, "[>=]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
where += k + ">=? "
res = append(res, v)
case "[<=]":
k = strings.Replace(k, "[<=]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
where += k + "<=? "
res = append(res, v)
case "[><]":
k = strings.Replace(k, "[><]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
where += k + " NOT BETWEEN ? AND ? "
res = append(res, v.(Slice)[0])
@@ -758,29 +971,31 @@ func (that *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) {
case "[<>]":
k = strings.Replace(k, "[<>]", "", -1)
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
where += k + " BETWEEN ? AND ? "
res = append(res, v.(Slice)[0])
res = append(res, v.(Slice)[1])
default:
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
if reflect.ValueOf(v).Type().String() == "common.Slice" {
if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
vs := ObjToSlice(v)
if len(vs) == 0 {
return where, res
}
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])
res = append(res, vs...)
for i := 0; i < len(vs); i++ {
if i+1 != len(vs) {
where += "?,"
} else {
where += "?) "
}
//res=append(res,(v.(Slice))[i])
}
} else {
@@ -791,41 +1006,31 @@ func (that *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) {
}
}
} else if k == "[#]" {
k = strings.Replace(k, "[#]", "", -1)
where += " " + ObjToStr(v) + " "
} else {
//fmt.Println(reflect.ValueOf(v).Type().String())
if !strings.Contains(k, ".") {
k = "`" + k + "`"
k = "`" + k + "` "
}
if v == nil {
where += k + " IS NULL"
} else if reflect.ValueOf(v).Type().String() == "common.Slice" {
where += k + " IS NULL "
} else if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
vs := ObjToSlice(v)
//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])
if len(vs) == 0 {
return where, res
}
} else if reflect.ValueOf(v).Type().String() == "[]interface {}" {
where += k + " IN ("
res = append(res, vs...)
where += k + " IN ("
res = append(res, v.([]interface{})...)
for i := 0; i < len(v.([]interface{})); i++ {
if i+1 != len(v.([]interface{})) {
for i := 0; i < len(vs); i++ {
if i+1 != len(vs) {
where += "?,"
} else {
where += "?) "
}
//res=append(res,(v.(Slice))[i])
}
} else {
where += k + "=? "
@@ -837,7 +1042,7 @@ func (that *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) {
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})
// that.Db.Update("user",hotime.Map{"ustate":"1"},hotime.Map{"AND":hotime.Map{"OR":hotime.Map{"uid":4,"uname":"dasda"}},"ustate":1})
func (that *HoTimeDB) notIn(k string, v interface{}, where string, res []interface{}) (string, []interface{}) {
//where:=""
//fmt.Println(reflect.ValueOf(v).Type().String())
@@ -845,28 +1050,23 @@ func (that *HoTimeDB) notIn(k string, v interface{}, where string, res []interfa
where += k + " IS NOT NULL "
} else if reflect.ValueOf(v).Type().String() == "common.Slice" {
} else if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
vs := ObjToSlice(v)
if len(vs) == 0 {
return where, res
}
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{})) {
res = append(res, vs...)
for i := 0; i < len(vs); i++ {
if i+1 != len(vs) {
where += "?,"
} else {
where += "?) "
}
//res=append(res,(v.(Slice))[i])
}
} else {
where += k + " !=? "
@@ -882,7 +1082,15 @@ func (that *HoTimeDB) cond(tag string, data Map) (string, []interface{}) {
res := make([]interface{}, 0)
lens := len(data)
//fmt.Println(lens)
for k, v := range data {
testQu := []string{}
//testQuData:= qu[0].(Map)
for key, _ := range data {
//fmt.Println(key, ":", value)
testQu = append(testQu, key)
}
sort.Strings(testQu)
for _, k := range testQu {
v := data[k]
x := 0
for i := 0; i < len(condition); i++ {
@@ -905,7 +1113,10 @@ func (that *HoTimeDB) cond(tag string, data Map) (string, []interface{}) {
if x == len(condition) {
tv, vv := that.varCond(k, v)
if tv == "" {
lens--
continue
}
res = append(res, vv...)
if lens--; lens <= 0 {
where += tv + ""
@@ -936,7 +1147,7 @@ func (that *HoTimeDB) Update(table string, data Map, where Map) int64 {
} else {
qs = append(qs, v)
}
query += "`" + k + "`=" + vstr + ""
query += "`" + k + "`=" + vstr + " "
if tp--; tp != 0 {
query += ", "
}
@@ -945,7 +1156,7 @@ func (that *HoTimeDB) Update(table string, data Map, where Map) int64 {
temp, resWhere := that.where(where)
//fmt.Println(resWhere)
query += temp
query += temp + ";"
qs = append(qs, resWhere...)
res, err := that.Exec(query, qs...)
@@ -970,7 +1181,7 @@ func (that *HoTimeDB) Delete(table string, data map[string]interface{}) int64 {
query := "DELETE FROM " + that.Prefix + table + " "
temp, resWhere := that.where(data)
query += temp
query += temp + ";"
res, err := that.Exec(query, resWhere...)
rows := int64(0)
@@ -1000,13 +1211,29 @@ func (that *HoTimeDB) Insert(table string, data map[string]interface{}) int64 {
tempLen := 0
for k, v := range data {
tempLen++
values = append(values, v)
if tempLen < lens {
queryString += "`" + k + "`,"
valueString += "?,"
vstr := "?"
if Substr(k, len(k)-3, 3) == "[#]" {
k = strings.Replace(k, "[#]", "", -1)
vstr = ObjToStr(v)
if tempLen < lens {
queryString += "`" + k + "`,"
valueString += vstr + ","
} else {
queryString += "`" + k + "`) "
valueString += vstr + ");"
}
} else {
queryString += "`" + k + "`) "
valueString += "?);"
values = append(values, v)
if tempLen < lens {
queryString += "`" + k + "`,"
valueString += "?,"
} else {
queryString += "`" + k + "`) "
valueString += "?);"
}
}
}
+142
View File
@@ -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
}
+54
View File
@@ -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)+"&region="+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
View File
@@ -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 {
+2 -2
View File
@@ -1,15 +1,15 @@
package download
import (
. "../../common"
"bytes"
. "code.hoteas.com/golang/hotime/common"
"io"
"io/ioutil"
"net/http"
"os"
)
//下载文件
// Down 下载文件
func Down(url, path, name string, e ...*Error) bool {
os.MkdirAll(path, os.ModeDir)
+143
View File
@@ -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)
}
+119
View File
@@ -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)
}
+104
View File
@@ -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()
}
+4 -4
View File
@@ -1,7 +1,7 @@
package upload
import (
. "../../common"
. "code.hoteas.com/golang/hotime/common"
"errors"
"io"
"mime/multipart"
@@ -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
}
@@ -51,7 +51,7 @@ func (this *Upload) UpFile(Request *http.Request, fieldName, savefilepath, saveP
}
}
filename := time.Unix(int64(t), 0).Format("2006-01-02-15-22-25") + ObjToStr(Rand(6))
filePath = path + "/" + filename + this.Path
filePath = path + "/" + filename + that.Path
} else {
filePath = savePath
}
+79
View File
@@ -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
//}
+66
View File
@@ -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
}
+142
View File
@@ -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
}
-64
View File
@@ -1,64 +0,0 @@
package admin
import (
. "../../../hotime"
. "../../../hotime/common"
)
var ID = "3c5fc9732b8ddcb0a91b428976f2ce86"
// Project 管理端项目
var Project = Proj{
//"user": UserCtr,
"admin": adminCtr,
"category": categoryCtr,
"ctg_order_date": ctg_order_dateCtr,
"order": orderCtr,
"org": orgCtr,
"role": roleCtr,
"user": userCtr,
"hotime": Ctr{
"login": func(this *Context) {
name := this.Req.FormValue("name")
password := this.Req.FormValue("password")
if name == "" || password == "" {
this.Display(3, "参数不足")
return
}
user := this.Db.Get("admin", "*", Map{"AND": Map{"name": name, "password": Md5(password)}})
if user == nil {
this.Display(5, "登录失败")
return
}
this.Session("admin_id", user.GetCeilInt("id"))
this.Session("admin_name", name)
this.Display(0, this.SessionId)
},
"logout": func(this *Context) {
this.Session("admin_id", nil)
this.Session("admin_name", nil)
this.Display(0, "退出登录成功")
},
"info": func(that *Context) {
re := that.Db.Get("admin", that.MakeCode.Info("admin"), Map{"id": that.Session("admin_id").ToCeilInt()})
if re == nil {
that.Display(4, "找不到对应信息")
return
}
for k, v := range re {
column := that.MakeCode.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"), column.GetString("value"), Map{"id": v})
}
}
that.Display(0, re)
},
},
}
+104
View File
@@ -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})
},
}
-34
View File
@@ -1,34 +0,0 @@
package app
import (
. "../../../hotime"
. "../../../hotime/common"
)
var categoryCtr = Ctr{
"info": func(that *Context) {
parentId := ObjToInt(that.Req.FormValue("id"))
//parentId := ObjToInt(that.RouterString[2])
childData:=[]Map{}
if parentId == 0 {
childData1 := that.Db.Select("category", "*", Map{"parent_id": nil})
for _, v := range childData1 {
data := that.Db.Get("category", "*", Map{"parent_id": v.GetCeilInt("id")})
childData=append(childData,data)
}
}else{
childData = that.Db.Select("category", "*", Map{"parent_id": parentId})
}
for _, v := range childData {
v["child"] = that.Db.Select("category", "*", Map{"parent_id": v.GetCeilInt("id")})
}
that.Display(0, childData)
},
}
+77
View File
@@ -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)
},
}
-104
View File
@@ -1,104 +0,0 @@
package app
import (
. "../../../hotime"
. "../../../hotime/common"
"fmt"
"time"
)
var ctg_order_dateCtr = Ctr{
"info": func(that *Context) {
//today:=time.Now().Weekday()
id := ObjToInt(that.Req.FormValue("id"))
category := that.Db.Get("category", "*", Map{"id": id})
if category == nil {
that.Display(4, "找不到该类别!")
return
}
todayPMTime, _ := time.Parse("2006-01-02 15:04", time.Now().Format("2006-01-02")+" 14:00")
todayAMTime, _ := time.Parse("2006-01-02 15:04", time.Now().Format("2006-01-02"+" 09:00"))
weekDay := 1
switch time.Now().Weekday().String() {
case "Monday":
weekDay = 1
case "Tuesday":
weekDay = 2
case "Wednesday":
weekDay = 3
case "Thursday":
weekDay = 4
case "Friday":
weekDay = 5
case "Saturday":
weekDay = 6
case "Sunday":
weekDay = 7
}
////future:=that.Db.Select("ctg_order_date","*",Map{"category_id":that.RouterString[2],"date[>]":time})
date := Slice{}
for i := 0; i < 7; i++ {
day := weekDay + i + 1
if day > 7 {
day = day - 7
}
if day == 6 || day == 7 {
continue
}
//fmt.Println(todayAMTime.Unix() + int64(24*60*60*(i+1)))
dayAM := that.Db.Get("ctg_order_date", "*", Map{"AND": Map{"category_id": category.GetCeilInt("id"),
"date": todayAMTime.Unix() + int64(24*60*60*(i+1))}})
if dayAM == nil {
dayAM = Map{"name": "9:00-12:00",
"date": todayAMTime.Unix() + int64(24*60*60*(i+1)),
"create_time": time.Now().Unix(),
"modify_time": time.Now().Unix(),
"start_sn": category.GetCeilInt("start_sn"),
"max_sn": category.GetCeilInt("start_sn") + category.GetCeilInt("am"+ObjToStr(day)),
"now_sn": category.GetCeilInt("start_sn"),
"category_id": category.GetCeilInt("id"),
}
dayAM["id"] = that.Db.Insert("ctg_order_date", dayAM)
if dayAM.GetCeilInt64("id") == 0 {
that.Display(4, "内部错误!")
return
}
}
dayPM := that.Db.Get("ctg_order_date", "*", Map{"AND": Map{"category_id": category.GetCeilInt("id"), "date": todayPMTime.Unix() + int64(24*60*60*(i+1))}})
fmt.Println(that.Db.LastQuery, that.Db.LastData, dayPM, that.Db.LastErr)
if dayPM == nil {
fmt.Println("dasdasdasda")
dayPM = Map{"name": "14:00-16:00",
"date": todayPMTime.Unix() + int64(24*60*60*(i+1)),
"create_time": time.Now().Unix(),
"modify_time": time.Now().Unix(),
"start_sn": category.GetCeilInt("start_sn"),
"max_sn": category.GetCeilInt("start_sn") + category.GetCeilInt("pm"+ObjToStr(day)),
"now_sn": category.GetCeilInt("start_sn"),
"category_id": category.GetCeilInt("id"),
}
dayPM["id"] = that.Db.Insert("ctg_order_date", dayPM)
if dayPM.GetCeilInt64("id") == 0 {
that.Display(4, "内部错误!")
return
}
}
date = append(date, Map{"name": "星期" + ObjToStr(day) + "(" + time.Unix(todayPMTime.Unix()+int64(24*60*60*(i+1)), 0).Format("01-02") + ")",
"am": dayAM,
"pm": dayPM,
})
}
that.Display(0, date)
},
}
+115 -74
View File
@@ -1,86 +1,127 @@
package app
import (
. "../../../hotime"
. "../../../hotime/common"
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"io/ioutil"
"net/http"
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
"time"
)
var ID = "aad1472b19e575a71ee8f75629e27867"
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", "*")
// Project 管理端项目
var Project = Proj{
//"user": UserCtr,
"category": categoryCtr,
"ctg_order_date": ctg_order_dateCtr,
"order": orderCtr,
"user": userCtr,
"sms": Sms,
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))
},
},
}
//生成随机码的4位随机数
func getCode() string {
//res := ""
//for i := 0; i < 4; i++ {
res := ObjToStr(RandX(1000, 9999))
//}
return res
}
func tencentSendYzm(umobile, code string) error {
random := RandX(999999, 9999999)
url := "https://yun.tim.qq.com/v5/tlssmssvr/sendsms?sdkappid=1400235813&random=" + ObjToStr(random)
fmt.Println("URL:>", url)
h := sha256.New()
h.Write([]byte(`appkey=d511de15e5ccb43fc171772dbb8b599f&random=` + ObjToStr(random) + `&time=` + ObjToStr(time.Now().Unix()) + `&mobile=` + umobile))
bs := h.Sum(nil)
s256 := hex.EncodeToString(bs)
//json序列化
post := `{
"ext": "",
"extend": "",
"params": [
"` + code + `"
],
"sig": "` + s256 + `",
"sign": "乐呵呵旅游网",
"tel": {
"mobile": "` + umobile + `",
"nationcode": "86"
},
"time": ` + ObjToStr(time.Now().Unix()) + `,
"tpl_id": 378916
}`
fmt.Println(url, "post", post)
var jsonStr = []byte(post)
fmt.Println("jsonStr", jsonStr)
fmt.Println("new_str", bytes.NewBuffer(jsonStr))
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
// req.Header.Set("X-Custom-Header", "myvalue")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
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})
}
defer resp.Body.Close()
fmt.Println("response Status:", resp.Status)
fmt.Println("response Headers:", resp.Header)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println("response Body:", string(body))
return nil
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)
}
}
+96
View File
@@ -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})
},
}
-70
View File
@@ -1,70 +0,0 @@
package app
import (
. "../../../hotime"
. "../../../hotime/common"
"time"
)
var orderCtr = Ctr{
"count": func(this *Context) {
if this.Session("id").ToCeilInt() == 0 {
this.Display(2, "没有登录!")
return
}
re := Map{}
re["total"] = this.Db.Count("order", Map{"user_id": this.Session("id").ToCeilInt()})
re["finish"] = this.Db.Count("order", Map{"AND": Map{"user_id": this.Session("id").ToCeilInt(), "status": 2}})
re["late"] = this.Db.Count("order", Map{"AND": Map{"user_id": this.Session("id").ToCeilInt(), "status": 3}})
this.Display(0, re)
},
"add": func(this *Context) {
if this.Session("id").ToCeilInt() == 0 {
this.Display(2, "没有登录!")
return
}
ctgOrderDateId := ObjToInt(this.Req.FormValue("ctg_order_date_id"))
//ctgId:=ObjToInt(this.Req.FormValue("category_id"))
ctgOrderDate := this.Db.Get("ctg_order_date", "*", Map{"id": ctgOrderDateId})
if ctgOrderDate.GetCeilInt64("now_sn")+1 > ctgOrderDate.GetCeilInt64("max_sn") {
this.Display(5, "当前排号已经用完")
return
}
data := Map{"create_time": time.Now().Unix(),
"modify_time": time.Now().Unix(),
"user_id": this.Session("id").ToCeilInt(),
"date": ctgOrderDate.GetString("date"),
"sn": ctgOrderDate.GetCeilInt64("now_sn") + 1,
"category_id": ctgOrderDate.GetCeilInt("category_id"),
"admin_id": 1,
"status": 1,
"name": time.Unix(ctgOrderDate.GetCeilInt64("date"), 0).Format("2006-01-02 ") + ctgOrderDate.GetString("name"),
}
data["id"] = this.Db.Insert("order", data)
if data.GetCeilInt("id") == 0 {
this.Display(5, "预约失败")
return
}
this.Db.Update("ctg_order_date", Map{"now_sn": ctgOrderDate.GetCeilInt64("now_sn") + 1, "modify_time": time.Now().Unix()}, Map{"id": ctgOrderDate.GetCeilInt("id")})
this.Display(0, data)
},
"search": func(that *Context) {
if that.Session("id").ToCeilInt() == 0 {
that.Display(2, "没有登录!")
return
}
data := that.Db.Select("order", "*", Map{"user_id": that.Session("id").ToCeilInt()})
for _, v := range data {
v["category"] = that.Db.Get("category", "*", Map{"id": v.GetCeilInt("category_id")})
v["parent_category"] = that.Db.Get("category", "id,name", Map{"id": v.GetMap("category").GetCeilInt("parent_id")})
}
that.Display(0, data)
},
}
+60
View File
@@ -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)
},
}
-32
View File
@@ -1,32 +0,0 @@
package app
import (
. "../../../hotime"
)
var Sms = Ctr{
//只允许微信验证过的或者登录成功的发送短信
"send": func(this *Context) {
//if this.Session("uid").Data == nil && this.Session("wechatInfo").Data == nil {
// this.Display(2, "没有授权")
// return
//}
if len(this.Req.FormValue("token")) != 32 {
this.Display(2, "没有授权")
return
}
phone := this.Req.FormValue("phone")
if len(phone) < 11 {
this.Display(3, "手机号格式错误")
return
}
code := getCode()
this.Session("phone", phone)
this.Session("code", code)
tencentSendYzm(phone, code)
this.Display(0, "发送成功")
},
}
-54
View File
@@ -1,54 +0,0 @@
package app
import (
. "../../../hotime"
. "../../../hotime/common"
"time"
)
var userCtr = Ctr{
"token": func(this *Context) {
this.Display(0, this.SessionId)
},
"test": func(this *Context) {
this.Session("id", 1)
},
"add": func(this *Context) {
if this.Req.FormValue("code") != this.Session("code").ToStr() ||
this.Req.FormValue("phone") != this.Session("phone").ToStr() {
this.Display(3, "短信验证不通过")
return
}
phone := this.Req.FormValue("phone")
idcard := this.Req.FormValue("idcard")
name := this.Req.FormValue("name")
user := this.Db.Get("user", "*", Map{"phone": phone})
if user == nil {
user = Map{"phone": phone, "idcard": idcard, "name": name, "create_time": time.Now().Unix(), "modify_time": time.Now().Unix()}
user["id"] = this.Db.Insert("user", user)
} else {
user["phone"] = phone
user["idcard"] = idcard
user["name"] = name
user["modify_time"] = time.Now().Unix()
re := this.Db.Update("user", user, Map{"id": user.GetCeilInt64("id")})
if re == 0 {
this.Display(4, "系统错误")
return
}
}
if user.GetCeilInt64("id") == 0 {
this.Display(5, "登录失败")
return
}
this.Session("id", user.GetCeilInt("id"))
this.Display(0, "登录成功")
},
}
+14 -15
View File
@@ -2,7 +2,8 @@
"cache": {
"db": {
"db": false,
"session": true
"session": true,
"timeout": 7200
},
"memory": {
"db": true,
@@ -10,24 +11,22 @@
"timeout": 7200
}
},
"codeConfig": {
"admin": "config/app.json"
},
"codeConfig1": {
"admin": {
"config": "config/app.json",
"package": "admin",
"rule": "config/rule.json"
"codeConfig": [
{
"config": "config/admin.json",
"configDB": "config/adminDB.json",
"mode": 0,
"name": "",
"rule": "config/rule.json",
"table": "admin"
}
},
"crossDomain": "auto",
],
"db": {
"mysql": {
"host": "192.168.6.253",
"name": "bzyy",
"name": "dgs-cms",
"password": "dasda8454456",
"port": "3306",
"prefix": "",
"user": "root"
}
},
@@ -42,8 +41,8 @@
"4": "数据处理异常",
"5": "数据结果异常"
},
"mode": 0,
"port": "80",
"mode": 2,
"port": "8081",
"sessionName": "HOTIME",
"tpt": "tpt"
}
+12 -5
View File
@@ -20,10 +20,17 @@
},
"注释": "可配置memorydbredis,默认启用memory,默认优先级为memory\u003eredis\u003edb,memory与数据库缓存设置项一致,缓存数据填充会自动反方向反哺,加入memory缓存过期将自动从redis更新,但memory永远不会更新redis,如果是集群建议不要开启memory,配置即启用"
},
"codeConfig": {
"packageName": "默认无,必须,包名称以及应用名,生成代码的配置文件地址,比如config/app.json,数据库有更新时自动更新配置文件以及对应的生成文件",
"注释": "配置即启用,非必须,默认无"
},
"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": {
@@ -59,7 +66,7 @@
},
"logFile": "无默认,非必须,如果需要存储日志文件时使用,保存格式为:a/b/c/20060102150405.txt,将生成:a/b/c/年月日时分秒.txt,按需设置",
"logLevel": "默认0,必须,0关闭,1打印,日志等级",
"mode": "默认0,非必须,0生产模式,1,测试模式,2开发模式,在开发模式下会显示更多的数据用于开发测试,并能够辅助研发,自动生成配置文件、代码等功能,web无缓存,数据库不启用缓存",
"mode": "默认0,非必须,0生产模式,1,测试模式,2开发模式,3内嵌代码模式,在开发模式下会显示更多的数据用于开发测试,并能够辅助研发,自动生成配置文件、代码等功能,web无缓存,数据库不启用缓存",
"modeRouterStrict": "默认false,必须,路由严格模式false,为大小写忽略必须匹配,true必须大小写匹配",
"port": "默认80,必须,web服务开启Http端口,0为不启用http服务,默认80",
"sessionName": "默认HOTIME,必须,设置session的cookie名",
Binary file not shown.
+6 -181
View File
@@ -1,193 +1,18 @@
package main
import (
"../../hotime"
"../../hotime/common"
"./admin"
"./app"
"fmt"
"io"
"os"
"strings"
"time"
. "code.hoteas.com/golang/hotime"
"code.hoteas.com/golang/hotime/example/app"
)
func main() {
fmt.Println(time.Now().Weekday().String())
appIns := hotime.Init("config/config.json")
//RESTfull接口适配
appIns.SetConnectListener(func(context *hotime.Context) bool {
if len(context.RouterString)>1&& context.RouterString[0] == "admin" {
if context.RouterString[1] == "hotime" && context.RouterString[2] == "login" {
return true
}
appIns := Init("config/config.json")
appIns.SetConnectListener(func(that *Context) (isFinished bool) {
if context.Session("admin_id").Data == nil {
context.Display(2, "你还没有登录")
return false
}
}
//文件上传接口
if len(context.RouterString) == 1 && context.RouterString[0] == "file" && context.Req.Method == "POST" {
//读取网络文件
fi, fheader, err := context.Req.FormFile("file")
if err != nil {
context.Display(3, err)
return false
}
filePath := context.Config.GetString("filePath")
if filePath == "" {
filePath = "file/2006/01/02/"
}
path := time.Now().Format(filePath)
e := os.MkdirAll(context.Config.GetString("tpt")+"/"+path, os.ModeDir)
if e != nil {
context.Display(3, e)
return false
}
filePath = path + common.Md5(common.ObjToStr(common.RandX(100000, 9999999))) + fheader.Filename[strings.LastIndex(fheader.Filename, "."):]
newFile, e := os.Create(context.Config.GetString("tpt") + "/" + filePath)
if e != nil {
context.Display(3, e)
return false
}
_, e = io.Copy(newFile, fi)
if e != nil {
context.Display(3, e)
return false
}
context.Display(0, filePath)
return false
}
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 true
}
//排除无效操作
if len(context.RouterString) == 2 &&
context.Req.Method != "GET" &&
context.Req.Method != "POST" {
return true
}
//列表检索
if len(context.RouterString) == 2 &&
context.Req.Method == "GET" {
if context.Router[context.RouterString[0]][context.RouterString[1]]["search"] == nil {
return true
}
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 true
}
//查询单条
if len(context.RouterString) == 3 &&
context.Req.Method == "GET" {
if context.Router[context.RouterString[0]][context.RouterString[1]]["info"] == nil {
return true
}
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 false
return isFinished
})
//makeCode := code.MakeCode{}
//fmt.Println(common.ObjToStr(makeCode.Db2JSON("admin","test",appIns.Db)))
appIns.Run(Router{"app": app.AppProj})
appIns.Run(hotime.Router{
"admin": admin.Project,
"app": app.Project,
//"app": hotime.Proj{
// "index": hotime.Ctr{
// "test": func(this *hotime.Context) {
//
// data := this.Db.Get("cached", "*")
// fmt.Println(data)
// fmt.Println(this.Session("test").ToCeilInt())
// this.Session("test1", 98984984)
// fmt.Println(this.Session("test1").Data)
// this.Error.SetError(errors.New("dasdasdas"))
// //fmt.Println(this.Db.GetTag())
// //this.Application.Log.Error("dasdasdas")
// //this.Log.Error("dadasdasd")
// //x:=this.Db.Action(func(db hotime.HoTimeDB) bool {
// //
// // db.Insert("user",hotime.Map{"unickname":"dasdas"})
// //
// // return true
// //})
// //hotime.LogError("dasdasdasdasdas")
// 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 +0,0 @@
h3[data-v-b9167eee]{margin:40px 0 0}ul[data-v-b9167eee]{list-style-type:none;padding:0}li[data-v-b9167eee]{display:inline-block;margin:0 10px}a[data-v-b9167eee]{color:#42b983}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>hotime</title><style>body{
margin: 0px;
}</style><link href="css/chunk-038340db.e4ca99de.css" rel="prefetch"><link href="css/chunk-0524801b.1e910850.css" rel="prefetch"><link href="css/chunk-1c438cad.2fd7d31d.css" rel="prefetch"><link href="css/chunk-1d1a12b6.8c684de3.css" rel="prefetch"><link href="css/chunk-2c1d9b2f.78c69bda.css" rel="prefetch"><link href="css/chunk-5ccac4d0.5f1c91ca.css" rel="prefetch"><link href="css/chunk-c569a38e.01bc1b83.css" rel="prefetch"><link href="css/chunk-c7a51d14.4a0871eb.css" rel="prefetch"><link href="js/chunk-038340db.3c8b0cb6.js" rel="prefetch"><link href="js/chunk-0524801b.de9b433f.js" rel="prefetch"><link href="js/chunk-14f17cfa.c5104e8c.js" rel="prefetch"><link href="js/chunk-1c438cad.72a22c19.js" rel="prefetch"><link href="js/chunk-1d1a12b6.6900cc01.js" rel="prefetch"><link href="js/chunk-2c1d9b2f.39065c9e.js" rel="prefetch"><link href="js/chunk-5b2ead63.af868ff8.js" rel="prefetch"><link href="js/chunk-5ccac4d0.98ff45de.js" rel="prefetch"><link href="js/chunk-68815fbc.04152d5f.js" rel="prefetch"><link href="js/chunk-c569a38e.16d55c64.js" rel="prefetch"><link href="js/chunk-c7a51d14.3cd28dc4.js" rel="prefetch"><link href="css/app.5e2eb449.css" rel="preload" as="style"><link href="js/app.f5f26baa.js" rel="preload" as="script"><link href="css/app.5e2eb449.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but hotime doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/app.f5f26baa.js"></script></body></html>
+16
View File
@@ -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
)
+77
View File
@@ -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=
+19 -3
View File
@@ -82,10 +82,26 @@ func findCaller(skip int) string {
for i := 0; i < 10; i++ {
file, line = getCaller(skip + i)
if !strings.HasPrefix(file, "logrus") {
if file == "common/error.go" {
file, line = getCaller(skip + i + 1)
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.Contains(file, "application.go") {
file, line = getCaller(skip + i + j)
}
if j == 5 {
break
}
}
break
}
}
+6 -6
View File
@@ -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)
},
},
},
+2 -2
View File
@@ -1,8 +1,8 @@
package hotime
import (
. "./cache"
. "./common"
. "code.hoteas.com/golang/hotime/cache"
. "code.hoteas.com/golang/hotime/common"
)
//session对象
+1 -1
View File
@@ -5,4 +5,4 @@ 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 Method func(that *Context)
+30 -16
View File
@@ -1,7 +1,7 @@
package hotime
import (
. "./common"
. "code.hoteas.com/golang/hotime/common"
)
var IsRun = false //当前状态
@@ -11,14 +11,16 @@ var App = map[string]*Application{} //整个项目
var Config = Map{
"mode": 2, //模式 0生产模式,1,测试模式,2,开发模式
"codeConfig": Map{
"admin": "config/app.json",
},
"codeConfig1": Map{
"admin": Map{ //默认无,必须,接口类别名称
"package": "admin", //默认admin必须,有则生成代码到此目录,无则采用缺省模式不生成代码只生成接口
"config": "config/app.json", //默认config/app.json,必须,接口描述配置文件
"rule": "config/rule.json", //默认config/rule.json必须,有则按改规则生成接口,无则按系统内嵌方式生成
//"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{
@@ -51,13 +53,25 @@ var ConfigNote = Map{
"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开发模式,在开发模式下会显示更多的数据用于开发测试,并能够辅助研发,自动生成配置文件、代码等功能,web无缓存,数据库不启用缓存", //debug 0关闭1开启
"codeConfig": Map{
"注释": "配置即启用,非必须,默认无",
//"package":"默认admin,必须,mode模式为2时会自动生成包文件夹和代码文件",
//"path":""
"packageName": "默认无,必须,包名称以及应用名,生成代码的配置文件地址,比如config/app.json,数据库有更新时自动更新配置文件以及对应的生成文件",
"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数据库",
+202
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,163 @@
# Structs [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/structs) [![Build Status](http://img.shields.io/travis/fatih/structs.svg?style=flat-square)](https://travis-ci.org/fatih/structs) [![Coverage Status](http://img.shields.io/coveralls/fatih/structs.svg?style=flat-square)](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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,9 @@
// +build go1.8
package redis
import "crypto/tls"
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
return cfg.Clone()
}
+134
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,7 @@
/.idea
.vscode
settings.json
vendor
+201
View File
@@ -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
View File
@@ -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
[![Github](https://img.shields.io/github/followers/iGoogle-ink?label=Follow&style=social)](https://github.com/iGoogle-ink)
[![Github](https://img.shields.io/github/forks/go-pay/gopay?label=Fork&style=social)](https://github.com/go-pay/gopay/fork)
[![Golang](https://img.shields.io/badge/golang-1.16-brightgreen.svg)](https://golang.google.cn)
[![GoDoc](https://img.shields.io/badge/doc-pkg.go.dev-informational.svg)](https://pkg.go.dev/github.com/go-pay/gopay)
[![Drone CI](https://cloud.drone.io/api/badges/go-pay/gopay/status.svg)](https://cloud.drone.io/go-pay/gopay)
[![GitHub Release](https://img.shields.io/github/v/release/go-pay/gopay)](https://github.com/go-pay/gopay/releases)
[![License](https://img.shields.io/github/license/go-pay/gopay)](https://www.apache.org/licenses/LICENSE-2.0)
[![GoDoc](https://tokei.rs/b1/github.com/go-pay/gopay?category=lines)](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
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

+242
View File
@@ -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
}
+13
View File
@@ -0,0 +1,13 @@
package gopay
const (
NULL = ""
SUCCESS = "SUCCESS"
FAIL = "FAIL"
OK = "OK"
DebugOff = 0
DebugOn = 1
Version = "1.5.78"
)
type DebugSwitch int8
+16
View File
@@ -0,0 +1,16 @@
package gopay
import "errors"
var (
MissWechatInitParamErr = errors.New("missing wechat init parameter")
MissAlipayInitParamErr = errors.New("missing alipay init parameter")
MissPayPalInitParamErr = errors.New("missing paypal init parameter")
MissParamErr = errors.New("missing required parameter")
MarshalErr = errors.New("marshal error")
UnmarshalErr = errors.New("unmarshal error")
SignatureErr = errors.New("signature error")
VerifySignatureErr = errors.New("verify signature error")
CertNotMatchErr = errors.New("cert not match error")
GetSignDataErr = errors.New("get signature data error")
)
+5
View File
@@ -0,0 +1,5 @@
module github.com/go-pay/gopay
go 1.16
require golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
+9
View File
@@ -0,0 +1,9 @@
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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+43
View File
@@ -0,0 +1,43 @@
package aes
import (
"crypto/aes"
"crypto/cipher"
"errors"
)
// AES-CBC 加密数据
func CBCEncrypt(originData, key, iv []byte) ([]byte, error) {
return cbcEncrypt(originData, key, iv)
}
// AES-CBC 解密数据
func CBCDecrypt(secretData, key, iv []byte) ([]byte, error) {
return cbcDecrypt(secretData, key, iv)
}
func cbcEncrypt(originData, key, iv []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
originData = PKCS7Padding(originData, block.BlockSize())
secretData := make([]byte, len(originData))
blockMode := cipher.NewCBCEncrypter(block, iv[:block.BlockSize()])
blockMode.CryptBlocks(secretData, originData)
return secretData, nil
}
func cbcDecrypt(secretData, key, iv []byte) (originByte []byte, err error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
originByte = make([]byte, len(secretData))
blockMode := cipher.NewCBCDecrypter(block, iv[:block.BlockSize()])
blockMode.CryptBlocks(originByte, secretData)
if len(originByte) == 0 {
return nil, errors.New("blockMode.CryptBlocks error")
}
return PKCS7UnPadding(originByte), nil
}
+105
View File
@@ -0,0 +1,105 @@
package aes
import (
"crypto/aes"
"crypto/cipher"
"errors"
)
// AES-ECB 加密数据
func ECBEncrypt(originData, key []byte) ([]byte, error) {
return ecbEncrypt(originData, key)
}
// AES-ECB 解密数据
func ECBDecrypt(secretData, key []byte) ([]byte, error) {
return ecbDecrypt(secretData, key)
}
func ecbEncrypt(originData, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
originData = PKCS7Padding(originData, block.BlockSize())
secretData := make([]byte, len(originData))
blockMode := newECBEncrypter(block)
blockMode.CryptBlocks(secretData, originData)
return secretData, nil
}
func ecbDecrypt(secretData, key []byte) (originByte []byte, err error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockMode := newECBDecrypter(block)
originByte = make([]byte, len(secretData))
blockMode.CryptBlocks(originByte, secretData)
if len(originByte) == 0 {
return nil, errors.New("blockMode.CryptBlocks error")
}
return PKCS7UnPadding(originByte), nil
}
// ===========
type ecb struct {
b cipher.Block
blockSize int
}
func newECB(b cipher.Block) *ecb {
return &ecb{
b: b,
blockSize: b.BlockSize(),
}
}
type ecbEncrypter ecb
// newECBEncrypter returns a BlockMode which encrypts in electronic code book
// mode, using the given Block.
func newECBEncrypter(b cipher.Block) cipher.BlockMode {
return (*ecbEncrypter)(newECB(b))
}
func (x *ecbEncrypter) BlockSize() int { return x.blockSize }
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
x.b.Encrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}
type ecbDecrypter ecb
// newECBDecrypter returns a BlockMode which decrypts in electronic code book
// mode, using the given Block.
func newECBDecrypter(b cipher.Block) cipher.BlockMode {
return (*ecbDecrypter)(newECB(b))
}
func (x *ecbDecrypter) BlockSize() int { return x.blockSize }
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
x.b.Decrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}
+49
View File
@@ -0,0 +1,49 @@
package aes
import (
"crypto/aes"
"crypto/cipher"
"fmt"
"github.com/go-pay/gopay/pkg/util"
)
// AES-GCM 加密数据
func GCMEncrypt(originText, additional, key []byte) (nonce []byte, cipherText []byte, err error) {
return gcmEncrypt(originText, additional, key)
}
// AES-GCM 解密数据
func GCMDecrypt(cipherText, nonce, additional, key []byte) ([]byte, error) {
return gcmDecrypt(cipherText, nonce, additional, key)
}
func gcmDecrypt(secretData, nonce, additional, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf("cipher.NewGCM(),error:%w", err)
}
originByte, err := gcm.Open(nil, nonce, secretData, additional)
if err != nil {
return nil, err
}
return originByte, nil
}
func gcmEncrypt(originText, additional, key []byte) ([]byte, []byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, nil, err
}
nonce := []byte(util.RandomString(12))
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, nil, fmt.Errorf("cipher.NewGCM(),error:%w", err)
}
cipherBytes := gcm.Seal(nil, nonce, originText, additional)
return nonce, cipherBytes, nil
}
+42
View File
@@ -0,0 +1,42 @@
package aes
import "bytes"
// 加密填充模式(添加补全码) PKCS5Padding
// 加密时,如果加密bytes的length不是blockSize的整数倍,需要在最后面添加填充byte
func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
paddingCount := blockSize - len(ciphertext)%blockSize //需要padding的数目
paddingBytes := []byte{byte(paddingCount)}
padtext := bytes.Repeat(paddingBytes, paddingCount) //生成填充的文本
return append(ciphertext, padtext...)
}
// 解密填充模式(去除补全码) PKCS5UnPadding
// 解密时,需要在最后面去掉加密时添加的填充byte
func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1]) //找到Byte数组最后的填充byte
return origData[:(length - unpadding)] //只截取返回有效数字内的byte数组
}
// 加密填充模式(添加补全码) PKCS5Padding
// 加密时,如果加密bytes的length不是blockSize的整数倍,需要在最后面添加填充byte
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
paddingCount := blockSize - len(ciphertext)%blockSize //需要padding的数目
paddingBytes := []byte{byte(paddingCount)}
padtext := bytes.Repeat(paddingBytes, paddingCount) //生成填充的文本
return append(ciphertext, padtext...)
}
// 解密填充模式(去除补全码) PKCS7UnPadding
// 解密时,需要在最后面去掉加密时添加的填充byte
func PKCS7UnPadding(origData []byte) (bs []byte) {
length := len(origData)
unPaddingNumber := int(origData[length-1]) // 找到Byte数组最后的填充byte 数字
if unPaddingNumber <= 16 {
bs = origData[:(length - unPaddingNumber)] // 只截取返回有效数字内的byte数组
} else {
bs = origData
}
return
}
+126
View File
@@ -0,0 +1,126 @@
package errgroup
import (
"context"
"fmt"
"runtime"
"sync"
)
// A Group is a collection of goroutines working on subtasks that are part of
// the same overall task.
//
// A zero Group is valid and does not cancel on error.
type Group struct {
err error
wg sync.WaitGroup
errOnce sync.Once
workerOnce sync.Once
ch chan func(ctx context.Context) error
chs []func(ctx context.Context) error
workerNum int
ctx context.Context
cancel func()
}
// WithContext create a Group.
// given function from Go will receive this context,
func WithContext(ctx context.Context) *Group {
return &Group{ctx: ctx}
}
// WithCancel create a new Group and an associated Context derived from ctx.
//
// given function from Go will receive context derived from this ctx,
// The derived Context is canceled the first time a function passed to Go
// returns a non-nil error or the first time Wait returns, whichever occurs
// first.
func WithCancel(ctx context.Context) *Group {
ctx, cancel := context.WithCancel(ctx)
return &Group{ctx: ctx, cancel: cancel}
}
func (g *Group) do(f func(ctx context.Context) error) {
ctx := g.ctx
if ctx == nil {
ctx = context.Background()
}
var err error
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 64<<10)
buf = buf[:runtime.Stack(buf, false)]
err = fmt.Errorf("errgroup: panic recovered: %s\n%s", r, buf)
}
if err != nil {
g.errOnce.Do(func() {
g.err = err
if g.cancel != nil {
g.cancel()
}
})
}
g.wg.Done()
}()
err = f(ctx)
}
// GOMAXPROCS set max goroutine to work.
func (g *Group) GOMAXPROCS(n int) {
if n <= 0 {
panic("errgroup: GOMAXPROCS must great than 0")
}
g.workerOnce.Do(func() {
g.ch = make(chan func(context.Context) error, n)
for i := 0; i < n; i++ {
go func() {
for f := range g.ch {
g.do(f)
}
}()
}
})
}
// Go calls the given function in a new goroutine.
//
// The first call to return a non-nil error cancels the group; its error will be
// returned by Wait.
func (g *Group) Go(f func(ctx context.Context) error) {
g.wg.Add(1)
g.workerNum++
if g.ch != nil {
select {
case g.ch <- f:
default:
g.chs = append(g.chs, f)
}
return
}
go g.do(f)
}
// Wait blocks until all function calls from the Go method have returned, then
// returns the first non-nil error (if any) from them.
func (g *Group) Wait() error {
if g.ch != nil {
for _, f := range g.chs {
g.ch <- f
}
}
g.wg.Wait()
if g.ch != nil {
close(g.ch) // let all receiver exit
}
if g.cancel != nil {
g.cancel()
}
g.workerNum = 0
return g.err
}
func (g *Group) WorkNum() int {
return g.workerNum
}

Some files were not shown because too many files have changed in this diff Show More